[
  {
    "path": ".gitattributes",
    "content": "www/app/EditorThemes.ts linguist-vendored\n*.html linguist-vendored\n*.css linguist-vendored\n*.js linguist-vendored\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nIcon?\nehthumbs.db\nThumbs.db\n*.pyc\n*.egg\n*.egg-info\nbuild_keys.json\n/pritunl-cloud\n/builder/builder\n/agent/agent\n/imds/server/server\n"
  },
  {
    "path": "CHANGES",
    "content": "pritunl-cloud changelog\n=======================\n\n<%= version %>\n\nAdd Google Cloud DNS support\nAdd support to manual renew certificates\nFix host network bridge management issues\nOptimize database indexes\n\nVersion 2.0.3665.99 2025-12-06\n------------------------------\n\nAdd internal DNS system\nAdd arch linux support\nImprove Route53 DNS management\nImprove instance layout in web console\nImprove instance web console VNC\n\nVersion 2.0.3616.97 2025-10-18\n------------------------------\n\nAdd journal kind to pods\nAdd support for QEMU 10\nImprove QEMU tuning\nFix relations query\nFix RHEL 7 IPv6 network configuration\nAdd imds service recovery\nAdd update advisories\nWeb console layout improvements\n\nVersion 2.0.3592.49 2025-09-24\n------------------------------\n\nAdd pod system\nAdd imds service\nAdd secrets storage\nAdd LVM storage pools\nAdd DNS TXT verification for Lets Encrypt\nAdd support for custom vpc arp entries\nAdd node port support\nAdd dhcp client and server\nAdd cloud script support\n\nVersion 1.2.2933.86 2023-12-05\n------------------------------\n\nFix fast login issues\n\nVersion 1.2.2900.76 2023-11-02\n------------------------------\n\nImprove single sign-on fast login\n\nVersion 1.2.2853.96 2023-09-16\n------------------------------\n\nAdd VPC network maps\nFix QEMU desktop GUI on Fedora 38\n\nVersion 1.2.2759.95 2023-06-14\n------------------------------\n\nAdd support for Fedora 38\nAdd fast login\n\nVersion 1.2.2653.26 2023-02-28\n------------------------------\n\nImprove instance backup support\n\nVersion 1.2.2626.74 2023-02-01\n------------------------------\n\nFix kernel compatibility issue\n\nVersion 1.2.2626.25 2023-02-01\n------------------------------\n\nSupport RHEL 9\nWeb interface design changes\nFix network configuration issue with IPv6 instances\n\nVersion 1.2.2415.36 2022-07-05\n------------------------------\n\nUpdate cloud init configuration\n\nVersion 1.2.2415.30 2022-07-05\n------------------------------\n\nUpdate QEMU configuration\n\nVersion 1.2.2414.59 2022-07-04\n------------------------------\n\nAdd FreeBSD support\nAdd Windows 11 support\nAdd instance TPM support\nAdd instance DHCP server\nConfigure node firewall in node init\n\nVersion 1.2.2400.46 2022-06-20\n------------------------------\n\nFix QEMU feature detection\n\nVersion 1.2.2400.30 2022-06-20\n------------------------------\n\nAdd AlmaLinux image labels\n\nVersion 1.2.2399.48 2022-06-19\n------------------------------\n\nShow running QEMU version in instance info\nImprove Lets Encrypt certificates\n\nVersion 1.2.2397.83 2022-06-17\n------------------------------\n\nSupport QEMU 7.0.0\nSupport Azure users with more then 50 groups\n\nVersion 1.2.2336.5 2022-04-17\n-----------------------------\n\nFix compatibility issue with Fedora update\n\nVersion 1.2.2335.81 2022-04-16\n------------------------------\n\nAdd support for MongoDB unix socket\n\nVersion 1.2.2334.82 2022-04-15\n------------------------------\n\nSupport IPv6 only instances\n\nVersion 1.2.2334.69 2022-04-15\n------------------------------\n\nFix issue with instance startup\n\nVersion 1.2.2333.97 2022-04-14\n------------------------------\n\nImprove instance management\n\nVersion 1.2.2333.91 2022-04-14\n------------------------------\n\nImprove instance IPv6 networking\n\nVersion 1.2.2333.80 2022-04-14\n------------------------------\n\nImprove instance networking\n\nVersion 1.2.2330.66 2022-04-11\n------------------------------\n\nAdd option to disable public IPv6 address\n\nVersion 1.2.2330.2 2022-04-11\n-----------------------------\n\nImprove node init support\nImprove instance isolation\n\nVersion 1.2.2324.79 2022-04-05\n------------------------------\n\nImprove IPv6 networking\n\nVersion 1.2.2324.56 2022-04-05\n------------------------------\n\nImprove node init support\nAdd mtu check command\nFix static IPv6 issues\n\nVersion 1.2.2301.94 2022-03-13\n------------------------------\n\nAdd support for internal only jumbo frames\nUpdate image labels for Ubuntu\n\nVersion 1.2.2289.56 2022-03-01\n------------------------------\n\nImprove node init support\n\nVersion 1.2.2287.69 2022-02-27\n------------------------------\n\nImprove host networking\n\nVersion 1.2.2286.62 2022-02-26\n------------------------------\n\nFix issues with static IPv6 configuration\n\nVersion 1.2.2262.39 2022-02-02\n------------------------------\n\nFix U2F migration to WebAuthn issues\n\nVersion 1.2.2261.49 2022-02-01\n------------------------------\n\nWeb interface improvements\n\nVersion 1.2.2261.45 2022-02-01\n------------------------------\n\nImprove Oracle Cloud networking\n\nVersion 1.2.2260.40 2022-01-31\n------------------------------\n\nAdd hugepages support\nAdd support for Azure graph API\nAdd instance gui\nAdd instance spice server\nAdd instance source destination check\nAdd JumpCloud single sign-on\nAdd instance root password option\nImprove instance sandboxing\n\nVersion 1.2.2184.82 2021-11-16\n------------------------------\n\nAdd Oracle Cloud vnic support\nImprove qemu support\n\nVersion 1.2.2144.31 2021-10-07\n------------------------------\n\nWeb interface improvements\n\nVersion 1.2.2141.15 2021-10-04\n------------------------------\n\nFix zone defaults\n\nVersion 1.2.2140.92 2021-10-03\n------------------------------\n\nSupport instance networking without bridge\n\nVersion 1.2.2091.92 2021-08-15\n------------------------------\n\nImprove instance networking\n\nVersion 1.2.2011.58 2021-05-27\n------------------------------\n\nAdd uefi instance support\nAdd secure boot instance support\nAdd pci passthrough\nAdd disk resizing\nAdd disk passthrough\nAdd iscsi instance disk support\nAdd instance iso support\nSupport add and remove USB devices on running instance\nSupport add and remove disks on running instance\nSupport wildcard certificates\nFormat username capitalization on update\nMove instance runtime files to run directory\nWeb interface improvements\n\nVersion 1.2.1807.79 2020-11-04\n------------------------------\n\nImprove online disk backup\nAdd VNC client to web console\nAdd local backup command\nWeb interface improvements\n\nVersion 1.2.1772.58 2020-09-30\n------------------------------\n\nFix issue with multiple instance disks\nFix startup issue\n\nVersion 1.0.1743.55 2020-09-01\n------------------------------\n\nWeb server improvements\n\nVersion 1.0.1623.75 2020-05-04\n------------------------------\n\nAdd web socket support to load balancers\n\nVersion 1.2.1595.50 2020-04-06\n------------------------------\n\nAdd support for network alias interfaces\n\nVersion 1.2.1594.77 2020-04-05\n------------------------------\n\nImprove node management\n\nVersion 1.2.1594.70 2020-04-05\n------------------------------\n\nNetworking improvements\n\nVersion 1.2.1589.78 2020-03-31\n------------------------------\n\nAdd VPC subnets\nMove default data directory\nSupport IPv6 static blocks\nInterface improvements\nAdd load balancers\n\nVersion 1.0.1452.55 2019-11-15\n------------------------------\n\nImprove acme v2 support\n\nVersion 1.0.1448.49 2019-11-11\n------------------------------\n\nSupport acme v2\nInstance networking improvements\nWeb interface improvements\n\nVersion 1.0.1413.18 2019-10-07\n------------------------------\n\nAdd support for USB passthrough\n\nVersion 1.0.1338.74 2019-07-24\n------------------------------\n\nImprove instance management\n\nVersion 1.0.1337.5 2019-07-23\n-----------------------------\n\nImprove instance management\nAdd uptime to instance info\nGenerate random default password\n\nVersion 1.0.1213.83 2019-03-21\n------------------------------\n\nWeb interface improvements\n\nVersion 1.0.1213.82 2019-03-21\n------------------------------\n\nAdd option to disable instance public and host address\n\nVersion 1.0.1206.81 2019-03-14\n------------------------------\n\nAdd VGA option to node settings\n\nVersion 1.0.1200.31 2019-03-08\n------------------------------\n\nImprove network performance\n\nVersion 1.0.1200.25 2019-03-08\n------------------------------\n\nImprove instance management\n\nVersion 1.0.1199.30 2019-03-07\n------------------------------\n\nFix instance creation issue\n\nVersion 1.0.1199.28 2019-03-07\n------------------------------\n\nAdd instance vnc support\n\nVersion 1.0.1197.13 2019-03-05\n------------------------------\n\nSet instance hostname\n\nVersion 1.0.1189.79 2019-02-25\n------------------------------\n\nImprove U2F support\nWeb interface improvements\n\nVersion 1.0.1181.24 2019-02-17\n------------------------------\n\nAdd redirect server option to node settings\n\nVersion 1.0.1180.14 2019-02-16\n------------------------------\n\nAdd support for Oracle Cloud\n\nVersion 1.0.1179.24 2019-02-15\n------------------------------\n\nAdd host networking\n\nVersion 1.0.1173.24 2019-02-09\n------------------------------\n\nAdd VXLan support\n\nVersion 1.0.1169.1 2019-02-05\n-----------------------------\n\nFix node configuration\n\nVersion 1.0.1168.95 2019-02-04\n------------------------------\n\nImprove node management\n\nVersion 1.0.1168.94 2019-02-04\n------------------------------\n\nImprove node management\n\nVersion 1.0.1168.33 2019-02-04\n------------------------------\n\nAdd support for static instance IP addresses\nImprove instance management\n\nVersion 1.0.1155.32 2019-01-22\n------------------------------\n\nAdd support for Oracle Cloud archive storage\n\nVersion 1.0.1154.31 2019-01-21\n------------------------------\n\nImprove instance startup\n\nVersion 1.0.1154.28 2019-01-21\n------------------------------\n\nImprove instance management\n\nVersion 1.0.1153.36 2019-01-20\n------------------------------\n\nImprove storage management\n\nVersion 1.0.1153.8 2019-01-20\n-----------------------------\n\nAdd storage class to images\n\nVersion 1.0.1151.28 2019-01-18\n------------------------------\n\nImprove instance destroy\n\nVersion 1.0.1150.31 2019-01-17\n------------------------------\n\nAdd copy to clipboard to web console\n\nVersion 1.0.1149.32 2019-01-16\n------------------------------\n\nAdd automatic backup scheduling\n\nVersion 1.0.1147.30 2019-01-14\n------------------------------\n\nAdd disk backup and restore\n\nVersion 1.0.1144.28 2019-01-11\n------------------------------\n\nJumbo frames support\nInstance management interface improvements\n\nVersion 1.0.1142.87 2019-01-09\n------------------------------\n\nReliability improvements\n\nVersion 1.0.1141.35 2019-01-08\n------------------------------\n\nImprove instance management\n\nVersion 1.0.1140.33 2019-01-07\n------------------------------\n\nAdd instance and disk delete protection\nWeb interface improvements\n\nVersion 1.0.1135.30 2019-01-02\n------------------------------\n\nNode management improvements\n\nVersion 1.0.1134.7 2019-01-01\n-----------------------------\n\nImprove node scalability\nImprove firewall performance\n\nVersion 1.0.1129.93 2018-12-27\n------------------------------\n\nLinked disk improvements\n\nVersion 1.0.1129.29 2018-12-27\n------------------------------\n\nAdd support for multiple network interfaces\nAdd support for linked disk images\n\nVersion 1.0.1124.31 2018-12-22\n------------------------------\n\nImprove instances interface in web console\n\nVersion 1.0.1118.32 2018-12-16\n------------------------------\n\nVirtual machine improvements\n\nVersion 1.0.1108.99 2018-12-06\n------------------------------\n\nFix issue with Azure single sign-on\n\nVersion 1.0.1091.33 2018-11-19\n------------------------------\n\nInitial release\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2013-2026 Pritunl\n\n                               LICENSE SUMMARY\n\n* License does not expire\n* Can be used on unlimited sites, servers\n* Source-code or binary products cannot be resold or distributed\n* Non-commercial use only\n* Can modify source-code but cannot distribute modifications (derivative works)\n\n                                  PREAMBLE\n\nThis Agreement, signed on Nov 12, 2014 [hereinafter: Effective Date] governs\nthe relationship between you, a private person, (hereinafter: Licensee) and\nPritunl, a private person whose principal place of business is United States\n(Hereinafter: Licensor). This Agreement sets the terms, rights, restrictions\nand obligations on using [Pritunl] (hereinafter: The Software) created and\nowned by Licensor, as detailed herein\n\n                                LICENSE GRANT\n\nLicensor hereby grants Licensee a Personal, Non-assignable &\nnon-transferable, Non-commercial, Royalty free, Without the rights to create\nderivative works, Non-exclusive license, all with accordance with the terms\nset forth and other legal restrictions set forth in 3rd party software used\nwhile running Software.\n\nLimited: Licensee may use Software for the purpose of:\n\n* Running Software on Licensee's Website[s] and Server[s]\n* Allowing 3rd Parties to run Software on Licensee's Website[s] and Server[s]\n* Publishing Software's output to Licensee and 3rd Parties\n* Modify Software to suit Licensee's needs and specifications.\n\nNon Assignable & Non-Transferable: Licensee may not assign or transfer his\nrights and duties under this license.\n\nNon-Commercial: Licensee may not use Software for commercial purposes. for the\npurpose of this license, commercial purposes means that a 3rd party has to pay\nin order to access Software or that the Website that runs Software is behind a\npaywall.\n\n                             TERM & TERMINATION\n\nThe Term of this license shall be until terminated. Licensor may terminate\nthis Agreement, including Licensee's license in the case where Licensee:\n\n* became insolvent or otherwise entered into any liquidation process; or\nexported The Software to any jurisdiction where licensor may not enforce his\nrights under this agreements in; or\n* Licensee was in breach of any of this license's terms and conditions and\nsuch breach was not cured, immediately upon notification; or\n* Licensee in breach of any of the terms of clause 2 to this license; or\n* Licensee otherwise entered into any arrangement which caused Licensor to be\nunable to enforce his rights under this License.\n\n                        UPGRADES, UPDATES AND FIXES\n\nLicensor may provide Licensee, from time to time, with Upgrades, Updates or\nFixes, as detailed herein and according to his sole discretion. Licensee\nhereby warrants to keep The Software up-to-date and install all relevant\nupdates and fixes, and may, at his sole discretion, purchase upgrades,\naccording to the rates set by Licensor. Licensor shall provide any update or\nFix free of charge; however, nothing in this Agreement shall require Licensor\nto provide Updates or Fixes.\n\nUpgrades: for the purpose of this license, an Upgrade shall be a material\namendment in The Software, which contains new features and or major\nperformance improvements and shall be marked as a new version number. For\nexample, should Licensee purchase The Software under version 1.X.X, an upgrade\nshall commence under number 2.0.0.\n\nUpdates: for the purpose of this license, an update shall be a minor amendment\nin The Software, which may contain new features or minor improvements and\nshall be marked as a new sub-version number. For example, should Licensee\npurchase The Software under version 1.1.X, an upgrade shall commence under\nnumber 1.2.0.\n\nFix: for the purpose of this license, a fix shall be a minor amendment in The\nSoftware, intended to remove bugs or alter minor features which impair the The\nSoftware's functionality. A fix shall be marked as a new sub-sub-version\nnumber. For example, should Licensee purchase Software under version 1.1.1,\nan upgrade shall commence under number 1.1.2.\n\n                                  SUPPORT\n\nSoftware is provided under an AS-IS basis and without any support, updates or\nmaintenance. Nothing in this Agreement shall require Licensor to provide\nLicensee with support or fixes to any bug, failure, mis-performance or other\ndefect in The Software.\n\nBug Notification: Licensee may provide Licensor of details regarding any bug,\ndefect or failure in The Software promptly and with no delay from such event;\nLicensee shall comply with Licensor's request for information regarding bugs,\ndefects or failures and furnish him with information, screenshots and try to\nreproduce such bugs, defects or failures.\n\nFeature Request: Licensee may request additional features in Software,\nprovided, however, that (i) Licensee shall waive any claim or right in such\nfeature should feature be developed by Licensor; (ii) Licensee shall be\nprohibited from developing the feature, or disclose such feature request, or\nfeature, to any 3rd party directly competing with Licensor or any 3rd party\nwhich may be, following the development of such feature, in direct competition\nwith Licensor; (iii) Licensee warrants that feature does not infringe any 3rd\nparty patent, trademark, trade-secret or any other intellectual property\nright; and (iv) Licensee developed, envisioned or created the feature solely\nby himself.\n\nContribution: Licensee may submit additional code for the Software,\nprovided, however, that (i) Licensee hereby irrevocably assigns all right,\ntitle, and interest in such contribution to Licensor upon submission; (ii)\nLicensee hereby grants Licensor a perpetual, worldwide, royalty-free,\nirrevocable license to use, modify, sublicense, and relicense such\ncontribution under any license terms Licensor chooses, including without\nlimitation less restrictive licenses such as AGPL; and (iii) Licensee\nrepresents and warrants that the contribution is Licensee's original work\nand does not infringe upon any third-party intellectual property rights; and\n(iv) Licensee also waives any moral rights in the contribution to the extent\npermitted by law.\n\n                                 LIABILITY\n\nTo the extent permitted under Law, The Software is provided under an AS-IS\nbasis. Licensor shall never, and without any limit, be liable for any damage,\ncost, expense or any other payment incurred by Licensee as a result of\nSoftware's actions, failure, bugs and/or any other interaction between The\nSoftware  and Licensee's end-equipment, computers, other software or any 3rd\nparty, end-equipment, computer or services.  Moreover, Licensor shall never be\nliable for any defect in source code written by Licensee when relying on The\nSoftware or using The Software's source code.\n\n                                 WARRANTY\n\nIntellectual Property: Licensor hereby warrants that The Software does not\nviolate or infringe any 3rd party claims in regards to intellectual property,\npatents and/or trademarks and that to the best of its knowledge no legal\naction has been taken against it for any infringement or violation of any 3rd\nparty intellectual property rights.\n\nNo-Warranty: The Software is provided without any warranty; Licensor hereby\ndisclaims any warranty that The Software shall be error free, without defects\nor code which may cause damage to Licensee's computers or to Licensee, and\nthat Software shall be functional. Licensee shall be solely liable to any\ndamage, defect or loss incurred as a result of operating software and\nundertake the risks contained in running The Software on License's Server[s]\nand Website[s].\n\nPrior Inspection: Licensee hereby states that he inspected The Software\nthoroughly and found it satisfactory and adequate to his needs, that it does\nnot interfere with his regular operation and that it does meet the standards\nand scope of his computer systems and architecture. Licensee found that The\nSoftware interacts with his development, website and server environment and\nthat it does not infringe any of End User License Agreement of any software\nLicensee may use in performing his services. Licensee hereby waives any claims\nregarding The Software's incompatibility, performance, results and features,\nand warrants that he inspected the The Software.\n\n                               INDEMNIFICATION\n\nLicensee hereby warrants to hold Licensor harmless and indemnify Licensor for\nany lawsuit brought against it in regards to Licensee's use of The Software in\nmeans that violate, breach or otherwise circumvent this license, Licensor's\nintellectual property rights or Licensor's title in The Software. Licensor\nshall promptly notify Licensee in case of such legal action and request\nLicensee's consent prior to any settlement in relation to such lawsuit or\nclaim.\n\n                        GOVERNING LAW, JURISDICTION\n\nLicensee hereby agrees not to initiate class-action lawsuits against Licensor\nin relation to this license and to compensate Licensor for any legal fees,\ncost or attorney fees should any claim brought by Licensee against Licensor be\ndenied, in part or in full.\n"
  },
  {
    "path": "README.md",
    "content": "# pritunl-cloud: declarative kvm virtualization\n\n[![github](https://img.shields.io/badge/github-pritunl-181717.svg?style=flat)](https://github.com/pritunl)\n[![twitter](https://img.shields.io/badge/twitter-pritunl-55acee.svg?style=flat)](https://twitter.com/pritunl)\n[![substack](https://img.shields.io/badge/substack-pritunl-ff6719.svg?style=flat)](https://pritunl.substack.com/)\n[![forum](https://img.shields.io/badge/discussion-forum-ffffff.svg?style=flat)](https://forum.pritunl.com)\n\n[Pritunl-Cloud](https://cloud.pritunl.com) is a declarative KVM virtualization\nplatform with shell and python based live updating templates. Documentation\nand more information can be found at\n[docs.pritunl.com](https://docs.pritunl.com/kb/cloud)\n\n[![pritunl](img/logo_code.png)](https://docs.pritunl.com/kb/cloud)\n\n## Install from Source\n\n```bash\n# Install Required Tools\nsudo dnf -y install git-core iptables net-tools ipset ipvsadm xorriso qemu-kvm qemu-img swtpm-tools\n\nsudo rm -rf /usr/local/go\nwget https://go.dev/dl/go1.25.5.linux-amd64.tar.gz\necho \"9e9b755d63b36acf30c12a9a3fc379243714c1c6d3dd72861da637f336ebb35b go1.25.5.linux-amd64.tar.gz\" | sha256sum -c - && sudo tar -C /usr/local -xf go1.25.5.linux-amd64.tar.gz\nrm -f go1.25.5.linux-amd64.tar.gz\n\ntee -a ~/.bashrc << EOF\nexport GOPATH=\\$HOME/go\nexport GOROOT=/usr/local/go\nexport PATH=/usr/local/go/bin:\\$PATH\nEOF\nsource ~/.bashrc\n\n# Install MongoDB\nsudo dnf -y install podman\n\ngit clone https://github.com/pritunl/toolbox.git\ncd toolbox/mongodb-container\nsudo podman build --rm -t mongo .\ncd\n\nsudo mkdir /var/lib/mongo\nsudo chown 277:277 /var/lib/mongo\nsudo tee /etc/containers/systemd/mongodb-podman.container << EOF\n[Unit]\nDescription=MongoDB Podman Service\n\n[Container]\nImage=localhost/mongo\nContainerName=mongodb\nEnvironment=DB_NAME=pritunl-cloud\nEnvironment=CACHE_SIZE=1\nUser=mongodb\nVolume=/var/lib/mongo:/data/db:Z\nPublishPort=127.0.0.1:27017:27017\nPodmanArgs=--cpus=1 --memory=2g\n\n[Service]\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\nEOF\nsudo systemctl daemon-reload\nsudo systemctl start mongodb-podman.service\n\nsleep 3\nsudo cat /var/lib/mongo/credentials.txt\n\n# Build Pritunl Cloud (update with latest version from releases)\ngo install -v github.com/pritunl/pritunl-cloud@2.0.3616.97\ngo install -v github.com/pritunl/pritunl-cloud/redirect@2.0.3616.97\ngo install -v github.com/pritunl/pritunl-cloud/agent@2.0.3616.97\nGOOS=freebsd GOARCH=amd64 go install -v github.com/pritunl/pritunl-cloud/agent@2.0.3616.97\n\n# Install Systemd Units\nsudo cp $(ls -d ~/go/pkg/mod/github.com/pritunl/pritunl-cloud@v* | sort -V | tail -n 1)/tools/pritunl-cloud.service /etc/systemd/system/\nsudo cp $(ls -d ~/go/pkg/mod/github.com/pritunl/pritunl-cloud@v* | sort -V | tail -n 1)/tools/pritunl-cloud-redirect.socket /etc/systemd/system/\nsudo cp $(ls -d ~/go/pkg/mod/github.com/pritunl/pritunl-cloud@v* | sort -V | tail -n 1)/tools/pritunl-cloud-redirect.service /etc/systemd/system/\nsudo systemctl daemon-reload\nsudo useradd -r -s /sbin/nologin -c 'Pritunl web server' pritunl-cloud-web\n\n# Install Pritunl Cloud\nsudo mkdir -p /usr/share/pritunl-cloud/www/\nsudo cp -r $(ls -d ~/go/pkg/mod/github.com/pritunl/pritunl-cloud@v* | sort -V | tail -n 1)/www/dist/. /usr/share/pritunl-cloud/www/\nsudo cp ~/go/bin/pritunl-cloud /usr/bin/pritunl-cloud\nsudo cp ~/go/bin/redirect /usr/bin/pritunl-cloud-redirect\nsudo cp ~/go/bin/agent /usr/bin/pritunl-cloud-agent\nsudo cp ~/go/bin/freebsd_amd64/agent /usr/bin/pritunl-cloud-agent-bsd\n\nsudo systemctl enable --now pritunl-cloud\n```\n\n## License\n\nPlease refer to the [`LICENSE`](LICENSE) file for a copy of the license.\n"
  },
  {
    "path": "acme/acme.go",
    "content": "package acme\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/dns\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/crypto/acme\"\n)\n\nfunc Generate(db *database.Database, cert *certificate.Certificate) (\n\terr error) {\n\n\tacmeType := cert.AcmeType\n\tif acmeType == \"\" {\n\t\tacmeType = certificate.AcmeHTTP\n\t}\n\n\tacmeAuth := cert.AcmeAuth\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"certificate\": cert.Name,\n\t\t\"domains\":     cert.AcmeDomains,\n\t\t\"acme_type\":   acmeType,\n\t\t\"acme_auth\":   acmeAuth,\n\t}).Info(\"acme: Generating acme certificate\")\n\n\tif cert.AcmeDomains == nil || len(cert.AcmeDomains) == 0 {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"acme: No acme domains\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvar dnsSvc dns.Service\n\tif acmeType == certificate.AcmeDNS {\n\t\tvar secr *secret.Secret\n\t\tif cert.Organization.IsZero() {\n\t\t\tsecr, err = secret.Get(db, cert.AcmeSecret)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tsecr, err = secret.GetOrg(db, cert.Organization, cert.AcmeSecret)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif secr == nil {\n\t\t\terr = &errortypes.UnknownError{\n\t\t\t\terrors.Wrap(err, \"acme: ACME secret not found\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif acmeAuth == certificate.AcmeAWS {\n\t\t\tdnsSvc = &dns.Aws{}\n\t\t} else if acmeAuth == certificate.AcmeCloudflare {\n\t\t\tdnsSvc = &dns.Cloudflare{}\n\t\t} else if acmeAuth == certificate.AcmeOracleCloud {\n\t\t\tdnsSvc = &dns.Oracle{}\n\t\t} else if acmeAuth == certificate.AcmeGoogleCloud {\n\t\t\tdnsSvc = &dns.Google{}\n\t\t} else {\n\t\t\terr = &errortypes.UnknownError{\n\t\t\t\terrors.Wrapf(err,\n\t\t\t\t\t\"acme: Unknown acme auth type %s\", acmeAuth),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = dnsSvc.Connect(db, secr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tvar acctKey *rsa.PrivateKey\n\n\tif cert.AcmeAccount != \"\" {\n\t\tacctBlock, _ := pem.Decode([]byte(cert.AcmeAccount))\n\t\tif acctBlock == nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"acme: Failed to decode account key\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tacctKey, err = x509.ParsePKCS1PrivateKey(acctBlock.Bytes)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"acme: Failed to parse account key\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tacctKey, err = rsa.GenerateKey(rand.Reader, 2048)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(err, \"acme: Failed to generate account key\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tacctBlock := &pem.Block{\n\t\t\tType:  \"RSA PRIVATE KEY\",\n\t\t\tBytes: x509.MarshalPKCS1PrivateKey(acctKey),\n\t\t}\n\n\t\tcert.AcmeAccount = string(pem.EncodeToMemory(acctBlock))\n\t\terr = cert.CommitFields(db, set.NewSet(\"acme_account\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tacct := &acme.Account{}\n\n\tclient := &acme.Client{\n\t\tDirectoryURL: AcmeDirectory,\n\t\tKey:          acctKey,\n\t}\n\n\t_, err = client.Register(context.Background(), acct, acme.AcceptTOS)\n\tif err != nil {\n\t\tif err == acme.ErrAccountAlreadyExists {\n\t\t\terr = nil\n\t\t} else {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"acme: Failed to register account\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\torder, err := client.AuthorizeOrder(\n\t\tcontext.Background(), acme.DomainIDs(cert.AcmeDomains...))\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"acme: Failed to authorize order\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif order.Status == acme.StatusReady {\n\t\terr = create(db, cert, client, order)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t} else if order.Status != acme.StatusPending {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"acme: Authorize order status '%s' not pending\",\n\t\t\t\torder.Status,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tauthzUrls := order.AuthzURLs\n\n\tfor _, authzUrl := range authzUrls {\n\t\tauthz, e := client.GetAuthorization(\n\t\t\tcontext.Background(), authzUrl)\n\t\tif e != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(e, \"acme: Failed to get authorization\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif authz.Status != acme.StatusPending {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar authzChal *acme.Challenge\n\t\tfor _, c := range authz.Challenges {\n\t\t\tif acmeType == certificate.AcmeDNS {\n\t\t\t\tif c.Type == \"dns-01\" {\n\t\t\t\t\tauthzChal = c\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif c.Type == \"http-01\" {\n\t\t\t\t\tauthzChal = c\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif authzChal == nil {\n\t\t\trevoke(client, authzUrls)\n\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.New(\n\t\t\t\t\t\"acme: Authorization challenge not available\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tvar chal *Challenge\n\t\tvar chalToken string\n\t\tvar chalDomain string\n\t\tif acmeType == certificate.AcmeDNS {\n\t\t\tchalToken, err = client.DNS01ChallengeRecord(authzChal.Token)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(err, \"acme: Challenge record error\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tchalDomain = fmt.Sprintf(\n\t\t\t\t\"_acme-challenge.%s.\", authz.Identifier.Value)\n\n\t\t\tops := []*dns.Operation{\n\t\t\t\t&dns.Operation{\n\t\t\t\t\tOperation: dns.UPSERT,\n\t\t\t\t\tValue:     \"\\\"\" + chalToken + \"\\\"\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\terr = dnsSvc.DnsCommit(db, chalDomain, \"TXT\", ops)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdefer func() {\n\t\t\t\tdelOps := []*dns.Operation{\n\t\t\t\t\t&dns.Operation{\n\t\t\t\t\t\tOperation: dns.DELETE,\n\t\t\t\t\t\tValue:     \"\\\"\" + chalToken + \"\\\"\",\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\te := dnsSvc.DnsCommit(db, chalDomain, \"TXT\", delOps)\n\t\t\t\tif e != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"certificate\": cert.Name,\n\t\t\t\t\t\t\"domain\":      chalDomain,\n\t\t\t\t\t\t\"acme_type\":   acmeType,\n\t\t\t\t\t\t\"acme_auth\":   acmeAuth,\n\t\t\t\t\t\t\"error\":       e,\n\t\t\t\t\t}).Error(\"acme: Failed to remove DNS TXT record\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tmatched, e := DnsTxtWait(chalDomain, chalToken)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !matched {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"certificate\": cert.Name,\n\t\t\t\t\t\"domain\":      chalDomain,\n\t\t\t\t\t\"acme_type\":   acmeType,\n\t\t\t\t\t\"acme_auth\":   acmeAuth,\n\t\t\t\t}).Warning(\"acme: Local DNS TXT test lookup failed\")\n\t\t\t}\n\n\t\t\ttime.Sleep(time.Duration(settings.Acme.DnsDelay) * time.Second)\n\t\t} else {\n\t\t\tresp, e := client.HTTP01ChallengeResponse(authzChal.Token)\n\t\t\tif e != nil {\n\t\t\t\trevoke(client, authzUrls)\n\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(e, \"acme: Challenge response failed\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tchal = &Challenge{\n\t\t\t\tId:        authzChal.Token,\n\t\t\t\tResource:  resp,\n\t\t\t\tTimestamp: time.Now(),\n\t\t\t}\n\n\t\t\terr = chal.Insert(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tchalMsg := &ChallengeMsg{\n\t\t\t\tToken:    authzChal.Token,\n\t\t\t\tResponse: resp,\n\t\t\t}\n\n\t\t\terr = chalMsg.Publish(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttime.Sleep(300 * time.Millisecond)\n\t\t}\n\n\t\t_, err = client.Accept(context.Background(), authzChal)\n\t\tif err != nil {\n\t\t\trevoke(client, authzUrls)\n\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"acme: Authorization accept failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t_, err = client.WaitAuthorization(\n\t\t\tcontext.Background(), authzChal.URI)\n\t\tif err != nil {\n\t\t\trevoke(client, authzUrls)\n\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"acme: Authorization wait failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif chal != nil {\n\t\t\terr = chal.Remove(db)\n\t\t\tif err != nil {\n\t\t\t\trevoke(client, authzUrls)\n\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\torder, err = client.WaitOrder(context.Background(), order.URI)\n\tif err != nil {\n\t\trevoke(client, authzUrls)\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"acme: Order wait failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif order.Status != acme.StatusReady {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"acme: Authorize order status '%s' not ready\",\n\t\t\t\torder.Status,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\terr = create(db, cert, client, order)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc create(db *database.Database, cert *certificate.Certificate,\n\tclient *acme.Client, order *acme.Order) (err error) {\n\n\tvar csr []byte\n\tvar keyPem []byte\n\n\tif settings.System.AcmeKeyAlgorithm == \"ec\" {\n\t\tcsr, keyPem, err = newEcCsr(cert.AcmeDomains)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcsr, keyPem, err = newRsaCsr(cert.AcmeDomains)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tderChain, _, err := client.CreateOrderCert(\n\t\tcontext.Background(),\n\t\torder.FinalizeURL,\n\t\tcsr,\n\t\ttrue,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"acme: Create order cert failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcertPem := \"\"\n\n\tfor _, der := range derChain {\n\t\tcertBlock := &pem.Block{\n\t\t\tType:  \"CERTIFICATE\",\n\t\t\tBytes: der,\n\t\t}\n\n\t\tif certPem != \"\" {\n\t\t\tcertPem += \"\\n\"\n\t\t}\n\t\tcertPem += strings.TrimSpace(string(pem.EncodeToMemory(certBlock)))\n\t}\n\n\tcert.Key = strings.TrimSpace(string(keyPem))\n\tcert.Certificate = certPem\n\tcert.AcmeHash = cert.Hash()\n\n\t_, err = cert.Validate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = cert.CommitFields(db, set.NewSet(\n\t\t\"key\", \"certificate\", \"acme_hash\", \"info\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"certificate.change\")\n\n\treturn\n}\n\nfunc Renew(db *database.Database, cert *certificate.Certificate, force bool) (\n\terr error) {\n\n\tif cert.Type != certificate.LetsEncrypt {\n\t\treturn\n\t}\n\n\tif force || cert.AcmeHash != cert.Hash() || (cert.Info != nil &&\n\t\t!cert.Info.ExpiresOn.IsZero() &&\n\t\ttime.Until(cert.Info.ExpiresOn) < 168*time.Hour) {\n\n\t\terr = Generate(db, cert)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RenewBackground(cert *certificate.Certificate, force bool) {\n\tgo func() {\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\terr := Renew(db, cert, force)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"certificate_id\":   cert.Id.Hex(),\n\t\t\t\t\"certificate_name\": cert.Name,\n\t\t\t\t\"error\":            err,\n\t\t\t}).Error(\"task: Failed to renew certificate\")\n\t\t}\n\t}()\n}\n"
  },
  {
    "path": "acme/challenge.go",
    "content": "package acme\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n)\n\ntype Challenge struct {\n\tId        string    `bson:\"_id\"`\n\tResource  string    `bson:\"resource\"`\n\tTimestamp time.Time `bson:\"timestamp\"`\n}\n\nfunc (c *Challenge) Insert(db *database.Database) (err error) {\n\tcoll := db.AcmeChallenges()\n\n\t_, err = coll.InsertOne(db, c)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Challenge) Remove(db *database.Database) (err error) {\n\tcoll := db.AcmeChallenges()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": c.Id,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\ntype ChallengeMsg struct {\n\tToken    string `bson:\"token\" json:\"token\"`\n\tResponse string `bson:\"response\" json:\"response\"`\n}\n\nfunc (c *ChallengeMsg) Publish(db *database.Database) (err error) {\n\terr = event.Publish(db, \"acme\", c)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "acme/constants.go",
    "content": "package acme\n\nconst (\n\tAcmeDirectory = \"https://acme-v02.api.letsencrypt.org/directory\"\n\tAcmePath      = \"/.well-known/acme-challenge/\"\n)\n"
  },
  {
    "path": "acme/utils.go",
    "content": "package acme\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"golang.org/x/crypto/acme\"\n)\n\nfunc revoke(client *acme.Client, authzUrls []string) {\n\tif authzUrls == nil {\n\t\treturn\n\t}\n\n\tfor _, authzUrl := range authzUrls {\n\t\tauthz, err := client.GetAuthorization(\n\t\t\tcontext.Background(), authzUrl)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif authz.Status != acme.StatusPending {\n\t\t\tcontinue\n\t\t}\n\n\t\t_ = client.RevokeAuthorization(context.Background(), authzUrl)\n\t}\n}\n\nfunc ParsePath(path string) string {\n\tsplit := strings.SplitN(path, AcmePath, 2)\n\tif len(split) == 2 {\n\t\treturn split[1]\n\t}\n\treturn \"\"\n}\n\nfunc DnsTxtWait(domain, val string) (found bool, err error) {\n\tstart := time.Now()\n\titerDelay := time.Duration(settings.Acme.DnsRetryRate) * time.Second\n\ttimeout := time.Duration(settings.Acme.DnsTimeout) * time.Second\n\n\tfor i := 0; i < 60; i++ {\n\t\tif time.Since(start) > timeout {\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(iterDelay)\n\n\t\trecords, e := net.LookupTXT(domain)\n\t\tif e != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, record := range records {\n\t\t\tif record == val {\n\t\t\t\tfound = true\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GetChallenge(token string) (challenge *Challenge, err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tcoll := db.AcmeChallenges()\n\tchallenge = &Challenge{}\n\n\terr = coll.FindOneId(token, challenge)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc newRsaCsr(domains []string) (csr []byte, keyPem []byte, err error) {\n\tkey, err := rsa.GenerateKey(rand.Reader, 4096)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"acme: Failed to generate private key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcsrReq := &x509.CertificateRequest{\n\t\tSignatureAlgorithm: x509.SHA256WithRSA,\n\t\tPublicKeyAlgorithm: x509.RSA,\n\t\tPublicKey:          key.Public(),\n\t\tSubject: pkix.Name{\n\t\t\tCommonName: domains[0],\n\t\t},\n\t\tDNSNames: domains,\n\t}\n\n\tcsr, err = x509.CreateCertificateRequest(rand.Reader, csrReq, key)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"acme: Failed to create certificate request\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcertKeyByte := x509.MarshalPKCS1PrivateKey(key)\n\n\tcertKeyBlock := &pem.Block{\n\t\tType:  \"RSA PRIVATE KEY\",\n\t\tBytes: certKeyByte,\n\t}\n\n\tkeyPem = pem.EncodeToMemory(certKeyBlock)\n\n\treturn\n}\n\nfunc newEcCsr(domains []string) (csr []byte, keyPem []byte, err error) {\n\tkey, err := ecdsa.GenerateKey(\n\t\telliptic.P384(),\n\t\trand.Reader,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"acme: Failed to generate private key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcsrReq := &x509.CertificateRequest{\n\t\tSignatureAlgorithm: x509.ECDSAWithSHA256,\n\t\tPublicKeyAlgorithm: x509.ECDSA,\n\t\tPublicKey:          key.Public(),\n\t\tSubject: pkix.Name{\n\t\t\tCommonName: domains[0],\n\t\t},\n\t\tDNSNames: domains,\n\t}\n\n\tcsr, err = x509.CreateCertificateRequest(rand.Reader, csrReq, key)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"acme: Failed to create certificate request\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcertKeyByte, err := x509.MarshalECPrivateKey(key)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"acme: Failed to parse private key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcertKeyBlock := &pem.Block{\n\t\tType:  \"EC PRIVATE KEY\",\n\t\tBytes: certKeyByte,\n\t}\n\n\tkeyPem = pem.EncodeToMemory(certKeyBlock)\n\n\treturn\n}\n"
  },
  {
    "path": "advisory/advisory.go",
    "content": "package advisory\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nvar client = &http.Client{\n\tTimeout: 10 * time.Second,\n}\n\ntype Advisory struct {\n\tId              string    `bson:\"id\" json:\"id\"`\n\tTimestamp       time.Time `bson:\"timestamp\" json:\"timestamp\"`\n\tStatus          string    `bson:\"status\" json:\"status\"`\n\tDescription     string    `bson:\"description\" json:\"description\"`\n\tScore           float64   `bson:\"score\" json:\"score\"`\n\tSeverity        string    `bson:\"severity\" json:\"severity\"`\n\tVector          string    `bson:\"vector\" json:\"vector\"`\n\tComplexity      string    `bson:\"complexity\" json:\"complexity\"`\n\tPrivileges      string    `bson:\"privileges\" json:\"privileges\"`\n\tInteraction     string    `bson:\"interaction\" json:\"interaction\"`\n\tScope           string    `bson:\"scope\" json:\"scope\"`\n\tConfidentiality string    `bson:\"confidentiality\" json:\"confidentiality\"`\n\tIntegrity       string    `bson:\"integrity\" json:\"integrity\"`\n\tAvailability    string    `bson:\"availability\" json:\"availability\"`\n}\n\nfunc (a *Advisory) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif a.Id == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"id_required\",\n\t\t\tMessage: \"Missing required advisory ID\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Timestamp.IsZero() {\n\t\ta.Timestamp = time.Now()\n\t}\n\n\tif !cveIdReg.MatchString(a.Id) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"id_invalid\",\n\t\t\tMessage: \"Invalid advisory ID\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Status != \"\" && !ValidStatuses.Contains(a.Status) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_status\",\n\t\t\tMessage: \"Invalid advisory status\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Score < 0 || a.Score > 10 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_score\",\n\t\t\tMessage: \"Advisory score must be between 0 and 10\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Severity != \"\" && !ValidSeverities.Contains(a.Severity) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_severity\",\n\t\t\tMessage: \"Invalid advisory severity\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Vector != \"\" && !ValidVectors.Contains(a.Vector) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_vector\",\n\t\t\tMessage: \"Invalid advisory attack vector\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Complexity != \"\" && !ValidComplexities.Contains(a.Complexity) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_complexity\",\n\t\t\tMessage: \"Invalid advisory attack complexity\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Privileges != \"\" && !ValidPrivileges.Contains(a.Privileges) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_privileges\",\n\t\t\tMessage: \"Invalid advisory privileges required\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Interaction != \"\" && !ValidInteractions.Contains(a.Interaction) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_interaction\",\n\t\t\tMessage: \"Invalid advisory user interaction\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Scope != \"\" && !ValidScopes.Contains(a.Scope) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_scope\",\n\t\t\tMessage: \"Invalid advisory scope\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Confidentiality != \"\" &&\n\t\t!ValidImpacts.Contains(a.Confidentiality) {\n\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_confidentiality\",\n\t\t\tMessage: \"Invalid advisory confidentiality impact\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Integrity != \"\" && !ValidImpacts.Contains(a.Integrity) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_integrity\",\n\t\t\tMessage: \"Invalid advisory integrity impact\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Availability != \"\" && !ValidImpacts.Contains(a.Availability) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_availability\",\n\t\t\tMessage: \"Invalid advisory availability impact\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (a *Advisory) IsFresh() bool {\n\tif a == nil {\n\t\treturn false\n\t}\n\n\tif (a.Status == Analyzed || a.Status == Deferred) &&\n\t\ttime.Since(a.Timestamp) < time.Duration(\n\t\t\tsettings.Telemetry.NvdFinalTtl)*time.Second {\n\n\t\treturn true\n\t}\n\n\tif time.Since(a.Timestamp) < time.Duration(\n\t\tsettings.Telemetry.NvdTtl)*time.Second {\n\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (a *Advisory) Commit(db *database.Database) (err error) {\n\tcoll := db.Advisories()\n\n\t_, err = coll.UpdateOne(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": a.Id,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"timestamp\":       a.Timestamp,\n\t\t\t\t\"status\":          a.Status,\n\t\t\t\t\"description\":     a.Description,\n\t\t\t\t\"score\":           a.Score,\n\t\t\t\t\"severity\":        a.Severity,\n\t\t\t\t\"vector\":          a.Vector,\n\t\t\t\t\"complexity\":      a.Complexity,\n\t\t\t\t\"privileges\":      a.Privileges,\n\t\t\t\t\"interaction\":     a.Interaction,\n\t\t\t\t\"scope\":           a.Scope,\n\t\t\t\t\"confidentiality\": a.Confidentiality,\n\t\t\t\t\"integrity\":       a.Integrity,\n\t\t\t\t\"availability\":    a.Availability,\n\t\t\t},\n\t\t},\n\t\toptions.UpdateOne().SetUpsert(true),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "advisory/constants.go",
    "content": "package advisory\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\nconst (\n\tNone      = \"none\"\n\tLow       = \"low\"\n\tMedium    = \"medium\"\n\tHigh      = \"high\"\n\tCritical  = \"critical\"\n\tNetwork   = \"network\"\n\tAdjacent  = \"adjacent\"\n\tLocal     = \"local\"\n\tPhysical  = \"physical\"\n\tRequired  = \"required\"\n\tUnchanged = \"unchanged\"\n\tChanged   = \"changed\"\n\n\tAnalyzed         = \"analyzed\"\n\tAwaitingAnalysis = \"awaiting_analysis\"\n\tRejected         = \"rejected\"\n\tUndergoing       = \"undergoing_analysis\"\n\tModified         = \"modified\"\n\tDeferred         = \"deferred\"\n\tPending          = \"pending\"\n\n\tnvdApi = \"https://services.nvd.nist.gov/rest/json/cves/2.0\"\n)\n\nvar (\n\tcveIdReg = regexp.MustCompile(`^CVE-\\d{4}-\\d{4,}$`)\n\n\tValidStatuses = set.NewSet(\n\t\tAnalyzed,\n\t\tAwaitingAnalysis,\n\t\tRejected,\n\t\tUndergoing,\n\t\tModified,\n\t\tDeferred,\n\t)\n\n\tValidSeverities = set.NewSet(\n\t\tNone,\n\t\tLow,\n\t\tMedium,\n\t\tHigh,\n\t\tCritical,\n\t)\n\n\tValidVectors = set.NewSet(\n\t\tNetwork,\n\t\tAdjacent,\n\t\tLocal,\n\t\tPhysical,\n\t)\n\n\tValidComplexities = set.NewSet(\n\t\tLow,\n\t\tHigh,\n\t)\n\n\tValidPrivileges = set.NewSet(\n\t\tNone,\n\t\tLow,\n\t\tHigh,\n\t)\n\n\tValidInteractions = set.NewSet(\n\t\tNone,\n\t\tRequired,\n\t)\n\n\tValidScopes = set.NewSet(\n\t\tUnchanged,\n\t\tChanged,\n\t)\n\n\tValidImpacts = set.NewSet(\n\t\tNone,\n\t\tLow,\n\t\tHigh,\n\t)\n)\n"
  },
  {
    "path": "advisory/utils.go",
    "content": "package advisory\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nvar (\n\tlastCall time.Time\n)\n\ntype nvdCvssData struct {\n\tVectorString          string  `json:\"vectorString\"`\n\tBaseScore             float64 `json:\"baseScore\"`\n\tBaseSeverity          string  `json:\"baseSeverity\"`\n\tAttackVector          string  `json:\"attackVector\"`\n\tAttackComplexity      string  `json:\"attackComplexity\"`\n\tPrivilegesRequired    string  `json:\"privilegesRequired\"`\n\tUserInteraction       string  `json:\"userInteraction\"`\n\tScope                 string  `json:\"scope\"`\n\tConfidentialityImpact string  `json:\"confidentialityImpact\"`\n\tIntegrityImpact       string  `json:\"integrityImpact\"`\n\tAvailabilityImpact    string  `json:\"availabilityImpact\"`\n}\n\ntype nvdCvssMetric struct {\n\tType     string      `json:\"type\"`\n\tCvssData nvdCvssData `json:\"cvssData\"`\n}\n\ntype nvdMetrics struct {\n\tCvssMetricV31 []nvdCvssMetric `json:\"cvssMetricV31\"`\n}\n\ntype nvdDescription struct {\n\tLang  string `json:\"lang\"`\n\tValue string `json:\"value\"`\n}\n\ntype nvdCve struct {\n\tID           string           `json:\"id\"`\n\tVulnStatus   string           `json:\"vulnStatus\"`\n\tDescriptions []nvdDescription `json:\"descriptions\"`\n\tMetrics      nvdMetrics       `json:\"metrics\"`\n}\n\ntype nvdVulnerability struct {\n\tCve nvdCve `json:\"cve\"`\n}\n\ntype nvdResponse struct {\n\tTotalResults    int                `json:\"totalResults\"`\n\tVulnerabilities []nvdVulnerability `json:\"vulnerabilities\"`\n}\n\nfunc normalizeStatus(status string) string {\n\tswitch status {\n\tcase \"Analyzed\":\n\t\treturn Analyzed\n\tcase \"Awaiting Analysis\":\n\t\treturn AwaitingAnalysis\n\tcase \"Rejected\":\n\t\treturn Rejected\n\tcase \"Undergoing Analysis\":\n\t\treturn Undergoing\n\tcase \"Modified\":\n\t\treturn Modified\n\tcase \"Deferred\":\n\t\treturn Deferred\n\tdefault:\n\t\treturn strings.ToLower(strings.ReplaceAll(status, \" \", \"_\"))\n\t}\n}\n\nfunc normalizeValue(val string) string {\n\tswitch strings.ToUpper(val) {\n\tcase \"NONE\":\n\t\treturn None\n\tcase \"LOW\":\n\t\treturn Low\n\tcase \"MEDIUM\":\n\t\treturn Medium\n\tcase \"HIGH\":\n\t\treturn High\n\tcase \"CRITICAL\":\n\t\treturn Critical\n\tcase \"NETWORK\":\n\t\treturn Network\n\tcase \"ADJACENT_NETWORK\", \"ADJACENT\":\n\t\treturn Adjacent\n\tcase \"LOCAL\":\n\t\treturn Local\n\tcase \"PHYSICAL\":\n\t\treturn Physical\n\tcase \"REQUIRED\":\n\t\treturn Required\n\tcase \"UNCHANGED\":\n\t\treturn Unchanged\n\tcase \"CHANGED\":\n\t\treturn Changed\n\tdefault:\n\t\treturn strings.ToLower(val)\n\t}\n}\n\nfunc getOne(db *database.Database, query *bson.M) (adv *Advisory, err error) {\n\tcoll := db.Advisories()\n\tadv = &Advisory{}\n\n\terr = coll.FindOne(db, query).Decode(adv)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc getOneNvd(db *database.Database, cveId string) (\n\tadv *Advisory, err error) {\n\n\treq, err := http.NewRequest(\"GET\", nvdApi, nil)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"advisory: Failed to create request\"),\n\t\t}\n\t\treturn\n\t}\n\n\tquery := req.URL.Query()\n\tquery.Set(\"cveId\", cveId)\n\treq.URL.RawQuery = query.Encode()\n\n\tnvdApiKey := settings.Telemetry.NvdApiKey\n\tif nvdApiKey != \"\" {\n\t\treq.Header.Set(\"apiKey\", nvdApiKey)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"advisory: Request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\"advisory: Bad status status %d\", resp.StatusCode),\n\t\t}\n\t\treturn\n\t}\n\n\tnvdResp := &nvdResponse{}\n\terr = json.NewDecoder(resp.Body).Decode(nvdResp)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"advisory: Failed to decode response\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif nvdResp.TotalResults == 0 || len(nvdResp.Vulnerabilities) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"advisory: Not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcve := nvdResp.Vulnerabilities[0].Cve\n\n\tadv = &Advisory{\n\t\tId:        strings.ToUpper(cve.ID),\n\t\tTimestamp: time.Now(),\n\t\tStatus:    normalizeStatus(cve.VulnStatus),\n\t}\n\n\tfor _, desc := range cve.Descriptions {\n\t\tif desc.Lang == \"en\" {\n\t\t\tadv.Description = desc.Value\n\t\t\tbreak\n\t\t}\n\t}\n\n\tmetrics := cve.Metrics.CvssMetricV31\n\tif len(metrics) > 0 {\n\t\tvar cvss *nvdCvssMetric\n\n\t\tfor i := range metrics {\n\t\t\tif metrics[i].Type == \"Primary\" {\n\t\t\t\tcvss = &metrics[i]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif cvss == nil {\n\t\t\tcvss = &metrics[0]\n\t\t}\n\n\t\tadv.Score = cvss.CvssData.BaseScore\n\t\tadv.Severity = normalizeValue(cvss.CvssData.BaseSeverity)\n\t\tadv.Vector = normalizeValue(cvss.CvssData.AttackVector)\n\t\tadv.Complexity = normalizeValue(cvss.CvssData.AttackComplexity)\n\t\tadv.Privileges = normalizeValue(cvss.CvssData.PrivilegesRequired)\n\t\tadv.Interaction = normalizeValue(cvss.CvssData.UserInteraction)\n\t\tadv.Scope = normalizeValue(cvss.CvssData.Scope)\n\t\tadv.Confidentiality = normalizeValue(\n\t\t\tcvss.CvssData.ConfidentialityImpact)\n\t\tadv.Integrity = normalizeValue(cvss.CvssData.IntegrityImpact)\n\t\tadv.Availability = normalizeValue(cvss.CvssData.AvailabilityImpact)\n\t}\n\n\terrData, err := adv.Validate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\tif errData != nil {\n\t\terr = errData.GetError()\n\t\treturn\n\t}\n\n\terr = adv.Commit(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOneLimit(db *database.Database, cveId string) (\n\tadv *Advisory, err error) {\n\n\tadv, err = getOne(db, &bson.M{\n\t\t\"_id\": cveId,\n\t})\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tadv = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif adv.IsFresh() {\n\t\treturn\n\t}\n\n\tsince := time.Since(lastCall)\n\tvar limit time.Duration\n\tif settings.Telemetry.NvdApiKey != \"\" {\n\t\tlimit = time.Duration(settings.Telemetry.NvdApiAuthLimit) * time.Second\n\t} else {\n\t\tlimit = time.Duration(settings.Telemetry.NvdApiLimit) * time.Second\n\t}\n\tif since < limit {\n\t\ttime.Sleep(limit - since)\n\t}\n\tlastCall = time.Now()\n\n\tadv, err = getOneNvd(db, cveId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, cveId string) (adv *Advisory, err error) {\n\tadv, err = getOne(db, &bson.M{\n\t\t\"_id\": cveId,\n\t})\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tadv = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif adv.IsFresh() {\n\t\treturn\n\t}\n\n\tadv, err = getOneNvd(db, cveId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, advId bson.ObjectID) (err error) {\n\tcoll := db.Advisories()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": advId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "agent/agent.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/agent/constants\"\n\t\"github.com/pritunl/pritunl-cloud/agent/imds\"\n\t\"github.com/pritunl/pritunl-cloud/agent/utils\"\n\t\"github.com/pritunl/pritunl-cloud/engine\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\tpritunl_utils \"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\nconst help = `\nUsage: pci COMMAND\n\nCommands:\n  get          Get value from IMDS\n  image        Sanitize host files and initiate shutdown for imaging\n  version      Show version\n`\n\nvar (\n\tdaemon = flag.Bool(\"daemon\", false, \"Fork agent to background\")\n)\n\nfunc main() {\n\tdefer func() {\n\t\tpanc := recover()\n\t\tif panc != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\"panic\": panc,\n\t\t\t}).Error(\"agent: Panic in main\")\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tos.Exit(1)\n\t\t}\n\t}()\n\tflag.Usage = func() {\n\t\tfmt.Printf(help)\n\t}\n\n\tflag.Parse()\n\n\tlogger.Init(\n\t\tlogger.SetTimeFormat(\"\"),\n\t)\n\n\tlogger.AddHandler(func(record *logger.Record) {\n\t\tfmt.Print(record.String())\n\t})\n\n\tcommand := flag.Arg(0)\n\tif command == \"engine\" || command == \"agent\" {\n\t\tenvStateData := os.Getenv(\"IMDS_STATE\")\n\t\tenvStateDatas := strings.Split(envStateData, \":\")\n\t\tif len(envStateDatas) != 2 {\n\t\t\tfmt.Println(\"pritunl-cloud-agent: Invalid state\")\n\t\t\tos.Exit(1)\n\t\t\treturn\n\t\t}\n\t\tstage := envStateDatas[0]\n\t\tenvState := envStateDatas[1]\n\n\t\tif stage == \"run\" {\n\t\t\terr := imds.SetState(envState)\n\t\t\tif err != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"agent: Failed to write imds state\")\n\t\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tconfState := imds.GetState()\n\n\t\t\tif envState != \"\" && confState != envState {\n\t\t\t\tfmt.Println(\"pritunl-cloud-agent: Waiting for state\")\n\t\t\t\tos.Exit(0)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif *daemon {\n\t\t\terr := daemonFork()\n\t\t\tif err != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"agent: Failed to fork to background\")\n\t\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tswitch command {\n\tcase \"get\":\n\t\tids := &imds.Imds{}\n\n\t\terr := ids.Init(nil)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Initialize failed\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\t\tdefer ids.Close()\n\n\t\tval, err := ids.Get(flag.Arg(1))\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Get imds failed\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Print(val)\n\n\t\tbreak\n\tcase \"engine\":\n\t\teng := &engine.Engine{\n\t\t\tOnStatus: imds.SetStatus,\n\t\t}\n\t\tids := &imds.Imds{}\n\n\t\terr := ids.Init(eng)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to init imds\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\t\tdefer ids.Close()\n\n\t\terr = ids.OpenLog()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = ids.SyncReady(5 * time.Minute)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to sync imds initial\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\timage := false\n\t\tphase := engine.Reboot\n\t\tswitch flag.Arg(1) {\n\t\tcase engine.Image:\n\t\t\timage = true\n\t\t\tphase = engine.Initial\n\t\t\tbreak\n\t\tcase engine.Initial:\n\t\t\tphase = engine.Initial\n\t\t\tbreak\n\t\t}\n\n\t\terr = eng.Init()\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to init engine\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tdata, err := utils.Read(\"/etc/pritunl-deploy.md\")\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to read deploy spec\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tblocks, err := engine.Parse(data)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to parse deploy spec\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tids.RunSync(image)\n\n\t\trunStatus := types.Running\n\t\tfatal, err := eng.Run(phase, blocks)\n\t\tif err != nil {\n\t\t\trunStatus = types.Fault\n\t\t\tif fatal {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"agent: Fatal initial engine run error\")\n\t\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"agent: Non-fatal initial engine run error\")\n\t\t\t\terr = nil\n\t\t\t}\n\t\t}\n\n\t\tif !image {\n\t\t\tids.SetInitialized()\n\n\t\t\terr = ids.SyncStatus(runStatus)\n\t\t\tif err != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"agent: Failed to sync status\")\n\t\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = ids.Wait()\n\t\t\tif err != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"agent: Failed to run\")\n\t\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\ttime.Sleep(500 * time.Millisecond)\n\n\t\t_, err = ids.Sync()\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to sync\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tbreak\n\tcase \"agent\":\n\t\tids := &imds.Imds{}\n\n\t\terr := ids.Init(nil)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to init imds\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\t\tdefer ids.Close()\n\n\t\terr = ids.OpenLog()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = ids.SyncReady(5 * time.Minute)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to sync imds initial\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tids.RunSync(false)\n\t\tids.SetInitialized()\n\n\t\terr = ids.SyncStatus(types.Running)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to sync status\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\terr = ids.Wait()\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to run\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(500 * time.Millisecond)\n\n\t\t_, err = ids.Sync()\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to sync\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tbreak\n\tcase \"image\":\n\t\tids := &imds.Imds{}\n\n\t\terr := ids.Init(nil)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Iniatilize failed\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\terr = utils.Sanitize()\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Sanitize failed\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\terr = ids.SyncStatus(types.Imaged)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Sync status failed\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\terr = utils.SanitizeImds()\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Sanitize imds failed\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tbreak\n\tcase \"status\":\n\t\tmem, err := pritunl_utils.GetMemInfo()\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"imds: Failed to get memory\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Println(\"Memory Information:\")\n\t\tfmt.Printf(\"  Total Memory:           %d KB\\n\", mem.Total)\n\t\tfmt.Printf(\"  Free Memory:            %d KB\\n\", mem.Free)\n\t\tfmt.Printf(\"  Available Memory:       %d KB\\n\", mem.Available)\n\t\tfmt.Printf(\"  Buffers:                %d KB\\n\", mem.Buffers)\n\t\tfmt.Printf(\"  Cached:                 %d KB\\n\", mem.Cached)\n\t\tfmt.Printf(\"  Used Memory:            %d KB\\n\", mem.Used)\n\t\tfmt.Printf(\"  Used Percentage:        %.2f%%\\n\", mem.UsedPercent)\n\t\tfmt.Printf(\"  Dirty:                  %d KB\\n\", mem.Dirty)\n\t\tfmt.Println(\"\\nSwap Information:\")\n\t\tfmt.Printf(\"  Swap Total:             %d KB\\n\", mem.SwapTotal)\n\t\tfmt.Printf(\"  Swap Free:              %d KB\\n\", mem.SwapFree)\n\t\tfmt.Printf(\"  Swap Used:              %d KB\\n\", mem.SwapUsed)\n\t\tfmt.Printf(\"  Swap Used Percentage:   %.2f%%\\n\", mem.SwapUsedPercent)\n\t\tfmt.Println(\"\\nHugePages Information:\")\n\t\tfmt.Printf(\"  HugePages Total:        %d\\n\", mem.HugePagesTotal)\n\t\tfmt.Printf(\"  HugePages Free:         %d\\n\", mem.HugePagesFree)\n\t\tfmt.Printf(\"  HugePages Reserved:     %d\\n\", mem.HugePagesReserved)\n\t\tfmt.Printf(\"  HugePages Used:         %d\\n\", mem.HugePagesUsed)\n\t\tfmt.Printf(\"  HugePages Used Percent: %.2f%%\\n\", mem.HugePagesUsedPercent)\n\t\tfmt.Printf(\"  HugePage Size:          %d KB\\n\", mem.HugePageSize)\n\n\t\tload, err := pritunl_utils.LoadAverage()\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"imds: Failed to get load\")\n\t\t\tutils.DelayExit(1, 1*time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Println(\"\\nLoad Average Information:\")\n\t\tfmt.Printf(\"  CPU Units:              %d\\n\", load.CpuUnits)\n\t\tfmt.Printf(\"  Load Average (1 min):   %.2f%%\\n\", load.Load1)\n\t\tfmt.Printf(\"  Load Average (5 min):   %.2f%%\\n\", load.Load5)\n\t\tfmt.Printf(\"  Load Average (15 min):  %.2f%%\\n\", load.Load15)\n\t\tbreak\n\tcase \"version\":\n\t\tfmt.Printf(\"pci v%s\\n\", constants.Version)\n\t\tbreak\n\tdefault:\n\t\tfmt.Printf(help)\n\t}\n\n\treturn\n}\n\nfunc daemonFork() (err error) {\n\tfmt.Println(\"pritunl-cloud-agent: Forking daemon process\")\n\n\tapp, err := os.Executable()\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"agent: Failed to get executable path\"),\n\t\t}\n\t\treturn\n\t}\n\n\targs := []string{app}\n\targs = append(args, flag.Args()...)\n\n\tdevNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"agent: Failed to open /dev/null\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = os.StartProcess(app, args, &os.ProcAttr{\n\t\tFiles: []*os.File{nil, devNull, devNull},\n\t\tSys: &syscall.SysProcAttr{\n\t\t\tSetsid: true,\n\t\t},\n\t})\n\tif err != nil {\n\t\tdevNull.Close()\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"agent: Failed to fork agent process\"),\n\t\t}\n\t\treturn\n\t}\n\n\tos.Exit(0)\n\treturn\n}\n"
  },
  {
    "path": "agent/constants/constants.go",
    "content": "package constants\n\nconst (\n\tVersion      = \"1.0.3248.95\"\n\tImdsConfPath = \"/etc/pritunl-imds.json\"\n\tImdsLogPath  = \"/var/log/pritunl-imds.log\"\n)\n"
  },
  {
    "path": "agent/imds/imds.go",
    "content": "package imds\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/agent/constants\"\n\t\"github.com/pritunl/pritunl-cloud/agent/logging\"\n\t\"github.com/pritunl/pritunl-cloud/engine\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/telemetry\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/tools/logger\"\n\t\"github.com/pritunl/tools/set\"\n)\n\nvar (\n\tclient = &http.Client{\n\t\tTimeout: 10 * time.Second,\n\t}\n\tcurSyncHash uint32\n)\n\ntype Imds struct {\n\tAddress     string              `json:\"address\"`\n\tPort        int                 `json:\"port\"`\n\tSecret      string              `json:\"secret\"`\n\tState       string              `json:\"state\"`\n\tengine      *engine.Engine      `json:\"-\"`\n\tinitialized bool                `json:\"-\"`\n\twaiter      sync.WaitGroup      `json:\"-\"`\n\tjournals    map[string]*Journal `json:\"-\"`\n\tsyncLock    sync.Mutex          `json:\"-\"`\n\tlogger      *logging.Redirect   `json:\"-\"`\n}\n\nfunc (m *Imds) NewRequest(method, pth string, data interface{}) (\n\treq *http.Request, err error) {\n\n\tu := &url.URL{}\n\tu.Scheme = \"http\"\n\tu.Host = fmt.Sprintf(\"%s:%d\", m.Address, m.Port)\n\tu.Path = pth\n\n\tvar body io.Reader\n\tif data != nil {\n\t\treqDataBuf := &bytes.Buffer{}\n\t\terr = json.NewEncoder(reqDataBuf).Encode(data)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"agent: Failed to parse request data\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tbody = reqDataBuf\n\t}\n\n\treq, err = http.NewRequest(method, u.String(), body)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"agent: Failed to create imds request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"pritunl-imds\")\n\treq.Header.Set(\"Auth-Token\", m.Secret)\n\tif data != nil {\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t}\n\n\treturn\n}\n\nfunc (m *Imds) Get(query string) (val string, err error) {\n\tquery = strings.TrimPrefix(query, \"+\")\n\n\treq, err := m.NewRequest(\"GET\", \"/query\"+query, nil)\n\n\tresp, e := client.Do(req)\n\tif e != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(e, \"agent: Imds request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tbody := \"\"\n\t\tdata, _ := ioutil.ReadAll(resp.Body)\n\t\tif data != nil {\n\t\t\tbody = string(data)\n\t\t}\n\n\t\terrData := &errortypes.ErrorData{}\n\t\terr = json.Unmarshal(data, errData)\n\t\tif err != nil || errData.Error == \"\" {\n\t\t\terrData = nil\n\t\t}\n\n\t\tif errData != nil && errData.Message != \"\" {\n\t\t\tbody = errData.Message\n\t\t}\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"agent: Imds server get error %d - %s\",\n\t\t\t\tresp.StatusCode, body),\n\t\t}\n\t\treturn\n\t}\n\n\tdata, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"agent: Imds failed to read body\"),\n\t\t}\n\t\treturn\n\t}\n\n\tval = string(data)\n\n\treturn\n}\n\ntype SyncResp struct {\n\tSpec     string           `json:\"spec\"`\n\tHash     uint32           `json:\"hash\"`\n\tJournals []*types.Journal `json:\"journals\"`\n}\n\nfunc (m *Imds) SyncReady(timeout time.Duration) (err error) {\n\tctx, cancel := context.WithTimeout(context.Background(), timeout)\n\tdefer cancel()\n\n\tticker := time.NewTicker(200 * time.Millisecond)\n\tdefer ticker.Stop()\n\n\tvar lastErr error\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tif lastErr != nil {\n\t\t\t\terr = lastErr\n\t\t\t} else {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.New(\"agent: Initial config timeout\"),\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\tready, e := m.Sync()\n\t\t\tif e != nil {\n\t\t\t\tlastErr = e\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !ready {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (m *Imds) Sync() (ready bool, err error) {\n\tm.syncLock.Lock()\n\tdefer m.syncLock.Unlock()\n\n\tdata, err := m.GetState(curSyncHash)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif m.logger != nil {\n\t\tdata.Output = m.logger.GetOutput()\n\t}\n\n\tif m.journals != nil {\n\t\tjournals := map[string][]*types.Entry{}\n\t\tfor _, jrnl := range m.journals {\n\t\t\tjournals[jrnl.Key] = jrnl.Handler.GetOutput()\n\t\t}\n\t\tif len(journals) > 0 {\n\t\t\tdata.Journals = journals\n\t\t}\n\t} else {\n\t\tm.journals = map[string]*Journal{}\n\t}\n\n\treq, err := m.NewRequest(\"PUT\", \"/sync\", data)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"agent: Imds request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tbody := \"\"\n\t\tdata, _ := ioutil.ReadAll(resp.Body)\n\t\tif data != nil {\n\t\t\tbody = string(data)\n\t\t}\n\n\t\terrData := &errortypes.ErrorData{}\n\t\terr = json.Unmarshal(data, errData)\n\t\tif err != nil || errData.Error == \"\" {\n\t\t\terrData = nil\n\t\t}\n\n\t\tif errData != nil && errData.Message != \"\" {\n\t\t\tbody = errData.Message\n\t\t}\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\"agent: Imds server sync error %d - %s\",\n\t\t\t\tresp.StatusCode, body),\n\t\t}\n\t\treturn\n\t}\n\n\trespData := &SyncResp{}\n\terr = json.NewDecoder(resp.Body).Decode(respData)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"agent: Failed to decode imds sync resp\"),\n\t\t}\n\t\treturn\n\t}\n\n\tready = true\n\tif respData.Hash == 0 {\n\t\tready = false\n\t} else if curSyncHash == 0 {\n\t\tcurSyncHash = respData.Hash\n\t} else if respData.Hash != curSyncHash && respData.Spec != \"\" &&\n\t\tm.engine != nil && m.initialized {\n\n\t\tcurSyncHash = respData.Hash\n\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"spec_len\": len(respData.Spec),\n\t\t\t\"hash\":     int(respData.Hash),\n\t\t}).Info(\"agent: Queuing engine reload\")\n\n\t\tm.engine.Queue(respData.Spec)\n\t}\n\n\tactiveJournals := set.NewSet()\n\tfor unit := range m.journals {\n\t\tactiveJournals.Add(unit)\n\t}\n\n\tfor _, jrnlConf := range respData.Journals {\n\t\tif activeJournals.Contains(jrnlConf.Key) {\n\t\t\tactiveJournals.Remove(jrnlConf.Key)\n\t\t\tjrnl := m.journals[jrnlConf.Key]\n\t\t\tif jrnl != nil {\n\t\t\t\tif jrnl.Index != jrnlConf.Index ||\n\t\t\t\t\tjrnl.Key != jrnlConf.Key ||\n\t\t\t\t\tjrnl.Type != jrnlConf.Type ||\n\t\t\t\t\tjrnl.Unit != jrnlConf.Unit ||\n\t\t\t\t\tjrnl.Path != jrnlConf.Path {\n\n\t\t\t\t\tjrnl.Close()\n\t\t\t\t\tdelete(m.journals, jrnlConf.Key)\n\t\t\t\t} else {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tjrnl := &Journal{\n\t\t\tIndex: jrnlConf.Index,\n\t\t\tKey:   jrnlConf.Key,\n\t\t\tType:  jrnlConf.Type,\n\t\t\tUnit:  jrnlConf.Unit,\n\t\t\tPath:  jrnlConf.Path,\n\t\t}\n\n\t\tif jrnl.Type == \"file\" {\n\t\t\tjrnl.Handler = logging.NewFile(jrnlConf.Path)\n\t\t} else if jrnl.Type == \"systemd\" {\n\t\t\tjrnl.Handler = logging.NewSystemd(jrnlConf.Unit)\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\n\t\terr = jrnl.Open()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tm.journals[jrnlConf.Key] = jrnl\n\t}\n\n\tfor jrnlKeyInf := range activeJournals.Iter() {\n\t\tjrnlKey := jrnlKeyInf.(string)\n\t\tjrnl := m.journals[jrnlKey]\n\t\tif jrnl != nil {\n\t\t\tjrnl.Close()\n\t\t\tdelete(m.journals, jrnlKey)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (m *Imds) SetInitialized() {\n\tm.initialized = true\n\tif m.engine != nil {\n\t\tm.engine.StartRunner()\n\t}\n}\n\nfunc (m *Imds) RunSync(fast bool) {\n\tm.waiter.Add(1)\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tpanc := recover()\n\t\t\tif panc != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\t\"panic\": panc,\n\t\t\t\t}).Error(\"sync: Panic in telemetry\")\n\t\t\t}\n\t\t}()\n\n\t\tfor {\n\t\t\ttelemetry.Refresh()\n\t\t\ttime.Sleep(1 * time.Minute)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tdefer m.waiter.Done()\n\n\t\tfor {\n\t\t\tfunc() {\n\t\t\t\tdefer func() {\n\t\t\t\t\tpanc := recover()\n\t\t\t\t\tif panc != nil {\n\t\t\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\t\t\t\"panic\": panc,\n\t\t\t\t\t\t}).Error(\"agent: Panic in sync\")\n\t\t\t\t\t\ttime.Sleep(3 * time.Second)\n\t\t\t\t\t\tos.Exit(1)\n\t\t\t\t\t}\n\t\t\t\t}()\n\n\t\t\t\t_, err := m.Sync()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\t\"error\": err,\n\t\t\t\t\t}).Error(\"agent: Failed to sync\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tif fast {\n\t\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\t} else {\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (m *Imds) SyncStatus(status string) (err error) {\n\tSetStatus(status)\n\n\t_, err = m.Sync()\n\tif err != nil {\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"status\": status,\n\t\t\t\"error\":  err,\n\t\t}).Error(\"agent: Failed to sync status\")\n\t}\n\n\treturn\n}\n\nfunc (m *Imds) Wait() (err error) {\n\tm.waiter.Wait()\n\n\treturn\n}\n\nfunc (m *Imds) Init(eng *engine.Engine) (err error) {\n\tm.engine = eng\n\n\tconfData, err := utils.Read(constants.ImdsConfPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = json.Unmarshal([]byte(confData), m)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"agent: Failed to unmarshal imds conf\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (m *Imds) OpenLog() (err error) {\n\tm.logger = &logging.Redirect{}\n\n\terr = m.logger.Open()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (m *Imds) Close() {\n\tif m.logger != nil {\n\t\tm.logger.Close()\n\t}\n}\n"
  },
  {
    "path": "agent/imds/journal.go",
    "content": "package imds\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/agent/logging\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\ntype Journal struct {\n\tIndex   int32           `json:\"-\"`\n\tKey     string          `json:\"-\"`\n\tType    string          `json:\"-\"`\n\tUnit    string          `json:\"-\"`\n\tPath    string          `json:\"-\"`\n\tHandler logging.Handler `json:\"-\"`\n}\n\nfunc (j *Journal) Open() (err error) {\n\tlogger.WithFields(logger.Fields{\n\t\t\"index\": j.Index,\n\t\t\"key\":   j.Key,\n\t\t\"type\":  j.Type,\n\t\t\"unit\":  j.Unit,\n\t\t\"path\":  j.Path,\n\t}).Info(\"agent: Starting journal\")\n\n\terr = j.Handler.Open()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (j *Journal) Close() {\n\tlogger.WithFields(logger.Fields{\n\t\t\"index\": j.Index,\n\t\t\"key\":   j.Key,\n\t\t\"type\":  j.Type,\n\t\t\"unit\":  j.Unit,\n\t\t\"path\":  j.Path,\n\t}).Info(\"agent: Stopping journal\")\n\n\terr := j.Handler.Close()\n\tif err != nil {\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"index\": j.Index,\n\t\t\t\"key\":   j.Key,\n\t\t\t\"type\":  j.Type,\n\t\t\t\"unit\":  j.Unit,\n\t\t\t\"path\":  j.Path,\n\t\t\t\"error\": err,\n\t\t}).Error(\"agent: Error stopping journal\")\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "agent/imds/sync.go",
    "content": "package imds\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/telemetry\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\nvar (\n\tcurStatus     = types.Initializing\n\tcurStatusLock sync.Mutex\n)\n\ntype StateData struct {\n\t*types.State\n}\n\nfunc (m *Imds) GetState(curHash uint32) (data *StateData, err error) {\n\tdata = &StateData{\n\t\t&types.State{},\n\t}\n\n\tdata.Hash = curHash\n\tcurStatusLock.Lock()\n\tdata.Status = curStatus\n\tcurStatusLock.Unlock()\n\n\tmem, err := utils.GetMemInfo()\n\tif err != nil {\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"error\": err,\n\t\t}).Limit(30 * time.Minute).Error(\"imds: Failed to get memory\")\n\t} else {\n\t\tdata.Memory = utils.ToFixed(mem.UsedPercent, 2)\n\t\tdata.HugePages = utils.ToFixed(mem.HugePagesUsedPercent, 2)\n\t}\n\n\tload, err := utils.LoadAverage()\n\tif err != nil {\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"error\": err,\n\t\t}).Limit(30 * time.Minute).Error(\"imds: Failed to get load\")\n\t} else {\n\t\tdata.Load1 = load.Load1\n\t\tdata.Load5 = load.Load5\n\t\tdata.Load15 = load.Load15\n\t}\n\n\tupdates, ok := telemetry.Updates.Get()\n\tif ok {\n\t\tdata.Updates = updates\n\t} else {\n\t\tdata.Updates = nil\n\t}\n\n\treturn\n}\n\nfunc SetStatus(status string) {\n\tcurStatusLock.Lock()\n\tcurStatus = status\n\tcurStatusLock.Unlock()\n}\n"
  },
  {
    "path": "agent/imds/utils.go",
    "content": "package imds\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/agent/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc GetState() string {\n\tconfData, err := utils.Read(constants.ImdsConfPath)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tconf := &Imds{}\n\n\terr = json.Unmarshal([]byte(confData), conf)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\treturn conf.State\n}\n\nfunc SetState(state string) (err error) {\n\tconfData, err := utils.Read(constants.ImdsConfPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tconf := &Imds{}\n\n\terr = json.Unmarshal([]byte(confData), conf)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"agent: Failed to unmarshal imds conf\"),\n\t\t}\n\t\treturn\n\t}\n\n\tconf.State = state\n\n\tdataByt, err := json.Marshal(conf)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"agent: Failed to unmarshal imds conf\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = utils.Write(constants.ImdsConfPath, string(dataByt), 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "agent/logging/file.go",
    "content": "package logging\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"os/exec\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\ntype File struct {\n\tpath   string\n\toutput chan *types.Entry\n\tcmd    *exec.Cmd\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\nfunc (f *File) GetOutput() (entries []*types.Entry) {\n\tfor {\n\t\tselect {\n\t\tcase entry := <-f.output:\n\t\t\tentries = append(entries, entry)\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (f *File) followJournal() (err error) {\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"path\":  f.path,\n\t\t\t\t\"panic\": rec,\n\t\t\t}).Error(\"agent: File follower panic\")\n\t\t}\n\t}()\n\n\tf.cmd = exec.CommandContext(f.ctx, \"tail\",\n\t\t\"-F\", f.path,\n\t)\n\n\tstdout, err := f.cmd.StdoutPipe()\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"agent: Error creating stdout pipe\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = f.cmd.Start()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"agent: Error starting tail\"),\n\t\t}\n\t\treturn\n\t}\n\n\tscanner := bufio.NewScanner(stdout)\n\tbuf := make([]byte, maxCapacity)\n\tscanner.Buffer(buf, maxCapacity)\n\n\tfor scanner.Scan() {\n\t\tselect {\n\t\tcase <-f.ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\n\t\tline := scanner.Text()\n\n\t\ttimestamp := time.Now()\n\t\tlevel := int32(5)\n\n\t\tif strings.Contains(strings.ToLower(line), \"error\") {\n\t\t\tlevel = 3\n\t\t}\n\n\t\tselect {\n\t\tcase f.output <- &types.Entry{\n\t\t\tTimestamp: timestamp,\n\t\t\tLevel:     level,\n\t\t\tMessage:   line,\n\t\t}:\n\t\tdefault:\n\t\t}\n\t\tcontinue\n\t}\n\n\terr = scanner.Err()\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"agent: Error reading tail\"),\n\t\t}\n\t\treturn\n\t}\n\n\tf.cmd.Wait()\n\n\treturn\n}\n\nfunc (f *File) Open() (err error) {\n\tf.output = make(chan *types.Entry, 10000)\n\tf.ctx, f.cancel = context.WithCancel(context.Background())\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tpanc := recover()\n\t\t\tif panc != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\t\"panic\": panc,\n\t\t\t\t}).Error(\"sync: Panic in tail open\")\n\t\t\t}\n\t\t}()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-f.ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\te := f.followJournal()\n\n\t\t\tselect {\n\t\t\tcase <-f.ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\tif e != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"path\":  f.path,\n\t\t\t\t\t\"error\": e,\n\t\t\t\t}).Error(\"agent: Journal follower error, restarting\")\n\t\t\t} else {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"path\": f.path,\n\t\t\t\t}).Info(\"agent: Journal follower exited, restarting\")\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-time.After(3 * time.Second):\n\t\t\tcase <-f.ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (f *File) Close() (err error) {\n\tdefer func() {\n\t\tpanc := recover()\n\t\tif panc != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\"panic\": panc,\n\t\t\t}).Error(\"sync: Panic in journal close\")\n\t\t}\n\t}()\n\n\tif f.cancel != nil {\n\t\tf.cancel()\n\t}\n\n\tif f.cmd != nil && f.cmd.Process != nil {\n\t\tf.cmd.Process.Kill()\n\t}\n\n\tif f.output != nil {\n\t\tclose(f.output)\n\t}\n\n\treturn\n}\n\nfunc NewFile(path string) *File {\n\treturn &File{\n\t\tpath: path,\n\t}\n}\n"
  },
  {
    "path": "agent/logging/handler.go",
    "content": "package logging\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n)\n\ntype Handler interface {\n\tOpen() (err error)\n\tClose() (err error)\n\tGetOutput() (entries []*types.Entry)\n}\n"
  },
  {
    "path": "agent/logging/logging.go",
    "content": "package logging\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/agent/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n)\n\nconst maxCapacity = 128 * 1024\n\ntype Redirect struct {\n\tfile         *os.File\n\twriter       io.Writer\n\torigStout    *os.File\n\torigStderr   *os.File\n\toutput       chan *types.Entry\n\tstdoutReader *os.File\n\tstdoutWriter *os.File\n\tstderrReader *os.File\n\tstderrWriter *os.File\n}\n\nfunc (r *Redirect) GetOutput() (entries []*types.Entry) {\n\tfor {\n\t\tselect {\n\t\tcase entry := <-r.output:\n\t\t\tentries = append(entries, entry)\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (r *Redirect) handleOutput(reader *os.File, level int32) {\n\tscanner := bufio.NewScanner(reader)\n\tbuf := make([]byte, maxCapacity)\n\tscanner.Buffer(buf, maxCapacity)\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\ttimestamp := time.Now()\n\n\t\tfmt.Fprintf(\n\t\t\tr.writer, \"[%s] %s\\n\",\n\t\t\ttimestamp.Format(\"2006-01-02 15:04:05\"),\n\t\t\tline,\n\t\t)\n\n\t\tselect {\n\t\tcase r.output <- &types.Entry{\n\t\t\tTimestamp: timestamp,\n\t\t\tLevel:     level,\n\t\t\tMessage:   line,\n\t\t}:\n\t\tdefault:\n\t\t}\n\t}\n}\n\nfunc (r *Redirect) Open() (err error) {\n\tr.file, err = os.OpenFile(\n\t\tconstants.ImdsLogPath,\n\t\tos.O_APPEND|os.O_CREATE|os.O_WRONLY,\n\t\t0600,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"agent: Failed to create log file\"),\n\t\t}\n\t\treturn\n\t}\n\n\tr.output = make(chan *types.Entry, 10000)\n\tr.writer = io.MultiWriter(r.file, os.Stdout)\n\tr.origStout = os.Stdout\n\tr.origStderr = os.Stderr\n\n\tr.stdoutReader, r.stdoutWriter, err = os.Pipe()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"agent: Failed to create stdout pipe\"),\n\t\t}\n\t\treturn\n\t}\n\n\tr.stderrReader, r.stderrWriter, err = os.Pipe()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"agent: Failed to create stderr pipe\"),\n\t\t}\n\t\treturn\n\t}\n\n\tos.Stdout = r.stdoutWriter\n\tos.Stderr = r.stderrWriter\n\n\tgo r.handleOutput(r.stdoutReader, types.Info)\n\tgo r.handleOutput(r.stderrReader, types.Error)\n\n\treturn\n}\n\nfunc (r *Redirect) Close() (err error) {\n\tos.Stdout = r.origStout\n\tos.Stderr = r.origStderr\n\n\tif r.stdoutWriter != nil {\n\t\tr.stdoutWriter.Close()\n\t}\n\tif r.stdoutReader != nil {\n\t\tr.stdoutReader.Close()\n\t}\n\tif r.stderrWriter != nil {\n\t\tr.stderrWriter.Close()\n\t}\n\tif r.stderrReader != nil {\n\t\tr.stderrReader.Close()\n\t}\n\n\terr = r.file.Close()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"agent: Failed to close log pipe\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "agent/logging/systemd.go",
    "content": "package logging\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"os/exec\"\n\t\"runtime/debug\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/tools/commander\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\ntype Systemd struct {\n\tunit   string\n\toutput chan *types.Entry\n\tcmd    *exec.Cmd\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\ntype journalEntry struct {\n\tMessage   string `json:\"MESSAGE\"`\n\tPriority  string `json:\"PRIORITY\"`\n\tTimestamp string `json:\"__REALTIME_TIMESTAMP\"`\n}\n\nfunc (s *Systemd) GetOutput() (entries []*types.Entry) {\n\tfor {\n\t\tselect {\n\t\tcase entry := <-s.output:\n\t\t\tentries = append(entries, entry)\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (s *Systemd) followJournal() (err error) {\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"unit\":  s.unit,\n\t\t\t\t\"panic\": rec,\n\t\t\t}).Error(\"agent: Journal follower panic\")\n\t\t}\n\t}()\n\n\texistsTime := time.Time{}\n\tfor {\n\t\tresp, _ := commander.Exec(&commander.Opt{\n\t\t\tName: \"systemctl\",\n\t\t\tArgs: []string{\n\t\t\t\t\"status\",\n\t\t\t\ts.unit,\n\t\t\t},\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\n\t\tif resp.ExitCode != 4 {\n\t\t\tif existsTime.IsZero() {\n\t\t\t\texistsTime = time.Now()\n\t\t\t}\n\n\t\t\tif resp.ExitCode == 0 || time.Since(existsTime) > 10*time.Second {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\ttime.Sleep(800 * time.Millisecond)\n\t}\n\n\ts.cmd = exec.CommandContext(s.ctx, \"journalctl\",\n\t\t\"-f\",\n\t\t\"-b\",\n\t\t\"-n\", \"20\",\n\t\t\"-o\", \"json\",\n\t\t\"-u\", s.unit,\n\t)\n\n\tstdout, err := s.cmd.StdoutPipe()\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"agent: Error creating stdout pipe\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = s.cmd.Start()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"agent: Error starting journalctl\"),\n\t\t}\n\t\treturn\n\t}\n\n\tscanner := bufio.NewScanner(stdout)\n\tbuf := make([]byte, maxCapacity)\n\tscanner.Buffer(buf, maxCapacity)\n\n\tfor scanner.Scan() {\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\n\t\tline := scanner.Bytes()\n\n\t\tvar entry journalEntry\n\t\te := json.Unmarshal(line, &entry)\n\t\tif e != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar timestamp time.Time\n\t\tts, e := strconv.ParseInt(entry.Timestamp, 10, 64)\n\t\tif e == nil {\n\t\t\ttimestamp = time.Unix(0, ts*1000)\n\t\t} else {\n\t\t\ttimestamp = time.Now()\n\t\t}\n\n\t\tlevel := int32(5)\n\t\tif entry.Priority != \"\" {\n\t\t\tswitch entry.Priority {\n\t\t\tcase \"0\":\n\t\t\t\tlevel = 1\n\t\t\tcase \"1\", \"2\":\n\t\t\t\tlevel = 2\n\t\t\tcase \"3\":\n\t\t\t\tlevel = 3\n\t\t\tcase \"4\":\n\t\t\t\tlevel = 4\n\t\t\tcase \"5\", \"6\":\n\t\t\t\tlevel = 5\n\t\t\tcase \"7\":\n\t\t\t\tlevel = 6\n\t\t\t}\n\t\t}\n\n\t\tselect {\n\t\tcase s.output <- &types.Entry{\n\t\t\tTimestamp: timestamp,\n\t\t\tLevel:     level,\n\t\t\tMessage:   strings.TrimSuffix(entry.Message, \"\\n\"),\n\t\t}:\n\t\tdefault:\n\t\t}\n\t}\n\n\terr = scanner.Err()\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"agent: Error reading journal\"),\n\t\t}\n\t\treturn\n\t}\n\n\ts.cmd.Wait()\n\n\treturn\n}\n\nfunc (s *Systemd) Open() (err error) {\n\ts.output = make(chan *types.Entry, 10000)\n\ts.ctx, s.cancel = context.WithCancel(context.Background())\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tpanc := recover()\n\t\t\tif panc != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\t\"panic\": panc,\n\t\t\t\t}).Error(\"sync: Panic in journal open\")\n\t\t\t}\n\t\t}()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\te := s.followJournal()\n\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\tif e != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"unit\":  s.unit,\n\t\t\t\t\t\"error\": e,\n\t\t\t\t}).Error(\"agent: Journal follower error, restarting\")\n\t\t\t} else {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"unit\": s.unit,\n\t\t\t\t}).Info(\"agent: Journal follower exited, restarting\")\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-time.After(3 * time.Second):\n\t\t\tcase <-s.ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (s *Systemd) Close() (err error) {\n\tdefer func() {\n\t\tpanc := recover()\n\t\tif panc != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\"panic\": panc,\n\t\t\t}).Error(\"sync: Panic in journal close\")\n\t\t}\n\t}()\n\n\tif s.cancel != nil {\n\t\ts.cancel()\n\t}\n\n\tif s.cmd != nil && s.cmd.Process != nil {\n\t\ts.cmd.Process.Kill()\n\t}\n\n\tif s.output != nil {\n\t\tclose(s.output)\n\t}\n\n\treturn\n}\n\nfunc NewSystemd(unit string) *Systemd {\n\treturn &Systemd{\n\t\tunit: unit,\n\t}\n}\n"
  },
  {
    "path": "agent/utils/sanitize.go",
    "content": "package utils\n\nimport (\n\t\"strings\"\n\n\t\"github.com/pritunl/pritunl-cloud/agent/constants\"\n\t\"github.com/pritunl/tools/commander\"\n)\n\ntype fileOp struct {\n\tcmd  string\n\targs []string\n\tpath string\n}\n\ntype findOp struct {\n\tpath    string\n\tpattern string\n\taction  string\n}\n\nfunc Sanitize() (err error) {\n\toperations := []fileOp{\n\t\t{\"rm\", []string{\"-f\"}, \"/var/lib/systemd/random-seed\"},\n\t\t{\"rm\", []string{\"-f\"}, \"/etc/machine-id\"},\n\t\t{\"rm\", []string{\"-rf\"}, \"/root/.cache\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/root/.ssh/authorized_keys\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/root/.bash_history\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/var/log/lastlog\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/var/log/secure\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/var/log/utmp\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/var/log/wtmp\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/var/log/btmp\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/var/log/dmesg\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/var/log/dmesg.old\"},\n\t\t{\"shred\", []string{\"-u\"}, \"/var/lib/systemd/random-seed\"},\n\t}\n\n\tfindOps := []findOp{\n\t\t{\"/var/tmp\", \"-name dnf-*\", \"rm -rf\"},\n\t\t{\"/home\", \"-type d -name .cache\", \"rm -rf\"},\n\t\t{\"/home\", \"-type f -name .bash_history\", \"rm -f\"},\n\t\t{\"/var/log\", \"-type f -name *.gz\", \"rm -f\"},\n\t\t{\"/var/log\", \"-type f -name *.[0-9]\", \"rm -f\"},\n\t\t{\"/var/log\", \"-type f -name *-????????\", \"rm -f\"},\n\t\t{\"/var/lib/cloud/instances\", \"-mindepth 1\", \"rm -rf\"},\n\t\t{\"/etc/ssh\", \"-type f -name *_key\", \"shred -u\"},\n\t\t{\"/etc/ssh\", \"-type f -name *_key.pub\", \"shred -u\"},\n\t\t{\"/var/log\", \"-mtime -1 -type f\", \"truncate -s 0\"},\n\t}\n\n\t_, _ = commander.Exec(&commander.Opt{\n\t\tName:    \"sync\",\n\t\tArgs:    []string{},\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\n\tfor _, op := range operations {\n\t\t_, _ = commander.Exec(&commander.Opt{\n\t\t\tName:    op.cmd,\n\t\t\tArgs:    append(op.args, op.path),\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t}\n\n\tfor _, op := range findOps {\n\t\targs := []string{op.path}\n\t\tif op.pattern != \"\" {\n\t\t\targs = append(args, strings.Split(op.pattern, \" \")...)\n\t\t}\n\t\targs = append(args, \"-exec\")\n\t\targs = append(args, strings.Split(op.action, \" \")...)\n\t\targs = append(args, \"{}\", \";\")\n\n\t\t_, _ = commander.Exec(&commander.Opt{\n\t\t\tName:    \"find\",\n\t\t\tArgs:    args,\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t}\n\n\t_, _ = commander.Exec(&commander.Opt{\n\t\tName:    \"touch\",\n\t\tArgs:    []string{\"/etc/machine-id\"},\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\n\t_, _ = commander.Exec(&commander.Opt{\n\t\tName:    \"sync\",\n\t\tArgs:    []string{},\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\n\t_, _ = commander.Exec(&commander.Opt{\n\t\tName:    \"history\",\n\t\tArgs:    []string{\"-c\"},\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\n\t_, _ = commander.Exec(&commander.Opt{\n\t\tName:    \"fstrim\",\n\t\tArgs:    []string{\"-av\"},\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\n\treturn\n}\n\nfunc SanitizeImds() (err error) {\n\t_, _ = commander.Exec(&commander.Opt{\n\t\tName:    \"shred\",\n\t\tArgs:    []string{\"-u\", constants.ImdsLogPath},\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\n\t_, _ = commander.Exec(&commander.Opt{\n\t\tName:    \"shred\",\n\t\tArgs:    []string{\"-u\", constants.ImdsConfPath},\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\n\treturn\n}\n"
  },
  {
    "path": "agent/utils/sys.go",
    "content": "package utils\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc Read(path string) (data string, err error) {\n\tdataByt, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to read '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\tdata = string(dataByt)\n\treturn\n}\n\nfunc DelayExit(code int, delay time.Duration) {\n\ttime.Sleep(delay)\n\tos.Exit(code)\n}\n"
  },
  {
    "path": "aggregate/block.go",
    "content": "package aggregate\n\nimport (\n\t\"sync\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\ntype BlockPipe struct {\n\tblock.Block `bson:\",inline\"`\n\tIpCount     int64 `bson:\"ip_count\"`\n}\n\ntype BlocksPipe struct {\n\tMetadata []*Metadata  `bson:\"meta\"`\n\tBlocks   []*BlockPipe `bson:\"blocks\"`\n}\n\ntype BlockAggregate struct {\n\tblock.Block\n\tAvailable int64 `json:\"available\"`\n\tCapacity  int64 `json:\"capacity\"`\n}\n\nfunc GetBlockPaged(db *database.Database, query *bson.M, page,\n\tpageCount int64) (blocks []*BlockAggregate, count int64, err error) {\n\n\tcoll := db.Blocks()\n\tblocks = []*BlockAggregate{}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tskip := page * pageCount\n\n\taddBlock := func(doc *BlockPipe) error {\n\t\ttotal, e := doc.GetIpCount()\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\n\t\tblck := &BlockAggregate{\n\t\t\tBlock:     doc.Block,\n\t\t\tAvailable: total - doc.IpCount,\n\t\t\tCapacity:  total,\n\t\t}\n\n\t\tblocks = append(blocks, blck)\n\t\treturn nil\n\t}\n\n\tvar cursor *mongo.Cursor\n\tif len(*query) == 0 {\n\t\twaiter := &sync.WaitGroup{}\n\t\tvar countErr error\n\n\t\twaiter.Add(1)\n\t\tgo func() {\n\t\t\tdefer waiter.Done()\n\n\t\t\tcount, countErr = coll.EstimatedDocumentCount(db)\n\t\t\tif countErr != nil {\n\t\t\t\tcountErr = database.ParseError(countErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$skip\": skip,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$limit\": pageCount,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\"from\":         \"blocks_ip\",\n\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\"foreignField\": \"block\",\n\t\t\t\t\t\"as\":           \"ips\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$addFields\": &bson.M{\n\t\t\t\t\t\"ip_count\": &bson.M{\n\t\t\t\t\t\t\"$size\": \"$ips\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$project\": &bson.M{\n\t\t\t\t\t\"ips\": 0,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tfor cursor.Next(db) {\n\t\t\tdoc := &BlockPipe{}\n\t\t\terr = cursor.Decode(doc)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = addBlock(doc)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\twaiter.Wait()\n\t\tif countErr != nil {\n\t\t\terr = countErr\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$facet\": &bson.M{\n\t\t\t\t\t\"meta\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$count\": \"count\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"blocks\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$skip\": skip,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$limit\": pageCount,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\t\t\t\"from\":         \"blocks_ip\",\n\t\t\t\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\t\t\t\"foreignField\": \"block\",\n\t\t\t\t\t\t\t\t\"as\":           \"ips\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$addFields\": &bson.M{\n\t\t\t\t\t\t\t\t\"ip_count\": &bson.M{\n\t\t\t\t\t\t\t\t\t\"$size\": \"$ips\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$project\": &bson.M{\n\t\t\t\t\t\t\t\t\"ips\": 0,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tif !cursor.Next(db) {\n\t\t\terr = &database.NotFoundError{\n\t\t\t\terrors.New(\"aggregate: Not found\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tdoc := &BlocksPipe{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif len(doc.Metadata) > 0 {\n\t\t\tcount = doc.Metadata[0].Count\n\t\t}\n\n\t\tfor _, blockDoc := range doc.Blocks {\n\t\t\terr = addBlock(blockDoc)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "aggregate/deployment.go",
    "content": "package aggregate\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/journal\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\ntype DeploymentPipe struct {\n\tDeployment   `bson:\",inline\"`\n\tSpecDocs     []*spec.Spec         `bson:\"spec_docs\"`\n\tInstanceDocs []*instance.Instance `bson:\"instance_docs\"`\n\tZoneDocs     []*zone.Zone         `bson:\"zone_docs\"`\n\tNodeDocs     []*node.Node         `bson:\"node_docs\"`\n\tImageDocs    []*image.Image       `bson:\"image_docs\"`\n}\n\ntype Deployment struct {\n\tId                  bson.ObjectID            `bson:\"_id\" json:\"id\"`\n\tPod                 bson.ObjectID            `bson:\"pod\" json:\"pod\"`\n\tUnit                bson.ObjectID            `bson:\"unit\" json:\"unit\"`\n\tSpec                bson.ObjectID            `bson:\"spec\" json:\"spec\"`\n\tSpecOffset          int                      `bson:\"spec_offset\" json:\"spec_offset\"`\n\tSpecIndex           int                      `bson:\"spec_index\" json:\"spec_index\"`\n\tSpecTimestamp       time.Time                `bson:\"spec_timestamp\" json:\"spec_timestamp\"`\n\tTimestamp           time.Time                `bson:\"timestamp\" json:\"timestamp\"`\n\tTags                []string                 `bson:\"tags\" json:\"tags\"`\n\tKind                string                   `bson:\"kind\" json:\"kind\"`\n\tState               string                   `bson:\"state\" json:\"state\"`\n\tAction              string                   `bson:\"action\" json:\"action\"`\n\tStatus              string                   `bson:\"status\" json:\"status\"`\n\tNode                bson.ObjectID            `bson:\"node\" json:\"node\"`\n\tInstance            bson.ObjectID            `bson:\"instance\" json:\"instance\"`\n\tInstanceData        *deployment.InstanceData `bson:\"instance_data\" json:\"instance_data\"`\n\tDomainData          *deployment.DomainData   `bson:\"domain_data\" json:\"domain_data\"`\n\tJournals            []*deployment.Journal    `bson:\"journals\" json:\"journals\"`\n\tImageId             bson.ObjectID            `bson:\"image_id\" json:\"image_id\"`\n\tImageName           string                   `bson:\"image_name\" json:\"image_name\"`\n\tZoneName            string                   `bson:\"-\" json:\"zone_name\"`\n\tNodeName            string                   `bson:\"-\" json:\"node_name\"`\n\tInstanceName        string                   `bson:\"-\" json:\"instance_name\"`\n\tInstanceRoles       []string                 `bson:\"-\" json:\"instance_roles\"`\n\tInstanceMemory      int                      `bson:\"-\" json:\"instance_memory\"`\n\tInstanceProcessors  int                      `bson:\"-\" json:\"instance_processors\"`\n\tInstanceStatus      string                   `bson:\"-\" json:\"instance_status\"`\n\tInstanceUptime      string                   `bson:\"-\" json:\"instance_uptime\"`\n\tInstanceState       string                   `bson:\"-\" json:\"instance_state\"`\n\tInstanceAction      string                   `bson:\"-\" json:\"instance_action\"`\n\tInstanceGuestStatus string                   `bson:\"-\" json:\"instance_guest_status\"`\n\tInstanceTimestamp   time.Time                `bson:\"-\" json:\"instance_timestamp\"`\n\tInstanceHeartbeat   time.Time                `bson:\"-\" json:\"instance_heartbeat\"`\n\tInstanceMemoryUsage float64                  `bson:\"-\" json:\"instance_memory_usage\"`\n\tInstanceHugePages   float64                  `bson:\"-\" json:\"instance_hugepages\"`\n\tInstanceLoad1       float64                  `bson:\"-\" json:\"instance_load1\"`\n\tInstanceLoad5       float64                  `bson:\"-\" json:\"instance_load5\"`\n\tInstanceLoad15      float64                  `bson:\"-\" json:\"instance_load15\"`\n}\n\nfunc GetDeployments(db *database.Database, unt *unit.Unit) (\n\tdeplys []*Deployment, err error) {\n\n\tcoll := db.Deployments()\n\tdeplys = []*Deployment{}\n\n\tcursor, err := coll.Aggregate(db, []*bson.M{\n\t\t&bson.M{\n\t\t\t\"$match\": &bson.M{\n\t\t\t\t\"unit\": unt.Id,\n\t\t\t},\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\"timestamp\": -1,\n\t\t\t},\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\"from\":         \"specs\",\n\t\t\t\t\"localField\":   \"spec\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"spec_docs\",\n\t\t\t},\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\"from\":         \"instances\",\n\t\t\t\t\"localField\":   \"instance\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"instance_docs\",\n\t\t\t},\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\"from\":         \"zones\",\n\t\t\t\t\"localField\":   \"zone\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"zone_docs\",\n\t\t\t},\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\"from\":         \"nodes\",\n\t\t\t\t\"localField\":   \"node\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"node_docs\",\n\t\t\t},\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\"from\":         \"images\",\n\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\"foreignField\": \"deployment\",\n\t\t\t\t\"as\":           \"image_docs\",\n\t\t\t},\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$project\": &bson.D{\n\t\t\t\t{\"_id\", 1},\n\t\t\t\t{\"pod\", 1},\n\t\t\t\t{\"unit\", 1},\n\t\t\t\t{\"timestamp\", 1},\n\t\t\t\t{\"tags\", 1},\n\t\t\t\t{\"spec\", 1},\n\t\t\t\t{\"kind\", 1},\n\t\t\t\t{\"state\", 1},\n\t\t\t\t{\"action\", 1},\n\t\t\t\t{\"status\", 1},\n\t\t\t\t{\"node\", 1},\n\t\t\t\t{\"instance\", 1},\n\t\t\t\t{\"instance_data\", 1},\n\t\t\t\t{\"domain_data\", 1},\n\t\t\t\t{\"journals\", 1},\n\t\t\t\t{\"spec_docs.index\", 1},\n\t\t\t\t{\"spec_docs.timestamp\", 1},\n\t\t\t\t{\"instance_docs.name\", 1},\n\t\t\t\t{\"instance_docs.roles\", 1},\n\t\t\t\t{\"instance_docs.memory\", 1},\n\t\t\t\t{\"instance_docs.processors\", 1},\n\t\t\t\t{\"instance_docs.state\", 1},\n\t\t\t\t{\"instance_docs.action\", 1},\n\t\t\t\t{\"instance_docs.timestamp\", 1},\n\t\t\t\t{\"instance_docs.restart\", 1},\n\t\t\t\t{\"instance_docs.restart_block_ip\", 1},\n\t\t\t\t{\"instance_docs.guest\", 1},\n\t\t\t\t{\"zone_docs.name\", 1},\n\t\t\t\t{\"node_docs.name\", 1},\n\t\t\t\t{\"image_docs._id\", 1},\n\t\t\t\t{\"image_docs.name\", 1},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tlatest := true\n\tfor cursor.Next(db) {\n\t\tdoc := &DeploymentPipe{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdeply := &doc.Deployment\n\n\t\tif deply.Tags == nil {\n\t\t\tdeply.Tags = []string{}\n\t\t}\n\t\tif latest {\n\t\t\tlatest = false\n\t\t\tif doc.Kind == deployment.Image {\n\t\t\t\tdeply.Tags = append([]string{\"latest\"}, deply.Tags...)\n\t\t\t}\n\t\t}\n\n\t\tdeply.Journals = append([]*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t}, deply.Journals...)\n\n\t\tif len(doc.ZoneDocs) > 0 {\n\t\t\tzne := doc.ZoneDocs[0]\n\t\t\tdeply.ZoneName = zne.Name\n\t\t}\n\n\t\tif len(doc.NodeDocs) > 0 {\n\t\t\tnde := doc.NodeDocs[0]\n\t\t\tdeply.NodeName = nde.Name\n\t\t}\n\n\t\tif len(doc.SpecDocs) > 0 {\n\t\t\tspc := doc.SpecDocs[0]\n\n\t\t\tdeply.SpecOffset = spc.Index - unt.SpecIndex\n\t\t\tdeply.SpecIndex = spc.Index\n\t\t\tdeply.SpecTimestamp = spc.Timestamp\n\t\t}\n\n\t\tif len(doc.InstanceDocs) > 0 {\n\t\t\tinst := doc.InstanceDocs[0]\n\t\t\tinst.Json(true)\n\n\t\t\tdeply.InstanceName = inst.Name\n\t\t\tdeply.InstanceRoles = inst.Roles\n\t\t\tdeply.InstanceMemory = inst.Memory\n\t\t\tdeply.InstanceProcessors = inst.Processors\n\t\t\tdeply.InstanceStatus = inst.Status\n\t\t\tdeply.InstanceUptime = inst.Uptime\n\t\t\tdeply.InstanceState = inst.State\n\t\t\tdeply.InstanceAction = inst.Action\n\n\t\t\tif inst.Guest != nil {\n\t\t\t\tdeply.InstanceGuestStatus = inst.Guest.Status\n\t\t\t\tdeply.InstanceTimestamp = inst.Guest.Timestamp\n\t\t\t\tdeply.InstanceHeartbeat = inst.Guest.Heartbeat\n\t\t\t\tif inst.IsActive() {\n\t\t\t\t\tdeply.InstanceMemoryUsage = inst.Guest.Memory\n\t\t\t\t\tdeply.InstanceHugePages = inst.Guest.HugePages\n\t\t\t\t\tdeply.InstanceLoad1 = inst.Guest.Load1\n\t\t\t\t\tdeply.InstanceLoad5 = inst.Guest.Load5\n\t\t\t\t\tdeply.InstanceLoad15 = inst.Guest.Load15\n\t\t\t\t} else {\n\t\t\t\t\tdeply.InstanceGuestStatus = types.Offline\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(doc.ImageDocs) > 0 {\n\t\t\timg := doc.ImageDocs[0]\n\n\t\t\tdeply.ImageId = img.Id\n\t\t\tdeply.ImageName = img.Name\n\t\t}\n\n\t\tdeplys = append(deplys, deply)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "aggregate/disk.go",
    "content": "package aggregate\n\nimport (\n\t\"sync\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\ntype DiskPipe struct {\n\tdisk.Disk `bson:\",inline\"`\n\tImageDocs []*node.Node `bson:\"image_docs\"`\n}\n\ntype DisksPipe struct {\n\tMetadata []*Metadata `bson:\"meta\"`\n\tDisks    []*DiskPipe `bson:\"disks\"`\n}\n\ntype DiskBackup struct {\n\tImage bson.ObjectID `json:\"image\"`\n\tName  string        `json:\"name\"`\n}\n\ntype DiskAggregate struct {\n\tdisk.Disk\n\tBackups []*DiskBackup `json:\"backups\"`\n}\n\nfunc GetDiskPaged(db *database.Database, query *bson.M, page,\n\tpageCount int64) (disks []*DiskAggregate, count int64, err error) {\n\n\tcoll := db.Disks()\n\tdisks = []*DiskAggregate{}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tskip := page * pageCount\n\n\taddDisk := func(doc *DiskPipe) {\n\t\tbackups := []*DiskBackup{}\n\n\t\tfor _, img := range doc.ImageDocs {\n\t\t\tbackup := &DiskBackup{\n\t\t\t\tImage: img.Id,\n\t\t\t\tName:  img.Name,\n\t\t\t}\n\n\t\t\tbackups = append(backups, backup)\n\t\t}\n\n\t\tdsk := &DiskAggregate{\n\t\t\tDisk:    doc.Disk,\n\t\t\tBackups: backups,\n\t\t}\n\n\t\tdisks = append(disks, dsk)\n\t}\n\n\tvar cursor *mongo.Cursor\n\tif len(*query) == 0 {\n\t\twaiter := &sync.WaitGroup{}\n\t\tvar countErr error\n\n\t\twaiter.Add(1)\n\t\tgo func() {\n\t\t\tdefer waiter.Done()\n\n\t\t\tcount, countErr = coll.EstimatedDocumentCount(db)\n\t\t\tif countErr != nil {\n\t\t\t\tcountErr = database.ParseError(countErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$skip\": skip,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$limit\": pageCount,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\"from\":         \"images\",\n\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\"foreignField\": \"disk\",\n\t\t\t\t\t\"as\":           \"image_docs\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tfor cursor.Next(db) {\n\t\t\tdoc := &DiskPipe{}\n\t\t\terr = cursor.Decode(doc)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\taddDisk(doc)\n\t\t}\n\n\t\twaiter.Wait()\n\t\tif countErr != nil {\n\t\t\terr = countErr\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$facet\": &bson.M{\n\t\t\t\t\t\"meta\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$count\": \"count\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"disks\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$skip\": skip,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$limit\": pageCount,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\t\t\t\"from\":         \"images\",\n\t\t\t\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\t\t\t\"foreignField\": \"disk\",\n\t\t\t\t\t\t\t\t\"as\":           \"image_docs\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tif !cursor.Next(db) {\n\t\t\terr = &database.NotFoundError{\n\t\t\t\terrors.New(\"aggregate: Not found\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tdoc := &DisksPipe{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif len(doc.Metadata) > 0 {\n\t\t\tcount = doc.Metadata[0].Count\n\t\t}\n\n\t\tfor _, diskDoc := range doc.Disks {\n\t\t\taddDisk(diskDoc)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "aggregate/domain.go",
    "content": "package aggregate\n\nimport (\n\t\"sync\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n)\n\ntype Domain struct {\n\tdomain.Domain `bson:\",inline\"`\n\tRecords       []*domain.Record `bson:\"records\" json:\"records\"`\n}\n\ntype DomainsPipe struct {\n\tMetadata []*Metadata `bson:\"meta\"`\n\tDomains  []*Domain   `bson:\"domains\"`\n}\n\nfunc GetDomainPaged(db *database.Database, query *bson.M, page,\n\tpageCount int64) (domains []*domain.Domain, count int64, err error) {\n\n\tcoll := db.Domains()\n\tdomains = []*domain.Domain{}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tskip := page * pageCount\n\n\taddDomain := func(domn *Domain) {\n\t\tdomn.Domain.Records = domn.Records\n\t\tdomn.Json()\n\t\tdomains = append(domains, &domn.Domain)\n\t}\n\n\tvar cursor *mongo.Cursor\n\tif len(*query) == 0 {\n\t\twaiter := &sync.WaitGroup{}\n\t\tvar countErr error\n\n\t\twaiter.Add(1)\n\t\tgo func() {\n\t\t\tdefer waiter.Done()\n\n\t\t\tcount, countErr = coll.EstimatedDocumentCount(db)\n\t\t\tif countErr != nil {\n\t\t\t\tcountErr = database.ParseError(countErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$skip\": skip,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$limit\": pageCount,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\"from\":         \"domains_records\",\n\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\"foreignField\": \"domain\",\n\t\t\t\t\t\"as\":           \"records\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tfor cursor.Next(db) {\n\t\t\tdomn := &Domain{}\n\t\t\terr = cursor.Decode(domn)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\taddDomain(domn)\n\t\t}\n\n\t\twaiter.Wait()\n\t\tif countErr != nil {\n\t\t\terr = countErr\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$facet\": &bson.M{\n\t\t\t\t\t\"meta\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$count\": \"count\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"domains\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$skip\": skip,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$limit\": pageCount,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\t\t\t\"from\":         \"domains_records\",\n\t\t\t\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\t\t\t\"foreignField\": \"domain\",\n\t\t\t\t\t\t\t\t\"as\":           \"records\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tif !cursor.Next(db) {\n\t\t\terr = &database.NotFoundError{\n\t\t\t\terrors.New(\"aggregate: Not found\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tdoc := &DomainsPipe{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif len(doc.Metadata) > 0 {\n\t\t\tcount = doc.Metadata[0].Count\n\t\t}\n\n\t\tfor _, domn := range doc.Domains {\n\t\t\taddDomain(domn)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "aggregate/instance.go",
    "content": "package aggregate\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/drive\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/iso\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/pci\"\n\t\"github.com/pritunl/pritunl-cloud/usb\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\ntype InstancePipe struct {\n\tinstance.Instance `bson:\",inline\"`\n\tNodeDocs          []*node.Node             `bson:\"node_docs\"`\n\tDatacenterDocs    []*datacenter.Datacenter `bson:\"datacenter_docs\"`\n\tDiskDocs          []*disk.Disk             `bson:\"disk_docs\"`\n}\n\ntype InstancesPipe struct {\n\tMetadata  []*Metadata     `bson:\"meta\"`\n\tInstances []*InstancePipe `bson:\"instances\"`\n}\n\ntype InstanceInfo struct {\n\tNode          string              `json:\"node\"`\n\tNodePublicIp  string              `json:\"node_public_ip\"`\n\tMtu           int                 `json:\"mtu\"`\n\tIscsi         bool                `json:\"iscsi\"`\n\tDisks         []string            `json:\"disks\"`\n\tFirewallRules map[string]string   `json:\"firewall_rules\"`\n\tAuthorities   []string            `json:\"authorities\"`\n\tIsos          []*iso.Iso          `json:\"isos\"`\n\tUsbDevices    []*usb.Device       `json:\"usb_devices\"`\n\tPciDevices    []*pci.Device       `json:\"pci_devices\"`\n\tDriveDevices  []*drive.Device     `json:\"drive_devices\"`\n\tCloudSubnets  []*node.CloudSubnet `json:\"cloud_subnets\"`\n}\n\ntype InstanceAggregate struct {\n\tinstance.Instance\n\tInfo *InstanceInfo `json:\"info\"`\n}\n\nfunc GetInstancePaged(db *database.Database, query *bson.M, page,\n\tpageCount int64) (insts []*InstanceAggregate, count int64, err error) {\n\n\tcoll := db.Instances()\n\tinsts = []*InstanceAggregate{}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tskip := page * pageCount\n\n\tfiresOrg := map[bson.ObjectID]map[string][]*firewall.Firewall{}\n\tfiresRoles := map[bson.ObjectID]set.Set{}\n\tauthrsOrg := map[bson.ObjectID]map[string][]*authority.Authority{}\n\tauthrsRoles := map[bson.ObjectID]set.Set{}\n\n\taddInstance := func(doc *InstancePipe) error {\n\t\tinfo := &InstanceInfo{\n\t\t\tNode:          \"Unknown\",\n\t\t\tDisks:         []string{},\n\t\t\tFirewallRules: map[string]string{},\n\t\t\tAuthorities:   []string{},\n\t\t\tCloudSubnets:  []*node.CloudSubnet{},\n\t\t}\n\n\t\tvar nde *node.Node\n\t\tvar dc *datacenter.Datacenter\n\n\t\tif len(doc.NodeDocs) > 0 {\n\t\t\tnde = doc.NodeDocs[0]\n\t\t}\n\n\t\tif len(doc.DatacenterDocs) > 0 {\n\t\t\tdc = doc.DatacenterDocs[0]\n\t\t}\n\n\t\tif nde != nil {\n\t\t\tinfo.Node = nde.Name\n\t\t\tif len(nde.PublicIps) > 0 {\n\t\t\t\tinfo.NodePublicIp = nde.PublicIps[0]\n\t\t\t}\n\t\t\tinfo.Iscsi = nde.Iscsi\n\n\t\t\tinfo.Isos = nde.LocalIsos\n\n\t\t\tinfo.CloudSubnets = nde.GetCloudSubnetsName()\n\n\t\t\tif nde.UsbPassthrough {\n\t\t\t\tinfo.UsbDevices = nde.UsbDevices\n\t\t\t}\n\n\t\t\tif nde.PciDevices != nil {\n\t\t\t\tinfo.PciDevices = nde.PciDevices\n\t\t\t}\n\n\t\t\tif nde.InstanceDrives != nil {\n\t\t\t\tinfo.DriveDevices = nde.InstanceDrives\n\t\t\t}\n\t\t}\n\n\t\tif nde != nil && dc != nil {\n\t\t\tinfo.Mtu = dc.GetInstanceMtu()\n\t\t}\n\n\t\tfor _, dsk := range doc.DiskDocs {\n\t\t\tinfo.Disks = append(\n\t\t\t\tinfo.Disks,\n\t\t\t\tfmt.Sprintf(\"%s: %s\", dsk.Index, dsk.Name),\n\t\t\t)\n\t\t}\n\n\t\tfires := firesOrg[doc.Organization]\n\t\tif fires == nil {\n\t\t\tvar e error\n\t\t\tfires, e = firewall.GetOrgMapRoles(db, doc.Organization)\n\t\t\tif e != nil {\n\t\t\t\treturn e\n\t\t\t}\n\n\t\t\tfor _, roleFires := range fires {\n\t\t\t\tfor _, fire := range roleFires {\n\t\t\t\t\tif _, ok := firesRoles[fire.Id]; ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\troles := set.NewSet()\n\t\t\t\t\tfor _, role := range fire.Roles {\n\t\t\t\t\t\troles.Add(role)\n\t\t\t\t\t}\n\t\t\t\t\tfiresRoles[fire.Id] = roles\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfiresOrg[doc.Organization] = fires\n\t\t}\n\n\t\tauthrs := authrsOrg[doc.Organization]\n\t\tif authrs == nil {\n\t\t\tvar e error\n\t\t\tauthrs, e = authority.GetOrgMapRoles(db, doc.Organization)\n\t\t\tif e != nil {\n\t\t\t\treturn e\n\t\t\t}\n\n\t\t\tfor _, roleAuthrs := range authrs {\n\t\t\t\tfor _, authr := range roleAuthrs {\n\t\t\t\t\tif _, ok := authrsRoles[authr.Id]; ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\troles := set.NewSet()\n\t\t\t\t\tfor _, role := range authr.Roles {\n\t\t\t\t\t\troles.Add(role)\n\t\t\t\t\t}\n\t\t\t\t\tauthrsRoles[authr.Id] = roles\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tauthrsOrg[doc.Organization] = authrs\n\t\t}\n\n\t\tcurFires := set.NewSet()\n\n\t\tfirewallRules := map[string]set.Set{}\n\t\tfirewallRulesKeys := []string{}\n\t\tauthrNames := set.NewSet()\n\t\tfor _, role := range doc.Roles {\n\t\t\troleFires := fires[role]\n\t\t\tfor _, fire := range roleFires {\n\t\t\t\tif curFires.Contains(fire.Id) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcurFires.Add(fire.Id)\n\n\t\t\t\tfor _, rule := range fire.Ingress {\n\t\t\t\t\tkey := rule.Protocol\n\t\t\t\t\tif rule.Port != \"\" {\n\t\t\t\t\t\tkey += \":\" + rule.Port\n\t\t\t\t\t}\n\n\t\t\t\t\trules := firewallRules[key]\n\t\t\t\t\tif rules == nil {\n\t\t\t\t\t\trules = set.NewSet()\n\t\t\t\t\t\tfirewallRules[key] = rules\n\t\t\t\t\t\tfirewallRulesKeys = append(\n\t\t\t\t\t\t\tfirewallRulesKeys,\n\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, sourceIp := range rule.SourceIps {\n\t\t\t\t\t\trules.Add(sourceIp)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\troleAuthrs := authrs[role]\n\t\t\tfor _, authr := range roleAuthrs {\n\t\t\t\tauthrNames.Add(authr.Name)\n\t\t\t}\n\t\t}\n\n\t\tif !doc.Instance.Deployment.IsZero() {\n\t\t\tdoc.Instance.LoadVirt(nil, nil)\n\n\t\t\tspecRules, _, e := firewall.GetSpecRulesSlow(\n\t\t\t\tdb, doc.Instance.Node, []*instance.Instance{&doc.Instance})\n\t\t\tif e != nil {\n\t\t\t\treturn e\n\t\t\t}\n\n\t\t\tinstNamespace := vm.GetNamespace(doc.Instance.Id, 0)\n\t\t\tfor namespace, rules := range specRules {\n\t\t\t\tif namespace != instNamespace {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfor _, rule := range rules {\n\t\t\t\t\tkey := rule.Protocol\n\t\t\t\t\tif rule.Port != \"\" {\n\t\t\t\t\t\tkey += \":\" + rule.Port\n\t\t\t\t\t}\n\n\t\t\t\t\trules := firewallRules[key]\n\t\t\t\t\tif rules == nil {\n\t\t\t\t\t\trules = set.NewSet()\n\t\t\t\t\t\tfirewallRules[key] = rules\n\t\t\t\t\t\tfirewallRulesKeys = append(\n\t\t\t\t\t\t\tfirewallRulesKeys,\n\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, sourceIp := range rule.SourceIps {\n\t\t\t\t\t\trules.Add(sourceIp)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsort.Strings(firewallRulesKeys)\n\t\tfor _, key := range firewallRulesKeys {\n\t\t\trules := firewallRules[key]\n\n\t\t\tvals := []string{}\n\t\t\tfor rule := range rules.Iter() {\n\t\t\t\tvals = append(vals, rule.(string))\n\t\t\t}\n\t\t\tsort.Strings(vals)\n\n\t\t\tinfo.FirewallRules[key] = strings.Join(vals, \", \")\n\t\t}\n\n\t\tfor authr := range authrNames.Iter() {\n\t\t\tinfo.Authorities = append(info.Authorities, authr.(string))\n\t\t}\n\t\tsort.Strings(info.Authorities)\n\n\t\tinst := &InstanceAggregate{\n\t\t\tInstance: doc.Instance,\n\t\t\tInfo:     info,\n\t\t}\n\n\t\tinsts = append(insts, inst)\n\t\treturn nil\n\t}\n\n\tvar cursor *mongo.Cursor\n\tif len(*query) == 0 {\n\t\twaiter := &sync.WaitGroup{}\n\t\tvar countErr error\n\n\t\twaiter.Add(1)\n\t\tgo func() {\n\t\t\tdefer waiter.Done()\n\n\t\t\tcount, countErr = coll.EstimatedDocumentCount(db)\n\t\t\tif countErr != nil {\n\t\t\t\tcountErr = database.ParseError(countErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$skip\": skip,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$limit\": pageCount,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\"from\":         \"nodes\",\n\t\t\t\t\t\"localField\":   \"node\",\n\t\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\t\"as\":           \"node_docs\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\"from\":         \"datacenters\",\n\t\t\t\t\t\"localField\":   \"datacenter\",\n\t\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\t\"as\":           \"datacenter_docs\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\"from\":         \"disks\",\n\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\"foreignField\": \"instance\",\n\t\t\t\t\t\"as\":           \"disk_docs\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tfor cursor.Next(db) {\n\t\t\tdoc := &InstancePipe{}\n\t\t\terr = cursor.Decode(doc)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = addInstance(doc)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\twaiter.Wait()\n\t\tif countErr != nil {\n\t\t\terr = countErr\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$facet\": &bson.M{\n\t\t\t\t\t\"meta\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$count\": \"count\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"instances\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$skip\": skip,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$limit\": pageCount,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\t\t\t\"from\":         \"nodes\",\n\t\t\t\t\t\t\t\t\"localField\":   \"node\",\n\t\t\t\t\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\t\t\t\t\"as\":           \"node_docs\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\t\t\t\"from\":         \"datacenters\",\n\t\t\t\t\t\t\t\t\"localField\":   \"datacenter\",\n\t\t\t\t\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\t\t\t\t\"as\":           \"datacenter_docs\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\t\t\t\"from\":         \"disks\",\n\t\t\t\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\t\t\t\"foreignField\": \"instance\",\n\t\t\t\t\t\t\t\t\"as\":           \"disk_docs\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tif !cursor.Next(db) {\n\t\t\terr = &database.NotFoundError{\n\t\t\t\terrors.New(\"aggregate: Not found\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tdoc := &InstancesPipe{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif len(doc.Metadata) > 0 {\n\t\t\tcount = doc.Metadata[0].Count\n\t\t}\n\n\t\tfor _, instDoc := range doc.Instances {\n\t\t\terr = addInstance(instDoc)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "aggregate/pod.go",
    "content": "package aggregate\n\nimport (\n\t\"sort\"\n\t\"sync\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n)\n\nfunc sortUnits(units []*unit.Unit) {\n\tsort.SliceStable(units, func(i, j int) bool {\n\t\treturn units[i].Name < units[j].Name\n\t})\n}\n\ntype PodPipe struct {\n\tpod.Pod  `bson:\",inline\"`\n\tUnitDocs []*unit.Unit `bson:\"unit_docs\"`\n}\n\ntype Metadata struct {\n\tCount int64 `bson:\"count\"`\n}\n\ntype PodsPipe struct {\n\tMetadata []*Metadata `bson:\"meta\"`\n\tPods     []*PodPipe  `bson:\"pods\"`\n}\n\ntype PodAggregate struct {\n\tpod.Pod\n\tUnits []*unit.Unit `json:\"units\"`\n}\n\nfunc GetPod(db *database.Database, usrId bson.ObjectID, query *bson.M) (\n\tpd *PodAggregate, err error) {\n\n\tcoll := db.Pods()\n\n\tcursor, err := coll.Aggregate(db, []*bson.M{\n\t\t&bson.M{\n\t\t\t\"$match\": query,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\"from\":         \"units\",\n\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\"foreignField\": \"pod\",\n\t\t\t\t\"as\":           \"unit_docs\",\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tif !cursor.Next(db) {\n\t\terr = &database.NotFoundError{\n\t\t\terrors.New(\"aggregate: Pod not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdoc := &PodPipe{}\n\terr = cursor.Decode(doc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tsortUnits(doc.UnitDocs)\n\n\tpd = &PodAggregate{\n\t\tPod:   doc.Pod,\n\t\tUnits: doc.UnitDocs,\n\t}\n\n\tpd.Json(usrId)\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetPodsPaged(db *database.Database, usrId bson.ObjectID,\n\tquery *bson.M, page, pageCount int64) (pods []*PodAggregate,\n\tcount int64, err error) {\n\n\tcoll := db.Pods()\n\tpods = []*PodAggregate{}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tskip := page * pageCount\n\n\tvar cursor *mongo.Cursor\n\tif len(*query) == 0 {\n\t\twaiter := &sync.WaitGroup{}\n\t\tvar countErr error\n\n\t\twaiter.Add(1)\n\t\tgo func() {\n\t\t\tdefer waiter.Done()\n\n\t\t\tcount, countErr = coll.EstimatedDocumentCount(db)\n\t\t\tif countErr != nil {\n\t\t\t\tcountErr = database.ParseError(countErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$skip\": skip,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$limit\": pageCount,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\"from\":         \"units\",\n\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\"foreignField\": \"pod\",\n\t\t\t\t\t\"as\":           \"unit_docs\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tfor cursor.Next(db) {\n\t\t\tdoc := &PodPipe{}\n\t\t\terr = cursor.Decode(doc)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tsortUnits(doc.UnitDocs)\n\n\t\t\tpd := &PodAggregate{\n\t\t\t\tPod:   doc.Pod,\n\t\t\t\tUnits: doc.UnitDocs,\n\t\t\t}\n\n\t\t\tpd.Json(usrId)\n\n\t\t\tpods = append(pods, pd)\n\t\t}\n\n\t\twaiter.Wait()\n\t\tif countErr != nil {\n\t\t\terr = countErr\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$facet\": &bson.M{\n\t\t\t\t\t\"meta\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$count\": \"count\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"pods\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$skip\": skip,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$limit\": pageCount,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$lookup\": &bson.M{\n\t\t\t\t\t\t\t\t\"from\":         \"units\",\n\t\t\t\t\t\t\t\t\"localField\":   \"_id\",\n\t\t\t\t\t\t\t\t\"foreignField\": \"pod\",\n\t\t\t\t\t\t\t\t\"as\":           \"unit_docs\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tif !cursor.Next(db) {\n\t\t\terr = &database.NotFoundError{\n\t\t\t\terrors.New(\"aggregate: Not found\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tdoc := &PodsPipe{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif len(doc.Metadata) > 0 {\n\t\t\tcount = doc.Metadata[0].Count\n\t\t}\n\n\t\tfor _, podDoc := range doc.Pods {\n\t\t\tsortUnits(podDoc.UnitDocs)\n\n\t\t\tpd := &PodAggregate{\n\t\t\t\tPod:   podDoc.Pod,\n\t\t\t\tUnits: podDoc.UnitDocs,\n\t\t\t}\n\n\t\t\tpd.Json(usrId)\n\n\t\t\tpods = append(pods, pd)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "aggregate/shape.go",
    "content": "package aggregate\n\nimport (\n\t\"sync\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n)\n\ntype ShapePipe struct {\n\tshape.Shape `bson:\",inline\"`\n\tNodeDocs    []*node.Node `bson:\"node_docs\"`\n}\n\ntype ShapesPipe struct {\n\tMetadata []*Metadata  `bson:\"meta\"`\n\tShapes   []*ShapePipe `bson:\"shapes\"`\n}\n\nfunc GetShapePaged(db *database.Database, query *bson.M, page,\n\tpageCount int64) (shapes []*shape.Shape, count int64, err error) {\n\n\tcoll := db.Shapes()\n\tshapes = []*shape.Shape{}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tskip := page * pageCount\n\n\tsizeQuery := &bson.M{\n\t\t\"$ifNull\": bson.A{\n\t\t\t&bson.M{\n\t\t\t\t\"$setIntersection\": bson.A{\n\t\t\t\t\t&bson.M{\"$ifNull\": bson.A{\"$roles\", bson.A{}}},\n\t\t\t\t\t&bson.M{\"$ifNull\": bson.A{\"$$shape_roles\", bson.A{}}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tbson.A{},\n\t\t},\n\t}\n\n\tnodeLookup := &bson.M{\n\t\t\"$lookup\": &bson.M{\n\t\t\t\"from\": \"nodes\",\n\t\t\t\"let\": &bson.M{\n\t\t\t\t\"shape_roles\": \"$roles\",\n\t\t\t},\n\t\t\t\"pipeline\": []*bson.M{\n\t\t\t\t{\n\t\t\t\t\t\"$match\": &bson.M{\n\t\t\t\t\t\t\"$expr\": &bson.M{\n\t\t\t\t\t\t\t\"$gt\": bson.A{\n\t\t\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\t\t\"$size\": sizeQuery,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"as\": \"node_docs\",\n\t\t},\n\t}\n\n\taddShape := func(doc *ShapePipe) {\n\t\tshpe := &doc.Shape\n\t\tshpe.NodeCount = len(doc.NodeDocs)\n\t\tshapes = append(shapes, shpe)\n\t}\n\n\tvar cursor *mongo.Cursor\n\tif len(*query) == 0 {\n\t\twaiter := &sync.WaitGroup{}\n\t\tvar countErr error\n\n\t\twaiter.Add(1)\n\t\tgo func() {\n\t\t\tdefer waiter.Done()\n\n\t\t\tcount, countErr = coll.EstimatedDocumentCount(db)\n\t\t\tif countErr != nil {\n\t\t\t\tcountErr = database.ParseError(countErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$skip\": skip,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$limit\": pageCount,\n\t\t\t},\n\t\t\tnodeLookup,\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tfor cursor.Next(db) {\n\t\t\tdoc := &ShapePipe{}\n\t\t\terr = cursor.Decode(doc)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\taddShape(doc)\n\t\t}\n\n\t\twaiter.Wait()\n\t\tif countErr != nil {\n\t\t\terr = countErr\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcursor, err = coll.Aggregate(db, []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"$match\": query,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$sort\": &bson.M{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$facet\": &bson.M{\n\t\t\t\t\t\"meta\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$count\": \"count\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"shapes\": []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$skip\": skip,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"$limit\": pageCount,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnodeLookup,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tif !cursor.Next(db) {\n\t\t\terr = &database.NotFoundError{\n\t\t\t\terrors.New(\"aggregate: Not found\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tdoc := &ShapesPipe{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif len(doc.Metadata) > 0 {\n\t\t\tcount = doc.Metadata[0].Count\n\t\t}\n\n\t\tfor _, shapeDoc := range doc.Shapes {\n\t\t\taddShape(shapeDoc)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "ahandlers/alert.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/alert\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype alertData struct {\n\tId           bson.ObjectID `json:\"id\"`\n\tName         string        `json:\"name\"`\n\tComment      string        `json:\"comment\"`\n\tOrganization bson.ObjectID `json:\"organization\"`\n\tRoles        []string      `json:\"roles\"`\n\tResource     string        `json:\"resource\"`\n\tLevel        int           `json:\"level\"`\n\tFrequency    int           `json:\"frequency\"`\n\tIgnores      []string      `json:\"ignores\"`\n\tValueInt     int           `json:\"value_int\"`\n\tValueStr     string        `json:\"value_str\"`\n}\n\ntype alertsData struct {\n\tAlerts []*alert.Alert `json:\"alerts\"`\n\tCount  int64          `json:\"count\"`\n}\n\nfunc alertPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &alertData{}\n\n\talertId, ok := utils.ParseObjectId(c.Param(\"alert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\talrt, err := alert.Get(db, alertId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\talrt.Name = data.Name\n\talrt.Comment = data.Comment\n\talrt.Organization = data.Organization\n\talrt.Roles = data.Roles\n\talrt.Resource = data.Resource\n\talrt.Level = data.Level\n\talrt.Frequency = data.Frequency\n\talrt.Ignores = data.Ignores\n\talrt.ValueInt = data.ValueInt\n\talrt.ValueStr = data.ValueStr\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"organization\",\n\t\t\"roles\",\n\t\t\"resource\",\n\t\t\"level\",\n\t\t\"frequency\",\n\t\t\"ignores\",\n\t\t\"value_int\",\n\t\t\"value_str\",\n\t)\n\n\terrData, err := alrt.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = alrt.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_ = event.PublishDispatch(db, \"alert.change\")\n\n\tc.JSON(200, alrt)\n}\n\nfunc alertPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &alertData{\n\t\tName:     \"new-alert\",\n\t\tResource: alert.InstanceOffline,\n\t\tLevel:    alert.Medium,\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\talrt := &alert.Alert{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: data.Organization,\n\t\tRoles:        data.Roles,\n\t\tResource:     data.Resource,\n\t\tLevel:        data.Level,\n\t\tFrequency:    data.Frequency,\n\t\tIgnores:      data.Ignores,\n\t\tValueInt:     data.ValueInt,\n\t\tValueStr:     data.ValueStr,\n\t}\n\n\terrData, err := alrt.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = alrt.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_ = event.PublishDispatch(db, \"alert.change\")\n\n\tc.JSON(200, alrt)\n}\n\nfunc alertDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\talertId, ok := utils.ParseObjectId(c.Param(\"alert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := alert.Remove(db, alertId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_ = event.PublishDispatch(db, \"alert.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc alertsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := []bson.ObjectID{}\n\n\terr := c.Bind(&dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = alert.RemoveMulti(db, dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_ = event.PublishDispatch(db, \"alert.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc alertGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\talrt := demo.Alerts[0]\n\t\tc.JSON(200, alrt)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\talertId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\talrt, err := alert.Get(db, alertId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, alrt)\n}\n\nfunc alertsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &alertsData{\n\t\t\tAlerts: demo.Alerts,\n\t\t\tCount:  int64(len(demo.Alerts)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\talertId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = alertId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"name\": &bson.M{\n\t\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\talerts, count, err := alert.GetAllPaged(\n\t\tdb, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdta := &alertsData{\n\t\tAlerts: alerts,\n\t\tCount:  count,\n\t}\n\n\tc.JSON(200, dta)\n}\n"
  },
  {
    "path": "ahandlers/audit.go",
    "content": "package ahandlers\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype auditsData struct {\n\tAudits []*audit.Audit `json:\"audits\"`\n\tCount  int64          `json:\"count\"`\n}\n\nfunc auditsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &auditsData{\n\t\t\tAudits: demo.Audits,\n\t\t\tCount:  int64(len(demo.Audits)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tuserId, ok := utils.ParseObjectId(c.Param(\"user_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\taudits, count, err := audit.GetAll(db, userId, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &auditsData{\n\t\tAudits: audits,\n\t\tCount:  count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/auth.go",
    "content": "package ahandlers\n\nimport (\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/auth\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/cookie\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/device\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/secondary\"\n\t\"github.com/pritunl/pritunl-cloud/session\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/validator\"\n)\n\nfunc authStateGet(c *gin.Context) {\n\tdata := auth.GetState()\n\n\tif demo.IsDemo() {\n\t\tprovider := &auth.StateProvider{\n\t\t\tId:    \"demo\",\n\t\t\tType:  \"demo\",\n\t\t\tLabel: \"demo\",\n\t\t}\n\t\tdata.Providers = append(data.Providers, provider)\n\t}\n\n\tc.JSON(200, data)\n}\n\ntype authData struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\nfunc authSessionPost(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &authData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, errData, err := auth.Local(db, data.Username, data.Password)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminPrimaryApprove,\n\t\taudit.Fields{\n\t\t\t\"method\": \"local\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevAuth, secProviderId, errAudit, errData, err := validator.ValidateAdmin(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"local\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tif devAuth {\n\t\tdeviceCount, err := device.CountSecondary(db, usr.Id)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tsecType := \"\"\n\t\tvar secProvider bson.ObjectID\n\t\tif deviceCount == 0 {\n\t\t\tif secProviderId.IsZero() {\n\t\t\t\tsecType = secondary.AdminDeviceRegister\n\t\t\t\tsecProvider = secondary.DeviceProvider\n\t\t\t} else {\n\t\t\t\tsecType = secondary.Admin\n\t\t\t\tsecProvider = secProviderId\n\t\t\t}\n\t\t} else {\n\t\t\tsecType = secondary.AdminDevice\n\t\t\tsecProvider = secondary.DeviceProvider\n\t\t}\n\n\t\tsecd, err := secondary.New(db, usr.Id, secType, secProvider)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata, err := secd.GetData()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(201, data)\n\t\treturn\n\t} else if !secProviderId.IsZero() {\n\t\tsecd, err := secondary.New(db, usr.Id, secondary.Admin, secProviderId)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata, err := secd.GetData()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(201, data)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminLogin,\n\t\taudit.Fields{\n\t\t\t\"method\": \"local\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewAdmin(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.Admin)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.Status(200)\n}\n\ntype secondaryData struct {\n\tToken    string `json:\"token\"`\n\tFactor   string `json:\"factor\"`\n\tPasscode string `json:\"passcode\"`\n}\n\nfunc authSecondaryPost(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &secondaryData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token, secondary.Admin)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := secd.Handle(db, c.Request, data.Factor, data.Passcode)\n\tif err != nil {\n\t\tif _, ok := err.(*secondary.IncompleteError); ok {\n\t\t\tc.Status(201)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLoginFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"method\":      \"secondary\",\n\t\t\t\t\"provider_id\": secd.ProviderId,\n\t\t\t\t\"error\":       errData.Error,\n\t\t\t\t\"message\":     errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminSecondaryApprove,\n\t\taudit.Fields{\n\t\t\t\"provider_id\": secd.ProviderId,\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdeviceAuth, _, errAudit, errData, err := validator.ValidateAdmin(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"secondary\"\n\t\terrAudit[\"provider_id\"] = secd.ProviderId\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tif deviceAuth {\n\t\tdeviceCount, err := device.CountSecondary(db, usr.Id)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif deviceCount == 0 {\n\t\t\tsecd, err := secondary.New(db, usr.Id,\n\t\t\t\tsecondary.AdminDeviceRegister, secondary.DeviceProvider)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdata, err := secd.GetData()\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tc.JSON(201, data)\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminLogin,\n\t\taudit.Fields{\n\t\t\t\"method\": \"secondary\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewAdmin(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.Admin)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.Status(200)\n}\n\nfunc logoutGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tif authr.IsValid() {\n\t\terr := authr.Remove(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tusr, _ := authr.GetUser(db)\n\tif usr != nil {\n\t\terr := audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLogout,\n\t\t\taudit.Fields{},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tc.Redirect(302, \"/login\")\n}\n\nfunc authRequestGet(c *gin.Context) {\n\tauth.Request(c, auth.Admin)\n}\n\nfunc authCallbackGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tsig := c.Query(\"sig\")\n\tquery := strings.Split(c.Request.URL.RawQuery, \"&sig=\")[0]\n\n\tusr, _, errAudit, errData, err := auth.Callback(db, sig, query)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *auth.InvalidState:\n\t\t\tc.Redirect(302, \"/\")\n\t\t\tbreak\n\t\tdefault:\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif usr != nil {\n\t\t\tif errAudit == nil {\n\t\t\t\terrAudit = audit.Fields{\n\t\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\t\"message\": errData.Message,\n\t\t\t\t}\n\t\t\t}\n\t\t\terrAudit[\"method\"] = \"callback\"\n\n\t\t\terr = audit.New(\n\t\t\t\tdb,\n\t\t\t\tc.Request,\n\t\t\t\tusr.Id,\n\t\t\t\taudit.AdminLoginFailed,\n\t\t\t\terrAudit,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminPrimaryApprove,\n\t\taudit.Fields{\n\t\t\t\"method\": \"callback\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevAuth, secProviderId, errAudit, errData, err := validator.ValidateAdmin(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"callback\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tif devAuth {\n\t\tdeviceCount, err := device.CountSecondary(db, usr.Id)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tsecType := \"\"\n\t\tvar secProvider bson.ObjectID\n\t\tif deviceCount == 0 {\n\t\t\tif secProviderId.IsZero() {\n\t\t\t\tsecType = secondary.AdminDeviceRegister\n\t\t\t\tsecProvider = secondary.DeviceProvider\n\t\t\t} else {\n\t\t\t\tsecType = secondary.Admin\n\t\t\t\tsecProvider = secProviderId\n\t\t\t}\n\t\t} else {\n\t\t\tsecType = secondary.AdminDevice\n\t\t\tsecProvider = secondary.DeviceProvider\n\t\t}\n\n\t\tsecd, err := secondary.New(db, usr.Id, secType, secProvider)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\turlQuery, err := secd.GetQuery()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.Redirect(302, \"/login?\"+urlQuery)\n\t\treturn\n\t} else if !secProviderId.IsZero() {\n\t\tsecd, err := secondary.New(db, usr.Id, secondary.Admin, secProviderId)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\turlQuery, err := secd.GetQuery()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.Redirect(302, \"/login?\"+urlQuery)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminLogin,\n\t\taudit.Fields{\n\t\t\t\"method\": \"callback\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewAdmin(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.Admin)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.Redirect(302, \"/\")\n}\n\nfunc authWanRegisterGet(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\ttoken := c.Query(\"token\")\n\n\tif node.Self.WebauthnDomain == \"\" {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"webauthn_domain_unavailable\",\n\t\t\tMessage: \"WebAuthn domain must be configured\",\n\t\t}\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, token, secondary.AdminDeviceRegister)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminDeviceRegisterRequest,\n\t\taudit.Fields{},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tresp, errData, err := secd.DeviceRegisterRequest(db,\n\t\tutils.GetOrigin(c.Request))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLoginFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"method\":  \"device_register\",\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tc.JSON(200, resp)\n}\n\ntype authWanRegisterData struct {\n\tToken string `json:\"token\"`\n\tName  string `json:\"name\"`\n}\n\nfunc authWanRegisterPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &authWanRegisterData{}\n\n\tbody, err := utils.CopyBody(c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token, secondary.AdminDeviceRegister)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_, _, errAudit, errData, err := validator.ValidateAdmin(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"device_register\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tdevc, errData, err := secd.DeviceRegisterResponse(\n\t\tdb, utils.GetOrigin(c.Request), body, data.Name)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.DeviceRegisterFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminDeviceRegister,\n\t\taudit.Fields{\n\t\t\t\"device_id\": devc.Id,\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminLogin,\n\t\taudit.Fields{\n\t\t\t\"method\": \"device_register\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewAdmin(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.Admin)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.Status(200)\n}\n\nfunc authWanRequestGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\ttoken := c.Query(\"token\")\n\n\tsecd, err := secondary.Get(db, token, secondary.AdminDevice)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tresp, errData, err := secd.DeviceRequest(\n\t\tdb, utils.GetOrigin(c.Request))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLoginFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"method\":  \"device\",\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tc.JSON(200, resp)\n}\n\ntype authWanRespondData struct {\n\tToken string `json:\"token\"`\n}\n\nfunc authWanRespondPost(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &authWanRespondData{}\n\n\tbody, err := utils.CopyBody(c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token, secondary.AdminDevice)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_, secProviderId, errAudit, errData, err := validator.ValidateAdmin(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"device\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terrData, err = secd.DeviceRespond(\n\t\tdb, utils.GetOrigin(c.Request), body)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminLoginFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"method\":  \"device\",\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminDeviceApprove,\n\t\taudit.Fields{},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif !secProviderId.IsZero() {\n\t\tsecd, err := secondary.New(db, usr.Id, secondary.Admin,\n\t\t\tsecProviderId)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata, err := secd.GetData()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(201, data)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.AdminLogin,\n\t\taudit.Fields{\n\t\t\t\"method\": \"device\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewAdmin(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.Admin)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.Status(200)\n}\n"
  },
  {
    "path": "ahandlers/authority.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype authorityData struct {\n\tId           bson.ObjectID `json:\"id\"`\n\tName         string        `json:\"name\"`\n\tComment      string        `json:\"comment\"`\n\tType         string        `json:\"type\"`\n\tOrganization bson.ObjectID `json:\"organization\"`\n\tRoles        []string      `json:\"roles\"`\n\tKey          string        `json:\"key\"`\n\tPrincipals   []string      `json:\"principals\"`\n\tCertificate  string        `json:\"certificate\"`\n}\n\ntype authoritiesData struct {\n\tAuthorities []*authority.Authority `json:\"authorities\"`\n\tCount       int64                  `json:\"count\"`\n}\n\nfunc authorityPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &authorityData{}\n\n\tauthorityId, ok := utils.ParseObjectId(c.Param(\"authority_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tauthr, err := authority.Get(db, authorityId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tauthr.Name = data.Name\n\tauthr.Comment = data.Comment\n\tauthr.Type = data.Type\n\tauthr.Organization = data.Organization\n\tauthr.Roles = data.Roles\n\tauthr.Key = data.Key\n\tauthr.Principals = data.Principals\n\tauthr.Certificate = data.Certificate\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"type\",\n\t\t\"organization\",\n\t\t\"roles\",\n\t\t\"key\",\n\t\t\"principals\",\n\t\t\"certificate\",\n\t)\n\n\terrData, err := authr.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = authr.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"authority.change\")\n\n\tc.JSON(200, authr)\n}\n\nfunc authorityPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &authorityData{\n\t\tName: \"new-authority\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tauthr := &authority.Authority{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tType:         data.Type,\n\t\tOrganization: data.Organization,\n\t\tRoles:        data.Roles,\n\t\tKey:          data.Key,\n\t\tPrincipals:   data.Principals,\n\t\tCertificate:  data.Certificate,\n\t}\n\n\terrData, err := authr.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = authr.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"authority.change\")\n\n\tc.JSON(200, authr)\n}\n\nfunc authorityDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tauthorityId, ok := utils.ParseObjectId(c.Param(\"authority_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := authority.Remove(db, authorityId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"authority.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc authoritiesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = authority.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"authority.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc authorityGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tauthr := demo.Authorities[0]\n\t\tc.JSON(200, authr)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tauthorityId, ok := utils.ParseObjectId(c.Param(\"authority_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tauthr, err := authority.Get(db, authorityId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, authr)\n}\n\nfunc authoritiesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &authoritiesData{\n\t\t\tAuthorities: demo.Authorities,\n\t\t\tCount:       int64(len(demo.Authorities)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tauthrId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = authrId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\tprincipal := strings.TrimSpace(c.Query(\"principal\"))\n\tif principal != \"\" {\n\t\tquery[\"principals\"] = principal\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tauthorities, count, err := authority.GetAllPaged(\n\t\tdb, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &authoritiesData{\n\t\tAuthorities: authorities,\n\t\tCount:       count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/balancer.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/balancer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype balancerData struct {\n\tId           bson.ObjectID       `json:\"id\"`\n\tName         string              `json:\"name\"`\n\tComment      string              `json:\"comment\"`\n\tState        bool                `json:\"state\"`\n\tType         string              `json:\"type\"`\n\tOrganization bson.ObjectID       `json:\"organization\"`\n\tDatacenter   bson.ObjectID       `json:\"datacenter\"`\n\tCertificates []bson.ObjectID     `json:\"certificates\"`\n\tWebSockets   bool                `json:\"websockets\"`\n\tDomains      []*balancer.Domain  `json:\"domains\"`\n\tBackends     []*balancer.Backend `json:\"backends\"`\n\tCheckPath    string              `json:\"check_path\"`\n}\n\ntype balancersData struct {\n\tBalancers []*balancer.Balancer `json:\"balancers\"`\n\tCount     int64                `json:\"count\"`\n}\n\nfunc balancerPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &balancerData{}\n\n\tbalancerId, ok := utils.ParseObjectId(c.Param(\"balancer_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tbalnc, err := balancer.Get(db, balancerId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tbalnc.Name = data.Name\n\tbalnc.Comment = data.Comment\n\tbalnc.State = data.State\n\tbalnc.Type = data.Type\n\tbalnc.Organization = data.Organization\n\tbalnc.Datacenter = data.Datacenter\n\tbalnc.Certificates = data.Certificates\n\tbalnc.WebSockets = data.WebSockets\n\tbalnc.Domains = data.Domains\n\tbalnc.Backends = data.Backends\n\tbalnc.CheckPath = data.CheckPath\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"state\",\n\t\t\"type\",\n\t\t\"organization\",\n\t\t\"datacenter\",\n\t\t\"certificates\",\n\t\t\"websockets\",\n\t\t\"domains\",\n\t\t\"backends\",\n\t\t\"check_path\",\n\t)\n\n\terrData, err := balnc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = balnc.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"balancer.change\")\n\n\tbalnc.Json()\n\n\tc.JSON(200, balnc)\n}\n\nfunc balancerPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &balancerData{\n\t\tName: \"new-balancer\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tbalnc := &balancer.Balancer{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tState:        data.State,\n\t\tType:         data.Type,\n\t\tOrganization: data.Organization,\n\t\tDatacenter:   data.Datacenter,\n\t\tCertificates: data.Certificates,\n\t\tWebSockets:   data.WebSockets,\n\t\tDomains:      data.Domains,\n\t\tBackends:     data.Backends,\n\t\tCheckPath:    data.CheckPath,\n\t}\n\n\terrData, err := balnc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = balnc.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"balancer.change\")\n\n\tbalnc.Json()\n\n\tc.JSON(200, balnc)\n}\n\nfunc balancerDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tbalancerId, ok := utils.ParseObjectId(c.Param(\"balancer_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := balancer.Remove(db, balancerId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"balancer.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc balancersDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = balancer.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"balancer.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc balancerGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tbalnc := demo.Balancers[0]\n\t\tc.JSON(200, balnc)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tbalancerId, ok := utils.ParseObjectId(c.Param(\"balancer_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tbalnc, err := balancer.Get(db, balancerId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tbalnc.Json()\n\n\tc.JSON(200, balnc)\n}\n\nfunc balancersGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &balancersData{\n\t\t\tBalancers: demo.Balancers,\n\t\t\tCount:     int64(len(demo.Balancers)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tbalancerId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = balancerId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tdatacenter, ok := utils.ParseObjectId(c.Query(\"datacenter\"))\n\tif ok {\n\t\tquery[\"datacenter\"] = datacenter\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tbalncs, count, err := balancer.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfor _, balnc := range balncs {\n\t\tbalnc.Json()\n\t}\n\n\tdata := &balancersData{\n\t\tBalancers: balncs,\n\t\tCount:     count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/block.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype blockData struct {\n\tId       bson.ObjectID `json:\"id\"`\n\tName     string        `json:\"name\"`\n\tComment  string        `json:\"comment\"`\n\tVlan     int           `json:\"vlan\"`\n\tType     string        `json:\"type\"`\n\tSubnets  []string      `json:\"subnets\"`\n\tSubnets6 []string      `json:\"subnets6\"`\n\tExcludes []string      `json:\"excludes\"`\n\tNetmask  string        `json:\"netmask\"`\n\tGateway  string        `json:\"gateway\"`\n\tGateway6 string        `json:\"gateway6\"`\n}\n\ntype blocksData struct {\n\tBlocks []*aggregate.BlockAggregate `json:\"blocks\"`\n\tCount  int64                       `json:\"count\"`\n}\n\nfunc blockPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &blockData{}\n\n\tblckId, ok := utils.ParseObjectId(c.Param(\"block_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tblck, err := block.Get(db, blckId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tblck.Name = dta.Name\n\tblck.Comment = dta.Comment\n\tblck.Vlan = dta.Vlan\n\tblck.Subnets = dta.Subnets\n\tblck.Subnets6 = dta.Subnets6\n\tblck.Excludes = dta.Excludes\n\tblck.Netmask = dta.Netmask\n\tblck.Gateway = dta.Gateway\n\tblck.Gateway6 = dta.Gateway6\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"vlan\",\n\t\t\"subnets\",\n\t\t\"subnets6\",\n\t\t\"excludes\",\n\t\t\"netmask\",\n\t\t\"gateway\",\n\t\t\"gateway6\",\n\t)\n\n\terrData, err := blck.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = blck.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"block.change\")\n\n\tc.JSON(200, blck)\n}\n\nfunc blockPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &blockData{\n\t\tName: \"new-block\",\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tblck := &block.Block{\n\t\tName:     dta.Name,\n\t\tComment:  dta.Comment,\n\t\tVlan:     dta.Vlan,\n\t\tType:     dta.Type,\n\t\tSubnets:  dta.Subnets,\n\t\tSubnets6: dta.Subnets6,\n\t\tExcludes: dta.Excludes,\n\t\tNetmask:  dta.Netmask,\n\t\tGateway:  dta.Gateway,\n\t\tGateway6: dta.Gateway6,\n\t}\n\n\terrData, err := blck.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = blck.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"block.change\")\n\n\tc.JSON(200, blck)\n}\n\nfunc blockDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tblckId, ok := utils.ParseObjectId(c.Param(\"block_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"block\", blckId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = block.Remove(db, blckId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"block.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc blocksDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"block\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = block.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"block.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc blockGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tblck := demo.Blocks[0]\n\t\tc.JSON(200, blck)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tblckId, ok := utils.ParseObjectId(c.Param(\"block_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tblck, err := block.Get(db, blckId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, blck)\n}\n\nfunc blocksGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &blocksData{\n\t\t\tBlocks: demo.Blocks,\n\t\t\tCount:  int64(len(demo.Blocks)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tblockId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = blockId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tblcks, count, err := aggregate.GetBlockPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &blocksData{\n\t\tBlocks: blcks,\n\t\tCount:  count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/certificate.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/acme\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype certificateData struct {\n\tId           bson.ObjectID `json:\"id\"`\n\tName         string        `json:\"name\"`\n\tComment      string        `json:\"comment\"`\n\tOrganization bson.ObjectID `json:\"organization\"`\n\tType         string        `json:\"type\"`\n\tKey          string        `json:\"key\"`\n\tCertificate  string        `json:\"certificate\"`\n\tAcmeDomains  []string      `json:\"acme_domains\"`\n\tAcmeType     string        `json:\"acme_type\"`\n\tAcmeAuth     string        `json:\"acme_auth\"`\n\tAcmeSecret   bson.ObjectID `json:\"acme_secret\"`\n\tRefresh      bool          `json:\"refresh\"`\n}\n\ntype certificatesData struct {\n\tCertificates []*certificate.Certificate `json:\"certificates\"`\n\tCount        int64                      `json:\"count\"`\n}\n\nfunc certificatePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &certificateData{}\n\n\tcertId, ok := utils.ParseObjectId(c.Param(\"cert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcert, err := certificate.Get(db, certId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcert.Name = data.Name\n\tcert.Comment = data.Comment\n\tcert.Organization = data.Organization\n\tcert.Type = data.Type\n\tcert.AcmeDomains = data.AcmeDomains\n\tcert.AcmeType = data.AcmeType\n\tcert.AcmeAuth = data.AcmeAuth\n\tcert.AcmeSecret = data.AcmeSecret\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"organization\",\n\t\t\"type\",\n\t\t\"acme_domains\",\n\t\t\"acme_type\",\n\t\t\"acme_auth\",\n\t\t\"acme_secret\",\n\t\t\"info\",\n\t)\n\n\tif cert.Type != certificate.LetsEncrypt {\n\t\tcert.Key = data.Key\n\t\tfields.Add(\"key\")\n\t\tcert.Certificate = data.Certificate\n\t\tfields.Add(\"certificate\")\n\t}\n\n\terrData, err := cert.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = cert.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif cert.Type == certificate.LetsEncrypt {\n\t\tacme.RenewBackground(cert, data.Refresh)\n\t}\n\n\tevent.PublishDispatch(db, \"certificate.change\")\n\n\tc.JSON(200, cert)\n}\n\nfunc certificatePost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &certificateData{\n\t\tName: \"new-certificate\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcert := &certificate.Certificate{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: data.Organization,\n\t\tType:         data.Type,\n\t\tAcmeDomains:  data.AcmeDomains,\n\t\tAcmeType:     data.AcmeType,\n\t\tAcmeAuth:     data.AcmeAuth,\n\t\tAcmeSecret:   data.AcmeSecret,\n\t}\n\n\tif cert.Type != certificate.LetsEncrypt {\n\t\tcert.Key = data.Key\n\t\tcert.Certificate = data.Certificate\n\t}\n\n\terrData, err := cert.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = cert.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif cert.Type == certificate.LetsEncrypt {\n\t\tacme.RenewBackground(cert, false)\n\t}\n\n\tevent.PublishDispatch(db, \"certificate.change\")\n\n\tc.JSON(200, cert)\n}\n\nfunc certificateDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tcertId, ok := utils.ParseObjectId(c.Param(\"cert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"certificate\", certId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = certificate.Remove(db, certId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"certificate.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc certificatesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"certificate\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = certificate.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"certificate.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc certificateGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tcert := demo.Certificates[0]\n\t\tc.JSON(200, cert)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tcertId, ok := utils.ParseObjectId(c.Param(\"cert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tcert, err := certificate.Get(db, certId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif demo.IsDemo() {\n\t\tcert.Key = \"demo\"\n\t\tcert.AcmeAccount = \"demo\"\n\t}\n\n\tc.JSON(200, cert)\n}\n\nfunc certificatesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &certificatesData{\n\t\t\tCertificates: demo.Certificates,\n\t\t\tCount:        int64(len(demo.Certificates)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tcerts, err := certificate.GetAllNames(db, &bson.M{})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, certs)\n\t\treturn\n\t}\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tcertificateId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = certificateId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tcerts, count, err := certificate.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &certificatesData{\n\t\tCertificates: certs,\n\t\tCount:        count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/check.go",
    "content": "package ahandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc checkGet(c *gin.Context) {\n\tc.String(200, \"ok\")\n}\n"
  },
  {
    "path": "ahandlers/completion.go",
    "content": "package ahandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/completion\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/plan\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\nfunc completionGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif demo.IsDemo() {\n\t\tdata := &completion.Completion{}\n\n\t\tfor _, item := range demo.Organizations {\n\t\t\tdata.Organizations = append(data.Organizations, &database.Named{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Authorities {\n\t\t\tdata.Authorities = append(data.Authorities, &database.Named{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Policies {\n\t\t\tdata.Policies = append(data.Policies, &database.Named{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Domains {\n\t\t\tdata.Domains = append(data.Domains, &domain.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Vpcs {\n\t\t\tdata.Vpcs = append(data.Vpcs, &vpc.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t\tVpcId:        item.VpcId,\n\t\t\t\tNetwork:      item.Network,\n\t\t\t\tSubnets:      item.Subnets,\n\t\t\t\tDatacenter:   item.Datacenter,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Datacenters {\n\t\t\tdata.Datacenters = append(data.Datacenters, &datacenter.Completion{\n\t\t\t\tId:          item.Id,\n\t\t\t\tName:        item.Name,\n\t\t\t\tNetworkMode: item.NetworkMode,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Blocks {\n\t\t\tdata.Blocks = append(data.Blocks, &block.Completion{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t\tType: item.Type,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Nodes {\n\t\t\tdata.Nodes = append(data.Nodes, &node.Completion{\n\t\t\t\tId:    item.Id,\n\t\t\t\tName:  item.Name,\n\t\t\t\tZone:  item.Zone,\n\t\t\t\tTypes: item.Types,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Pools {\n\t\t\tdata.Pools = append(data.Pools, &pool.Completion{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t\tZone: item.Zone,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Zones {\n\t\t\tdata.Zones = append(data.Zones, &zone.Completion{\n\t\t\t\tId:         item.Id,\n\t\t\t\tDatacenter: item.Datacenter,\n\t\t\t\tName:       item.Name,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Shapes {\n\t\t\tdata.Shapes = append(data.Shapes, &shape.Completion{\n\t\t\t\tId:         item.Id,\n\t\t\t\tName:       item.Name,\n\t\t\t\tDatacenter: item.Datacenter,\n\t\t\t\tFlexible:   item.Flexible,\n\t\t\t\tMemory:     item.Memory,\n\t\t\t\tProcessors: item.Processors,\n\t\t\t})\n\t\t}\n\n\t\timgs, err := image.GetAllCompletion(db, &bson.M{})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tdata.Images = imgs\n\n\t\tfor _, item := range demo.Storages {\n\t\t\tdata.Storages = append(data.Storages, &storage.Completion{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t\tType: item.Type,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Instances {\n\t\t\tdata.Instances = append(data.Instances, &instance.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t\tZone:         item.Zone,\n\t\t\t\tVpc:          item.Vpc,\n\t\t\t\tSubnet:       item.Subnet,\n\t\t\t\tNode:         item.Node,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Plans {\n\t\t\tdata.Plans = append(data.Plans, &plan.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Certificates {\n\t\t\tdata.Certificates = append(\n\t\t\t\tdata.Certificates,\n\t\t\t\t&certificate.Completion{\n\t\t\t\t\tId:           item.Id,\n\t\t\t\t\tName:         item.Name,\n\t\t\t\t\tOrganization: item.Organization,\n\t\t\t\t\tType:         item.Type,\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\tfor _, item := range demo.Secrets {\n\t\t\tdata.Secrets = append(data.Secrets, &secret.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t\tType:         item.Type,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Pods {\n\t\t\tdata.Pods = append(data.Pods, &pod.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Units {\n\t\t\tdata.Units = append(data.Units, &unit.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tPod:          item.Pod,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t\tName:         item.Name,\n\t\t\t\tKind:         item.Kind,\n\t\t\t})\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tcmpl, err := completion.GetCompletion(db, bson.NilObjectID, nil)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, cmpl)\n}\n"
  },
  {
    "path": "ahandlers/csrf.go",
    "content": "package ahandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/csrf\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype csrfData struct {\n\tToken         string `json:\"token\"`\n\tTheme         string `json:\"theme\"`\n\tEditorTheme   string `json:\"editor_theme\"`\n\tOracleLicense bool   `json:\"oracle_license\"`\n}\n\nfunc csrfGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\ttoken, err := csrf.NewToken(db, authr.SessionId())\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\toracleLicense := usr.OracleLicense\n\tif demo.IsDemo() {\n\t\toracleLicense = true\n\t}\n\n\tdata := &csrfData{\n\t\tToken:         token,\n\t\tTheme:         usr.Theme,\n\t\tEditorTheme:   usr.EditorTheme,\n\t\tOracleLicense: oracleLicense,\n\t}\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/datacenter.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype datacenterData struct {\n\tId                  bson.ObjectID   `json:\"id\"`\n\tName                string          `json:\"name\"`\n\tComment             string          `json:\"comment\"`\n\tNetworkMode         string          `json:\"network_mode\"`\n\tMatchOrganizations  bool            `json:\"match_organizations\"`\n\tOrganizations       []bson.ObjectID `json:\"organizations\"`\n\tJumboMtu            int             `json:\"jumbo_mtu\"`\n\tPublicStorages      []bson.ObjectID `json:\"public_storages\"`\n\tPrivateStorage      bson.ObjectID   `json:\"private_storage\"`\n\tPrivateStorageClass string          `json:\"private_storage_class\"`\n\tBackupStorage       bson.ObjectID   `json:\"backup_storage\"`\n\tBackupStorageClass  string          `json:\"backup_storage_class\"`\n}\n\ntype datacentersData struct {\n\tDatacenters []*datacenter.Datacenter `json:\"datacenters\"`\n\tCount       int64                    `json:\"count\"`\n}\n\nfunc datacenterPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &datacenterData{}\n\n\tdcId, ok := utils.ParseObjectId(c.Param(\"dc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdc, err := datacenter.Get(db, dcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdc.Name = data.Name\n\tdc.Comment = data.Comment\n\tdc.NetworkMode = data.NetworkMode\n\tdc.MatchOrganizations = data.MatchOrganizations\n\tdc.Organizations = data.Organizations\n\tdc.JumboMtu = data.JumboMtu\n\tdc.PublicStorages = data.PublicStorages\n\tdc.PrivateStorage = data.PrivateStorage\n\tdc.PrivateStorageClass = data.PrivateStorageClass\n\tdc.BackupStorage = data.BackupStorage\n\tdc.BackupStorageClass = data.BackupStorageClass\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"network_mode\",\n\t\t\"match_organizations\",\n\t\t\"organizations\",\n\t\t\"jumbo_mtu\",\n\t\t\"public_storages\",\n\t\t\"private_storage\",\n\t\t\"private_storage_class\",\n\t\t\"backup_storage\",\n\t\t\"backup_storage_class\",\n\t)\n\n\terrData, err := dc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = dc.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"datacenter.change\")\n\n\tc.JSON(200, dc)\n}\n\nfunc datacenterPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &datacenterData{\n\t\tName: \"new-datacenter\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdc := &datacenter.Datacenter{\n\t\tName:                data.Name,\n\t\tComment:             data.Comment,\n\t\tNetworkMode:         data.NetworkMode,\n\t\tMatchOrganizations:  data.MatchOrganizations,\n\t\tOrganizations:       data.Organizations,\n\t\tJumboMtu:            data.JumboMtu,\n\t\tPublicStorages:      data.PublicStorages,\n\t\tPrivateStorage:      data.PrivateStorage,\n\t\tPrivateStorageClass: data.PrivateStorageClass,\n\t\tBackupStorage:       data.BackupStorage,\n\t\tBackupStorageClass:  data.BackupStorageClass,\n\t}\n\n\terrData, err := dc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = dc.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"datacenter.change\")\n\n\tc.JSON(200, dc)\n}\n\nfunc datacenterDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tdcId, ok := utils.ParseObjectId(c.Param(\"dc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"datacenter\", dcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = datacenter.Remove(db, dcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"datacenter.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc datacentersDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"datacenter\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = datacenter.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"datacenter.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc datacenterGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdc := demo.Datacenters[0]\n\t\tc.JSON(200, dc)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tdcId, ok := utils.ParseObjectId(c.Param(\"dc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdc, err := datacenter.Get(db, dcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, dc)\n}\n\nfunc datacentersGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &datacentersData{\n\t\t\tDatacenters: demo.Datacenters,\n\t\t\tCount:       int64(len(demo.Datacenters)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tdcs, err := datacenter.GetAllNames(db, &bson.M{})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, dcs)\n\t\treturn\n\t}\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tdatacenterId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = datacenterId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tdc, count, err := datacenter.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &datacentersData{\n\t\tDatacenters: dc,\n\t\tCount:       count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/devices.go",
    "content": "package ahandlers\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/alertevent\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/device\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/secondary\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype deviceData struct {\n\tUser   bson.ObjectID `json:\"user\"`\n\tName   string        `json:\"name\"`\n\tType   string        `json:\"type\"`\n\tMode   string        `json:\"mode\"`\n\tNumber string        `json:\"number\"`\n}\n\nfunc devicePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &deviceData{}\n\n\tdevcId, ok := utils.ParseObjectId(c.Param(\"device_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevc, err := device.Get(db, devcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevc.Name = data.Name\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t)\n\n\terrData, err := devc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = devc.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\tc.JSON(200, devc)\n}\n\nfunc devicePost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &deviceData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevc := device.New(data.User, data.Type, data.Mode)\n\n\tdevc.Name = data.Name\n\tdevc.Number = data.Number\n\n\terrData, err := devc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = devc.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\tc.JSON(200, devc)\n}\n\nfunc deviceDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tdevcId, ok := utils.ParseObjectId(c.Param(\"device_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := device.Remove(db, devcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc devicesGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tusrId, ok := utils.ParseObjectId(c.Param(\"user_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdevices, err := device.GetAllSorted(db, usrId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, devices)\n}\n\nfunc deviceAlertPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &deviceData{}\n\n\tdevcId, ok := utils.ParseObjectId(c.Param(\"resource_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevc, err := device.Get(db, devcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := alertevent.SendTest(db, devc)\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\tc.JSON(200, devc)\n}\n\nfunc deviceMethodPost(c *gin.Context) {\n\tswitch c.Param(\"method\") {\n\tcase \"alert\":\n\t\tdeviceAlertPost(c)\n\t\treturn\n\tdefault:\n\t\tutils.AbortWithStatus(c, 404)\n\t\treturn\n\t}\n\n\treturn\n}\n\ntype devicesWanRegisterRespData struct {\n\tToken   string      `json:\"token\"`\n\tOptions interface{} `json:\"options\"`\n}\n\nfunc deviceWanRegisterGet(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif node.Self.WebauthnDomain == \"\" {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"webauthn_domain_unavailable\",\n\t\t\tMessage: \"WebAuthn domain must be configured\",\n\t\t}\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tusrId, ok := utils.ParseObjectId(c.Param(\"user_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.New(db, usrId,\n\t\tsecondary.AdminDeviceRegister, secondary.DeviceProvider)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tjsonResp, errData, err := secd.DeviceRegisterRequest(db,\n\t\tutils.GetOrigin(c.Request))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tresp := &devicesWanRegisterRespData{\n\t\tToken:   secd.Id,\n\t\tOptions: jsonResp,\n\t}\n\n\tc.JSON(200, resp)\n}\n\ntype devicesWanRegisterData struct {\n\tToken string `json:\"token\"`\n\tName  string `json:\"name\"`\n}\n\nfunc deviceWanRegisterPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &devicesWanRegisterData{}\n\n\tbody, err := utils.CopyBody(c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusrId, ok := utils.ParseObjectId(c.Param(\"resource_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token,\n\t\tsecondary.AdminDeviceRegister)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(400, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tdevc, errData, err := secd.DeviceRegisterResponse(\n\t\tdb, utils.GetOrigin(c.Request), body, data.Name)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusrId,\n\t\taudit.AdminDeviceRegister,\n\t\taudit.Fields{\n\t\t\t\"admin_id\":  usr.Id,\n\t\t\t\"device_id\": devc.Id,\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\tc.JSON(200, nil)\n}\n"
  },
  {
    "path": "ahandlers/disk.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype diskData struct {\n\tId               bson.ObjectID `json:\"id\"`\n\tName             string        `json:\"name\"`\n\tComment          string        `json:\"comment\"`\n\tOrganization     bson.ObjectID `json:\"organization\"`\n\tInstance         bson.ObjectID `json:\"instance\"`\n\tIndex            string        `json:\"index\"`\n\tType             string        `json:\"type\"`\n\tNode             bson.ObjectID `json:\"node\"`\n\tPool             bson.ObjectID `json:\"pool\"`\n\tDeleteProtection bool          `json:\"delete_protection\"`\n\tFileSystem       string        `json:\"file_system\"`\n\tImage            bson.ObjectID `json:\"image\"`\n\tRestoreImage     bson.ObjectID `json:\"restore_image\"`\n\tBacking          bool          `json:\"backing\"`\n\tAction           string        `json:\"action\"`\n\tSize             int           `json:\"size\"`\n\tLvSize           int           `json:\"lv_size\"`\n\tNewSize          int           `json:\"new_size\"`\n\tBackup           bool          `json:\"backup\"`\n}\n\ntype disksMultiData struct {\n\tIds    []bson.ObjectID `json:\"ids\"`\n\tAction string          `json:\"action\"`\n}\n\ntype disksData struct {\n\tDisks []*aggregate.DiskAggregate `json:\"disks\"`\n\tCount int64                      `json:\"count\"`\n}\n\nfunc diskPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &diskData{}\n\n\tdiskId, ok := utils.ParseObjectId(c.Param(\"disk_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdsk, err := disk.Get(db, diskId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"type\",\n\t\t\"instance\",\n\t\t\"delete_protection\",\n\t\t\"index\",\n\t\t\"backup\",\n\t\t\"new_size\",\n\t)\n\n\tdsk.PreCommit()\n\n\tdsk.Name = dta.Name\n\tdsk.Comment = dta.Comment\n\tdsk.Instance = dta.Instance\n\tdsk.DeleteProtection = dta.DeleteProtection\n\tdsk.Index = dta.Index\n\tdsk.Backup = dta.Backup\n\n\tif dta.Action != \"\" && dsk.Action != \"\" {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"disk_actin_active\",\n\t\t\tMessage: \"Disk action already active\",\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tif dsk.IsActive() && dta.Action == disk.Snapshot {\n\t\tdsk.Action = disk.Snapshot\n\t\tfields.Add(\"action\")\n\t} else if dsk.IsActive() && dta.Action == disk.Backup {\n\t\tdsk.Action = disk.Backup\n\t\tfields.Add(\"action\")\n\t} else if dsk.IsActive() && dta.Action == disk.Expand {\n\t\tdsk.Action = disk.Expand\n\t\tdsk.NewSize = dta.NewSize\n\t\tfields.Add(\"action\")\n\t} else if dsk.IsActive() && dta.Action == disk.Restore {\n\t\timg, err := image.Get(db, dta.RestoreImage)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif img.Disk != dsk.Id {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_restore_image\",\n\t\t\t\tMessage: \"Invalid restore image\",\n\t\t\t}\n\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\n\t\tdsk.Action = disk.Restore\n\t\tdsk.RestoreImage = img.Id\n\n\t\tfields.Add(\"action\")\n\t\tfields.Add(\"restore_image\")\n\t}\n\n\terrData, err := dsk.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = dsk.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, dsk)\n}\n\nfunc diskPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &diskData{\n\t\tName: \"new-disk\",\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tnde, err := node.Get(db, dta.Node)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\timgSystemType := \"\"\n\timgSystemKind := \"\"\n\tif !dta.Image.IsZero() {\n\t\timg, err := image.GetOrgPublic(db, dta.Organization, dta.Image)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\timgSystemType = img.GetSystemType()\n\t\timgSystemKind = img.GetSystemKind()\n\n\t\tstore, err := storage.Get(db, img.Storage)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tavailable, err := data.ImageAvailable(store, img)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tif !available {\n\t\t\tif store.IsOracle() {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_available\",\n\t\t\t\t\tMessage: \"Image not restored from archive\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t} else {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_available\",\n\t\t\t\t\tMessage: \"Image not restored from glacier\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\t}\n\n\tdsk := &disk.Disk{\n\t\tName:             dta.Name,\n\t\tComment:          dta.Comment,\n\t\tOrganization:     dta.Organization,\n\t\tInstance:         dta.Instance,\n\t\tDatacenter:       nde.Datacenter,\n\t\tZone:             nde.Zone,\n\t\tIndex:            dta.Index,\n\t\tType:             dta.Type,\n\t\tSystemType:       imgSystemType,\n\t\tSystemKind:       imgSystemKind,\n\t\tNode:             dta.Node,\n\t\tPool:             dta.Pool,\n\t\tImage:            dta.Image,\n\t\tDeleteProtection: dta.DeleteProtection,\n\t\tFileSystem:       dta.FileSystem,\n\t\tBacking:          dta.Backing,\n\t\tSize:             dta.Size,\n\t\tLvSize:           dta.LvSize,\n\t\tBackup:           dta.Backup,\n\t}\n\n\terrData, err := dsk.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = dsk.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, dsk)\n}\n\nfunc disksPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &disksMultiData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif data.Action != disk.Snapshot && data.Action != disk.Backup {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"invalid_action\",\n\t\t\tMessage: \"Invalid disk action\",\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tdoc := bson.M{\n\t\t\"action\": data.Action,\n\t}\n\n\terr = disk.UpdateMulti(db, data.Ids, &doc)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc diskDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tdiskId, ok := utils.ParseObjectId(c.Param(\"disk_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdsk, err := disk.Get(db, diskId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif dsk.DeleteProtection {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"delete_protection\",\n\t\t\tMessage: \"Cannot delete disk with delete protection\",\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tif !dsk.Instance.IsZero() {\n\t\tinst, e := instance.Get(db, dsk.Instance)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif inst.DeleteProtection {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError: \"instance_delete_protection\",\n\t\t\t\tMessage: \"Cannot delete disk attached to \" +\n\t\t\t\t\t\"instance with delete protection\",\n\t\t\t}\n\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = disk.Delete(db, diskId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc disksDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := []bson.ObjectID{}\n\n\terr := c.Bind(&dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tforce := c.Query(\"force\")\n\tif force == \"true\" {\n\t\tfor _, diskId := range dta {\n\t\t\terr = disk.Remove(db, diskId)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\terr = disk.DeleteMulti(db, dta)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc diskGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdsk := demo.Disks[0]\n\t\tc.JSON(200, dsk)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tdiskId, ok := utils.ParseObjectId(c.Param(\"disk_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdsk, err := disk.Get(db, diskId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, dsk)\n}\n\nfunc disksGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &disksData{\n\t\t\tDisks: demo.Disks,\n\t\t\tCount: int64(len(demo.Disks)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tdiskId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = diskId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tinst, ok := utils.ParseObjectId(c.Query(\"instance\"))\n\tif ok {\n\t\tquery[\"instance\"] = inst\n\t}\n\n\tnodeId, ok := utils.ParseObjectId(c.Query(\"node\"))\n\tif ok {\n\t\tquery[\"node\"] = nodeId\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tdisks, count, err := aggregate.GetDiskPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdta := &disksData{\n\t\tDisks: disks,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, dta)\n}\n"
  },
  {
    "path": "ahandlers/domain.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype domainData struct {\n\tId           bson.ObjectID    `json:\"id\"`\n\tName         string           `json:\"name\"`\n\tComment      string           `json:\"comment\"`\n\tOrganization bson.ObjectID    `json:\"organization\"`\n\tType         string           `json:\"type\"`\n\tSecret       bson.ObjectID    `json:\"secret\"`\n\tRootDomain   string           `json:\"root_domain\"`\n\tRecords      []*domain.Record `json:\"records\"`\n}\n\ntype domainsData struct {\n\tDomains []*domain.Domain `json:\"domains\"`\n\tCount   int64            `json:\"count\"`\n}\n\nfunc domainPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &domainData{}\n\n\tdomainId, ok := utils.ParseObjectId(c.Param(\"domain_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdomn, err := domain.Get(db, domainId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = domn.LoadRecords(db, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdomn.PreCommit()\n\n\tdomn.Name = data.Name\n\tdomn.Comment = data.Comment\n\tdomn.Organization = data.Organization\n\tdomn.Type = data.Type\n\tdomn.Secret = data.Secret\n\tdomn.RootDomain = data.RootDomain\n\tdomn.Records = data.Records\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"organization\",\n\t\t\"type\",\n\t\t\"secret\",\n\t\t\"root_domain\",\n\t)\n\n\terrData, err := domn.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = domn.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = domn.CommitRecords(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\n\tc.JSON(200, domn)\n}\n\nfunc domainPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &domainData{\n\t\tName: \"new.domain\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdomn := &domain.Domain{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: data.Organization,\n\t\tType:         data.Type,\n\t\tSecret:       data.Secret,\n\t\tRootDomain:   data.RootDomain,\n\t}\n\n\terrData, err := domn.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = domn.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\n\tc.JSON(200, domn)\n}\n\nfunc domainDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tdomainId, ok := utils.ParseObjectId(c.Param(\"domain_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := domain.Remove(db, domainId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc domainsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = domain.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc domainGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdomn := demo.Domains[0]\n\t\tc.JSON(200, domn)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tdomainId, ok := utils.ParseObjectId(c.Param(\"domain_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdomn, err := domain.Get(db, domainId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = domn.LoadRecords(db, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdomn.Json()\n\n\tc.JSON(200, domn)\n}\n\nfunc domainsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &domainsData{\n\t\t\tDomains: demo.Domains,\n\t\t\tCount:   int64(len(demo.Domains)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tquery := &bson.M{}\n\n\t\tdomns, err := domain.GetAllName(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, domns)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{}\n\n\t\tdomainId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = domainId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"name\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\t\tif ok {\n\t\t\tquery[\"organization\"] = organization\n\t\t}\n\n\t\tdomains, count, err := aggregate.GetDomainPaged(\n\t\t\tdb, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata := &domainsData{\n\t\t\tDomains: domains,\n\t\t\tCount:   count,\n\t\t}\n\n\t\tc.JSON(200, data)\n\t}\n}\n"
  },
  {
    "path": "ahandlers/event.go",
    "content": "package ahandlers\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst (\n\twriteTimeout = 10 * time.Second\n\tpingInterval = 30 * time.Second\n\tpingWait     = 40 * time.Second\n)\n\nfunc eventGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tsocket := &event.WebSocket{}\n\n\tdefer func() {\n\t\tsocket.Close()\n\t\tevent.WebSocketsLock.Lock()\n\t\tevent.WebSockets.Remove(socket)\n\t\tevent.WebSocketsLock.Unlock()\n\t}()\n\n\tevent.WebSocketsLock.Lock()\n\tevent.WebSockets.Add(socket)\n\tevent.WebSocketsLock.Unlock()\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tsocket.Cancel = cancel\n\n\tconn, err := event.Upgrader.Upgrade(c.Writer, c.Request, nil)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"mhandlers: Failed to upgrade request\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tsocket.Conn = conn\n\n\terr = conn.SetReadDeadline(time.Now().Add(pingWait))\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"mhandlers: Failed to set read deadline\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tconn.SetPongHandler(func(x string) (err error) {\n\t\terr = conn.SetReadDeadline(time.Now().Add(pingWait))\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"mhandlers: Failed to set read deadline\"),\n\t\t\t}\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t})\n\n\tlst, err := event.SubscribeListener(db, []string{\"dispatch\"})\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tsocket.Listener = lst\n\n\tticker := time.NewTicker(pingInterval)\n\tsocket.Ticker = ticker\n\tsub := lst.Listen()\n\tdefer lst.Close()\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tr := recover()\n\t\t\tif r != nil && !socket.Closed {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": errors.New(fmt.Sprintf(\"%s\", r)),\n\t\t\t\t}).Error(\"mhandlers: Event panic\")\n\t\t\t}\n\t\t}()\n\t\tfor {\n\t\t\t_, _, err := conn.NextReader()\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase msg, ok := <-sub:\n\t\t\tif !ok {\n\t\t\t\terr = conn.WriteControl(websocket.CloseMessage, []byte{},\n\t\t\t\t\ttime.Now().Add(writeTimeout))\n\t\t\t\tif err != nil {\n\t\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\t\"mhandlers: Failed to set write control\"),\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = conn.SetWriteDeadline(time.Now().Add(writeTimeout))\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"mhandlers: Failed to set write deadline\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = conn.WriteJSON(msg)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"mhandlers: Failed to set write json\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-ticker.C:\n\t\t\terr = conn.WriteControl(websocket.PingMessage, []byte{},\n\t\t\t\ttime.Now().Add(writeTimeout))\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"mhandlers: Failed to set write control\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "ahandlers/firewall.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype firewallData struct {\n\tId           bson.ObjectID    `json:\"id\"`\n\tName         string           `json:\"name\"`\n\tComment      string           `json:\"comment\"`\n\tOrganization bson.ObjectID    `json:\"organization\"`\n\tRoles        []string         `json:\"roles\"`\n\tIngress      []*firewall.Rule `json:\"ingress\"`\n}\n\ntype firewallsData struct {\n\tFirewalls []*firewall.Firewall `json:\"firewalls\"`\n\tCount     int64                `json:\"count\"`\n}\n\nfunc firewallPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &firewallData{}\n\n\tfirewallId, ok := utils.ParseObjectId(c.Param(\"firewall_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfire, err := firewall.Get(db, firewallId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfire.Name = data.Name\n\tfire.Comment = data.Comment\n\tfire.Organization = data.Organization\n\tfire.Roles = data.Roles\n\tfire.Ingress = data.Ingress\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"organization\",\n\t\t\"roles\",\n\t\t\"ingress\",\n\t)\n\n\terrData, err := fire.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = fire.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"firewall.change\")\n\n\tc.JSON(200, fire)\n}\n\nfunc firewallPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &firewallData{\n\t\tName: \"new-firewall\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfire := &firewall.Firewall{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: data.Organization,\n\t\tRoles:        data.Roles,\n\t\tIngress:      data.Ingress,\n\t}\n\n\terrData, err := fire.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = fire.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"firewall.change\")\n\n\tc.JSON(200, fire)\n}\n\nfunc firewallDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tfirewallId, ok := utils.ParseObjectId(c.Param(\"firewall_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"firewall\", firewallId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = firewall.Remove(db, firewallId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"firewall.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc firewallsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"firewall\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = firewall.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"firewall.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc firewallGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tfire := demo.Firewalls[0]\n\t\tc.JSON(200, fire)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tfirewallId, ok := utils.ParseObjectId(c.Param(\"firewall_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tfire, err := firewall.Get(db, firewallId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, fire)\n}\n\nfunc firewallsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &firewallsData{\n\t\t\tFirewalls: demo.Firewalls,\n\t\t\tCount:     int64(len(demo.Firewalls)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tfirewallId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = firewallId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tfirewalls, count, err := firewall.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &firewallsData{\n\t\tFirewalls: firewalls,\n\t\tCount:     count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/handlers.go",
    "content": "package ahandlers\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/config\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/middlewear\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/pritunl/pritunl-cloud/static\"\n)\n\nvar (\n\tstore      *static.Store\n\tfileServer http.Handler\n)\n\nfunc Register(engine *gin.Engine) {\n\tengine.Use(middlewear.Limiter)\n\tengine.Use(middlewear.Counter)\n\tengine.Use(middlewear.Recovery)\n\tengine.Use(middlewear.Headers)\n\n\tdbGroup := engine.Group(\"\")\n\tdbGroup.Use(middlewear.Database)\n\n\tsessGroup := dbGroup.Group(\"\")\n\tsessGroup.Use(middlewear.SessionAdmin)\n\n\tauthGroup := sessGroup.Group(\"\")\n\tauthGroup.Use(middlewear.AuthAdmin)\n\n\tcsrfGroup := authGroup.Group(\"\")\n\tcsrfGroup.Use(middlewear.CsrfToken)\n\n\tengine.NoRoute(middlewear.NotFound)\n\n\tcsrfGroup.GET(\"/audit/:user_id\", auditsGet)\n\n\tcsrfGroup.GET(\"/alert\", alertsGet)\n\tcsrfGroup.GET(\"/alert/:alert_id\", alertGet)\n\tcsrfGroup.PUT(\"/alert/:alert_id\", alertPut)\n\tcsrfGroup.POST(\"/alert\", alertPost)\n\tcsrfGroup.DELETE(\"/alert\", alertsDelete)\n\tcsrfGroup.DELETE(\"/alert/:alert_id\", alertDelete)\n\n\tengine.GET(\"/auth/state\", authStateGet)\n\tdbGroup.POST(\"/auth/session\", authSessionPost)\n\tdbGroup.POST(\"/auth/secondary\", authSecondaryPost)\n\tdbGroup.GET(\"/auth/request\", authRequestGet)\n\tdbGroup.GET(\"/auth/callback\", authCallbackGet)\n\tdbGroup.GET(\"/auth/webauthn/request\", authWanRequestGet)\n\tdbGroup.POST(\"/auth/webauthn/respond\", authWanRespondPost)\n\tdbGroup.GET(\"/auth/webauthn/register\", authWanRegisterGet)\n\tdbGroup.POST(\"/auth/webauthn/register\", authWanRegisterPost)\n\tsessGroup.GET(\"/logout\", logoutGet)\n\n\tcsrfGroup.GET(\"/authority\", authoritiesGet)\n\tcsrfGroup.GET(\"/authority/:authority_id\", authorityGet)\n\tcsrfGroup.PUT(\"/authority/:authority_id\", authorityPut)\n\tcsrfGroup.POST(\"/authority\", authorityPost)\n\tcsrfGroup.DELETE(\"/authority\", authoritiesDelete)\n\tcsrfGroup.DELETE(\"/authority/:authority_id\", authorityDelete)\n\n\tcsrfGroup.GET(\"/balancer\", balancersGet)\n\tcsrfGroup.GET(\"/balancer/:balancer_id\", balancerGet)\n\tcsrfGroup.PUT(\"/balancer/:balancer_id\", balancerPut)\n\tcsrfGroup.POST(\"/balancer\", balancerPost)\n\tcsrfGroup.DELETE(\"/balancer\", balancersDelete)\n\tcsrfGroup.DELETE(\"/balancer/:balancer_id\", balancerDelete)\n\n\tcsrfGroup.GET(\"/block\", blocksGet)\n\tcsrfGroup.GET(\"/block/:block_id\", blockGet)\n\tcsrfGroup.PUT(\"/block/:block_id\", blockPut)\n\tcsrfGroup.POST(\"/block\", blockPost)\n\tcsrfGroup.DELETE(\"/block\", blocksDelete)\n\tcsrfGroup.DELETE(\"/block/:block_id\", blockDelete)\n\n\tcsrfGroup.GET(\"/certificate\", certificatesGet)\n\tcsrfGroup.GET(\"/certificate/:cert_id\", certificateGet)\n\tcsrfGroup.PUT(\"/certificate/:cert_id\", certificatePut)\n\tcsrfGroup.POST(\"/certificate\", certificatePost)\n\tcsrfGroup.DELETE(\"/certificate\", certificatesDelete)\n\tcsrfGroup.DELETE(\"/certificate/:cert_id\", certificateDelete)\n\n\tengine.GET(\"/check\", checkGet)\n\n\tauthGroup.GET(\"/csrf\", csrfGet)\n\n\tcsrfGroup.GET(\"/completion\", completionGet)\n\n\tcsrfGroup.GET(\"/datacenter\", datacentersGet)\n\tcsrfGroup.GET(\"/datacenter/:dc_id\", datacenterGet)\n\tcsrfGroup.PUT(\"/datacenter/:dc_id\", datacenterPut)\n\tcsrfGroup.POST(\"/datacenter\", datacenterPost)\n\tcsrfGroup.DELETE(\"/datacenter\", datacentersDelete)\n\tcsrfGroup.DELETE(\"/datacenter/:dc_id\", datacenterDelete)\n\n\tcsrfGroup.GET(\"/device/:user_id\", devicesGet)\n\tcsrfGroup.PUT(\"/device/:device_id\", devicePut)\n\tcsrfGroup.POST(\"/device\", devicePost)\n\tcsrfGroup.DELETE(\"/device/:device_id\", deviceDelete)\n\tcsrfGroup.POST(\"/device/:resource_id/:method\", deviceMethodPost)\n\tcsrfGroup.GET(\"/device/:user_id/webauthn/register\", deviceWanRegisterGet)\n\tcsrfGroup.POST(\"/device/:resource_id/webauthn/register\",\n\t\tdeviceWanRegisterPost)\n\n\tcsrfGroup.GET(\"/disk\", disksGet)\n\tcsrfGroup.GET(\"/disk/:disk_id\", diskGet)\n\tcsrfGroup.PUT(\"/disk\", disksPut)\n\tcsrfGroup.PUT(\"/disk/:disk_id\", diskPut)\n\tcsrfGroup.POST(\"/disk\", diskPost)\n\tcsrfGroup.DELETE(\"/disk\", disksDelete)\n\tcsrfGroup.DELETE(\"/disk/:disk_id\", diskDelete)\n\n\tcsrfGroup.GET(\"/domain\", domainsGet)\n\tcsrfGroup.GET(\"/domain/:domain_id\", domainGet)\n\tcsrfGroup.PUT(\"/domain/:domain_id\", domainPut)\n\tcsrfGroup.POST(\"/domain\", domainPost)\n\tcsrfGroup.DELETE(\"/domain\", domainsDelete)\n\tcsrfGroup.DELETE(\"/domain/:domain_id\", domainDelete)\n\n\tcsrfGroup.GET(\"/event\", eventGet)\n\n\tcsrfGroup.GET(\"/firewall\", firewallsGet)\n\tcsrfGroup.GET(\"/firewall/:firewall_id\", firewallGet)\n\tcsrfGroup.PUT(\"/firewall/:firewall_id\", firewallPut)\n\tcsrfGroup.POST(\"/firewall\", firewallPost)\n\tcsrfGroup.DELETE(\"/firewall\", firewallsDelete)\n\tcsrfGroup.DELETE(\"/firewall/:firewall_id\", firewallDelete)\n\n\tcsrfGroup.GET(\"/image\", imagesGet)\n\tcsrfGroup.GET(\"/image/:image_id\", imageGet)\n\tcsrfGroup.PUT(\"/image/:image_id\", imagePut)\n\tcsrfGroup.DELETE(\"/image\", imagesDelete)\n\tcsrfGroup.DELETE(\"/image/:image_id\", imageDelete)\n\n\tcsrfGroup.GET(\"/instance\", instancesGet)\n\tcsrfGroup.PUT(\"/instance\", instancesPut)\n\tcsrfGroup.GET(\"/instance/:instance_id\", instanceGet)\n\tcsrfGroup.GET(\"/instance/:instance_id/vnc\", instanceVncGet)\n\tcsrfGroup.PUT(\"/instance/:instance_id\", instancePut)\n\tcsrfGroup.POST(\"/instance\", instancePost)\n\tcsrfGroup.DELETE(\"/instance\", instancesDelete)\n\tcsrfGroup.DELETE(\"/instance/:instance_id\", instanceDelete)\n\n\tcsrfGroup.PUT(\"/license\", licensePut)\n\n\tcsrfGroup.GET(\"/log\", logsGet)\n\tcsrfGroup.GET(\"/log/:log_id\", logGet)\n\n\tcsrfGroup.GET(\"/node\", nodesGet)\n\tcsrfGroup.GET(\"/node/:node_id\", nodeGet)\n\tcsrfGroup.PUT(\"/node/:node_id\", nodePut)\n\tcsrfGroup.PUT(\"/node/:node_id/:operation\", nodeOperationPut)\n\tcsrfGroup.POST(\"/node/:node_id/init\", nodeInitPost)\n\tcsrfGroup.DELETE(\"/node/:node_id\", nodeDelete)\n\n\tcsrfGroup.GET(\"/organization\", organizationsGet)\n\tcsrfGroup.GET(\"/organization/:org_id\", organizationGet)\n\tcsrfGroup.PUT(\"/organization/:org_id\", organizationPut)\n\tcsrfGroup.POST(\"/organization\", organizationPost)\n\tcsrfGroup.DELETE(\"/organization/:org_id\", organizationDelete)\n\n\tcsrfGroup.GET(\"/plan\", plansGet)\n\tcsrfGroup.GET(\"/plan/:plan_id\", planGet)\n\tcsrfGroup.PUT(\"/plan/:plan_id\", planPut)\n\tcsrfGroup.POST(\"/plan\", planPost)\n\tcsrfGroup.DELETE(\"/plan\", plansDelete)\n\tcsrfGroup.DELETE(\"/plan/:plan_id\", planDelete)\n\n\tcsrfGroup.GET(\"/policy\", policiesGet)\n\tcsrfGroup.GET(\"/policy/:policy_id\", policyGet)\n\tcsrfGroup.PUT(\"/policy/:policy_id\", policyPut)\n\tcsrfGroup.POST(\"/policy\", policyPost)\n\tcsrfGroup.DELETE(\"/policy\", policiesDelete)\n\tcsrfGroup.DELETE(\"/policy/:policy_id\", policyDelete)\n\n\tcsrfGroup.GET(\"/pool\", poolsGet)\n\tcsrfGroup.GET(\"/pool/:pool_id\", poolGet)\n\tcsrfGroup.PUT(\"/pool/:pool_id\", poolPut)\n\tcsrfGroup.POST(\"/pool\", poolPost)\n\tcsrfGroup.DELETE(\"/pool\", poolsDelete)\n\tcsrfGroup.DELETE(\"/pool/:pool_id\", poolDelete)\n\n\tcsrfGroup.GET(\"/relations/:kind/:id\", relationsGet)\n\n\tcsrfGroup.GET(\"/secret\", secretsGet)\n\tcsrfGroup.GET(\"/secret/:secr_id\", secretGet)\n\tcsrfGroup.PUT(\"/secret/:secr_id\", secretPut)\n\tcsrfGroup.POST(\"/secret\", secretPost)\n\tcsrfGroup.DELETE(\"/secret\", secretsDelete)\n\tcsrfGroup.DELETE(\"/secret/:secr_id\", secretDelete)\n\n\tcsrfGroup.GET(\"/session/:user_id\", sessionsGet)\n\tcsrfGroup.DELETE(\"/session/:session_id\", sessionDelete)\n\n\tcsrfGroup.GET(\"/settings\", settingsGet)\n\tcsrfGroup.PUT(\"/settings\", settingsPut)\n\n\tcsrfGroup.GET(\"/pod\", podsGet)\n\tcsrfGroup.GET(\"/pod/:pod_id\", podGet)\n\tcsrfGroup.PUT(\"/pod/:pod_id\", podPut)\n\tcsrfGroup.PUT(\"/pod/:pod_id/drafts\", podDraftsPut)\n\tcsrfGroup.PUT(\"/pod/:pod_id/deploy\", podDeployPut)\n\tcsrfGroup.POST(\"/pod\", podPost)\n\tcsrfGroup.DELETE(\"/pod\", podsDelete)\n\tcsrfGroup.DELETE(\"/pod/:pod_id\", podDelete)\n\tcsrfGroup.GET(\"/pod/:pod_id/unit/:unit_id\", podUnitGet)\n\tcsrfGroup.PUT(\"/pod/:pod_id/unit/:unit_id/deployment\",\n\t\tpodUnitDeploymentsPut)\n\tcsrfGroup.POST(\"/pod/:pod_id/unit/:unit_id/deployment\",\n\t\tpodUnitDeploymentPost)\n\tcsrfGroup.PUT(\"/pod/:pod_id/unit/:unit_id/deployment/:deployment_id\",\n\t\tpodUnitDeploymentPut)\n\tcsrfGroup.GET(\n\t\t\"/pod/:pod_id/unit/:unit_id/deployment/:deployment_id/log\",\n\t\tpodUnitDeploymentLogGet,\n\t)\n\tcsrfGroup.GET(\"/pod/:pod_id/unit/:unit_id/spec\", podUnitSpecsGet)\n\tcsrfGroup.GET(\"/pod/:pod_id/unit/:unit_id/spec/:spec_id\", podUnitSpecGet)\n\n\tcsrfGroup.GET(\"/shape\", shapesGet)\n\tcsrfGroup.GET(\"/shape/:shape_id\", shapeGet)\n\tcsrfGroup.PUT(\"/shape/:shape_id\", shapePut)\n\tcsrfGroup.POST(\"/shape\", shapePost)\n\tcsrfGroup.DELETE(\"/shape\", shapesDelete)\n\tcsrfGroup.DELETE(\"/shape/:shape_id\", shapeDelete)\n\n\tcsrfGroup.GET(\"/storage\", storagesGet)\n\tcsrfGroup.GET(\"/storage/:store_id\", storageGet)\n\tcsrfGroup.PUT(\"/storage/:store_id\", storagePut)\n\tcsrfGroup.POST(\"/storage\", storagePost)\n\tcsrfGroup.DELETE(\"/storage\", storagesDelete)\n\tcsrfGroup.DELETE(\"/storage/:store_id\", storageDelete)\n\n\tcsrfGroup.GET(\"/subscription\", subscriptionGet)\n\tcsrfGroup.GET(\"/subscription/update\", subscriptionUpdateGet)\n\tcsrfGroup.POST(\"/subscription\", subscriptionPost)\n\n\tcsrfGroup.PUT(\"/theme\", themePut)\n\n\tcsrfGroup.GET(\"/user\", usersGet)\n\tcsrfGroup.GET(\"/user/:user_id\", userGet)\n\tcsrfGroup.PUT(\"/user/:user_id\", userPut)\n\tcsrfGroup.POST(\"/user\", userPost)\n\tcsrfGroup.DELETE(\"/user\", usersDelete)\n\n\tcsrfGroup.GET(\"/vpc\", vpcsGet)\n\tcsrfGroup.GET(\"/vpc/:vpc_id\", vpcGet)\n\tcsrfGroup.PUT(\"/vpc/:vpc_id\", vpcPut)\n\tcsrfGroup.GET(\"/vpc/:vpc_id/routes\", vpcRoutesGet)\n\tcsrfGroup.PUT(\"/vpc/:vpc_id/routes\", vpcRoutesPut)\n\tcsrfGroup.POST(\"/vpc\", vpcPost)\n\tcsrfGroup.DELETE(\"/vpc\", vpcsDelete)\n\tcsrfGroup.DELETE(\"/vpc/:vpc_id\", vpcDelete)\n\n\tcsrfGroup.GET(\"/zone\", zonesGet)\n\tcsrfGroup.GET(\"/zone/:zone_id\", zoneGet)\n\tcsrfGroup.PUT(\"/zone/:zone_id\", zonePut)\n\tcsrfGroup.POST(\"/zone\", zonePost)\n\tcsrfGroup.DELETE(\"/zone\", zonesDelete)\n\tcsrfGroup.DELETE(\"/zone/:zone_id\", zoneDelete)\n\n\tengine.GET(\"/robots.txt\", middlewear.RobotsGet)\n\n\tif constants.Production {\n\t\tsessGroup.GET(\"/\", staticIndexGet)\n\t\tengine.GET(\"/login\", staticLoginGet)\n\t\tengine.GET(\"/logo.png\", staticLogoGet)\n\t\tauthGroup.GET(\"/static/*path\", staticGet)\n\t} else {\n\t\tfs := gin.Dir(config.StaticTestingRoot, false)\n\t\tfileServer = http.FileServer(fs)\n\n\t\tsessGroup.GET(\"/\", staticTestingGet)\n\t\tengine.GET(\"/login\", staticTestingGet)\n\t\tengine.GET(\"/logo.png\", staticTestingGet)\n\t\tauthGroup.GET(\"/static/*path\", staticTestingGet)\n\t}\n}\n\nfunc init() {\n\tmodule := requires.New(\"ahandlers\")\n\tmodule.After(\"settings\")\n\n\tmodule.Handler = func() (err error) {\n\t\tif constants.Production {\n\t\t\tstore, err = static.NewStore(config.StaticRoot)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "ahandlers/image.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype imageData struct {\n\tId           bson.ObjectID `json:\"id\"`\n\tName         string        `json:\"name\"`\n\tComment      string        `json:\"comment\"`\n\tOrganization bson.ObjectID `json:\"organization\"`\n}\n\ntype imagesData struct {\n\tImages []*image.Image `json:\"images\"`\n\tCount  int64          `json:\"count\"`\n}\n\nfunc imagePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &imageData{}\n\n\timageId, ok := utils.ParseObjectId(c.Param(\"image_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\timg, err := image.Get(db, imageId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\timg.Name = dta.Name\n\timg.Comment = dta.Comment\n\timg.Organization = dta.Organization\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"organization\",\n\t)\n\n\terrData, err := img.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = img.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"image.change\")\n\n\tc.JSON(200, img)\n}\n\nfunc imageDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\timageId, ok := utils.ParseObjectId(c.Param(\"image_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := data.DeleteImage(db, imageId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"image.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc imagesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := []bson.ObjectID{}\n\n\terr := c.Bind(&dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = data.DeleteImages(db, dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"image.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc imageGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\timageId, ok := utils.ParseObjectId(c.Param(\"image_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\timg, err := image.Get(db, imageId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\timg.Json()\n\n\tc.JSON(200, img)\n}\n\nfunc imagesGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tdcId, _ := utils.ParseObjectId(c.Query(\"datacenter\"))\n\tif !dcId.IsZero() {\n\t\tdc, err := datacenter.Get(db, dcId)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tstorages := dc.PublicStorages\n\t\tif storages == nil {\n\t\t\tstorages = []bson.ObjectID{}\n\t\t}\n\n\t\tif len(storages) == 0 {\n\t\t\tc.JSON(200, []bson.ObjectID{})\n\t\t\treturn\n\t\t}\n\n\t\tquery := &bson.M{\n\t\t\t\"storage\": &bson.M{\n\t\t\t\t\"$in\": storages,\n\t\t\t},\n\t\t}\n\t\tif demo.IsDemo() {\n\t\t\tquery = &bson.M{}\n\t\t}\n\n\t\timages, err := image.GetAllNames(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, img := range images {\n\t\t\timg.Json()\n\t\t}\n\n\t\tif !dc.PrivateStorage.IsZero() {\n\t\t\tquery = &bson.M{\n\t\t\t\t\"storage\": dc.PrivateStorage,\n\t\t\t}\n\n\t\t\timages2, err := image.GetAllNames(db, query)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor _, img := range images2 {\n\t\t\t\timg.Json()\n\t\t\t\timages = append(images, img)\n\t\t\t}\n\t\t}\n\n\t\tc.JSON(200, images)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{}\n\n\t\timageId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = imageId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t&bson.M{\n\t\t\t\t\t\"name\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(name)),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&bson.M{\n\t\t\t\t\t\"key\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(name)),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\ttyp := strings.TrimSpace(c.Query(\"type\"))\n\t\tif typ != \"\" {\n\t\t\tquery[\"type\"] = typ\n\t\t}\n\n\t\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\t\tif ok {\n\t\t\tquery[\"organization\"] = organization\n\t\t}\n\n\t\timages, count, err := image.GetAllPaged(db, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, img := range images {\n\t\t\timg.Json()\n\t\t}\n\n\t\tdta := &imagesData{\n\t\t\tImages: images,\n\t\t\tCount:  count,\n\t\t}\n\n\t\tc.JSON(200, dta)\n\t}\n}\n"
  },
  {
    "path": "ahandlers/instance.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/drive\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/iscsi\"\n\t\"github.com/pritunl/pritunl-cloud/iso\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n\t\"github.com/pritunl/pritunl-cloud/pci\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/usb\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\ntype instanceData struct {\n\tId                  bson.ObjectID       `json:\"id\"`\n\tOrganization        bson.ObjectID       `json:\"organization\"`\n\tZone                bson.ObjectID       `json:\"zone\"`\n\tVpc                 bson.ObjectID       `json:\"vpc\"`\n\tSubnet              bson.ObjectID       `json:\"subnet\"`\n\tCloudSubnet         string              `json:\"cloud_subnet\"`\n\tShape               bson.ObjectID       `json:\"shape\"`\n\tNode                bson.ObjectID       `json:\"node\"`\n\tDiskType            string              `json:\"disk_type\"`\n\tDiskPool            bson.ObjectID       `json:\"disk_pool\"`\n\tImage               bson.ObjectID       `json:\"image\"`\n\tImageBacking        bool                `json:\"image_backing\"`\n\tName                string              `json:\"name\"`\n\tComment             string              `json:\"comment\"`\n\tAction              string              `json:\"action\"`\n\tRootEnabled         bool                `json:\"root_enabled\"`\n\tUefi                bool                `json:\"uefi\"`\n\tSecureBoot          bool                `json:\"secure_boot\"`\n\tTpm                 bool                `json:\"tpm\"`\n\tDhcpServer          bool                `json:\"dhcp_server\"`\n\tCloudType           string              `json:\"cloud_type\"`\n\tCloudScript         string              `json:\"cloud_script\"`\n\tDeleteProtection    bool                `json:\"delete_protection\"`\n\tSkipSourceDestCheck bool                `json:\"skip_source_dest_check\"`\n\tInitDiskSize        int                 `json:\"init_disk_size\"`\n\tMemory              int                 `json:\"memory\"`\n\tProcessors          int                 `json:\"processors\"`\n\tRoles               []string            `json:\"roles\"`\n\tIsos                []*iso.Iso          `json:\"isos\"`\n\tUsbDevices          []*usb.Device       `json:\"usb_devices\"`\n\tPciDevices          []*pci.Device       `json:\"pci_devices\"`\n\tDriveDevices        []*drive.Device     `json:\"drive_devices\"`\n\tIscsiDevices        []*iscsi.Device     `json:\"iscsi_devices\"`\n\tMounts              []*instance.Mount   `json:\"mounts\"`\n\tVnc                 bool                `json:\"vnc\"`\n\tSpice               bool                `json:\"spice\"`\n\tGui                 bool                `json:\"gui\"`\n\tNodePorts           []*nodeport.Mapping `json:\"node_ports\"`\n\tNoPublicAddress     bool                `json:\"no_public_address\"`\n\tNoPublicAddress6    bool                `json:\"no_public_address6\"`\n\tNoHostAddress       bool                `json:\"no_host_address\"`\n\tCount               int                 `json:\"count\"`\n}\n\ntype instanceMultiData struct {\n\tIds    []bson.ObjectID `json:\"ids\"`\n\tAction string          `json:\"action\"`\n}\n\ntype instancesData struct {\n\tInstances []*instance.Instance `json:\"instances\"`\n\tCount     int64                `json:\"count\"`\n}\n\nfunc instancePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &instanceData{}\n\n\tinstanceId, ok := utils.ParseObjectId(c.Param(\"instance_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tinst, err := instance.Get(db, instanceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tinst.PreCommit()\n\n\tinst.Name = dta.Name\n\tinst.Comment = dta.Comment\n\tinst.Vpc = dta.Vpc\n\tinst.Subnet = dta.Subnet\n\tinst.CloudSubnet = dta.CloudSubnet\n\tif dta.Action != \"\" {\n\t\tinst.Action = dta.Action\n\t}\n\tinst.Uefi = dta.Uefi\n\tinst.SecureBoot = dta.SecureBoot\n\tinst.Tpm = dta.Tpm\n\tinst.DhcpServer = dta.DhcpServer\n\tinst.CloudType = dta.CloudType\n\tinst.CloudScript = dta.CloudScript\n\tinst.DeleteProtection = dta.DeleteProtection\n\tinst.SkipSourceDestCheck = dta.SkipSourceDestCheck\n\tinst.Memory = dta.Memory\n\tinst.Processors = dta.Processors\n\tinst.Roles = dta.Roles\n\tinst.Isos = dta.Isos\n\tinst.UsbDevices = dta.UsbDevices\n\tinst.PciDevices = dta.PciDevices\n\tinst.DriveDevices = dta.DriveDevices\n\tinst.IscsiDevices = dta.IscsiDevices\n\tinst.Mounts = dta.Mounts\n\tinst.RootEnabled = dta.RootEnabled\n\tinst.Vnc = dta.Vnc\n\tinst.Spice = dta.Spice\n\tinst.Gui = dta.Gui\n\tinst.NodePorts = dta.NodePorts\n\tinst.NoPublicAddress = dta.NoPublicAddress\n\tinst.NoPublicAddress6 = dta.NoPublicAddress6\n\tinst.NoHostAddress = dta.NoHostAddress\n\n\tfields := set.NewSet(\n\t\t\"unix_id\",\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"datacenter\",\n\t\t\"vpc\",\n\t\t\"subnet\",\n\t\t\"dhcp_ip\",\n\t\t\"dhcp_ip6\",\n\t\t\"cloud_subnet\",\n\t\t\"state\",\n\t\t\"restart\",\n\t\t\"restart_block_ip\",\n\t\t\"uefi\",\n\t\t\"secure_boot\",\n\t\t\"tpm\",\n\t\t\"tpm_secret\",\n\t\t\"dhcp_server\",\n\t\t\"cloud_type\",\n\t\t\"cloud_script\",\n\t\t\"delete_protection\",\n\t\t\"skip_source_dest_check\",\n\t\t\"memory\",\n\t\t\"processors\",\n\t\t\"roles\",\n\t\t\"isos\",\n\t\t\"usb_devices\",\n\t\t\"pci_devices\",\n\t\t\"drive_devices\",\n\t\t\"iscsi_devices\",\n\t\t\"mounts\",\n\t\t\"root_enabled\",\n\t\t\"root_passwd\",\n\t\t\"vnc\",\n\t\t\"vnc_display\",\n\t\t\"vnc_password\",\n\t\t\"spice\",\n\t\t\"spice_port\",\n\t\t\"spice_password\",\n\t\t\"gui\",\n\t\t\"node_ports\",\n\t\t\"no_public_address\",\n\t\t\"no_public_address6\",\n\t\t\"no_host_address\",\n\t)\n\n\terrData, err := inst.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tdskChange, err := inst.PostCommit(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = inst.CommitFields(db, fields)\n\tif err != nil {\n\t\t_ = inst.Cleanup(db)\n\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = inst.Cleanup(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\tif dskChange {\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}\n\n\tc.JSON(200, inst)\n}\n\nfunc instancePost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &instanceData{\n\t\tName: \"new-instance\",\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tzne, err := zone.Get(db, dta.Zone)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif !dta.Shape.IsZero() {\n\t\tdta.Node = bson.NilObjectID\n\t\tdta.DiskType = \"\"\n\t\tdta.DiskPool = bson.NilObjectID\n\t} else {\n\t\tnde, err := node.Get(db, dta.Node)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif nde.Zone != zne.Id {\n\t\t\tutils.AbortWithStatus(c, 405)\n\t\t\treturn\n\t\t}\n\n\t\tif dta.DiskType == disk.Lvm {\n\t\t\tpoolMatch := false\n\t\t\tfor _, plId := range nde.Pools {\n\t\t\t\tif plId == dta.DiskPool {\n\t\t\t\t\tpoolMatch = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !poolMatch {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"pool_not_found\",\n\t\t\t\t\tMessage: \"Pool not found\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif !dta.Image.IsZero() {\n\t\timg, err := image.GetOrgPublic(db, dta.Organization, dta.Image)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_found\",\n\t\t\t\t\tMessage: \"Image not found\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t} else {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tstre, err := storage.Get(db, img.Storage)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tavailable, err := data.ImageAvailable(stre, img)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tif !available {\n\t\t\tif stre.IsOracle() {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_available\",\n\t\t\t\t\tMessage: \"Image not restored from archive\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t} else {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_available\",\n\t\t\t\t\tMessage: \"Image not restored from glacier\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\t}\n\n\tinsts := []*instance.Instance{}\n\n\tif dta.Count == 0 {\n\t\tdta.Count = 1\n\t}\n\n\tfor i := 0; i < dta.Count; i++ {\n\t\tname := \"\"\n\t\tif strings.Contains(dta.Name, \"%\") {\n\t\t\tname = fmt.Sprintf(dta.Name, i+1)\n\t\t} else {\n\t\t\tname = dta.Name\n\t\t}\n\n\t\tinst := &instance.Instance{\n\t\t\tAction:              dta.Action,\n\t\t\tOrganization:        dta.Organization,\n\t\t\tZone:                dta.Zone,\n\t\t\tVpc:                 dta.Vpc,\n\t\t\tSubnet:              dta.Subnet,\n\t\t\tCloudSubnet:         dta.CloudSubnet,\n\t\t\tShape:               dta.Shape,\n\t\t\tNode:                dta.Node,\n\t\t\tDiskType:            dta.DiskType,\n\t\t\tDiskPool:            dta.DiskPool,\n\t\t\tImage:               dta.Image,\n\t\t\tImageBacking:        dta.ImageBacking,\n\t\t\tUefi:                dta.Uefi,\n\t\t\tSecureBoot:          dta.SecureBoot,\n\t\t\tTpm:                 dta.Tpm,\n\t\t\tDhcpServer:          dta.DhcpServer,\n\t\t\tCloudType:           dta.CloudType,\n\t\t\tCloudScript:         dta.CloudScript,\n\t\t\tDeleteProtection:    dta.DeleteProtection,\n\t\t\tSkipSourceDestCheck: dta.SkipSourceDestCheck,\n\t\t\tName:                name,\n\t\t\tComment:             dta.Comment,\n\t\t\tInitDiskSize:        dta.InitDiskSize,\n\t\t\tMemory:              dta.Memory,\n\t\t\tProcessors:          dta.Processors,\n\t\t\tRoles:               dta.Roles,\n\t\t\tIsos:                dta.Isos,\n\t\t\tUsbDevices:          dta.UsbDevices,\n\t\t\tPciDevices:          dta.PciDevices,\n\t\t\tDriveDevices:        dta.DriveDevices,\n\t\t\tIscsiDevices:        dta.IscsiDevices,\n\t\t\tMounts:              dta.Mounts,\n\t\t\tRootEnabled:         dta.RootEnabled,\n\t\t\tVnc:                 dta.Vnc,\n\t\t\tSpice:               dta.Spice,\n\t\t\tGui:                 dta.Gui,\n\t\t\tNodePorts:           dta.NodePorts,\n\t\t\tNoPublicAddress:     dta.NoPublicAddress,\n\t\t\tNoPublicAddress6:    dta.NoPublicAddress6,\n\t\t\tNoHostAddress:       dta.NoHostAddress,\n\t\t}\n\n\t\terrData, err := inst.Validate(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\n\t\terr = inst.SyncNodePorts(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = inst.Insert(db)\n\t\tif err != nil {\n\t\t\t_ = inst.Cleanup(db)\n\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\terr = inst.Cleanup(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tinsts = append(insts, inst)\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\n\tif len(insts) == 1 {\n\t\tc.JSON(200, insts[0])\n\t} else {\n\t\tc.JSON(200, insts)\n\t}\n}\n\nfunc instancesPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &instanceMultiData{}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif !instance.ValidActions.Contains(dta.Action) {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"invalid_action\",\n\t\t\tMessage: \"Invalid instance action\",\n\t\t}\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tdoc := bson.M{\n\t\t\"action\": dta.Action,\n\t}\n\n\tif dta.Action != instance.Start {\n\t\tdoc[\"restart\"] = false\n\t\tdoc[\"restart_block_ip\"] = false\n\t}\n\n\terr = instance.UpdateMulti(db, dta.Ids, &doc)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc instanceDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tinstanceId, ok := utils.ParseObjectId(c.Param(\"instance_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tinst, err := instance.Get(db, instanceId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif inst.DeleteProtection {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"delete_protection\",\n\t\t\tMessage: \"Cannot delete instance with delete protection\",\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = instance.Delete(db, instanceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc instancesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := []bson.ObjectID{}\n\n\terr := c.Bind(&dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tforce := c.Query(\"force\")\n\tif force == \"true\" {\n\t\tfor _, instId := range dta {\n\t\t\terr = instance.Remove(db, instId)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\terr = instance.DeleteMulti(db, dta)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc instanceGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tinst := demo.Instances[0]\n\t\tinst.Guest.Timestamp = time.Now()\n\t\tinst.Guest.Heartbeat = time.Now()\n\t\tc.JSON(200, inst)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tinstanceId, ok := utils.ParseObjectId(c.Param(\"instance_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tinst, err := instance.Get(db, instanceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif demo.IsDemo() {\n\t\tinst.State = vm.Running\n\t\tinst.Action = instance.Start\n\t\tinst.Status = \"Running\"\n\t\tinst.PublicIps = []string{\n\t\t\tdemo.RandIp(inst.Id),\n\t\t}\n\t\tinst.PublicIps6 = []string{\n\t\t\tdemo.RandIp6(inst.Id),\n\t\t}\n\t\tinst.PrivateIps = []string{\n\t\t\tdemo.RandPrivateIp(inst.Id),\n\t\t}\n\t\tinst.PrivateIps6 = []string{\n\t\t\tdemo.RandPrivateIp6(inst.Id),\n\t\t}\n\t\tinst.NetworkNamespace = vm.GetNamespace(inst.Id, 0)\n\t}\n\n\tc.JSON(200, inst)\n}\n\nfunc instancesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tfor _, inst := range demo.Instances {\n\t\t\tinst.Guest.Timestamp = time.Now()\n\t\t\tinst.Guest.Heartbeat = time.Now()\n\t\t}\n\n\t\tdata := &instancesData{\n\t\t\tInstances: demo.Instances,\n\t\t\tCount:     int64(len(demo.Instances)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tndeId, _ := utils.ParseObjectId(c.Query(\"node_names\"))\n\tplId, _ := utils.ParseObjectId(c.Query(\"pool_names\"))\n\tif !ndeId.IsZero() {\n\t\tquery := &bson.M{\n\t\t\t\"node\": ndeId,\n\t\t}\n\n\t\tinsts, err := instance.GetAllName(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, insts)\n\t} else if !plId.IsZero() {\n\t\tnodes, err := node.GetAllPool(db, plId)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tndeIds := []bson.ObjectID{}\n\n\t\tfor _, nde := range nodes {\n\t\t\tndeIds = append(ndeIds, nde.Id)\n\t\t}\n\n\t\tquery := &bson.M{\n\t\t\t\"node\": &bson.M{\n\t\t\t\t\"$in\": ndeIds,\n\t\t\t},\n\t\t}\n\n\t\tinsts, err := instance.GetAllName(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, insts)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{}\n\n\t\tinstId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = instId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"name\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\trole := strings.TrimSpace(c.Query(\"role\"))\n\t\tif role != \"\" {\n\t\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\t\trole := role[1:]\n\t\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\t\"$ne\": role,\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tquery[\"roles\"] = role\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tnetworkNamespace := strings.TrimSpace(c.Query(\"network_namespace\"))\n\t\tif networkNamespace != \"\" {\n\t\t\tquery[\"network_namespace\"] = networkNamespace\n\t\t}\n\n\t\tnodeId, ok := utils.ParseObjectId(c.Query(\"node\"))\n\t\tif ok {\n\t\t\tquery[\"node\"] = nodeId\n\t\t}\n\n\t\tzoneId, ok := utils.ParseObjectId(c.Query(\"zone\"))\n\t\tif ok {\n\t\t\tquery[\"zone\"] = zoneId\n\t\t}\n\n\t\tvpcId, ok := utils.ParseObjectId(c.Query(\"vpc\"))\n\t\tif ok {\n\t\t\tquery[\"vpc\"] = vpcId\n\t\t}\n\n\t\tsubnetId, ok := utils.ParseObjectId(c.Query(\"subnet\"))\n\t\tif ok {\n\t\t\tquery[\"subnet\"] = subnetId\n\t\t}\n\n\t\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\t\tif ok {\n\t\t\tquery[\"organization\"] = organization\n\t\t}\n\n\t\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\t\tif comment != \"\" {\n\t\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\tinstances, count, err := instance.GetAllPaged(\n\t\t\tdb, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, inst := range instances {\n\t\t\tinst.Json(false)\n\t\t}\n\n\t\tdta := &instancesData{\n\t\t\tInstances: instances,\n\t\t\tCount:     count,\n\t\t}\n\n\t\tc.JSON(200, dta)\n\t}\n}\n\nfunc instanceVncGet(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tinstanceId, ok := utils.ParseObjectId(c.Param(\"instance_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tinst, err := instance.Get(db, instanceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = inst.VncConnect(db, c.Writer, c.Request)\n\tif err != nil {\n\t\tif _, ok := err.(*instance.VncDialError); ok {\n\t\t\tutils.AbortWithStatus(c, 504)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "ahandlers/license.go",
    "content": "package ahandlers\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype licenseData struct {\n\tOracle bool `json:\"oracle\"`\n}\n\nfunc licensePut(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, nil)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &licenseData{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr.OracleLicense = data.Oracle\n\n\terr = usr.CommitFields(db, set.NewSet(\"oracle_licese\"))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, data)\n\treturn\n}\n"
  },
  {
    "path": "ahandlers/log.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/log\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype logsData struct {\n\tLogs  []*log.Entry `json:\"logs\"`\n\tCount int64        `json:\"count\"`\n}\n\nfunc logGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, demo.Logs[1])\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tlogId, ok := utils.ParseObjectId(c.Param(\"log_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tusr, err := log.Get(db, logId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, usr)\n}\n\nfunc logsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &logsData{\n\t\t\tLogs:  demo.Logs,\n\t\t\tCount: int64(len(demo.Logs)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpageStr := c.Query(\"page\")\n\tpage, _ := strconv.ParseInt(pageStr, 10, 0)\n\tpageCountStr := c.Query(\"page_count\")\n\tpageCount, _ := strconv.ParseInt(pageCountStr, 10, 0)\n\n\tquery := bson.M{}\n\n\tmessage := strings.TrimSpace(c.Query(\"message\"))\n\tif message != \"\" {\n\t\tquery[\"message\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(message)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tlevel := strings.TrimSpace(c.Query(\"level\"))\n\tif level != \"\" {\n\t\tquery[\"level\"] = level\n\t}\n\n\tlogs, count, err := log.GetAll(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &logsData{\n\t\tLogs:  logs,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/node.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/drive\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/subscription\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\ntype nodeData struct {\n\tId                      bson.ObjectID           `json:\"id\"`\n\tZone                    bson.ObjectID           `json:\"zone\"`\n\tName                    string                  `json:\"name\"`\n\tComment                 string                  `json:\"comment\"`\n\tTypes                   []string                `json:\"types\"`\n\tPort                    int                     `json:\"port\"`\n\tHttp2                   bool                    `json:\"http2\"`\n\tNoRedirectServer        bool                    `json:\"no_redirect_server\"`\n\tProtocol                string                  `json:\"protocol\"`\n\tHypervisor              string                  `json:\"hypervisor\"`\n\tVga                     string                  `json:\"vga\"`\n\tVgaRender               string                  `json:\"vga_render\"`\n\tGui                     bool                    `json:\"gui\"`\n\tGuiUser                 string                  `json:\"gui_user\"`\n\tGuiMode                 string                  `json:\"gui_mode\"`\n\tCertificates            []bson.ObjectID         `json:\"certificates\"`\n\tAdminDomain             string                  `json:\"admin_domain\"`\n\tUserDomain              string                  `json:\"user_domain\"`\n\tWebauthnDomain          string                  `json:\"webauthn_domain\"`\n\tExternalInterfaces      []string                `json:\"external_interfaces\"`\n\tExternalInterfaces6     []string                `json:\"external_interfaces6\"`\n\tInternalInterfaces      []string                `json:\"internal_interfaces\"`\n\tCloudSubnets            []string                `json:\"cloud_subnets\"`\n\tNetworkMode             string                  `json:\"network_mode\"`\n\tNetworkMode6            string                  `json:\"network_mode6\"`\n\tBlocks                  []*node.BlockAttachment `json:\"blocks\"`\n\tBlocks6                 []*node.BlockAttachment `json:\"blocks6\"`\n\tShares                  []*node.Share           `json:\"shares\"`\n\tInstanceDrives          []*drive.Device         `json:\"instance_drives\"`\n\tNoHostNetwork           bool                    `json:\"no_host_network\"`\n\tNoNodePortNetwork       bool                    `json:\"no_node_port_network\"`\n\tHostNat                 bool                    `json:\"host_nat\"`\n\tDefaultNoPublicAddress  bool                    `json:\"default_no_public_address\"`\n\tDefaultNoPublicAddress6 bool                    `json:\"default_no_public_address6\"`\n\tJumboFrames             bool                    `json:\"jumbo_frames\"`\n\tJumboFramesInternal     bool                    `json:\"jumbo_frames_internal\"`\n\tIscsi                   bool                    `json:\"iscsi\"`\n\tUsbPassthrough          bool                    `json:\"usb_passthrough\"`\n\tPciPassthrough          bool                    `json:\"pci_passthrough\"`\n\tHugepages               bool                    `json:\"hugepages\"`\n\tHugepagesSize           int                     `json:\"hugepages_size\"`\n\tForwardedForHeader      string                  `json:\"forwarded_for_header\"`\n\tForwardedProtoHeader    string                  `json:\"forwarded_proto_header\"`\n\tFirewall                bool                    `json:\"firewall\"`\n\tRoles                   []string                `json:\"roles\"`\n\tOracleUser              string                  `json:\"oracle_user\"`\n\tOracleTenancy           string                  `json:\"oracle_tenancy\"`\n}\n\ntype nodesData struct {\n\tNodes []*node.Node `json:\"nodes\"`\n\tCount int64        `json:\"count\"`\n}\n\ntype nodeInitData struct {\n\tProvider          string        `json:\"provider\"`\n\tZone              bson.ObjectID `json:\"zone\"`\n\tFirewall          bool          `json:\"firewall\"`\n\tInternalInterface string        `json:\"internal_interface\"`\n\tExternalInterface string        `json:\"external_interface\"`\n\tBlockGateway      string        `json:\"block_gateway\"`\n\tBlockNetmask      string        `json:\"block_netmask\"`\n\tBlockSubnets      []string      `json:\"block_subnets\"`\n}\n\nfunc nodePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &nodeData{}\n\n\tnodeId, ok := utils.ParseObjectId(c.Param(\"node_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tnde, err := node.Get(db, nodeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tnodeTypes := set.NewSet()\n\tfor _, typ := range nde.Types {\n\t\tnodeTypes.Add(typ)\n\t}\n\n\tfor _, typ := range data.Types {\n\t\tif typ == node.User && !nodeTypes.Contains(typ) {\n\t\t\tif !subscription.Sub.Active {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"subscription_required\",\n\t\t\t\t\tMessage: \"Subscription required for multi-tenant\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tnde.Name = data.Name\n\tnde.Comment = data.Comment\n\tnde.Types = data.Types\n\tnde.Port = data.Port\n\tnde.Http2 = data.Http2\n\tnde.NoRedirectServer = data.NoRedirectServer\n\tnde.Protocol = data.Protocol\n\tnde.Hypervisor = data.Hypervisor\n\tnde.Vga = data.Vga\n\tnde.VgaRender = data.VgaRender\n\tnde.Gui = data.Gui\n\tnde.GuiUser = data.GuiUser\n\tnde.GuiMode = data.GuiMode\n\tnde.Certificates = data.Certificates\n\tnde.AdminDomain = data.AdminDomain\n\tnde.UserDomain = data.UserDomain\n\tnde.WebauthnDomain = data.WebauthnDomain\n\tnde.ExternalInterfaces = data.ExternalInterfaces\n\tnde.ExternalInterfaces6 = data.ExternalInterfaces6\n\tnde.InternalInterfaces = data.InternalInterfaces\n\tnde.CloudSubnets = data.CloudSubnets\n\tnde.NetworkMode = data.NetworkMode\n\tnde.NetworkMode6 = data.NetworkMode6\n\tnde.Blocks = data.Blocks\n\tnde.Blocks6 = data.Blocks6\n\tnde.Shares = data.Shares\n\tnde.InstanceDrives = data.InstanceDrives\n\tnde.NoHostNetwork = data.NoHostNetwork\n\tnde.NoNodePortNetwork = data.NoNodePortNetwork\n\tnde.HostNat = data.HostNat\n\tnde.DefaultNoPublicAddress = data.DefaultNoPublicAddress\n\tnde.DefaultNoPublicAddress6 = data.DefaultNoPublicAddress6\n\tnde.JumboFrames = data.JumboFrames\n\tnde.JumboFramesInternal = data.JumboFramesInternal\n\tnde.Iscsi = data.Iscsi\n\tnde.UsbPassthrough = data.UsbPassthrough\n\tnde.PciPassthrough = data.PciPassthrough\n\tnde.Hugepages = data.Hugepages\n\tnde.HugepagesSize = data.HugepagesSize\n\tnde.ForwardedForHeader = data.ForwardedForHeader\n\tnde.ForwardedProtoHeader = data.ForwardedProtoHeader\n\tnde.Firewall = data.Firewall\n\tnde.Roles = data.Roles\n\tnde.OracleUser = data.OracleUser\n\tnde.OracleTenancy = data.OracleTenancy\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"zone\",\n\t\t\"types\",\n\t\t\"port\",\n\t\t\"http2\",\n\t\t\"no_redirect_server\",\n\t\t\"protocol\",\n\t\t\"hypervisor\",\n\t\t\"vga\",\n\t\t\"vga_render\",\n\t\t\"gui\",\n\t\t\"gui_user\",\n\t\t\"gui_mode\",\n\t\t\"certificates\",\n\t\t\"admin_domain\",\n\t\t\"user_domain\",\n\t\t\"webauthn_domain\",\n\t\t\"external_interfaces\",\n\t\t\"external_interfaces6\",\n\t\t\"internal_interfaces\",\n\t\t\"cloud_subnets\",\n\t\t\"network_mode\",\n\t\t\"network_mode6\",\n\t\t\"blocks\",\n\t\t\"blocks6\",\n\t\t\"shares\",\n\t\t\"instance_drives\",\n\t\t\"no_host_network\",\n\t\t\"no_node_port_network\",\n\t\t\"host_nat\",\n\t\t\"default_no_public_address\",\n\t\t\"default_no_public_address6\",\n\t\t\"jumbo_frames\",\n\t\t\"jumbo_frames_internal\",\n\t\t\"iscsi\",\n\t\t\"usb_passthrough\",\n\t\t\"pci_passthrough\",\n\t\t\"hugepages\",\n\t\t\"hugepages_size\",\n\t\t\"forwarded_for_header\",\n\t\t\"forwarded_proto_header\",\n\t\t\"firewall\",\n\t\t\"roles\",\n\t\t\"oracle_user\",\n\t\t\"oracle_tenancy\",\n\t)\n\n\tif !data.Zone.IsZero() && data.Zone != nde.Zone {\n\t\tif !nde.Zone.IsZero() {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"zone_modified\",\n\t\t\t\tMessage: \"Cannot modify zone once set\",\n\t\t\t}\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\t\tnde.Zone = data.Zone\n\n\t\tzne, e := zone.Get(db, nde.Zone)\n\t\tif e != nil {\n\t\t\tutils.AbortWithError(c, 500, e)\n\t\t\treturn\n\t\t}\n\n\t\tfields.Add(\"datacenter\")\n\t\tnde.Datacenter = zne.Datacenter\n\t}\n\n\terrData, err := nde.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = nde.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"node.change\")\n\n\tc.JSON(200, nde)\n}\n\nfunc nodeOperationPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tnodeId, ok := utils.ParseObjectId(c.Param(\"node_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\toperation := c.Param(\"operation\")\n\tif operation != node.Restart {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tnde, err := node.Get(db, nodeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tnde.Operation = node.Restart\n\n\terrData, err := nde.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = nde.CommitFields(db, set.NewSet(\"operation\"))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"node.change\")\n\n\tc.JSON(200, nde)\n}\n\nfunc nodeInitPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &nodeInitData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tnodeId, ok := utils.ParseObjectId(c.Param(\"node_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tnde, err := node.Get(db, nodeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfields := set.NewSet(\n\t\t\"host_nat\",\n\t\t\"zone\",\n\t\t\"network_mode\",\n\t\t\"network_mode6\",\n\t\t\"internal_interfaces\",\n\t\t\"external_interfaces\",\n\t)\n\n\tnde.Zone = data.Zone\n\tnde.HostNat = true\n\n\tif data.Provider == \"phoenixnap\" {\n\t\tfields.Add(\"default_no_public_address\")\n\t\tnde.DefaultNoPublicAddress = true\n\t\tnde.NetworkMode = node.Static\n\t\tnde.NetworkMode6 = node.Disabled\n\t\tnde.InternalInterfaces = []string{\n\t\t\tdata.InternalInterface,\n\t\t}\n\t} else if data.Provider == \"vultr\" {\n\t\tfields.Add(\"default_no_public_address\")\n\t\tnde.DefaultNoPublicAddress = true\n\t\tnde.NetworkMode = node.Disabled\n\t\tnde.NetworkMode6 = node.Dhcp\n\t\tnde.InternalInterfaces = []string{\n\t\t\tsettings.Hypervisor.HostNetworkName,\n\t\t}\n\t\tnde.ExternalInterfaces = []string{\n\t\t\tdata.ExternalInterface,\n\t\t}\n\t} else {\n\t\tnde.NetworkMode = node.Disabled\n\t\tnde.NetworkMode6 = node.Dhcp\n\t\tnde.InternalInterfaces = []string{\n\t\t\tsettings.Hypervisor.HostNetworkName,\n\t\t}\n\t\tnde.ExternalInterfaces = []string{\n\t\t\tdata.ExternalInterface,\n\t\t}\n\t}\n\n\tdc, err := datacenter.Get(db, nde.Datacenter)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdc.NetworkMode = datacenter.Default\n\n\terr = dc.CommitFields(db, set.NewSet(\"network_mode\"))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"zone.change\")\n\n\tif data.Provider == \"phoenixnap\" {\n\t\tpublicBlck := &block.Block{\n\t\t\tName:    nde.Name + \"-public\",\n\t\t\tType:    block.IPv4,\n\t\t\tSubnets: data.BlockSubnets,\n\t\t}\n\n\t\tif data.BlockNetmask == \"\" {\n\t\t\t_, gateway, err := net.ParseCIDR(data.BlockGateway)\n\t\t\tif err != nil {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_block_gateway\",\n\t\t\t\t\tMessage: \"Invalid public gateway\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpublicBlck.Netmask = fmt.Sprintf(\n\t\t\t\t\"%d.%d.%d.%d\",\n\t\t\t\tgateway.Mask[0],\n\t\t\t\tgateway.Mask[1],\n\t\t\t\tgateway.Mask[2],\n\t\t\t\tgateway.Mask[3],\n\t\t\t)\n\t\t\tpublicBlck.Gateway = strings.Split(data.BlockGateway, \"/\")[0]\n\t\t} else {\n\t\t\tpublicBlck.Netmask = data.BlockNetmask\n\t\t\tpublicBlck.Gateway = data.BlockGateway\n\t\t}\n\n\t\terrData, err := publicBlck.Validate(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\n\t\terr = publicBlck.Insert(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tnde.Blocks = []*node.BlockAttachment{\n\t\t\t&node.BlockAttachment{\n\t\t\t\tInterface: data.ExternalInterface,\n\t\t\t\tBlock:     publicBlck.Id,\n\t\t\t},\n\t\t}\n\t\tfields.Add(\"blocks\")\n\t}\n\n\terrData, err := nde.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tif data.Firewall {\n\t\tnde.Firewall = true\n\t\tfields.Add(\"firewall\")\n\n\t\tif nde.Roles == nil {\n\t\t\tnde.Roles = []string{}\n\t\t}\n\n\t\thasRole := false\n\t\tfor _, role := range nde.Roles {\n\t\t\tif role == \"firewall\" {\n\t\t\t\thasRole = true\n\t\t\t}\n\t\t}\n\n\t\tif !hasRole {\n\t\t\tnde.Roles = append(nde.Roles, \"firewall\")\n\t\t\tfields.Add(\"roles\")\n\t\t}\n\n\t\tfires, err := firewall.GetAll(db, &bson.M{\n\t\t\t\"organization\": firewall.Global,\n\t\t\t\"roles\":        \"firewall\",\n\t\t})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif len(fires) == 0 {\n\t\t\tfire := &firewall.Firewall{\n\t\t\t\tName:    \"node-firewall\",\n\t\t\t\tComment: \"\",\n\t\t\t\tRoles: []string{\n\t\t\t\t\t\"firewall\",\n\t\t\t\t},\n\t\t\t\tIngress: []*firewall.Rule{\n\t\t\t\t\t&firewall.Rule{\n\t\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tProtocol: firewall.Icmp,\n\t\t\t\t\t},\n\t\t\t\t\t&firewall.Rule{\n\t\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\t\tPort:     \"22\",\n\t\t\t\t\t},\n\t\t\t\t\t&firewall.Rule{\n\t\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\t\tPort:     \"80\",\n\t\t\t\t\t},\n\t\t\t\t\t&firewall.Rule{\n\t\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\t\tPort:     \"443\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\terrData, err = fire.Validate(db)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif errData != nil {\n\t\t\t\tc.JSON(400, errData)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = fire.Insert(db)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\terr = nde.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"node.change\")\n\tevent.PublishDispatch(db, \"block.change\")\n\tif data.Firewall {\n\t\tevent.PublishDispatch(db, \"firewall.change\")\n\t}\n\n\tc.JSON(200, nde)\n}\n\nfunc nodeDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tnodeId, ok := utils.ParseObjectId(c.Param(\"node_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"node\", nodeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = node.Remove(db, nodeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"node.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc nodeGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tnde := demo.Nodes[0]\n\t\tnde.Timestamp = time.Now()\n\t\tc.JSON(200, nde)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tnodeId, ok := utils.ParseObjectId(c.Param(\"node_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tnde, err := node.Get(db, nodeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, nde)\n}\n\nfunc nodesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tfor _, nde := range demo.Nodes {\n\t\t\tnde.Timestamp = time.Now()\n\t\t}\n\n\t\tdata := &nodesData{\n\t\t\tNodes: demo.Nodes,\n\t\t\tCount: int64(len(demo.Nodes)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tzone, _ := utils.ParseObjectId(c.Query(\"zone\"))\n\n\t\tquery := &bson.M{\n\t\t\t\"zone\": zone,\n\t\t}\n\n\t\tnodes, err := node.GetAllHypervisors(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, nodes)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{}\n\n\t\tnodeId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = nodeId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"name\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\tzone, _ := utils.ParseObjectId(c.Query(\"zone\"))\n\t\tif !zone.IsZero() {\n\t\t\tquery[\"zone\"] = zone\n\t\t}\n\n\t\trole := strings.TrimSpace(c.Query(\"role\"))\n\t\tif role != \"\" {\n\t\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\t\trole := role[1:]\n\t\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\t\"$ne\": role,\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tquery[\"roles\"] = role\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttypes := []string{}\n\t\tnotTypes := []string{}\n\n\t\tadminType := c.Query(node.Admin)\n\t\tswitch adminType {\n\t\tcase \"true\":\n\t\t\ttypes = append(types, node.Admin)\n\t\t\tbreak\n\t\tcase \"false\":\n\t\t\tnotTypes = append(notTypes, node.Admin)\n\t\t\tbreak\n\t\t}\n\n\t\tuserType := c.Query(node.User)\n\t\tswitch userType {\n\t\tcase \"true\":\n\t\t\ttypes = append(types, node.User)\n\t\t\tbreak\n\t\tcase \"false\":\n\t\t\tnotTypes = append(notTypes, node.User)\n\t\t\tbreak\n\t\t}\n\n\t\thypervisorType := c.Query(node.Hypervisor)\n\t\tswitch hypervisorType {\n\t\tcase \"true\":\n\t\t\ttypes = append(types, node.Hypervisor)\n\t\t\tbreak\n\t\tcase \"false\":\n\t\t\tnotTypes = append(notTypes, node.Hypervisor)\n\t\t\tbreak\n\t\t}\n\n\t\ttypesQuery := bson.M{}\n\t\tif len(types) > 0 {\n\t\t\ttypesQuery[\"$all\"] = types\n\t\t}\n\t\tif len(notTypes) > 0 {\n\t\t\ttypesQuery[\"$nin\"] = notTypes\n\t\t}\n\t\tif len(types) > 0 || len(notTypes) > 0 {\n\t\t\tquery[\"types\"] = &typesQuery\n\t\t}\n\n\t\tnodes, count, err := node.GetAllPaged(db, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata := &nodesData{\n\t\t\tNodes: nodes,\n\t\t\tCount: count,\n\t\t}\n\n\t\tc.JSON(200, data)\n\t}\n}\n"
  },
  {
    "path": "ahandlers/organization.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/organization\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/subscription\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype organizationData struct {\n\tId      bson.ObjectID `json:\"id\"`\n\tName    string        `json:\"name\"`\n\tComment string        `json:\"comment\"`\n\tRoles   []string      `json:\"roles\"`\n}\n\ntype organizationsData struct {\n\tOrganizations []*organization.Organization `json:\"organizations\"`\n\tCount         int64                        `json:\"count\"`\n}\n\nfunc organizationPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &organizationData{}\n\n\torgId, ok := utils.ParseObjectId(c.Param(\"org_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\torg, err := organization.Get(db, orgId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\torg.Name = data.Name\n\torg.Comment = data.Comment\n\torg.Roles = data.Roles\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"roles\",\n\t)\n\n\terrData, err := org.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = org.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"organization.change\")\n\n\tc.JSON(200, org)\n}\n\nfunc organizationPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &organizationData{\n\t\tName: \"new-organization\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif !subscription.Sub.Active {\n\t\tcount, e := organization.Count(db)\n\t\tif e != nil {\n\t\t\tutils.AbortWithError(c, 500, e)\n\t\t\treturn\n\t\t}\n\n\t\tif count > 0 {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"subscription_required\",\n\t\t\t\tMessage: \"Subscription required for multiple organizations\",\n\t\t\t}\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\t}\n\n\torg := &organization.Organization{\n\t\tName:    data.Name,\n\t\tComment: data.Comment,\n\t\tRoles:   data.Roles,\n\t}\n\n\terrData, err := org.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = org.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"organization.change\")\n\n\tc.JSON(200, org)\n}\n\nfunc organizationDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\torgId, ok := utils.ParseObjectId(c.Param(\"org_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"organization\", orgId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = organization.Remove(db, orgId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"organization.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc organizationGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\torg := demo.Organizations[0]\n\t\tc.JSON(200, org)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\torgId, ok := utils.ParseObjectId(c.Param(\"org_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\torg, err := organization.Get(db, orgId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, org)\n}\n\nfunc organizationsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &organizationsData{\n\t\t\tOrganizations: demo.Organizations,\n\t\t\tCount:         int64(len(demo.Organizations)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\torganizationId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = organizationId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tsecrs, count, err := organization.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &organizationsData{\n\t\tOrganizations: secrs,\n\t\tCount:         count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/plan.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/plan\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype planData struct {\n\tId           bson.ObjectID     `json:\"id\"`\n\tName         string            `json:\"name\"`\n\tComment      string            `json:\"comment\"`\n\tOrganization bson.ObjectID     `json:\"organization\"`\n\tStatements   []*plan.Statement `json:\"statements\"`\n}\n\ntype plansData struct {\n\tPlans []*plan.Plan `json:\"plans\"`\n\tCount int64        `json:\"count\"`\n}\n\nfunc planPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &planData{}\n\n\tplanId, ok := utils.ParseObjectId(c.Param(\"plan_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpln, err := plan.Get(db, planId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpln.Name = data.Name\n\tpln.Comment = data.Comment\n\tpln.Organization = data.Organization\n\n\terr = pln.UpdateStatements(data.Statements)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"organization\",\n\t\t\"statements\",\n\t)\n\n\terrData, err := pln.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pln.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"plan.change\")\n\n\tc.JSON(200, pln)\n}\n\nfunc planPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &planData{\n\t\tName: \"new-plan\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpln := &plan.Plan{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: data.Organization,\n\t}\n\n\terr = pln.UpdateStatements(data.Statements)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := pln.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pln.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"plan.change\")\n\n\tc.JSON(200, pln)\n}\n\nfunc planDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tplanId, ok := utils.ParseObjectId(c.Param(\"plan_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := plan.Remove(db, planId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"plan.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc plansDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = plan.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"plan.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc planGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tpln := demo.Plans[0]\n\t\tc.JSON(200, pln)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tplanId, ok := utils.ParseObjectId(c.Param(\"plan_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tpln, err := plan.Get(db, planId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, pln)\n}\n\nfunc plansGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &plansData{\n\t\t\tPlans: demo.Plans,\n\t\t\tCount: int64(len(demo.Plans)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tquery := &bson.M{}\n\n\t\tplns, err := plan.GetAllName(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, plns)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{}\n\n\t\tplanId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = planId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"name\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\t\tif ok {\n\t\t\tquery[\"organization\"] = organization\n\t\t}\n\n\t\tplans, count, err := plan.GetAllPaged(db, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata := &plansData{\n\t\t\tPlans: plans,\n\t\t\tCount: count,\n\t\t}\n\n\t\tc.JSON(200, data)\n\t}\n}\n"
  },
  {
    "path": "ahandlers/pod.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/journal\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/scheduler\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype podData struct {\n\tId               bson.ObjectID     `json:\"id\"`\n\tName             string            `json:\"name\"`\n\tComment          string            `json:\"comment\"`\n\tOrganization     bson.ObjectID     `json:\"organization\"`\n\tDeleteProtection bool              `json:\"delete_protection\"`\n\tUnits            []*unit.UnitInput `json:\"units\"`\n\tDrafts           []*pod.UnitDraft  `json:\"drafts\"`\n\tCount            int               `json:\"count\"`\n}\n\ntype podsData struct {\n\tPods  []*aggregate.PodAggregate `json:\"pods\"`\n\tCount int64                     `json:\"count\"`\n}\n\ntype podsDeployData struct {\n\tCount int           `json:\"count\"`\n\tSpec  bson.ObjectID `json:\"spec\"`\n}\n\ntype deploymentData struct {\n\tId   bson.ObjectID `json:\"id\"`\n\tTags []string      `json:\"tags\"`\n}\n\ntype specsData struct {\n\tSpecs []*spec.Named `json:\"specs\"`\n\tCount int64         `json:\"count\"`\n}\n\nfunc podPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &podData{}\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpd, err := pod.Get(db, podId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpd.Name = data.Name\n\tpd.Comment = data.Comment\n\tpd.Organization = data.Organization\n\tpd.DeleteProtection = data.DeleteProtection\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"organization\",\n\t\t\"delete_protection\",\n\t)\n\n\terrData, err := pd.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terrData, err = pd.CommitFieldsUnits(db, data.Units, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = pod.UpdateDrafts(db, podId, usr.Id, []*pod.UnitDraft{})\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, pd)\n}\n\nfunc podDraftsPut(c *gin.Context) {\n\tif demo.BlockedSilent(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &podData{}\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = pod.UpdateDrafts(db, podId, usr.Id, data.Drafts)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, nil)\n}\n\nfunc podDeployPut(c *gin.Context) {\n\tif demo.BlockedSilent(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &podData{}\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tunits, err := unit.GetAll(db, &bson.M{\n\t\t\"pod\": podId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tunitsDataMap := map[bson.ObjectID]*unit.UnitInput{}\n\tfor _, unitData := range data.Units {\n\t\tunitsDataMap[unitData.Id] = unitData\n\t}\n\n\tfor _, unt := range units {\n\t\tunitData := unitsDataMap[unt.Id]\n\t\tif unitData == nil || unitData.DeploySpec.IsZero() {\n\t\t\tcontinue\n\t\t}\n\n\t\tdeploySpec, e := spec.Get(db, unitData.DeploySpec)\n\t\tif e != nil || deploySpec.Unit != unt.Id {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"unit_deploy_spec_invalid\",\n\t\t\t\tMessage: \"Invalid unit deployment commit\",\n\t\t\t}\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\n\t\tunt.DeploySpec = unitData.DeploySpec\n\t\terr = unt.CommitFields(db, set.NewSet(\"deploy_spec\"))\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &podData{\n\t\tName: \"new-pod\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpd := &pod.Pod{\n\t\tName:             data.Name,\n\t\tComment:          data.Comment,\n\t\tOrganization:     data.Organization,\n\t\tDeleteProtection: data.DeleteProtection,\n\t}\n\n\terrData, err := pd.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pd.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err = pd.InitUnits(db, data.Units)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, pd)\n}\n\nfunc podDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"pod\", podId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pod.Remove(db, podId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"pod\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pod.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tpd := demo.Pods[0]\n\t\tc.JSON(200, pd)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpd, err := aggregate.GetPod(db, usr.Id, &bson.M{\n\t\t\"_id\": podId,\n\t})\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tc.JSON(200, pd)\n}\n\nfunc podsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &podsData{\n\t\t\tPods:  demo.Pods,\n\t\t\tCount: int64(len(demo.Pods)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tpodId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = podId\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tpods, count, err := aggregate.GetPodsPaged(db, usr.Id,\n\t\t&query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &podsData{\n\t\tPods:  pods,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, data)\n}\n\ntype PodUnit struct {\n\tId          bson.ObjectID           `json:\"id\"`\n\tPod         bson.ObjectID           `json:\"pod\"`\n\tKind        string                  `json:\"kind\"`\n\tDeployments []*aggregate.Deployment `json:\"deployments\"`\n}\n\nfunc podUnitGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\tvar unit *unit.Unit\n\t\tfor _, unt := range demo.Units {\n\t\t\tif unt.Id == unitId {\n\t\t\t\tunit = unt\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tdeplys := []*aggregate.Deployment{}\n\t\tfor _, deply := range demo.Deployments {\n\t\t\tif deply.Unit == unit.Id {\n\t\t\t\tdeplys = append(deplys, deply)\n\t\t\t}\n\t\t}\n\n\t\tdata := &PodUnit{\n\t\t\tId:          unit.Id,\n\t\t\tPod:         demo.Pods[0].Id,\n\t\t\tKind:        unit.Kind,\n\t\t\tDeployments: deplys,\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tunt, err := unit.Get(db, unitId)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tdeploys, err := aggregate.GetDeployments(db, unt)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tpdUnit := &PodUnit{\n\t\tId:          unt.Id,\n\t\tPod:         unt.Pod,\n\t\tKind:        unt.Kind,\n\t\tDeployments: deploys,\n\t}\n\n\tc.JSON(200, pdUnit)\n}\n\nfunc podUnitDeploymentsPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tunt, err := unit.Get(db, unitId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\taction := c.Query(\"action\")\n\tswitch action {\n\tcase deployment.Archive:\n\t\terr = deployment.ArchiveMulti(db, unt.Id, data)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase deployment.Restore:\n\t\terr = deployment.RestoreMulti(db, unt.Id, data)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase deployment.Destroy:\n\t\terr = deployment.RemoveMulti(db, unt.Id, data)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase deployment.Migrate:\n\t\tcommitId, ok := utils.ParseObjectId(c.Query(\"commit\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\terrData, err := unt.MigrateDeployements(db, commitId, data)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\n\t\tbreak\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podUnitDeploymentPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &podsDeployData{}\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tunt, err := unit.Get(db, unitId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := scheduler.ManualSchedule(db, unt, data.Spec, data.Count)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podUnitDeploymentPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &deploymentData{}\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdeplyId, ok := utils.ParseObjectId(c.Param(\"deployment_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdeply, err := deployment.GetUnit(db, unitId, deplyId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdeply.Tags = data.Tags\n\n\tfields := set.NewSet(\n\t\t\"tags\",\n\t)\n\n\terrData, err := deply.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = deply.CommitFields(db, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podUnitDeploymentLogGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, demo.DeploymentLogs)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdeplyId, ok := utils.ParseObjectId(c.Param(\"deployment_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdeply, err := deployment.GetUnit(db, unitId, deplyId)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tkind := int32(0)\n\tresource := c.Query(\"resource\")\n\tif resource == \"agent\" {\n\t\tkind = journal.DeploymentAgent\n\t}\n\n\tfor _, jrnl := range deply.Journals {\n\t\tif jrnl.Key == resource {\n\t\t\tkind = jrnl.Index\n\t\t}\n\t}\n\n\tif kind == 0 {\n\t\tutils.AbortWithStatus(c, 404)\n\t\treturn\n\t}\n\n\tdata, err := journal.GetOutput(c, db, deply.Id, kind)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tc.JSON(200, data)\n}\n\nfunc podUnitSpecsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\tspecs := []*spec.Named{}\n\t\tfor _, spc := range demo.SpecsNamed {\n\t\t\tif spc.Unit == unitId {\n\t\t\t\tspecs = append(specs, spc)\n\t\t\t}\n\t\t}\n\n\t\tdata := &specsData{\n\t\t\tSpecs: specs,\n\t\t\tCount: int64(len(specs)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tspecs, count, err := spec.GetAllPaged(db, &bson.M{\n\t\t\"unit\": unitId,\n\t}, page, pageCount)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tdata := &specsData{\n\t\tSpecs: specs,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, data)\n}\n\nfunc podUnitSpecGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tspecId, ok := utils.ParseObjectId(c.Param(\"spec_id\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, spc := range demo.Specs {\n\t\t\tif spc.Id == specId {\n\t\t\t\tc.JSON(200, spc)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tc.AbortWithStatus(404)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tspecId, ok := utils.ParseObjectId(c.Param(\"spec_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tspec, err := spec.GetOne(db, &bson.M{\n\t\t\"_id\":  specId,\n\t\t\"unit\": unitId,\n\t})\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tc.JSON(200, spec)\n}\n"
  },
  {
    "path": "ahandlers/policy.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/policy\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype policyData struct {\n\tId                   bson.ObjectID           `json:\"id\"`\n\tName                 string                  `json:\"name\"`\n\tComment              string                  `json:\"comment\"`\n\tDisabled             bool                    `json:\"disabled\"`\n\tAuthorities          []bson.ObjectID         `json:\"authorities\"`\n\tRoles                []string                `json:\"roles\"`\n\tRules                map[string]*policy.Rule `json:\"rules\"`\n\tAdminSecondary       bson.ObjectID           `json:\"admin_secondary\"`\n\tUserSecondary        bson.ObjectID           `json:\"user_secondary\"`\n\tProxySecondary       bson.ObjectID           `json:\"proxy_secondary\"`\n\tAuthoritySecondary   bson.ObjectID           `json:\"authority_secondary\"`\n\tAdminDeviceSecondary bool                    `json:\"admin_device_secondary\"`\n\tUserDeviceSecondary  bool                    `json:\"user_device_secondary\"`\n}\n\ntype policiesData struct {\n\tPolicies []*policy.Policy `json:\"policies\"`\n\tCount    int64            `json:\"count\"`\n}\n\nfunc policyPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &policyData{}\n\n\tpolcyId, ok := utils.ParseObjectId(c.Param(\"policy_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpolcy, err := policy.Get(db, polcyId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpolcy.Name = data.Name\n\tpolcy.Comment = data.Comment\n\tpolcy.Disabled = data.Disabled\n\tpolcy.Roles = data.Roles\n\tpolcy.Rules = data.Rules\n\tpolcy.AdminSecondary = data.AdminSecondary\n\tpolcy.UserSecondary = data.UserSecondary\n\tpolcy.AdminDeviceSecondary = data.AdminDeviceSecondary\n\tpolcy.UserDeviceSecondary = data.UserDeviceSecondary\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"disabled\",\n\t\t\"roles\",\n\t\t\"rules\",\n\t\t\"admin_secondary\",\n\t\t\"user_secondary\",\n\t\t\"admin_device_secondary\",\n\t\t\"user_device_secondary\",\n\t)\n\n\terrData, err := polcy.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = polcy.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"policy.change\")\n\n\tc.JSON(200, polcy)\n}\n\nfunc policyPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &policyData{\n\t\tName: \"new-policy\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpolcy := &policy.Policy{\n\t\tName:                 data.Name,\n\t\tComment:              data.Comment,\n\t\tDisabled:             data.Disabled,\n\t\tRoles:                data.Roles,\n\t\tRules:                data.Rules,\n\t\tAdminSecondary:       data.AdminSecondary,\n\t\tUserSecondary:        data.UserSecondary,\n\t\tAdminDeviceSecondary: data.AdminDeviceSecondary,\n\t\tUserDeviceSecondary:  data.UserDeviceSecondary,\n\t}\n\n\terrData, err := polcy.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = polcy.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"policy.change\")\n\n\tc.JSON(200, polcy)\n}\n\nfunc policyDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpolcyId, ok := utils.ParseObjectId(c.Param(\"policy_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"policy\", polcyId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = policy.Remove(db, polcyId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"policy.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc policiesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"policy\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = policy.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"policy.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc policyGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tpolcy := demo.Policies[0]\n\t\tc.JSON(200, polcy)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpolcyId, ok := utils.ParseObjectId(c.Param(\"policy_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tpolcy, err := policy.Get(db, polcyId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, polcy)\n}\n\nfunc policiesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &policiesData{\n\t\t\tPolicies: demo.Policies,\n\t\t\tCount:    int64(len(demo.Policies)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tpolicyId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = policyId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tpolcies, count, err := policy.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &policiesData{\n\t\tPolicies: polcies,\n\t\tCount:    count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/pool.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype poolData struct {\n\tId               bson.ObjectID `json:\"id\"`\n\tName             string        `json:\"name\"`\n\tComment          string        `json:\"comment\"`\n\tDeleteProtection bool          `json:\"delete_protection\"`\n\tZone             bson.ObjectID `json:\"zone\"`\n\tType             string        `json:\"type\"`\n\tVgName           string        `json:\"vg_name\"`\n}\n\ntype poolsData struct {\n\tPools []*pool.Pool `json:\"pools\"`\n\tCount int64        `json:\"count\"`\n}\n\nfunc poolPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &poolData{}\n\n\tpoolId, ok := utils.ParseObjectId(c.Param(\"pool_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpl, err := pool.Get(db, poolId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpl.Name = data.Name\n\tpl.Comment = data.Comment\n\tpl.DeleteProtection = data.DeleteProtection\n\tpl.Type = data.Type\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"delete_protection\",\n\t\t\"type\",\n\t)\n\n\terrData, err := pl.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pl.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pool.change\")\n\n\tc.JSON(200, pl)\n}\n\nfunc poolPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &poolData{\n\t\tName: \"new-pool\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tzne, err := zone.Get(db, data.Zone)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpl := &pool.Pool{\n\t\tName:             data.Name,\n\t\tComment:          data.Comment,\n\t\tDeleteProtection: data.DeleteProtection,\n\t\tDatacenter:       zne.Datacenter,\n\t\tZone:             data.Zone,\n\t\tType:             data.Type,\n\t\tVgName:           data.VgName,\n\t}\n\n\terrData, err := pl.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pl.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pool.change\")\n\n\tc.JSON(200, pl)\n}\n\nfunc poolDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpoolId, ok := utils.ParseObjectId(c.Param(\"pool_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := pool.Remove(db, poolId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pool.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc poolsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = pool.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pool.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc poolGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tpl := demo.Pools[0]\n\t\tc.JSON(200, pl)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpoolId, ok := utils.ParseObjectId(c.Param(\"pool_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tpl, err := pool.Get(db, poolId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, pl)\n}\n\nfunc poolsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &poolsData{\n\t\t\tPools: demo.Pools,\n\t\t\tCount: int64(len(demo.Pools)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tnodeNames, err := node.GetAllNamesMap(db, &bson.M{})\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tpoolId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = poolId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tpools, count, err := pool.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfor _, pl := range pools {\n\t\tpl.Json(nodeNames)\n\t}\n\n\tdata := &poolsData{\n\t\tPools: pools,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/relations.go",
    "content": "package ahandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype relationsData struct {\n\tId   any    `json:\"id\"`\n\tKind string `json:\"kind\"`\n\tData string `json:\"data\"`\n}\n\nfunc relationsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tkind := c.Param(\"kind\")\n\t\tresourceId, ok := utils.ParseObjectId(c.Param(\"id\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\tdata := &relationsData{\n\t\t\tId:   resourceId,\n\t\t\tKind: kind,\n\t\t\tData: \"demo\",\n\t\t}\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tkind := c.Param(\"kind\")\n\tresourceId, ok := utils.ParseObjectId(c.Param(\"id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tresp, err := relations.Aggregate(db, kind, resourceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif resp == nil {\n\t\tutils.AbortWithStatus(c, 404)\n\t\treturn\n\t}\n\n\tdata := &relationsData{\n\t\tId:   resp.Id,\n\t\tKind: kind,\n\t\tData: resp.Yaml(),\n\t}\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/secret.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype secretData struct {\n\tId           bson.ObjectID `json:\"id\"`\n\tName         string        `json:\"name\"`\n\tComment      string        `json:\"comment\"`\n\tOrganization bson.ObjectID `json:\"organization\"`\n\tType         string        `json:\"type\"`\n\tKey          string        `json:\"key\"`\n\tValue        string        `json:\"value\"`\n\tData         string        `json:\"data\"`\n\tRegion       string        `json:\"region\"`\n}\n\ntype secretsData struct {\n\tSecrets []*secret.Secret `json:\"secrets\"`\n\tCount   int64            `json:\"count\"`\n}\n\nfunc secretPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &secretData{}\n\n\tsecrId, ok := utils.ParseObjectId(c.Param(\"secr_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecr, err := secret.Get(db, secrId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecr.Name = data.Name\n\tsecr.Comment = data.Comment\n\tsecr.Organization = data.Organization\n\tsecr.Type = data.Type\n\tsecr.Key = data.Key\n\tsecr.Value = data.Value\n\tsecr.Data = data.Data\n\tsecr.Region = data.Region\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"organization\",\n\t\t\"type\",\n\t\t\"key\",\n\t\t\"value\",\n\t\t\"data\",\n\t\t\"region\",\n\t\t\"public_key\",\n\t\t\"private_key\",\n\t)\n\n\terrData, err := secr.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = secr.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"secret.change\")\n\n\tc.JSON(200, secr)\n}\n\nfunc secretPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &secretData{\n\t\tName: \"new-secret\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecr := &secret.Secret{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: data.Organization,\n\t\tType:         data.Type,\n\t\tKey:          data.Key,\n\t\tValue:        data.Value,\n\t\tData:         data.Data,\n\t\tRegion:       data.Region,\n\t}\n\n\terrData, err := secr.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = secr.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"secret.change\")\n\n\tc.JSON(200, secr)\n}\n\nfunc secretDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tsecrId, ok := utils.ParseObjectId(c.Param(\"secr_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"secret\", secrId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = secret.Remove(db, secrId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"secret.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc secretsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"secret\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = secret.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"secret.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc secretGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tsecr := demo.Secrets[0]\n\t\tc.JSON(200, secr)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tsecrId, ok := utils.ParseObjectId(c.Param(\"secr_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tsecr, err := secret.Get(db, secrId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif demo.IsDemo() {\n\t\tsecr.Key = \"demo\"\n\t\tsecr.Value = \"demo\"\n\t}\n\n\tc.JSON(200, secr)\n}\n\nfunc secretsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &secretsData{\n\t\t\tSecrets: demo.Secrets,\n\t\t\tCount:   int64(len(demo.Secrets)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tsecrs, err := secret.GetAllNames(db, &bson.M{})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, secrs)\n\t\treturn\n\t}\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tsecretId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = secretId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tsecrs, count, err := secret.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &secretsData{\n\t\tSecrets: secrs,\n\t\tCount:   count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/session.go",
    "content": "package ahandlers\n\nimport (\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/session\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc sessionsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdemo.Sessions[0].LastActive = time.Now()\n\t\tc.JSON(200, demo.Sessions)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tshowRemoved, _ := strconv.ParseBool(c.Query(\"show_removed\"))\n\n\tuserId, ok := utils.ParseObjectId(c.Param(\"user_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tsessions, err := session.GetAll(db, userId, showRemoved)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, sessions)\n}\n\nfunc sessionDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tsessionId := c.Param(\"session_id\")\n\tif sessionId == \"\" {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := session.Remove(db, sessionId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"session.change\")\n\n\tc.JSON(200, nil)\n}\n"
  },
  {
    "path": "ahandlers/settings.go",
    "content": "package ahandlers\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype settingsData struct {\n\tAuthProviders             []*settings.Provider          `json:\"auth_providers\"`\n\tAuthSecondaryProviders    []*settings.SecondaryProvider `json:\"auth_secondary_providers\"`\n\tAuthAdminExpire           int                           `json:\"auth_admin_expire\"`\n\tAuthAdminMaxDuration      int                           `json:\"auth_admin_max_duration\"`\n\tAuthProxyExpire           int                           `json:\"auth_proxy_expire\"`\n\tAuthProxyMaxDuration      int                           `json:\"auth_proxy_max_duration\"`\n\tAuthUserExpire            int                           `json:\"auth_user_expire\"`\n\tAuthUserMaxDuration       int                           `json:\"auth_user_max_duration\"`\n\tAuthFastLogin             bool                          `json:\"auth_fast_login\"`\n\tAuthForceFastUserLogin    bool                          `json:\"auth_force_fast_user_login\"`\n\tAuthForceFastServiceLogin bool                          `json:\"auth_force_fast_service_login\"`\n\tTwilioAccount             string                        `json:\"twilio_account\"`\n\tTwilioSecret              string                        `json:\"twilio_secret\"`\n\tTwilioNumber              string                        `json:\"twilio_number\"`\n\tNvdApiKey                 string                        `json:\"nvd_api_key\"`\n}\n\nfunc getSettingsData() *settingsData {\n\tdata := &settingsData{\n\t\tAuthProviders:          settings.Auth.Providers,\n\t\tAuthSecondaryProviders: settings.Auth.SecondaryProviders,\n\t\tAuthAdminExpire:        settings.Auth.AdminExpire,\n\t\tAuthAdminMaxDuration:   settings.Auth.AdminMaxDuration,\n\t\tAuthUserExpire:         settings.Auth.UserExpire,\n\t\tAuthUserMaxDuration:    settings.Auth.UserMaxDuration,\n\t\tAuthFastLogin:          settings.Auth.FastLogin,\n\t\tAuthForceFastUserLogin: settings.Auth.ForceFastUserLogin,\n\t\tTwilioAccount:          settings.System.TwilioAccount,\n\t\tTwilioSecret:           settings.System.TwilioSecret,\n\t\tTwilioNumber:           settings.System.TwilioNumber,\n\t\tNvdApiKey:              settings.Telemetry.NvdApiKey,\n\t}\n\n\treturn data\n}\n\nfunc settingsGet(c *gin.Context) {\n\tdata := getSettingsData()\n\tc.JSON(200, data)\n}\n\nfunc settingsPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &settingsData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfields := set.NewSet()\n\n\tif settings.System.TwilioAccount != data.TwilioAccount {\n\t\tsettings.System.TwilioAccount = data.TwilioAccount\n\t\tfields.Add(\"twilio_account\")\n\t}\n\n\tif settings.System.TwilioSecret != data.TwilioSecret {\n\t\tsettings.System.TwilioSecret = data.TwilioSecret\n\t\tfields.Add(\"twilio_secret\")\n\t}\n\n\tif settings.System.TwilioNumber != data.TwilioNumber {\n\t\tsettings.System.TwilioNumber = data.TwilioNumber\n\t\tfields.Add(\"twilio_number\")\n\t}\n\n\tif fields.Len() != 0 {\n\t\terr = settings.Commit(db, settings.System, fields)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif settings.Telemetry.NvdApiKey != data.NvdApiKey {\n\t\tsettings.Telemetry.NvdApiKey = data.NvdApiKey\n\n\t\terr = settings.Commit(\n\t\t\tdb,\n\t\t\tsettings.Telemetry,\n\t\t\tset.NewSet(\"nvd_api_key\"),\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tfields = set.NewSet(\n\t\t\"providers\",\n\t\t\"secondary_providers\",\n\t)\n\n\tif settings.Auth.AdminExpire != data.AuthAdminExpire {\n\t\tsettings.Auth.AdminExpire = data.AuthAdminExpire\n\t\tfields.Add(\"admin_expire\")\n\t}\n\tif settings.Auth.AdminMaxDuration != data.AuthAdminMaxDuration {\n\t\tsettings.Auth.AdminMaxDuration = data.AuthAdminMaxDuration\n\t\tfields.Add(\"admin_max_duration\")\n\t}\n\tif settings.Auth.UserExpire != data.AuthUserExpire {\n\t\tsettings.Auth.UserExpire = data.AuthUserExpire\n\t\tfields.Add(\"user_expire\")\n\t}\n\tif settings.Auth.UserMaxDuration != data.AuthUserMaxDuration {\n\t\tsettings.Auth.UserMaxDuration = data.AuthUserMaxDuration\n\t\tfields.Add(\"user_max_duration\")\n\t}\n\tif settings.Auth.FastLogin != data.AuthFastLogin {\n\t\tsettings.Auth.FastLogin = data.AuthFastLogin\n\t\tfields.Add(\"fast_login\")\n\t}\n\tif settings.Auth.ForceFastUserLogin != data.AuthForceFastUserLogin {\n\t\tsettings.Auth.ForceFastUserLogin = data.AuthForceFastUserLogin\n\t\tfields.Add(\"force_fast_user_login\")\n\t}\n\n\tfor _, provider := range data.AuthProviders {\n\t\terrData, err := provider.Validate(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\t}\n\tsettings.Auth.Providers = data.AuthProviders\n\n\tfor _, provider := range data.AuthSecondaryProviders {\n\t\terrData, err := provider.Validate(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\t}\n\tsettings.Auth.SecondaryProviders = data.AuthSecondaryProviders\n\n\terr = settings.Commit(db, settings.Auth, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"settings.change\")\n\n\tdata = getSettingsData()\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/shape.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype shapeData struct {\n\tId               bson.ObjectID `json:\"id\"`\n\tName             string        `json:\"name\"`\n\tComment          string        `json:\"comment\"`\n\tType             string        `json:\"type\"`\n\tDeleteProtection bool          `json:\"delete_protection\"`\n\tDatacenter       bson.ObjectID `json:\"datacenter\"`\n\tRoles            []string      `json:\"roles\"`\n\tFlexible         bool          `json:\"flexible\"`\n\tDiskType         string        `json:\"disk_type\"`\n\tDiskPool         bson.ObjectID `json:\"disk_pool\"`\n\tMemory           int           `json:\"memory\"`\n\tProcessors       int           `json:\"processors\"`\n}\n\ntype shapesData struct {\n\tShapes []*shape.Shape `json:\"shapes\"`\n\tCount  int64          `json:\"count\"`\n}\n\nfunc shapePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &shapeData{}\n\n\tshapeId, ok := utils.ParseObjectId(c.Param(\"shape_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tshpe, err := shape.Get(db, shapeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tshpe.Name = data.Name\n\tshpe.Type = data.Type\n\tshpe.Comment = data.Comment\n\tshpe.DeleteProtection = data.DeleteProtection\n\tshpe.Datacenter = data.Datacenter\n\tshpe.Roles = data.Roles\n\tshpe.Flexible = data.Flexible\n\tshpe.DiskType = data.DiskType\n\tshpe.DiskPool = data.DiskPool\n\tshpe.Memory = data.Memory\n\tshpe.Processors = data.Processors\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"type\",\n\t\t\"comment\",\n\t\t\"delete_protection\",\n\t\t\"datacenter\",\n\t\t\"roles\",\n\t\t\"flexible\",\n\t\t\"disk_type\",\n\t\t\"disk_pool\",\n\t\t\"memory\",\n\t\t\"processors\",\n\t)\n\n\terrData, err := shpe.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = shpe.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"shape.change\")\n\n\tc.JSON(200, shpe)\n}\n\nfunc shapePost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &shapeData{\n\t\tName: \"new-shape\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tshpe := &shape.Shape{\n\t\tName:             data.Name,\n\t\tComment:          data.Comment,\n\t\tDeleteProtection: data.DeleteProtection,\n\t\tDatacenter:       data.Datacenter,\n\t\tRoles:            data.Roles,\n\t\tFlexible:         data.Flexible,\n\t\tDiskType:         data.DiskType,\n\t\tDiskPool:         data.DiskPool,\n\t\tMemory:           data.Memory,\n\t\tProcessors:       data.Processors,\n\t}\n\n\terrData, err := shpe.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = shpe.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"shape.change\")\n\n\tc.JSON(200, shpe)\n}\n\nfunc shapeDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tshapeId, ok := utils.ParseObjectId(c.Param(\"shape_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"shape\", shapeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = shape.Remove(db, shapeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"shape.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc shapesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"shape\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = shape.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"shape.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc shapeGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tshpe := demo.Shapes[0]\n\t\tc.JSON(200, shpe)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tshapeId, ok := utils.ParseObjectId(c.Param(\"shape_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tshpe, err := shape.Get(db, shapeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, shpe)\n}\n\nfunc shapesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &shapesData{\n\t\t\tShapes: demo.Shapes,\n\t\t\tCount:  int64(len(demo.Shapes)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tshapeId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = shapeId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tshapes, count, err := aggregate.GetShapePaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &shapesData{\n\t\tShapes: shapes,\n\t\tCount:  count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/static.go",
    "content": "package ahandlers\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/auth\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/config\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/middlewear\"\n\t\"github.com/pritunl/pritunl-cloud/static\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc staticPath(c *gin.Context, pth string, cache bool) {\n\tpth = config.StaticRoot + pth\n\n\tfile, ok := store.Files[pth]\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 404)\n\t\treturn\n\t}\n\n\tif constants.StaticCache && cache {\n\t\tc.Writer.Header().Add(\"Cache-Control\", \"public, max-age=86400\")\n\t\tc.Writer.Header().Add(\"ETag\", file.Hash)\n\t} else {\n\t\tc.Writer.Header().Add(\"Cache-Control\",\n\t\t\t\"no-cache, no-store, must-revalidate\")\n\t\tc.Writer.Header().Add(\"Pragma\", \"no-cache\")\n\t\tc.Writer.Header().Add(\"Expires\", \"0\")\n\t}\n\n\tif strings.Contains(c.Request.Header.Get(\"Accept-Encoding\"), \"gzip\") {\n\t\tc.Writer.Header().Add(\"Content-Encoding\", \"gzip\")\n\t\tc.Data(200, file.Type, file.GzipData)\n\t} else {\n\t\tc.Data(200, file.Type, file.Data)\n\t}\n}\n\nfunc staticIndexGet(c *gin.Context) {\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tif !authr.IsValid() {\n\t\tfastPth := auth.GetFastAdminPath()\n\t\tif fastPth != \"\" {\n\t\t\tc.Redirect(302, fastPth)\n\t\t\treturn\n\t\t}\n\n\t\tc.Redirect(302, \"/login\")\n\t\treturn\n\t}\n\n\tstaticPath(c, \"/index.html\", false)\n}\n\nfunc staticLoginGet(c *gin.Context) {\n\tstaticPath(c, \"/login.html\", false)\n}\n\nfunc staticLogoGet(c *gin.Context) {\n\tstaticPath(c, \"/logo.png\", true)\n}\n\nfunc staticGet(c *gin.Context) {\n\tstaticPath(c, \"/static\"+c.Params.ByName(\"path\"), true)\n}\n\nfunc staticTestingGet(c *gin.Context) {\n\tpth := c.Params.ByName(\"path\")\n\tif pth == \"\" {\n\t\tif c.Request.URL.Path == \"/config.js\" {\n\t\t\tpth = \"config.js\"\n\t\t} else if c.Request.URL.Path == \"/logo.png\" {\n\t\t\tpth = \"logo.png\"\n\t\t} else if c.Request.URL.Path == \"/build.js\" {\n\t\t\tpth = \"build.js\"\n\t\t} else if c.Request.URL.Path == \"/login\" {\n\t\t\tfastPth := auth.GetFastAdminPath()\n\t\t\tif fastPth != \"\" {\n\t\t\t\tc.Redirect(302, fastPth)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tc.Request.URL.Path = \"/login.html\"\n\t\t\tpth = \"login.html\"\n\t\t} else {\n\t\t\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\t\t\tif !authr.IsValid() {\n\t\t\t\tfastPth := auth.GetFastAdminPath()\n\t\t\t\tif fastPth != \"\" {\n\t\t\t\t\tc.Redirect(302, fastPth)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tc.Redirect(302, \"/login\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpth = \"index.html\"\n\t\t}\n\t}\n\n\tif strings.HasPrefix(c.Request.URL.Path, \"/node_modules/\") ||\n\t\tstrings.HasPrefix(c.Request.URL.Path, \"/jspm_packages/\") {\n\n\t\tc.Writer.Header().Add(\"Cache-Control\", \"public, max-age=86400\")\n\t} else {\n\t\tc.Writer.Header().Add(\"Cache-Control\",\n\t\t\t\"no-cache, no-store, must-revalidate\")\n\t\tc.Writer.Header().Add(\"Pragma\", \"no-cache\")\n\t\tc.Writer.Header().Add(\"Expires\", \"0\")\n\t}\n\n\tc.Writer.Header().Add(\"Content-Type\", static.GetMimeType(pth))\n\n\tgzipWriter := middlewear.NewGzipWriter(c)\n\tdefer gzipWriter.Close()\n\tfileServer.ServeHTTP(gzipWriter, c.Request)\n}\n"
  },
  {
    "path": "ahandlers/storage.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype storageData struct {\n\tId        bson.ObjectID `json:\"id\"`\n\tName      string        `json:\"name\"`\n\tComment   string        `json:\"comment\"`\n\tType      string        `json:\"type\"`\n\tEndpoint  string        `json:\"endpoint\"`\n\tBucket    string        `json:\"bucket\"`\n\tAccessKey string        `json:\"access_key\"`\n\tSecretKey string        `json:\"secret_key\"`\n\tInsecure  bool          `json:\"insecure\"`\n}\n\ntype storagesData struct {\n\tStorages []*storage.Storage `json:\"storages\"`\n\tCount    int64              `json:\"count\"`\n}\n\nfunc storagePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &storageData{}\n\n\tstoreId, ok := utils.ParseObjectId(c.Param(\"store_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tstore, err := storage.Get(db, storeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tstore.Name = dta.Name\n\tstore.Comment = dta.Comment\n\tstore.Type = dta.Type\n\tstore.Endpoint = dta.Endpoint\n\tstore.Bucket = dta.Bucket\n\tstore.AccessKey = dta.AccessKey\n\tstore.SecretKey = dta.SecretKey\n\tstore.Insecure = dta.Insecure\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"type\",\n\t\t\"endpoint\",\n\t\t\"bucket\",\n\t\t\"access_key\",\n\t\t\"secret_key\",\n\t\t\"insecure\",\n\t)\n\n\terrData, err := store.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = store.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\terr = data.Sync(db, store)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"storage: Failed to sync storage\")\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"image.change\")\n\t}()\n\n\tevent.PublishDispatch(db, \"storage.change\")\n\n\tc.JSON(200, store)\n}\n\nfunc storagePost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdta := &storageData{\n\t\tName: \"new-storage\",\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tstore := &storage.Storage{\n\t\tName:      dta.Name,\n\t\tComment:   dta.Comment,\n\t\tType:      dta.Type,\n\t\tEndpoint:  dta.Endpoint,\n\t\tBucket:    dta.Bucket,\n\t\tAccessKey: dta.AccessKey,\n\t\tSecretKey: dta.SecretKey,\n\t\tInsecure:  dta.Insecure,\n\t}\n\n\terrData, err := store.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = store.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\terr = data.Sync(db, store)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"storage: Failed to sync storage\")\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"image.change\")\n\t}()\n\n\tevent.PublishDispatch(db, \"storage.change\")\n\n\tc.JSON(200, store)\n}\n\nfunc storageDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tstoreId, ok := utils.ParseObjectId(c.Param(\"store_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := storage.Remove(db, storeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"storage.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc storagesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = storage.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"storage.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc storageGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tstore := demo.Storages[0]\n\t\tc.JSON(200, store)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tstoreId, ok := utils.ParseObjectId(c.Param(\"store_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tstore, err := storage.Get(db, storeId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif demo.IsDemo() {\n\t\tif store.AccessKey != \"\" {\n\t\t\tstore.AccessKey = \"demo\"\n\t\t}\n\t\tif store.SecretKey != \"\" {\n\t\t\tstore.SecretKey = \"demo\"\n\t\t}\n\t}\n\n\tc.JSON(200, store)\n}\n\nfunc storagesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &storagesData{\n\t\t\tStorages: demo.Storages,\n\t\t\tCount:    int64(len(demo.Storages)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tstorageId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = storageId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tdatacenter, ok := utils.ParseObjectId(c.Query(\"datacenter\"))\n\tif ok {\n\t\tquery[\"datacenter\"] = datacenter\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tstores, count, err := storage.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif demo.IsDemo() {\n\t\tfor _, store := range stores {\n\t\t\tif store.AccessKey != \"\" {\n\t\t\t\tstore.AccessKey = \"demo\"\n\t\t\t}\n\t\t\tif store.SecretKey != \"\" {\n\t\t\t\tstore.SecretKey = \"demo\"\n\t\t\t}\n\t\t}\n\t}\n\n\tdata := &storagesData{\n\t\tStorages: stores,\n\t\tCount:    count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "ahandlers/subscription.go",
    "content": "package ahandlers\n\nimport (\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/subscription\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype subscriptionPostData struct {\n\tLicense string `json:\"license\"`\n}\n\nfunc subscriptionGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, demo.Subscription)\n\t\treturn\n\t}\n\tc.JSON(200, subscription.Sub)\n}\n\nfunc subscriptionUpdateGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, demo.Subscription)\n\t\treturn\n\t}\n\n\terrData, err := subscription.Update()\n\tif err != nil {\n\t\tif errData != nil {\n\t\t\tc.JSON(400, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tc.JSON(200, subscription.Sub)\n}\n\nfunc subscriptionPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &subscriptionPostData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tlicense := strings.TrimSpace(data.License)\n\tlicense = strings.Replace(license, \"BEGIN LICENSE\", \"\", 1)\n\tlicense = strings.Replace(license, \"END LICENSE\", \"\", 1)\n\tlicense = strings.Replace(license, \"-\", \"\", -1)\n\tlicense = strings.Replace(license, \" \", \"\", -1)\n\tlicense = strings.Replace(license, \"\\n\", \"\", -1)\n\n\tsettings.System.License = license\n\n\terrData, err := subscription.Update()\n\tif err != nil {\n\t\tsettings.System.License = \"\"\n\t\tif errData != nil {\n\t\t\tc.JSON(400, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\terr = settings.Commit(db, settings.System, set.NewSet(\n\t\t\"license\",\n\t))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"subscription.change\")\n\tevent.PublishDispatch(db, \"settings.change\")\n\n\tc.JSON(200, subscription.Sub)\n}\n"
  },
  {
    "path": "ahandlers/theme.go",
    "content": "package ahandlers\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype themeData struct {\n\tTheme       string `json:\"theme\"`\n\tEditorTheme string `json:\"editor_theme\"`\n}\n\nfunc themePut(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, nil)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &themeData{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr.Theme = data.Theme\n\tusr.EditorTheme = data.EditorTheme\n\n\terr = usr.CommitFields(db, set.NewSet(\"theme\", \"editor_theme\"))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, data)\n\treturn\n}\n"
  },
  {
    "path": "ahandlers/user.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype userData struct {\n\tId             bson.ObjectID `json:\"id\"`\n\tType           string        `json:\"type\"`\n\tUsername       string        `json:\"username\"`\n\tPassword       string        `json:\"password\"`\n\tComment        string        `json:\"comment\"`\n\tRoles          []string      `json:\"roles\"`\n\tAdministrator  string        `json:\"administrator\"`\n\tPermissions    []string      `json:\"permissions\"`\n\tGenerateSecret bool          `json:\"generate_secret\"`\n\tDisabled       bool          `json:\"disabled\"`\n\tActiveUntil    time.Time     `json:\"active_until\"`\n}\n\ntype usersData struct {\n\tUsers []*user.User `json:\"users\"`\n\tCount int64        `json:\"count\"`\n}\n\nfunc userGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tusr := demo.Users[0]\n\t\tusr.LastActive = time.Now()\n\t\tc.JSON(200, usr)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tuserId, ok := utils.ParseObjectId(c.Param(\"user_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tusr, err := user.Get(db, userId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr.Secret = \"\"\n\n\tc.JSON(200, usr)\n}\n\nfunc userPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &userData{}\n\n\tuserId, ok := utils.ParseObjectId(c.Param(\"user_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, err := user.Get(db, userId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tshowSecret := false\n\tif usr.Type != data.Type {\n\t\tif data.Type == user.Api {\n\t\t\tusr.GenerateToken()\n\t\t\tshowSecret = true\n\t\t} else {\n\t\t\tusr.Token = \"\"\n\t\t\tusr.Secret = \"\"\n\t\t}\n\t}\n\n\tusr.Type = data.Type\n\tusr.Username = data.Username\n\tusr.Comment = data.Comment\n\tusr.Roles = data.Roles\n\tusr.Administrator = data.Administrator\n\tusr.Permissions = data.Permissions\n\tusr.Disabled = data.Disabled\n\tusr.ActiveUntil = data.ActiveUntil\n\n\tif usr.Disabled {\n\t\tusr.ActiveUntil = time.Time{}\n\t}\n\n\tif usr.Type == user.Api && data.GenerateSecret {\n\t\tusr.GenerateToken()\n\t\tshowSecret = true\n\t}\n\n\tfields := set.NewSet(\n\t\t\"type\",\n\t\t\"token\",\n\t\t\"secret\",\n\t\t\"username\",\n\t\t\"comment\",\n\t\t\"roles\",\n\t\t\"administrator\",\n\t\t\"permissions\",\n\t\t\"disabled\",\n\t\t\"active_until\",\n\t)\n\n\tif usr.Type == user.Local && data.Password != \"\" {\n\t\terr = usr.SetPassword(data.Password)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tfields.Add(\"password\")\n\t} else if usr.Type != user.Local && usr.Password != \"\" {\n\t\tusr.Password = \"\"\n\t\tfields.Add(\"password\")\n\t}\n\n\terrData, err := usr.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terrData, err = usr.SuperExists(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = usr.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"user.change\")\n\n\tif !showSecret {\n\t\tusr.Secret = \"\"\n\t}\n\n\tc.JSON(200, usr)\n}\n\nfunc userPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &userData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr := &user.User{\n\t\tType:          data.Type,\n\t\tUsername:      data.Username,\n\t\tComment:       data.Comment,\n\t\tRoles:         data.Roles,\n\t\tAdministrator: data.Administrator,\n\t\tPermissions:   data.Permissions,\n\t\tDisabled:      data.Disabled,\n\t\tActiveUntil:   data.ActiveUntil,\n\t}\n\n\tif usr.Disabled {\n\t\tusr.ActiveUntil = time.Time{}\n\t}\n\n\tif usr.Type == user.Local && data.Password != \"\" {\n\t\terr = usr.SetPassword(data.Password)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif usr.Type == user.Api {\n\t\tusr.GenerateToken()\n\t}\n\n\terrData, err := usr.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = usr.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"user.change\")\n\n\tc.JSON(200, usr)\n}\n\nfunc usersGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tfor _, usr := range demo.Users {\n\t\t\tusr.LastActive = time.Now()\n\t\t}\n\n\t\tdata := &usersData{\n\t\t\tUsers: demo.Users,\n\t\t\tCount: int64(len(demo.Users)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tuserId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = userId\n\t}\n\n\tusername := strings.TrimSpace(c.Query(\"username\"))\n\tif username != \"\" {\n\t\tquery[\"username\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(username)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\ttyp := strings.TrimSpace(c.Query(\"type\"))\n\tif typ != \"\" {\n\t\tquery[\"type\"] = typ\n\t}\n\n\tadministrator := c.Query(\"administrator\")\n\tswitch administrator {\n\tcase \"true\":\n\t\tquery[\"administrator\"] = \"super\"\n\t\tbreak\n\tcase \"false\":\n\t\tquery[\"administrator\"] = \"\"\n\t\tbreak\n\t}\n\n\tdisabled := c.Query(\"disabled\")\n\tswitch disabled {\n\tcase \"true\":\n\t\tquery[\"disabled\"] = true\n\t\tbreak\n\tcase \"false\":\n\t\tquery[\"disabled\"] = false\n\t\tbreak\n\t}\n\n\tusers, count, err := user.GetAll(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfor _, usr := range users {\n\t\tusr.Secret = \"\"\n\t}\n\n\tdata := &usersData{\n\t\tUsers: users,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, data)\n}\n\nfunc usersDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := user.Remove(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"user.change\")\n\n\tc.JSON(200, nil)\n}\n"
  },
  {
    "path": "ahandlers/vpc.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\ntype vpcData struct {\n\tId            bson.ObjectID `json:\"id\"`\n\tName          string        `json:\"name\"`\n\tComment       string        `json:\"comment\"`\n\tNetwork       string        `json:\"network\"`\n\tIcmpRedirects bool          `json:\"icmp_redirects\"`\n\tSubnets       []*vpc.Subnet `json:\"subnets\"`\n\tOrganization  bson.ObjectID `json:\"organization\"`\n\tDatacenter    bson.ObjectID `json:\"datacenter\"`\n\tRoutes        []*vpc.Route  `json:\"routes\"`\n\tMaps          []*vpc.Map    `json:\"maps\"`\n\tArps          []*vpc.Arp    `json:\"arps\"`\n}\n\ntype vpcsData struct {\n\tVpcs  []*vpc.Vpc `json:\"vpcs\"`\n\tCount int64      `json:\"count\"`\n}\n\nfunc vpcPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &vpcData{}\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvc, err := vpc.Get(db, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvc.PreCommit()\n\n\tvc.Name = data.Name\n\tvc.Comment = data.Comment\n\tvc.IcmpRedirects = data.IcmpRedirects\n\tvc.Routes = data.Routes\n\tvc.Maps = data.Maps\n\tvc.Arps = data.Arps\n\tvc.Subnets = data.Subnets\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"icmp_redirects\",\n\t\t\"routes\",\n\t\t\"maps\",\n\t\t\"arps\",\n\t\t\"subnets\",\n\t)\n\n\terrData, err := vc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terrData, err = vc.PostCommit(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vc.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tvc.Json()\n\n\tc.JSON(200, vc)\n}\n\nfunc vpcPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &vpcData{\n\t\tName: \"new-vpc\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"ahandler: Failed to bind\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvc := &vpc.Vpc{\n\t\tName:          data.Name,\n\t\tComment:       data.Comment,\n\t\tNetwork:       data.Network,\n\t\tSubnets:       data.Subnets,\n\t\tOrganization:  data.Organization,\n\t\tDatacenter:    data.Datacenter,\n\t\tIcmpRedirects: data.IcmpRedirects,\n\t\tRoutes:        data.Routes,\n\t\tMaps:          data.Maps,\n\t\tArps:          data.Arps,\n\t}\n\n\tvc.InitVpc()\n\n\terrData, err := vc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vc.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tvc.Json()\n\n\tc.JSON(200, vc)\n}\n\nfunc vpcDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"vpc\", vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vpc.Remove(db, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc vpcsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"vpc\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vpc.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc vpcGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tvc := demo.Vpcs[0]\n\t\tc.JSON(200, vc)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tvc, err := vpc.Get(db, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvc.Json()\n\n\tc.JSON(200, vc)\n}\n\nfunc vpcRoutesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tvc := demo.Vpcs[0]\n\t\tc.JSON(200, vc.Routes)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tvc, err := vpc.Get(db, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, vc.Routes)\n}\n\nfunc vpcRoutesPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []*vpc.Route{}\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvc, err := vpc.Get(db, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvc.Routes = data\n\n\tfields := set.NewSet(\n\t\t\"routes\",\n\t)\n\n\terrData, err := vc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vc.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tvc.Json()\n\n\tc.JSON(200, vc)\n}\n\nfunc vpcsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &vpcsData{\n\t\t\tVpcs:  demo.Vpcs,\n\t\t\tCount: int64(len(demo.Vpcs)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tquery := &bson.M{}\n\n\t\tvpcs, err := vpc.GetAllNames(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, vpcs)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{}\n\n\t\tvpcId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = vpcId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"name\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\tnetwork := strings.TrimSpace(c.Query(\"network\"))\n\t\tif network != \"\" {\n\t\t\tquery[\"network\"] = network\n\t\t}\n\n\t\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\t\tif ok {\n\t\t\tquery[\"organization\"] = organization\n\t\t}\n\n\t\tdc, ok := utils.ParseObjectId(c.Query(\"datacenter\"))\n\t\tif ok {\n\t\t\tquery[\"datacenter\"] = dc\n\t\t}\n\n\t\tvpcs, count, err := vpc.GetAllPaged(db, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, vc := range vpcs {\n\t\t\tvc.Json()\n\t\t}\n\n\t\tdata := &vpcsData{\n\t\t\tVpcs:  vpcs,\n\t\t\tCount: count,\n\t\t}\n\n\t\tc.JSON(200, data)\n\t}\n}\n"
  },
  {
    "path": "ahandlers/zone.go",
    "content": "package ahandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\ntype zoneData struct {\n\tId         bson.ObjectID `json:\"id\"`\n\tDatacenter bson.ObjectID `json:\"datacenter\"`\n\tName       string        `json:\"name\"`\n\tComment    string        `json:\"comment\"`\n}\n\ntype zonesData struct {\n\tZones []*zone.Zone `json:\"zones\"`\n\tCount int64        `json:\"count\"`\n}\n\nfunc zonePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &zoneData{}\n\n\tzoneId, ok := utils.ParseObjectId(c.Param(\"zone_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tzne, err := zone.Get(db, zoneId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tzne.Name = data.Name\n\tzne.Comment = data.Comment\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t)\n\n\terrData, err := zne.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = zne.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"zone.change\")\n\n\tc.JSON(200, zne)\n}\n\nfunc zonePost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &zoneData{\n\t\tName: \"new-zone\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tzne := &zone.Zone{\n\t\tDatacenter: data.Datacenter,\n\t\tName:       data.Name,\n\t\tComment:    data.Comment,\n\t}\n\n\terrData, err := zne.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = zne.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"zone.change\")\n\n\tc.JSON(200, zne)\n}\n\nfunc zoneDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tzoneId, ok := utils.ParseObjectId(c.Param(\"zone_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDelete(db, \"zone\", zoneId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = zone.Remove(db, zoneId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"zone.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc zonesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteAll(db, \"zone\", data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = zone.RemoveMulti(db, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"zone.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc zoneGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tzne := demo.Zones[0]\n\t\tc.JSON(200, zne)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tzoneId, ok := utils.ParseObjectId(c.Param(\"zone_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tzne, err := zone.Get(db, zoneId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, zne)\n}\n\nfunc zonesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &zonesData{\n\t\t\tZones: demo.Zones,\n\t\t\tCount: int64(len(demo.Zones)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tdcs, err := zone.GetAllNames(db, &bson.M{})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, dcs)\n\t\treturn\n\t}\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{}\n\n\tzoneId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = zoneId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\torganization, ok := utils.ParseObjectId(c.Query(\"organization\"))\n\tif ok {\n\t\tquery[\"organization\"] = organization\n\t}\n\n\tdatacenter, ok := utils.ParseObjectId(c.Query(\"datacenter\"))\n\tif ok {\n\t\tquery[\"datacenter\"] = datacenter\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tznes, count, err := zone.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &zonesData{\n\t\tZones: znes,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "alert/alert.go",
    "content": "package alert\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Alert struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tComment      string        `bson:\"comment\" json:\"comment\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tRoles        []string      `bson:\"roles\" json:\"roles\"`\n\tResource     string        `bson:\"resource\" json:\"resource\"`\n\tLevel        int           `bson:\"level\" json:\"level\"`\n\tFrequency    int           `bson:\"frequency\" json:\"frequency\"`\n\tIgnores      []string      `bson:\"ignores\" json:\"ignores\"`\n\tValueInt     int           `bson:\"value_int\" json:\"value_int\"`\n\tValueStr     string        `bson:\"value_str\" json:\"value_str\"`\n}\n\nfunc (a *Alert) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif a.Id.IsZero() {\n\t\ta.Id, err = utils.RandObjectId()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\ta.Name = utils.FilterName(a.Name)\n\n\tif a.Roles == nil {\n\t\ta.Roles = []string{}\n\t}\n\n\tif a.Frequency == 0 {\n\t\ta.Frequency = 300\n\t}\n\n\tif a.Frequency < 300 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"alert_frequency_invalid\",\n\t\t\tMessage: \"Alert frequency cannot be less then 300 seconds\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Frequency > 604800 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"alert_frequency_invalid\",\n\t\t\tMessage: \"Alert frequency too large\",\n\t\t}\n\t\treturn\n\t}\n\n\tif a.Ignores != nil {\n\t\ta.Ignores = []string{}\n\t}\n\n\tswitch a.Resource {\n\tcase InstanceOffline:\n\t\ta.ValueInt = 0\n\t\ta.ValueStr = \"\"\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"alert_resource_name_invalid\",\n\t\t\tMessage: \"Alert resource name is invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch a.Level {\n\tcase Low, Medium, High:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"alert_resource_level_invalid\",\n\t\t\tMessage: \"Alert resource level is invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (a *Alert) Commit(db *database.Database) (err error) {\n\tcoll := db.Alerts()\n\n\terr = coll.Commit(a.Id, a)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (a *Alert) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Alerts()\n\n\terr = coll.CommitFields(a.Id, a, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (a *Alert) Insert(db *database.Database) (err error) {\n\tcoll := db.Alerts()\n\n\t_, err = coll.InsertOne(db, a)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "alert/constants.go",
    "content": "package alert\n\nconst (\n\tLow    = 1\n\tMedium = 5\n\tHigh   = 10\n)\n\nconst (\n\tInstanceOffline = \"instance_offline\"\n)\n"
  },
  {
    "path": "alert/utils.go",
    "content": "package alert\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, alertId bson.ObjectID) (\n\talrt *Alert, err error) {\n\n\tcoll := db.Alerts()\n\talrt = &Alert{}\n\n\terr = coll.FindOneId(alertId, alrt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, alertId bson.ObjectID) (\n\talrt *Alert, err error) {\n\n\tcoll := db.Alerts()\n\talrt = &Alert{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          alertId,\n\t\t\"organization\": orgId,\n\t}).Decode(alrt)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetMulti(db *database.Database, alertIds []bson.ObjectID) (\n\talerts []*Alert, err error) {\n\n\tcoll := db.Alerts()\n\talerts = []*Alert{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": alertIds,\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\talrt := &Alert{}\n\t\terr = cursor.Decode(alrt)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\talerts = append(alerts, alrt)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database) (alerts []*Alert, err error) {\n\tcoll := db.Alerts()\n\talerts = []*Alert{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\talrt := &Alert{}\n\t\terr = cursor.Decode(alrt)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\talerts = append(alerts, alrt)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (alerts []*Alert, count int64, err error) {\n\n\tcoll := db.Alerts()\n\talerts = []*Alert{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\talrt := &Alert{}\n\t\terr = cursor.Decode(alrt)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\talerts = append(alerts, alrt)\n\t\talrt = &Alert{}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetRoles(db *database.Database, roles []string) (\n\talerts []*Alert, err error) {\n\n\tcoll := db.Alerts()\n\talerts = []*Alert{}\n\n\tif roles == nil {\n\t\troles = []string{}\n\t}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"roles\": &bson.M{\n\t\t\t\t\"$in\": roles,\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tpolcy := &Alert{}\n\t\terr = cursor.Decode(polcy)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\talerts = append(alerts, polcy)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetRolesMapped(db *database.Database, rolesSet set.Set) (\n\talertsMap map[string][]*Alert, err error) {\n\n\talertsMap = map[string][]*Alert{}\n\n\troles := []string{}\n\tfor role := range rolesSet.Iter() {\n\t\troles = append(roles, role.(string))\n\t}\n\n\talerts, err := GetRoles(db, roles)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, alrt := range alerts {\n\t\tfor _, role := range alrt.Roles {\n\t\t\troleAlrts := alertsMap[role]\n\t\t\tif roleAlrts == nil {\n\t\t\t\troleAlrts = []*Alert{}\n\t\t\t}\n\t\t\talertsMap[role] = append(roleAlrts, alrt)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database,\n\talertId bson.ObjectID) (err error) {\n\n\tcoll := db.Alerts()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": alertId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, alertId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Alerts()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          alertId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, alertIds []bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Alerts()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": alertIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\talertIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Alerts()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": alertIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "alertevent/alertevent.go",
    "content": "package alertevent\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/device\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Alert struct {\n\tId         string        `bson:\"_id\" json:\"_id\"`\n\tName       string        `bson:\"name\" json:\"name\"`\n\tTimestamp  time.Time     `bson:\"timestamp\" json:\"timestamp\"`\n\tRoles      []string      `bson:\"roles\" json:\"roles\"`\n\tSource     bson.ObjectID `bson:\"source\" json:\"source\"`\n\tSourceName string        `bson:\"source_name\" json:\"source_name\"`\n\tLevel      int           `bson:\"level\" json:\"level\"`\n\tResource   string        `bson:\"resource\" json:\"resource\"`\n\tMessage    string        `bson:\"message\" json:\"message\"`\n\tFrequency  time.Duration `bson:\"frequency\" json:\"frequency\"`\n}\n\nfunc (a *Alert) DocId() string {\n\ttimestamp := a.Timestamp.Unix()\n\ttimekey := timestamp - (timestamp % int64(a.Frequency.Seconds()))\n\n\treturn fmt.Sprintf(\n\t\t\"%s-%s-%d\",\n\t\ta.Source.Hex(),\n\t\ta.Resource,\n\t\ttimekey,\n\t)\n}\n\nfunc (a *Alert) Key(devc *device.Device) string {\n\ttimestamp := a.Timestamp.Unix()\n\ttimekey := timestamp - (timestamp % int64(a.Frequency.Seconds()))\n\n\treturn fmt.Sprintf(\n\t\t\"%s-%s-%s-%d\",\n\t\ta.Source.Hex(),\n\t\ta.Resource,\n\t\tdevc.Id.Hex(),\n\t\ttimekey,\n\t)\n}\n\nfunc (a *Alert) Lock(db *database.Database, devc *device.Device) (\n\tsuccess bool, err error) {\n\n\tcoll := db.AlertsEventLock()\n\n\t_, err = coll.InsertOne(db, &bson.M{\n\t\t\"_id\":       a.Key(devc),\n\t\t\"timestamp\": time.Now(),\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\terr = nil\n\t\t}\n\t\treturn\n\t}\n\n\tsuccess = true\n\n\treturn\n}\n\nfunc (a *Alert) FormattedTextMessage() string {\n\treturn fmt.Sprintf(\"%s:%s == %s\", a.Name, a.SourceName, a.Message)\n}\n\nfunc (a *Alert) FormattedCallMessage() string {\n\treturn fmt.Sprintf(\"%s. %s\", a.SourceName, a.Message)\n}\n\nfunc (a *Alert) Send(db *database.Database, roles []string) (err error) {\n\tcoll := db.AlertsEvent()\n\talrt := &Alert{}\n\n\terr = coll.FindOneId(a.Id, alrt)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\talrt = nil\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif alrt != nil && time.Since(alrt.Timestamp) < alrt.Frequency {\n\t\treturn\n\t}\n\n\tusers, _, err := user.GetAll(db, &bson.M{\n\t\t\"roles\": &bson.D{\n\t\t\t{\"$in\", roles},\n\t\t},\n\t}, 0, 0)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, usr := range users {\n\t\tdevices, e := usr.GetDevices(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tfor _, devc := range devices {\n\t\t\tif devc.Mode != device.Phone || !devc.CheckLevel(a.Level) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsuccess, e := a.Lock(db, devc)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !success {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmsg := \"\"\n\t\t\tif devc.Type == device.Call {\n\t\t\t\tmsg = a.FormattedCallMessage()\n\t\t\t} else {\n\t\t\t\tmsg = a.FormattedTextMessage()\n\t\t\t}\n\n\t\t\terrData, e := Send(devc.Number, msg, devc.Type)\n\t\t\tif e != nil {\n\t\t\t\tif errData != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"server_error\":   errData.Error,\n\t\t\t\t\t\t\"server_message\": errData.Message,\n\t\t\t\t\t\t\"error\":          e,\n\t\t\t\t\t}).Error(\"alert: Failed to send alert\")\n\t\t\t\t} else {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"error\": e,\n\t\t\t\t\t}).Error(\"alert: Failed to send alert\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = coll.InsertOne(db, a)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\terr = nil\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc New(roles []string, source bson.ObjectID,\n\tname, sourceName, resource, message string, level int,\n\tfrequency time.Duration) {\n\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\talrt := &Alert{\n\t\tName:       name,\n\t\tTimestamp:  time.Now(),\n\t\tRoles:      roles,\n\t\tSource:     source,\n\t\tSourceName: sourceName,\n\t\tLevel:      level,\n\t\tResource:   resource,\n\t\tMessage:    message,\n\t\tFrequency:  frequency,\n\t}\n\n\talrt.Id = alrt.DocId()\n\n\terr := alrt.Send(db, roles)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"alert: Failed to process alert\")\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "alertevent/utils.go",
    "content": "package alertevent\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/device\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/twilio\"\n)\n\nvar (\n\tclient = &http.Client{\n\t\tTimeout: 10 * time.Second,\n\t}\n)\n\ntype AlertParams struct {\n\tLicense string `json:\"license\"`\n\tNumber  string `json:\"number\"`\n\tType    string `json:\"type\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc SendTest(db *database.Database, devc *device.Device) (\n\terrData *errortypes.ErrorData, err error) {\n\n\terr = devc.SetActive(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terrData, err = Send(devc.Number, \"Test alert message\", devc.Type)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Send(number, message, alertType string) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif settings.System.TwilioAccount != \"\" {\n\t\tif alertType == device.Call {\n\t\t\terr = twilio.PhoneCall(number, message)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else if alertType == device.Message {\n\t\t\terr = twilio.TextMessage(number, message)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\terr, \"alert: Unknown alert type\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tparams := &AlertParams{\n\t\t\tLicense: settings.System.License,\n\t\t\tNumber:  number,\n\t\t\tType:    alertType,\n\t\t\tMessage: message,\n\t\t}\n\n\t\talertBody, e := json.Marshal(params)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\te, \"alert: Failed to parse alert params\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treq, e := http.NewRequest(\n\t\t\t\"POST\",\n\t\t\t\"https://app.pritunl.com/alert\",\n\t\t\tbytes.NewBuffer(alertBody),\n\t\t)\n\t\tif e != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(e, \"alert: Failed to create alert request\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treq.Header.Set(\"User-Agent\", \"pritunl-cloud\")\n\t\treq.Header.Set(\"Accept\", \"application/json\")\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t\tresp, e := client.Do(req)\n\t\tif e != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(e, \"alert: Alert request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != 200 {\n\t\t\tbody := \"\"\n\t\t\tdata, _ := ioutil.ReadAll(resp.Body)\n\t\t\tif data != nil {\n\t\t\t\tbody = string(data)\n\t\t\t}\n\n\t\t\terrData = &errortypes.ErrorData{}\n\t\t\terr = json.Unmarshal(data, errData)\n\t\t\tif err != nil || errData.Error == \"\" {\n\t\t\t\terrData = nil\n\t\t\t}\n\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"alert: Alert server error %d - %s\",\n\t\t\t\t\tresp.StatusCode, body),\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "arp/arp.go",
    "content": "package arp\n\nimport (\n\t\"encoding/json\"\n\t\"net\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\ntype Record struct {\n\tIp  string\n\tMac string\n}\n\ntype entry struct {\n\tDst    string   `json:\"dst\"`\n\tDev    string   `json:\"dev\"`\n\tLladdr string   `json:\"lladdr,omitempty\"`\n\tState  []string `json:\"state\"`\n\tRouter string   `json:\"router,omitempty\"`\n}\n\nfunc GetRecords(namespace string) (records set.Set, err error) {\n\trecords = set.NewSet()\n\n\toutput, _ := utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ip\", \"--json\",\n\t\t\"neighbor\",\n\t)\n\n\tif output == \"\" {\n\t\treturn\n\t}\n\n\tvar entries []*entry\n\terr = json.Unmarshal([]byte(output), &entries)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"arp: Failed to process arp output\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, ent := range entries {\n\t\tif ent.Dev != settings.Hypervisor.BridgeIfaceName {\n\t\t\tcontinue\n\t\t}\n\n\t\tmac := \"\"\n\t\tif ent.State != nil {\n\t\t\tfor _, state := range ent.State {\n\t\t\t\tif state == \"PERMANENT\" {\n\t\t\t\t\tmac = ent.Lladdr\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tip := net.ParseIP(ent.Dst)\n\t\tif ip != nil {\n\t\t\trecords.Add(Record{\n\t\t\t\tIp:  ip.String(),\n\t\t\t\tMac: mac,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc BuildState(instances []*instance.Instance,\n\tvpcsMap map[bson.ObjectID]*vpc.Vpc,\n\tvpcIpsMap map[bson.ObjectID][]*vpc.VpcIp) (\n\trecrds map[string]set.Set) {\n\n\trecrds = map[string]set.Set{}\n\n\tfor _, inst := range instances {\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor i, adapter := range inst.Virt.NetworkAdapters {\n\t\t\tnamespace := vm.GetNamespace(inst.Id, i)\n\t\t\tvc := vpcsMap[adapter.Vpc]\n\t\t\tvpcIps := vpcIpsMap[adapter.Vpc]\n\n\t\t\tnewRecrds := set.NewSet()\n\n\t\t\tfor _, vpcIp := range vpcIps {\n\t\t\t\tif vpcIp.Instance.IsZero() {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\taddr := vpcIp.GetIp()\n\n\t\t\t\tnewRecrds.Add(Record{\n\t\t\t\t\tIp:  vpc.GetIp6(vpcIp.Vpc, vpcIp.Instance).String(),\n\t\t\t\t\tMac: vm.GetMacAddr(vpcIp.Instance, adapter.Vpc),\n\t\t\t\t})\n\n\t\t\t\tnewRecrds.Add(Record{\n\t\t\t\t\tIp:  addr.String(),\n\t\t\t\t\tMac: vm.GetMacAddr(vpcIp.Instance, adapter.Vpc),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif vc != nil && vc.Arps != nil {\n\t\t\t\tfor _, ap := range vc.Arps {\n\t\t\t\t\tnewRecrds.Add(Record{\n\t\t\t\t\t\tIp:  ap.Ip,\n\t\t\t\t\t\tMac: ap.Mac,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trecrds[namespace] = newRecrds\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc ApplyState(namespace string, oldState, newState set.Set) (\n\tchanged bool, err error) {\n\n\taddRecords := newState.Copy()\n\tremRecords := oldState.Copy()\n\n\taddRecords.Subtract(oldState)\n\tremRecords.Subtract(newState)\n\n\tfor recordInf := range remRecords.Iter() {\n\t\trecrd := recordInf.(Record)\n\t\tchanged = true\n\n\t\tutils.ExecCombinedOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"No such file\",\n\t\t\t},\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"neighbor\",\n\t\t\t\"del\", recrd.Ip,\n\t\t\t\"dev\", settings.Hypervisor.BridgeIfaceName,\n\t\t)\n\t}\n\n\tfor recordInf := range addRecords.Iter() {\n\t\trecrd := recordInf.(Record)\n\t\tchanged = true\n\n\t\tutils.ExecCombinedOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"No such file\",\n\t\t\t},\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"neighbor\",\n\t\t\t\"del\", recrd.Ip,\n\t\t\t\"dev\", settings.Hypervisor.BridgeIfaceName,\n\t\t)\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"File exists\",\n\t\t\t},\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"neighbor\",\n\t\t\t\"replace\", recrd.Ip,\n\t\t\t\"lladdr\", recrd.Mac,\n\t\t\t\"dev\", settings.Hypervisor.BridgeIfaceName,\n\t\t\t\"nud\", \"permanent\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "audit/audit.go",
    "content": "package audit\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/useragent\"\n)\n\ntype Fields map[string]interface{}\n\ntype Audit struct {\n\tId        bson.ObjectID    `bson:\"_id,omitempty\" json:\"id\"`\n\tUser      bson.ObjectID    `bson:\"u\" json:\"user\"`\n\tTimestamp time.Time        `bson:\"t\" json:\"timestamp\"`\n\tType      string           `bson:\"y\" json:\"type\"`\n\tFields    Fields           `bson:\"f\" json:\"fields\"`\n\tAgent     *useragent.Agent `bson:\"a\" json:\"agent\"`\n}\n\nfunc (a *Audit) Insert(db *database.Database) (err error) {\n\tcoll := db.Audits()\n\n\tif !a.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"audit: Entry already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = coll.InsertOne(db, a)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "audit/constants.go",
    "content": "package audit\n\nconst (\n\tAdminLogin                 = \"admin_login\"\n\tAdminLoginFailed           = \"admin_login_failed\"\n\tAdminAuthFailed            = \"admin_auth_failed\"\n\tAdminLogout                = \"admin_logout\"\n\tAdminPrimaryApprove        = \"admin_primary_approve\"\n\tAdminSecondaryApprove      = \"admin_secondary_approve\"\n\tAdminDeviceApprove         = \"admin_device_approve\"\n\tAdminDeviceRegisterRequest = \"admin_device_register_request\"\n\tAdminDeviceRegister        = \"admin_device_register\"\n\n\tProxyLogin                 = \"proxy_login\"\n\tProxyLoginFailed           = \"proxy_login_failed\"\n\tProxyAuthFailed            = \"proxy_auth_failed\"\n\tProxyLogout                = \"proxy_logout\"\n\tProxyPrimaryApprove        = \"proxy_primary_approve\"\n\tProxySecondaryApprove      = \"proxy_secondary_approve\"\n\tProxyDeviceApprove         = \"proxy_device_approve\"\n\tProxyDeviceRegisterRequest = \"proxy_device_register_request\"\n\tProxyDeviceRegister        = \"proxy_device_register\"\n\n\tUserLogin                 = \"user_login\"\n\tUserLoginFailed           = \"user_login_failed\"\n\tUserAuthFailed            = \"user_auth_failed\"\n\tUserLogout                = \"user_logout\"\n\tUserLogoutAll             = \"user_logout_all\"\n\tUserPrimaryApprove        = \"user_primary_approve\"\n\tUserSecondaryApprove      = \"user_secondary_approve\"\n\tUserDeviceApprove         = \"user_device_approve\"\n\tUserDeviceRegisterRequest = \"user_device_register_request\"\n\tUserDeviceRegister        = \"user_device_register\"\n\tUserAccountDisable        = \"user_account_disable\"\n\n\tDeviceRegister       = \"device_register\"\n\tDeviceRegisterFailed = \"device_register_failed\"\n\tDuoApprove           = \"duo_approve\"\n\tDuoDeny              = \"duo_deny\"\n\tOneLoginApprove      = \"one_login_approve\"\n\tOneLoginDeny         = \"one_login_deny\"\n\tOktaApprove          = \"okta_approve\"\n\tOktaDeny             = \"okta_deny\"\n)\n"
  },
  {
    "path": "audit/utils.go",
    "content": "package audit\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/useragent\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, adtId string) (\n\tadt *Audit, err error) {\n\n\tcoll := db.Audits()\n\tadt = &Audit{}\n\n\terr = coll.FindOneId(adtId, adt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, userId bson.ObjectID,\n\tpage, pageCount int64) (audits []*Audit, count int64, err error) {\n\n\tcoll := db.Audits()\n\taudits = []*Audit{}\n\n\tcount, err = coll.CountDocuments(db, &bson.M{\n\t\t\"u\": userId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"u\": userId,\n\t}, options.Find().\n\t\tSetSort(&bson.D{\n\t\t\t{\"$natural\", -1},\n\t\t}).\n\t\tSetSkip(skip).\n\t\tSetLimit(pageCount))\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tadt := &Audit{}\n\t\terr = cursor.Decode(adt)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\taudits = append(audits, adt)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc New(db *database.Database, r *http.Request,\n\tuserId bson.ObjectID, typ string, fields Fields) (\n\terr error) {\n\n\tif settings.System.Demo {\n\t\treturn\n\t}\n\n\tagnt, err := useragent.Parse(db, r)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tadt := &Audit{\n\t\tUser:      userId,\n\t\tTimestamp: time.Now(),\n\t\tType:      typ,\n\t\tFields:    fields,\n\t\tAgent:     agnt,\n\t}\n\n\terr = adt.Insert(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "auth/auth.go",
    "content": "package auth\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nvar (\n\tclient = &http.Client{\n\t\tTimeout: 20 * time.Second,\n\t}\n)\n\ntype authData struct {\n\tUrl string `json:\"url\"`\n}\n\ntype Token struct {\n\tId        string        `bson:\"_id\"`\n\tType      string        `bson:\"type\"`\n\tSecret    string        `bson:\"secret\"`\n\tTimestamp time.Time     `bson:\"timestamp\"`\n\tProvider  bson.ObjectID `bson:\"provider,omitempty\"`\n\tQuery     string        `bson:\"query\"`\n}\n\nfunc (t *Token) Remove(db *database.Database) (err error) {\n\tcoll := db.Tokens()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": t.Id,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "auth/authzero.go",
    "content": "package auth\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nconst (\n\tAuthZero = \"authzero\"\n)\n\nfunc AuthZeroRequest(db *database.Database, location, query string,\n\tprovider *settings.Provider) (redirect string, err error) {\n\n\tcoll := db.Tokens()\n\n\tstate, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsecret, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(struct {\n\t\tLicense   string `json:\"license\"`\n\t\tCallback  string `json:\"callback\"`\n\t\tState     string `json:\"state\"`\n\t\tSecret    string `json:\"secret\"`\n\t\tAppDomain string `json:\"app_domain\"`\n\t\tAppId     string `json:\"app_id\"`\n\t\tAppSecret string `json:\"app_secret\"`\n\t}{\n\t\tLicense:   settings.System.License,\n\t\tCallback:  location + \"/auth/callback\",\n\t\tState:     state,\n\t\tSecret:    secret,\n\t\tAppDomain: provider.Domain,\n\t\tAppId:     provider.ClientId,\n\t\tAppSecret: provider.ClientSecret,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq, err := http.NewRequest(\n\t\t\"POST\",\n\t\tsettings.Auth.Server+\"/v1/request/authzero\",\n\t\tbytes.NewBuffer(data),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Auth server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tauthData := &authData{}\n\terr = json.NewDecoder(resp.Body).Decode(authData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"auth: Failed to parse auth response\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\ttokn := &Token{\n\t\tId:        state,\n\t\tType:      AuthZero,\n\t\tSecret:    secret,\n\t\tTimestamp: time.Now(),\n\t\tProvider:  provider.Id,\n\t\tQuery:     query,\n\t}\n\n\t_, err = coll.InsertOne(db, tokn)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tredirect = authData.Url\n\n\treturn\n}\n\ntype authZeroJwks struct {\n\tKeys []json.RawMessage `json:\"keys\"`\n}\n\ntype authZeroTokenReq struct {\n\tGrantType    string `json:\"grant_type\"`\n\tClientId     string `json:\"client_id\"`\n\tClientSecret string `json:\"client_secret\"`\n\tAudience     string `json:\"audience\"`\n}\n\ntype authZeroTokenData struct {\n\tAccessToken string `json:\"access_token\"`\n\tTokenType   string `json:\"token_type\"`\n\tExpiresIn   int    `json:\"expires_in\"`\n}\n\ntype authZeroToken struct {\n\tIss   string `json:\"iss\"`\n\tSub   string `json:\"sub\"`\n\tAud   string `json:\"aud\"`\n\tExp   int    `json:\"exp\"`\n\tIat   int    `json:\"iat\"`\n\tScope string `json:\"scope\"`\n}\n\ntype authZeroAppAuthorization struct {\n\tGroups []string `json:\"groups\"`\n\tRoles  []string `json:\"roles\"`\n}\n\ntype authZeroAppMetadata struct {\n\tAuthorization authZeroAppAuthorization `json:\"authorization\"`\n}\n\ntype authZeroUser struct {\n\tUserId      string              `json:\"user_id\"`\n\tEmail       string              `json:\"email\"`\n\tAppMetadata authZeroAppMetadata `json:\"app_metadata\"`\n}\n\n//func authZeroGetJwk(provider *settings.Provider) (\n//\tjwk *jose.JSONWebKey, err error) {\n//\n//\treq, err := http.NewRequest(\n//\t\t\"GET\",\n//\t\tfmt.Sprintf(\n//\t\t\t\"https://%s.auth0.com/.well-known/jwks.json\",\n//\t\t\tprovider.Domain,\n//\t\t),\n//\t\tnil,\n//\t)\n//\tif err != nil {\n//\t\terr = &errortypes.RequestError{\n//\t\t\terrors.Wrap(err, \"auth: Failed to create auth0 request\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\tresp, err := client.Do(req)\n//\tif err != nil {\n//\t\terr = &errortypes.RequestError{\n//\t\t\terrors.Wrap(err, \"auth: auth0 request failed\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\tdefer resp.Body.Close()\n//\n//\tdata := &authZeroJwks{}\n//\terr = json.NewDecoder(resp.Body).Decode(data)\n//\tif err != nil {\n//\t\terr = &errortypes.ParseError{\n//\t\t\terrors.Wrap(err, \"auth: Failed to parse response\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\tif len(data.Keys) < 1 {\n//\t\terr = &errortypes.ParseError{\n//\t\t\terrors.Wrap(err, \"auth: No JWK keys available\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\tjwk = &jose.JSONWebKey{}\n//\n//\terr = jwk.UnmarshalJSON(data.Keys[0])\n//\tif err != nil {\n//\t\terr = &errortypes.ParseError{\n//\t\t\terrors.Wrap(err, \"auth: Failed to parse jwt key\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\treturn\n//}\n//\n//func authZeroGetJwkToken(provider *settings.Provider) (\n//\taccessToken string, token *authZeroToken, err error) {\n//\n//\treqData := &authZeroTokenReq{\n//\t\tGrantType:    \"client_credentials\",\n//\t\tClientId:     provider.ClientId,\n//\t\tClientSecret: provider.ClientSecret,\n//\t\tAudience: fmt.Sprintf(\n//\t\t\t\"https://%s.auth0.com/api/v2/\", provider.Domain),\n//\t}\n//\n//\treqDataBuf := &bytes.Buffer{}\n//\terr = json.NewEncoder(reqDataBuf).Encode(reqData)\n//\tif err != nil {\n//\t\terr = &errortypes.ParseError{\n//\t\t\terrors.Wrap(err, \"auth: Failed to parse request data\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\treq, err := http.NewRequest(\n//\t\t\"POST\",\n//\t\tfmt.Sprintf(\"https://%s.auth0.com/oauth/token\", provider.Domain),\n//\t\treqDataBuf,\n//\t)\n//\tif err != nil {\n//\t\terr = &errortypes.RequestError{\n//\t\t\terrors.Wrap(err, \"auth: Failed to create auth0 request\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\treq.Header.Add(\"Content-Type\", \"application/json\")\n//\n//\tresp, err := client.Do(req)\n//\tif err != nil {\n//\t\terr = &errortypes.RequestError{\n//\t\t\terrors.Wrap(err, \"auth: auth0 request failed\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\tdefer resp.Body.Close()\n//\n//\ttokenData := &authZeroTokenData{}\n//\terr = json.NewDecoder(resp.Body).Decode(tokenData)\n//\tif err != nil {\n//\t\terr = &errortypes.ParseError{\n//\t\t\terrors.Wrap(err, \"auth: Failed to parse response\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\taccessToken = tokenData.AccessToken\n//\n//\tobject, err := jose.ParseSigned(tokenData.AccessToken)\n//\tif err != nil {\n//\t\terr = &errortypes.ParseError{\n//\t\t\terrors.Wrap(err, \"auth: Failed to parse jwt data\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\tjwt, err := authZeroGetJwk(provider)\n//\tif err != nil {\n//\t\treturn\n//\t}\n//\n//\tdata, err := object.Verify(jwt)\n//\tif err != nil {\n//\t\terr = &errortypes.ParseError{\n//\t\t\terrors.Wrap(err, \"auth: Failed to verify jwt data\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\ttoken = &authZeroToken{}\n//\terr = json.Unmarshal(data, token)\n//\tif err != nil {\n//\t\terr = &errortypes.ParseError{\n//\t\t\terrors.Wrap(err, \"auth: Failed to parse jwt token\"),\n//\t\t}\n//\t\treturn\n//\t}\n//\n//\treturn\n//}\n\nfunc authZeroGetToken(provider *settings.Provider) (token string, err error) {\n\treqData := &authZeroTokenReq{\n\t\tGrantType:    \"client_credentials\",\n\t\tClientId:     provider.ClientId,\n\t\tClientSecret: provider.ClientSecret,\n\t\tAudience: fmt.Sprintf(\n\t\t\t\"https://%s.auth0.com/api/v2/\", provider.Domain),\n\t}\n\n\treqDataBuf := &bytes.Buffer{}\n\terr = json.NewEncoder(reqDataBuf).Encode(reqData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse request data\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq, err := http.NewRequest(\n\t\t\"POST\",\n\t\tfmt.Sprintf(\"https://%s.auth0.com/oauth/token\", provider.Domain),\n\t\treqDataBuf,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Failed to create auth0 request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: auth0 request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Auth server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttokenData := &authZeroTokenData{}\n\terr = json.NewDecoder(resp.Body).Decode(tokenData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse response\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttoken = tokenData.AccessToken\n\n\treturn\n}\n\nfunc AuthZeroRoles(provider *settings.Provider, username string) (\n\troles []string, err error) {\n\n\troles = []string{}\n\n\ttoken, err := authZeroGetToken(provider)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treqUrl, err := url.Parse(fmt.Sprintf(\n\t\t\"https://%s.auth0.com/api/v2/users\",\n\t\tprovider.Domain,\n\t))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse auth0 url\"),\n\t\t}\n\t\treturn\n\t}\n\n\tquery := reqUrl.Query()\n\tquery.Set(\"search_engine\", \"v3\")\n\tquery.Set(\"email\", username)\n\treqUrl.RawQuery = query.Encode()\n\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Failed to create auth0 request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: auth0 request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Auth server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata := []*authZeroUser{}\n\terr = json.NewDecoder(resp.Body).Decode(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse response\"),\n\t\t}\n\t\treturn\n\t}\n\n\tuserId := \"\"\n\n\tfor _, usr := range data {\n\t\tif usr.Email != username {\n\t\t\tcontinue\n\t\t}\n\n\t\tuserId = usr.UserId\n\n\t\tif usr.AppMetadata.Authorization.Roles != nil {\n\t\t\troles = usr.AppMetadata.Authorization.Roles\n\t\t}\n\n\t\tbreak\n\t}\n\n\tif userId == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"auth: Failed to find auth0 user\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc AuthZeroSync(db *database.Database, usr *user.User,\n\tprovider *settings.Provider) (active bool, err error) {\n\n\ttoken, err := authZeroGetToken(provider)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treqUrl, err := url.Parse(fmt.Sprintf(\n\t\t\"https://%s.auth0.com/api/v2/users\",\n\t\tprovider.Domain,\n\t))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse auth0 url\"),\n\t\t}\n\t\treturn\n\t}\n\n\tquery := reqUrl.Query()\n\tquery.Set(\"search_engine\", \"v3\")\n\tquery.Set(\"email\", usr.Username)\n\treqUrl.RawQuery = query.Encode()\n\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Failed to create auth0 request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: auth0 request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Auth server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata := []*authZeroUser{}\n\terr = json.NewDecoder(resp.Body).Decode(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse response\"),\n\t\t}\n\t\treturn\n\t}\n\n\tuserId := \"\"\n\n\tfor _, authUser := range data {\n\t\tif authUser.Email != usr.Username {\n\t\t\tcontinue\n\t\t}\n\n\t\tuserId = authUser.UserId\n\n\t\tbreak\n\t}\n\n\tif userId == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"auth: Failed to find auth0 user\"),\n\t\t}\n\t\treturn\n\t}\n\n\tactive = true\n\n\treturn\n}\n"
  },
  {
    "path": "auth/azure.go",
    "content": "package auth\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nconst (\n\tAzure = \"azure\"\n)\n\nfunc azureGetUrls(provider *settings.Provider) (loginUrl, graphUrl string) {\n\tswitch provider.Region {\n\tcase \"us-gov\", \"us-gov2\":\n\t\tloginUrl = \"https://login.microsoftonline.us\"\n\t\tgraphUrl = \"https://graph.microsoft.us\"\n\tcase \"us-dod\", \"us-dod2\":\n\t\tloginUrl = \"https://login.microsoftonline.us\"\n\t\tgraphUrl = \"https://dod-graph.microsoft.us\"\n\tcase \"china\", \"china2\":\n\t\tloginUrl = \"https://login.partner.microsoftonline.cn\"\n\t\tgraphUrl = \"https://microsoftgraph.chinacloudapi.cn\"\n\tdefault:\n\t\tloginUrl = \"https://login.microsoftonline.com\"\n\t\tgraphUrl = \"https://graph.microsoft.com\"\n\t}\n\treturn\n}\n\nfunc AzureRequest(db *database.Database, location, query string,\n\tprovider *settings.Provider) (redirect string, err error) {\n\n\tcoll := db.Tokens()\n\n\tstate, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsecret, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(struct {\n\t\tLicense     string `json:\"license\"`\n\t\tCallback    string `json:\"callback\"`\n\t\tState       string `json:\"state\"`\n\t\tSecret      string `json:\"secret\"`\n\t\tRegion      string `json:\"region\"`\n\t\tDirectoryId string `json:\"directory_id\"`\n\t\tAppId       string `json:\"app_id\"`\n\t\tAppSecret   string `json:\"app_secret\"`\n\t}{\n\t\tLicense:     settings.System.License,\n\t\tCallback:    location + \"/auth/callback\",\n\t\tState:       state,\n\t\tSecret:      secret,\n\t\tRegion:      provider.Region,\n\t\tDirectoryId: provider.Tenant,\n\t\tAppId:       provider.ClientId,\n\t\tAppSecret:   provider.ClientSecret,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq, err := http.NewRequest(\n\t\t\"POST\",\n\t\tsettings.Auth.Server+\"/v1/request/azure\",\n\t\tbytes.NewBuffer(data),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Azure server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tathData := &authData{}\n\terr = json.NewDecoder(resp.Body).Decode(athData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"auth: Failed to parse auth response\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\ttokn := &Token{\n\t\tId:        state,\n\t\tType:      Azure,\n\t\tSecret:    secret,\n\t\tTimestamp: time.Now(),\n\t\tProvider:  provider.Id,\n\t\tQuery:     query,\n\t}\n\n\t_, err = coll.InsertOne(db, tokn)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tredirect = athData.Url\n\n\treturn\n}\n\ntype azureTokenData struct {\n\tAccessToken string `json:\"access_token\"`\n\tResource    string `json:\"resource\"`\n\tTokenType   string `json:\"token_type\"`\n}\n\ntype azureMemberData struct {\n\tNextLink string           `json:\"@odata.nextLink\"`\n\tValue    []azureGroupData `json:\"value\"`\n}\n\ntype azureUserData struct {\n\tId                string `json:\"id\"`\n\tUserPrincipalName string `json:\"userPrincipalName\"`\n\tAccountEnabled    bool   `json:\"accountEnabled\"`\n}\n\ntype azureGroupData struct {\n\tDisplayName string `json:\"displayName\"`\n}\n\nfunc azureGetToken(provider *settings.Provider) (token string, err error) {\n\tloginUrl, graphUrl := azureGetUrls(provider)\n\n\treqForm := url.Values{}\n\treqForm.Add(\"grant_type\", \"client_credentials\")\n\treqForm.Add(\"client_id\", provider.ClientId)\n\treqForm.Add(\"client_secret\", provider.ClientSecret)\n\treqForm.Add(\"resource\", graphUrl)\n\n\treq, err := http.NewRequest(\n\t\t\"POST\",\n\t\tfmt.Sprintf(\n\t\t\t\"%s/%s/oauth2/token\",\n\t\t\tloginUrl,\n\t\t\tprovider.Tenant,\n\t\t),\n\t\tstrings.NewReader(reqForm.Encode()),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Failed to create azure request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Azure request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Azure server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttokenData := &azureTokenData{}\n\terr = json.NewDecoder(resp.Body).Decode(tokenData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse response\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttoken = tokenData.AccessToken\n\n\treturn\n}\n\nfunc AzureRoles(provider *settings.Provider, username string) (\n\troles []string, err error) {\n\n\t_, graphUrl := azureGetUrls(provider)\n\n\tuserId, active, err := AzureSync(provider, username)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !active {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Azure sync user disabled\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif userId == \"\" {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Azure sync missing user ID\"),\n\t\t}\n\t\treturn\n\t}\n\n\troles = []string{}\n\n\ttoken, err := azureGetToken(provider)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treqUrlStr := fmt.Sprintf(\n\t\t\"%s/v1.0/users/%s/memberOf\",\n\t\tgraphUrl,\n\t\tuserId,\n\t)\n\tstart := time.Now()\n\n\tfor {\n\t\treqUrl, e := url.Parse(reqUrlStr)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"auth: Failed to parse azure url\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treqData, e := json.Marshal(struct {\n\t\t\tSecurityEnabledOnly string `json:\"securityEnabledOnly\"`\n\t\t}{\n\t\t\tSecurityEnabledOnly: \"false\",\n\t\t})\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\treq, e := http.NewRequest(\n\t\t\t\"GET\",\n\t\t\treqUrl.String(),\n\t\t\tbytes.NewBuffer(reqData),\n\t\t)\n\t\tif e != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(e, \"auth: Failed to create azure request\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t\tresp, e := client.Do(req)\n\t\tif e != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(e, \"auth: Azure request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\terr = utils.CheckRequest(resp, \"auth: Azure server error\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tdata := &azureMemberData{}\n\t\terr = json.NewDecoder(resp.Body).Decode(data)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"auth: Failed to parse response\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfor _, groupData := range data.Value {\n\t\t\tgroupName := groupData.DisplayName\n\t\t\tif groupName == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\troles = append(roles, groupName)\n\t\t}\n\n\t\tif data.NextLink != \"\" {\n\t\t\treqUrlStr = data.NextLink\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\n\t\tif time.Since(start) > 45*time.Second {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.New(\"auth: Azure group paging timeout\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc AzureSync(provider *settings.Provider, username string) (\n\tuserId string, active bool, err error) {\n\n\t_, graphUrl := azureGetUrls(provider)\n\n\ttoken, err := azureGetToken(provider)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treqUrl, err := url.Parse(fmt.Sprintf(\n\t\t\"%s/v1.0/%s/users/%s\",\n\t\tgraphUrl,\n\t\tprovider.Tenant,\n\t\turl.QueryEscape(username),\n\t))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse azure url\"),\n\t\t}\n\t\treturn\n\t}\n\n\tquery := reqUrl.Query()\n\tquery.Set(\"$select\", \"id,userPrincipalName,accountEnabled\")\n\treqUrl.RawQuery = query.Encode()\n\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Failed to create azure request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Azure request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Azure server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata := &azureUserData{}\n\terr = json.NewDecoder(resp.Body).Decode(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse response\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif strings.ToLower(username) != strings.ToLower(\n\t\tdata.UserPrincipalName) {\n\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrapf(\n\t\t\t\terr,\n\t\t\t\t\"auth: Azure principal name '%s' does not match user '%s'\",\n\t\t\t\tdata.UserPrincipalName, username,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tuserId = data.Id\n\tactive = data.AccountEnabled\n\n\treturn\n}\n"
  },
  {
    "path": "auth/constants.go",
    "content": "package auth\n\nconst (\n\tAdmin = \"admin\"\n\tUser  = \"user\"\n)\n"
  },
  {
    "path": "auth/errortypes.go",
    "content": "package auth\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype InvalidState struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "auth/google.go",
    "content": "package auth\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n\tadmin \"google.golang.org/api/admin/directory/v1\"\n)\n\nconst (\n\tGoogle = \"google\"\n)\n\nfunc GoogleRequest(db *database.Database, location, query string) (\n\tredirect string, err error) {\n\n\tcoll := db.Tokens()\n\n\tstate, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsecret, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(struct {\n\t\tLicense  string `json:\"license\"`\n\t\tCallback string `json:\"callback\"`\n\t\tState    string `json:\"state\"`\n\t\tSecret   string `json:\"secret\"`\n\t}{\n\t\tLicense:  settings.System.License,\n\t\tCallback: location + \"/auth/callback\",\n\t\tState:    state,\n\t\tSecret:   secret,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq, err := http.NewRequest(\n\t\t\"POST\",\n\t\tsettings.Auth.Server+\"/v1/request/google\",\n\t\tbytes.NewBuffer(data),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Auth server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tauthData := &authData{}\n\terr = json.NewDecoder(resp.Body).Decode(authData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"auth: Failed to parse auth response\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\ttokn := &Token{\n\t\tId:        state,\n\t\tType:      Google,\n\t\tSecret:    secret,\n\t\tTimestamp: time.Now(),\n\t\tQuery:     query,\n\t}\n\n\t_, err = coll.InsertOne(db, tokn)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tredirect = authData.Url\n\n\treturn\n}\n\nfunc GoogleRoles(provider *settings.Provider, username string) (\n\troles []string, err error) {\n\n\troles = []string{}\n\n\tif provider.GoogleKey == \"\" && provider.GoogleEmail == \"\" {\n\t\treturn\n\t}\n\n\tconf, err := google.JWTConfigFromJSON(\n\t\t[]byte(provider.GoogleKey),\n\t\t\"https://www.googleapis.com/auth/admin.directory.group.readonly\",\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"auth: Failed to parse google key\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tconf.Subject = provider.GoogleEmail\n\n\tclient := conf.Client(oauth2.NoContext)\n\n\tservice, err := admin.New(client)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"auth: Failed to parse google client\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tresults, err := service.Groups.List().UserKey(username).Do()\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"auth: Google api error getting user groups\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, group := range results.Groups {\n\t\troles = append(roles, group.Name)\n\t}\n\n\treturn\n}\n\nfunc GoogleSync(db *database.Database, usr *user.User) (\n\tactive bool, err error) {\n\n\treqVals := url.Values{}\n\treqVals.Set(\"user\", usr.Username)\n\treqVals.Set(\"license\", settings.System.License)\n\n\treqUrl, _ := url.Parse(settings.Auth.Server + \"/update/google\")\n\treqUrl.RawQuery = reqVals.Encode()\n\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Google request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Google request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode == 200 {\n\t\tactive = true\n\t} else {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\"auth: Google request bad status %d\",\n\t\t\t\tresp.StatusCode),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "auth/handler.go",
    "content": "package auth\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha512\"\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Local(db *database.Database, username, password string) (\n\tusr *user.User, errData *errortypes.ErrorData, err error) {\n\n\tusername = strings.ToLower(username)\n\n\tif username == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"auth_invalid\",\n\t\t\tMessage: \"Authentication credentials are invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err = user.GetUsername(db, user.Local, username)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\tusr = nil\n\t\t\terr = nil\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"auth_invalid\",\n\t\t\t\tMessage: \"Authentication credentials are invalid\",\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\treturn\n\t}\n\n\tvalid := usr.CheckPassword(password)\n\tif !valid {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"auth_invalid\",\n\t\t\tMessage: \"Authentication credentials are invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Request(c *gin.Context, typ string) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tdomains := []string{}\n\n\tswitch typ {\n\tcase Admin:\n\t\tdomains = append(domains, node.Self.AdminDomain)\n\tcase User:\n\t\tdomains = append(domains, node.Self.UserDomain)\n\t}\n\n\tloc := utils.GetLocation(c.Request, domains)\n\tif loc == \"\" {\n\t\terr := &errortypes.ParseError{\n\t\t\terrors.New(\"auth: Missing domains in node settings\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tid := c.Query(\"id\")\n\n\tvals := c.Request.URL.Query()\n\tvals.Del(\"id\")\n\tquery := vals.Encode()\n\n\tif id == Google {\n\t\tredirect, err := GoogleRequest(db, loc, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.Redirect(302, redirect)\n\t\treturn\n\t} else {\n\t\tproviderId, err := bson.ObjectIDFromHex(id)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"auth: ObjectId parse error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tvar provider *settings.Provider\n\t\tfor _, prvidr := range settings.Auth.Providers {\n\t\t\tif prvidr.Id == providerId {\n\t\t\t\tprovider = prvidr\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif provider == nil {\n\t\t\tutils.AbortWithStatus(c, 404)\n\t\t\treturn\n\t\t}\n\n\t\tswitch provider.Type {\n\t\tcase Azure:\n\t\t\tredirect, err := AzureRequest(db, loc, query, provider)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tc.Redirect(302, redirect)\n\t\t\treturn\n\t\tcase AuthZero:\n\t\t\tredirect, err := AuthZeroRequest(db, loc, query, provider)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tc.Redirect(302, redirect)\n\t\t\treturn\n\t\tcase OneLogin, Okta, JumpCloud:\n\t\t\tbody, err := SamlRequest(db, loc, query, provider)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tc.Data(200, \"text/html;charset=utf-8\", body)\n\t\t\treturn\n\t\t}\n\t}\n\n\tutils.AbortWithStatus(c, 404)\n}\n\nfunc Callback(db *database.Database, sig, query string) (\n\tusr *user.User, tokn *Token, errAudit audit.Fields,\n\terrData *errortypes.ErrorData, err error) {\n\n\tparams, err := url.ParseQuery(query)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse query\"),\n\t\t}\n\t\treturn\n\t}\n\n\tstate := params.Get(\"state\")\n\n\ttokn, err = Get(db, state)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = &InvalidState{\n\t\t\t\terrors.Wrap(err, \"auth: Invalid state\"),\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\treturn\n\t}\n\n\tif tokn.Secret == \"\" {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"auth: Empty secret\"),\n\t\t}\n\t\treturn\n\t}\n\n\thashFunc := hmac.New(sha512.New, []byte(tokn.Secret))\n\thashFunc.Write([]byte(query))\n\trawSignature := hashFunc.Sum(nil)\n\ttestSig := base64.URLEncoding.EncodeToString(rawSignature)\n\n\tif subtle.ConstantTimeCompare([]byte(sig), []byte(testSig)) != 1 {\n\t\terrAudit = audit.Fields{\n\t\t\t\"error\":   \"signature_mismatch\",\n\t\t\t\"message\": \"Signature hash does not match\",\n\t\t}\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"authentication_error\",\n\t\t\tMessage: \"Authentication error occurred\",\n\t\t}\n\t\treturn\n\t}\n\n\tusername := strings.ToLower(params.Get(\"username\"))\n\n\tif username == \"\" {\n\t\terrAudit = audit.Fields{\n\t\t\t\"error\":   \"invalid_username\",\n\t\t\t\"message\": \"Invalid username\",\n\t\t}\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_username\",\n\t\t\tMessage: \"Invalid username\",\n\t\t}\n\t\treturn\n\t}\n\n\tvar provider *settings.Provider\n\tif tokn.Type == Google {\n\t\tdomainSpl := strings.SplitN(username, \"@\", 2)\n\t\tif len(domainSpl) == 2 {\n\t\t\tdomain := domainSpl[1]\n\t\t\tif domain != \"\" {\n\t\t\t\tfor _, prv := range settings.Auth.Providers {\n\t\t\t\t\tif prv.Type == Google && prv.Domain == domain {\n\t\t\t\t\t\tprovider = prv\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif provider == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   \"provider_unavailable\",\n\t\t\t\t\"message\": \"Google provider is unavailable\",\n\t\t\t}\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"unauthorized\",\n\t\t\t\tMessage: \"Not authorized\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tprovider = settings.Auth.GetProvider(tokn.Provider)\n\t\tif provider == nil {\n\t\t\terr = &errortypes.NotFoundError{\n\t\t\t\terrors.New(\"auth: Auth provider not found\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tif provider.Type == Azure {\n\t\tusernameSpl := strings.SplitN(username, \"/\", 2)\n\t\tif len(usernameSpl) != 2 {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   \"invalid_username\",\n\t\t\t\t\"message\": \"Azure username missing tenant\",\n\t\t\t}\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_username\",\n\t\t\t\tMessage: \"Invalid username\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\ttenant := usernameSpl[0]\n\t\tusername = usernameSpl[1]\n\n\t\tif tenant != provider.Tenant {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   \"invalid_tenant\",\n\t\t\t\t\"message\": \"Azure tenant mismatch\",\n\t\t\t}\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_tenant\",\n\t\t\t\tMessage: \"Invalid tenant\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = tokn.Remove(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\troles := []string{}\n\troles = append(roles, provider.DefaultRoles...)\n\n\troleParam := params.Get(\"roles\")\n\tif roleParam == \"\" {\n\t\troleParam = params.Get(\"groups\")\n\t}\n\n\tsplitChar := \",\"\n\tif strings.Contains(roleParam, \";\") {\n\t\tsplitChar = \";\"\n\t}\n\n\tfor _, role := range strings.Split(roleParam, splitChar) {\n\t\tif role != \"\" {\n\t\t\troles = append(roles, role)\n\t\t}\n\t}\n\n\tswitch provider.Type {\n\tcase Google:\n\t\tgoogleRoles, e := GoogleRoles(provider, username)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, role := range googleRoles {\n\t\t\troles = append(roles, role)\n\t\t}\n\t\tbreak\n\tcase AuthZero:\n\t\tauthZeroRoles, e := AuthZeroRoles(provider, username)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, role := range authZeroRoles {\n\t\t\troles = append(roles, role)\n\t\t}\n\t\tbreak\n\tcase Azure:\n\t\tazureRoles, e := AzureRoles(provider, username)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, role := range azureRoles {\n\t\t\troles = append(roles, role)\n\t\t}\n\t\tbreak\n\t}\n\n\tusr, err = user.GetUsername(db, provider.Type, username)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\tusr = nil\n\t\t\terr = nil\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\tif usr == nil {\n\t\tif provider.AutoCreate {\n\t\t\tusr = &user.User{\n\t\t\t\tType:     provider.Type,\n\t\t\t\tUsername: username,\n\t\t\t\tProvider: provider.Id,\n\t\t\t\tRoles:    roles,\n\t\t\t}\n\n\t\t\terr = usr.Upsert(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tevent.PublishDispatch(db, \"user.change\")\n\n\t\t\terrData, err = usr.Validate(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif errData != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   \"user_unavailable\",\n\t\t\t\t\"message\": \"User does not exist with auto create false\",\n\t\t\t}\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"user_unavailable\",\n\t\t\t\tMessage: \"Not authorized\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tfields := set.NewSet(\"provider\")\n\t\tusr.Provider = provider.Id\n\n\t\tswitch provider.RoleManagement {\n\t\tcase settings.Merge:\n\t\t\tchanged := usr.RolesMerge(roles)\n\t\t\tif changed {\n\t\t\t\terrData, err = usr.Validate(db)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif errData != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tfields.Add(\"roles\")\n\t\t\t}\n\t\t\tbreak\n\t\tcase settings.Overwrite:\n\t\t\tchanged := usr.RolesOverwrite(roles)\n\t\t\tif changed {\n\t\t\t\terrData, err = usr.Validate(db)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif errData != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tfields.Add(\"roles\")\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\terr = usr.CommitFields(db, fields)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"user.change\")\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "auth/jumpcloud.go",
    "content": "package auth\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tJumpCloud = \"jumpcloud\"\n)\n\ntype jumpcloudResponse struct {\n\tResults    []*jumpcloudUser `json:\"results\"`\n\tTotalCount int              `json:\"totalCount\"`\n}\n\ntype jumpcloudUser struct {\n\tId            string `json:\"id\"`\n\tIdAlt         string `json:\"_id\"`\n\tEmail         string `json:\"email\"`\n\tAccountLocked bool   `json:\"account_locked\"`\n\tSuspended     bool   `json:\"suspended\"`\n\tActivated     bool   `json:\"activated\"`\n}\n\ntype jumpcloudApp struct {\n\tId string `json:\"id\"`\n}\n\nfunc jumpcloudCheckApp(provider *settings.Provider, userId string) (\n\tattached bool, err error) {\n\n\treqUrl := &url.URL{\n\t\tScheme: \"https\",\n\t\tHost:   \"console.jumpcloud.com\",\n\t\tPath:   fmt.Sprintf(\"/api/v2/users/%s/applications\", userId),\n\t}\n\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Failed to create jumpcloud request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"X-Api-Key\", provider.JumpCloudSecret)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Jumpcloud request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Auth server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata := []*jumpcloudApp{}\n\terr = json.NewDecoder(resp.Body).Decode(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse jumpcloud response\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, app := range data {\n\t\tif app.Id == provider.JumpCloudAppId {\n\t\t\tattached = true\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc JumpcloudSync(db *database.Database, usr *user.User,\n\tprovider *settings.Provider) (active bool, err error) {\n\n\treqUrl := &url.URL{\n\t\tScheme: \"https\",\n\t\tHost:   \"console.jumpcloud.com\",\n\t\tPath:   \"/api/systemusers\",\n\t}\n\n\tquery := reqUrl.Query()\n\tquery.Set(\"filter\", fmt.Sprintf(\"email:$eq:%s\", usr.Username))\n\n\treqUrl.RawQuery = query.Encode()\n\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Failed to create jumpcloud request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"X-Api-Key\", provider.JumpCloudSecret)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Jumpcloud request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Auth server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata := &jumpcloudResponse{}\n\terr = json.NewDecoder(resp.Body).Decode(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"auth: Failed to parse jumpcloud response\"),\n\t\t}\n\t\treturn\n\t}\n\n\tuserId := \"\"\n\tif data.TotalCount > 0 && data.Results != nil {\n\t\tfor _, authUser := range data.Results {\n\t\t\tif authUser.Email != usr.Username {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif authUser.AccountLocked || authUser.Suspended ||\n\t\t\t\t!authUser.Activated {\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"user_id\":  usr.Id.Hex(),\n\t\t\t\t\t\"username\": usr.Username,\n\t\t\t\t}).Info(\"auth: Jumpcloud user disabled\")\n\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tif authUser.Id != \"\" {\n\t\t\t\t\tuserId = authUser.Id\n\t\t\t\t} else {\n\t\t\t\t\tuserId = authUser.IdAlt\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif userId == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"auth: Jumpcloud user not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif provider.JumpCloudAppId != \"\" {\n\t\tattached, e := jumpcloudCheckApp(provider, userId)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif attached {\n\t\t\tactive = true\n\t\t} else {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"user_id\":  usr.Id.Hex(),\n\t\t\t\t\"username\": usr.Username,\n\t\t\t}).Info(\"auth: Jumpcloud user not bound to application\")\n\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tactive = true\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "auth/saml.go",
    "content": "package auth\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nconst (\n\tOneLogin = \"onelogin\"\n\tOkta     = \"okta\"\n)\n\nfunc SamlRequest(db *database.Database, location, query string,\n\tprovider *settings.Provider) (body []byte, err error) {\n\n\tif provider.Type != OneLogin && provider.Type != Okta &&\n\t\tprovider.Type != JumpCloud {\n\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"auth: Invalid provider type\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcoll := db.Tokens()\n\n\tstate, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsecret, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(struct {\n\t\tLicense   string `json:\"license\"`\n\t\tCallback  string `json:\"callback\"`\n\t\tState     string `json:\"state\"`\n\t\tSecret    string `json:\"secret\"`\n\t\tSsoUrl    string `json:\"sso_url\"`\n\t\tIssuerUrl string `json:\"issuer_url\"`\n\t\tCert      string `json:\"cert\"`\n\t}{\n\t\tLicense:   settings.System.License,\n\t\tCallback:  location + \"/auth/callback\",\n\t\tState:     state,\n\t\tSecret:    secret,\n\t\tSsoUrl:    provider.SamlUrl,\n\t\tIssuerUrl: provider.IssuerUrl,\n\t\tCert:      provider.SamlCert,\n\t})\n\n\treq, err := http.NewRequest(\n\t\t\"POST\",\n\t\tsettings.Auth.Server+\"/v1/request/saml\",\n\t\tbytes.NewBuffer(data),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"auth: Auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"auth: Auth server error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tbody, err = ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"auth: Failed to parse auth response\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\ttokn := &Token{\n\t\tId:        state,\n\t\tType:      provider.Type,\n\t\tSecret:    secret,\n\t\tTimestamp: time.Now(),\n\t\tProvider:  provider.Id,\n\t\tQuery:     query,\n\t}\n\n\t_, err = coll.InsertOne(db, tokn)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "auth/state.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\ntype StateProvider struct {\n\tId    interface{} `json:\"id\"`\n\tType  string      `json:\"type\"`\n\tLabel string      `json:\"label\"`\n}\n\ntype StateProviders []*StateProvider\n\nfunc (s StateProviders) Len() int {\n\treturn len(s)\n}\n\nfunc (s StateProviders) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc (s StateProviders) Less(i, j int) bool {\n\treturn s[i].Label < s[j].Label\n}\n\ntype State struct {\n\tProviders StateProviders `json:\"providers\"`\n}\n\nfunc GetState() (state *State) {\n\tstate = &State{\n\t\tProviders: StateProviders{},\n\t}\n\n\tif !settings.Local.NoLocalAuth {\n\t\tprv := &StateProvider{\n\t\t\tType: \"local\",\n\t\t}\n\n\t\tstate.Providers = append(state.Providers, prv)\n\t}\n\n\tgoogle := false\n\n\tfor _, provider := range settings.Auth.Providers {\n\t\tprv := &StateProvider{\n\t\t\tType:  provider.Type,\n\t\t\tLabel: provider.Label,\n\t\t}\n\n\t\tif provider.Type == Google {\n\t\t\tif google {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgoogle = true\n\t\t\tprv.Id = Google\n\t\t} else {\n\t\t\tprv.Id = provider.Id\n\t\t}\n\n\t\tstate.Providers = append(state.Providers, prv)\n\t}\n\n\tsort.Sort(state.Providers)\n\n\treturn\n}\n\nfunc GetFastAdminPath() (path string) {\n\tif !settings.Local.NoLocalAuth || !settings.Auth.FastLogin ||\n\t\tlen(settings.Auth.Providers) == 0 {\n\n\t\treturn\n\t}\n\n\tif len(settings.Auth.Providers) > 1 {\n\t\tgoogleOnly := true\n\t\tfor _, provider := range settings.Auth.Providers {\n\t\t\tif provider.Type != Google {\n\t\t\t\tgoogleOnly = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !googleOnly {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, provider := range settings.Auth.Providers {\n\t\tif provider.Type == Google {\n\t\t\tpath = fmt.Sprintf(\"/auth/request?id=%s\", Google)\n\t\t} else {\n\t\t\tpath = fmt.Sprintf(\"/auth/request?id=%s\", provider.Id.Hex())\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetFastUserPath() (path string) {\n\tif settings.Auth.FastLogin && settings.Auth.ForceFastUserLogin &&\n\t\tlen(settings.Auth.Providers) != 0 {\n\t} else if !settings.Local.NoLocalAuth || !settings.Auth.FastLogin ||\n\t\tlen(settings.Auth.Providers) == 0 {\n\n\t\treturn\n\t}\n\n\tif len(settings.Auth.Providers) > 1 {\n\t\tgoogleOnly := true\n\t\tfor _, provider := range settings.Auth.Providers {\n\t\t\tif provider.Type != Google {\n\t\t\t\tgoogleOnly = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !googleOnly {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, provider := range settings.Auth.Providers {\n\t\tif provider.Type == Google {\n\t\t\tpath = fmt.Sprintf(\"/auth/request?id=%s\", Google)\n\t\t} else {\n\t\t\tpath = fmt.Sprintf(\"/auth/request?id=%s\", provider.Id.Hex())\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "auth/sync.go",
    "content": "package auth\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n)\n\nfunc SyncUser(db *database.Database, usr *user.User) (\n\tactive bool, err error) {\n\n\tif time.Since(usr.LastSync) < time.Duration(\n\t\tsettings.Auth.Sync)*time.Second {\n\n\t\tactive = true\n\t\treturn\n\t}\n\n\tprovider := settings.Auth.GetProvider(usr.Provider)\n\n\tif usr.Type == user.AuthZero && provider != nil &&\n\t\tprovider.Type == user.AuthZero {\n\n\t\tactive, err = AuthZeroSync(db, usr, provider)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else if usr.Type == user.Azure && provider != nil &&\n\t\tprovider.Type == user.Azure {\n\n\t\t_, active, err = AzureSync(provider, usr.Username)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else if usr.Type == user.Google {\n\t\tactive, err = GoogleSync(db, usr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else if usr.Type == user.JumpCloud {\n\t\tactive, err = JumpcloudSync(db, usr, provider)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tactive = true\n\t}\n\n\tif active {\n\t\tusr.LastSync = time.Now()\n\t\terr = usr.CommitFields(db, set.NewSet(\"last_sync\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "auth/utils.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/pritunl/pritunl-cloud/cookie\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/session\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, state string) (tokn *Token, err error) {\n\tcoll := db.Tokens()\n\ttokn = &Token{}\n\n\terr = coll.FindOneId(state, tokn)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc CookieSessionAdmin(db *database.Database,\n\tw http.ResponseWriter, r *http.Request) (\n\tcook *cookie.Cookie, sess *session.Session, err error) {\n\n\tcook, err = cookie.GetAdmin(w, r)\n\tif err != nil {\n\t\tsess = nil\n\t\terr = nil\n\t\treturn\n\t}\n\n\tsess, err = cook.GetSession(db, r, session.Admin)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *errortypes.NotFoundError:\n\t\t\tsess = nil\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc CookieSessionUser(db *database.Database, w http.ResponseWriter,\n\tr *http.Request) (cook *cookie.Cookie, sess *session.Session, err error) {\n\n\tcook, err = cookie.GetUser(w, r)\n\tif err != nil {\n\t\tsess = nil\n\t\terr = nil\n\t\treturn\n\t}\n\n\tsess, err = cook.GetSession(db, r, session.User)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *errortypes.NotFoundError:\n\t\t\tsess = nil\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc CsrfCheck(w http.ResponseWriter, r *http.Request, domain string) bool {\n\tport := \"\"\n\tif node.Self.Protocol == \"http\" {\n\t\tif node.Self.Port != 80 {\n\t\t\tport += fmt.Sprintf(\":%d\", node.Self.Port)\n\t\t}\n\t} else {\n\t\tif node.Self.Port != 443 {\n\t\t\tport += fmt.Sprintf(\":%d\", node.Self.Port)\n\t\t}\n\t}\n\tmatch := fmt.Sprintf(\"http://%s%s\", domain, port)\n\tmatchSec := fmt.Sprintf(\"https://%s%s\", domain, port)\n\n\torigin := r.Header.Get(\"Origin\")\n\tif origin != \"\" {\n\t\tu, err := url.Parse(origin)\n\t\tif err != nil {\n\t\t\tutils.WriteUnauthorized(w, \"CSRF origin invalid\")\n\t\t\treturn false\n\t\t}\n\t\torigin = fmt.Sprintf(\"%s://%s\", u.Scheme, u.Host)\n\t}\n\n\tif origin != \"\" && origin != match && origin != matchSec {\n\t\tutils.WriteUnauthorized(w, \"CSRF origin error\")\n\t\treturn false\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "authority/authority.go",
    "content": "package authority\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Authority struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tComment      string        `bson:\"comment\" json:\"comment\"`\n\tType         string        `bson:\"type\" json:\"type\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tRoles        []string      `bson:\"roles\" json:\"roles\"`\n\tKey          string        `bson:\"key\" json:\"key\"`\n\tPrincipals   []string      `bson:\"principals\" json:\"principals\"`\n\tCertificate  string        `bson:\"certificate\" json:\"certificate\"`\n}\n\nfunc (f *Authority) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tf.Name = utils.FilterName(f.Name)\n\n\tif f.Roles == nil {\n\t\tf.Roles = []string{}\n\t}\n\n\tif f.Principals == nil {\n\t\tf.Principals = []string{}\n\t}\n\n\tif f.Type == \"\" {\n\t\tf.Type = SshKey\n\t}\n\n\tswitch f.Type {\n\tcase SshKey:\n\t\tf.Principals = []string{}\n\t\tf.Certificate = \"\"\n\t\tbreak\n\tcase SshCertificate:\n\t\tf.Key = \"\"\n\t\tbreak\n\t}\n\n\treturn\n}\n\nfunc (f *Authority) Commit(db *database.Database) (err error) {\n\tcoll := db.Authorities()\n\n\terr = coll.Commit(f.Id, f)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (f *Authority) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Authorities()\n\n\terr = coll.CommitFields(f.Id, f, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (f *Authority) Insert(db *database.Database) (err error) {\n\tcoll := db.Authorities()\n\n\tif !f.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"authority: Authority already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := coll.InsertOne(db, f)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tf.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  },
  {
    "path": "authority/constants.go",
    "content": "package authority\n\nimport \"github.com/pritunl/mongo-go-driver/v2/bson\"\n\nconst (\n\tSshKey         = \"ssh_key\"\n\tSshCertificate = \"ssh_certificate\"\n)\n\nvar (\n\tGlobal = bson.NilObjectID\n)\n"
  },
  {
    "path": "authority/utils.go",
    "content": "package authority\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, authrId bson.ObjectID) (\n\tauthr *Authority, err error) {\n\n\tcoll := db.Authorities()\n\tauthr = &Authority{}\n\n\terr = coll.FindOneId(authrId, authr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, authrId bson.ObjectID) (\n\tauthr *Authority, err error) {\n\n\tcoll := db.Authorities()\n\tauthr = &Authority{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          authrId,\n\t\t\"organization\": orgId,\n\t}).Decode(authr)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tauthrs []*Authority, err error) {\n\n\tcoll := db.Authorities()\n\tauthrs = []*Authority{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tauthr := &Authority{}\n\t\terr = cursor.Decode(authr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tauthrs = append(authrs, authr)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetRoles(db *database.Database, roles []string) (\n\tauthrs []*Authority, err error) {\n\n\tcoll := db.Authorities()\n\tauthrs = []*Authority{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"organization\": Global,\n\t\t\"roles\": &bson.M{\n\t\t\t\"$in\": roles,\n\t\t},\n\t}, options.Find().SetSort(&bson.D{\n\t\t{\"_id\", 1},\n\t}))\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tauthr := &Authority{}\n\t\terr = cursor.Decode(authr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tauthrs = append(authrs, authr)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetMapRoles(db *database.Database, query *bson.M) (\n\tauthrs map[string][]*Authority, err error) {\n\n\tcoll := db.Authorities()\n\tauthrs = map[string][]*Authority{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tauthr := &Authority{}\n\t\terr = cursor.Decode(authr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, role := range authr.Roles {\n\t\t\troleAuthrs := authrs[role]\n\t\t\tif roleAuthrs == nil {\n\t\t\t\troleAuthrs = []*Authority{}\n\t\t\t}\n\t\t\tauthrs[role] = append(roleAuthrs, authr)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrgMapRoles(db *database.Database, orgId bson.ObjectID) (\n\tauthrs map[string][]*Authority, err error) {\n\n\tcoll := db.Authorities()\n\tauthrs = map[string][]*Authority{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tauthr := &Authority{}\n\t\terr = cursor.Decode(authr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, role := range authr.Roles {\n\t\t\troleAuthrs := authrs[role]\n\t\t\tif roleAuthrs == nil {\n\t\t\t\troleAuthrs = []*Authority{}\n\t\t\t}\n\t\t\tauthrs[role] = append(roleAuthrs, authr)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrgRoles(db *database.Database, orgId bson.ObjectID,\n\troles []string) (authrs []*Authority, err error) {\n\n\tcoll := db.Authorities()\n\tauthrs = []*Authority{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"organization\": orgId,\n\t\t\"roles\": &bson.M{\n\t\t\t\"$in\": roles,\n\t\t},\n\t}, options.Find().SetSort(&bson.D{\n\t\t{\"_id\", 1},\n\t}))\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tauthr := &Authority{}\n\t\terr = cursor.Decode(authr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tauthrs = append(authrs, authr)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNames(db *database.Database, query *bson.M) (\n\tauthrs []*database.Named, err error) {\n\n\tcoll := db.Authorities()\n\tauthrs = []*database.Named{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetProjection(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tauthr := &database.Named{}\n\t\terr = cursor.Decode(authr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tauthrs = append(authrs, authr)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (authrs []*Authority, count int64, err error) {\n\n\tcoll := db.Authorities()\n\tauthrs = []*Authority{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tauthr := &Authority{}\n\t\terr = cursor.Decode(authr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tauthrs = append(authrs, authr)\n\t\tauthr = &Authority{}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, authrId bson.ObjectID) (err error) {\n\tcoll := db.Authorities()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": authrId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, authrId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Authorities()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          authrId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database,\n\tauthrIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Authorities()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": authrIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tauthrIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Authorities()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": authrIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "authorizer/authorizer.go",
    "content": "package authorizer\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/pritunl/pritunl-cloud/cookie\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/session\"\n\t\"github.com/pritunl/pritunl-cloud/signature\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n)\n\ntype Authorizer struct {\n\ttyp  string\n\tcook *cookie.Cookie\n\tsess *session.Session\n\tsig  *signature.Signature\n\tusr  *user.User\n}\n\nfunc (a *Authorizer) IsApi() bool {\n\treturn a.sig != nil\n}\n\nfunc (a *Authorizer) IsValid() bool {\n\treturn a.sess != nil || a.sig != nil\n}\n\nfunc (a *Authorizer) AddSignature(db *database.Database,\n\tsig *signature.Signature) (err error) {\n\n\terr = sig.Validate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ta.sig = sig\n\n\treturn\n}\n\nfunc (a *Authorizer) AddCookie(cook *cookie.Cookie,\n\tsess *session.Session) (err error) {\n\n\ta.cook = cook\n\ta.sess = sess\n\n\treturn\n}\n\nfunc (a *Authorizer) Clear(db *database.Database, w http.ResponseWriter,\n\tr *http.Request) (err error) {\n\n\ta.sess = nil\n\ta.sig = nil\n\n\tif a.cook != nil {\n\t\terr = a.cook.Remove(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tswitch a.typ {\n\tcase Admin:\n\t\tcookie.CleanAdmin(w, r)\n\t\tbreak\n\tcase User:\n\t\tcookie.CleanUser(w, r)\n\t\tbreak\n\t}\n\n\treturn\n}\n\nfunc (a *Authorizer) Remove(db *database.Database) error {\n\tif a.sess == nil {\n\t\treturn nil\n\t}\n\n\treturn a.sess.Remove(db)\n}\n\nfunc (a *Authorizer) GetUser(db *database.Database) (\n\tusr *user.User, err error) {\n\n\tif a.sess != nil {\n\t\tif a.usr != nil {\n\t\t\tusr = a.usr\n\t\t\treturn\n\t\t}\n\n\t\tif db != nil {\n\t\t\tusr, err = a.sess.GetUser(db)\n\t\t\tif err != nil {\n\t\t\t\tswitch err.(type) {\n\t\t\t\tcase *database.NotFoundError:\n\t\t\t\t\tusr = nil\n\t\t\t\t\terr = nil\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif usr == nil {\n\t\t\ta.sess = nil\n\t\t} else {\n\t\t\ta.usr = usr\n\t\t}\n\t} else if a.sig != nil {\n\t\tif a.usr != nil {\n\t\t\tusr = a.usr\n\t\t\treturn\n\t\t}\n\n\t\tif db != nil {\n\t\t\tusr, err = a.sig.GetUser(db)\n\t\t\tif err != nil {\n\t\t\t\tswitch err.(type) {\n\t\t\t\tcase *database.NotFoundError:\n\t\t\t\t\tusr = nil\n\t\t\t\t\terr = nil\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif usr == nil {\n\t\t\ta.sig = nil\n\t\t} else {\n\t\t\ta.usr = usr\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (a *Authorizer) GetSession() *session.Session {\n\treturn a.sess\n}\n\nfunc (a *Authorizer) SessionId() string {\n\tif a.sess != nil {\n\t\treturn a.sess.Id\n\t}\n\n\treturn \"\"\n}\n"
  },
  {
    "path": "authorizer/constants.go",
    "content": "package authorizer\n\nconst (\n\tAdmin = \"admin\"\n\tUser  = \"user\"\n)\n"
  },
  {
    "path": "authorizer/utils.go",
    "content": "package authorizer\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/pritunl/pritunl-cloud/auth\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/signature\"\n)\n\nfunc AuthorizeAdmin(db *database.Database, w http.ResponseWriter,\n\tr *http.Request) (authr *Authorizer, err error) {\n\n\tauthr = NewAdmin()\n\n\ttoken := r.Header.Get(\"Auth-Token\")\n\tsigStr := r.Header.Get(\"Auth-Signature\")\n\n\tif token != \"\" && sigStr != \"\" {\n\t\ttimestamp := r.Header.Get(\"Auth-Timestamp\")\n\t\tnonce := r.Header.Get(\"Auth-Nonce\")\n\n\t\tsig, e := signature.Parse(\n\t\t\ttoken,\n\t\t\tsigStr,\n\t\t\ttimestamp,\n\t\t\tnonce,\n\t\t\tr.Method,\n\t\t\tr.URL.Path,\n\t\t)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\terr = authr.AddSignature(db, sig)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcook, sess, e := auth.CookieSessionAdmin(db, w, r)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\terr = authr.AddCookie(cook, sess)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc AuthorizeUser(db *database.Database, w http.ResponseWriter,\n\tr *http.Request) (authr *Authorizer, err error) {\n\n\tauthr = NewUser()\n\n\ttoken := r.Header.Get(\"Auth-Token\")\n\tsigStr := r.Header.Get(\"Auth-Signature\")\n\n\tif token != \"\" && sigStr != \"\" {\n\t\ttimestamp := r.Header.Get(\"Auth-Timestamp\")\n\t\tnonce := r.Header.Get(\"Auth-Nonce\")\n\n\t\tsig, e := signature.Parse(\n\t\t\ttoken,\n\t\t\tsigStr,\n\t\t\ttimestamp,\n\t\t\tnonce,\n\t\t\tr.Method,\n\t\t\tr.URL.Path,\n\t\t)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\terr = authr.AddSignature(db, sig)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcook, sess, e := auth.CookieSessionUser(db, w, r)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\terr = authr.AddCookie(cook, sess)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc NewAdmin() (authr *Authorizer) {\n\tauthr = &Authorizer{\n\t\ttyp: Admin,\n\t}\n\n\treturn\n}\n\nfunc NewUser() (authr *Authorizer) {\n\tauthr = &Authorizer{\n\t\ttyp: User,\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "backup/backup.go",
    "content": "package backup\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/config\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/qmp\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Backup struct {\n\tDestination string\n\tnode        *node.Node\n\tvirtPath    string\n\terrorCount  int\n}\n\nfunc (b *Backup) backupDisk(db *database.Database,\n\tdsk *disk.Disk, dest string) (err error) {\n\n\tonline := false\n\tif !dsk.Instance.IsZero() {\n\t\tinst, e := instance.Get(db, dsk.Instance)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif inst != nil {\n\t\t\tif inst.State == vm.Starting {\n\t\t\t\ttime.Sleep(5 * time.Second)\n\t\t\t\tonline = true\n\t\t\t}\n\n\t\t\tif inst.State == vm.Running {\n\t\t\t\tonline = true\n\t\t\t}\n\t\t}\n\t}\n\n\t_ = os.Remove(dest)\n\n\tif online {\n\t\terr = qmp.BackupDisk(dsk.Instance, dsk, dest)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*qmp.DiskNotFound); ok {\n\t\t\t\tonline = false\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif !online {\n\t\tdskPth := path.Join(b.virtPath, \"disks\",\n\t\t\tfmt.Sprintf(\"%s.qcow2\", dsk.Id.Hex()))\n\t\terr = utils.Exec(\"\", \"cp\", dskPth, dest)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *Backup) backupDisks(db *database.Database) (err error) {\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"node_id\": b.node.Id.Hex(),\n\t}).Info(\"backup: Exporting disks\")\n\n\ttrashDir := path.Join(b.Destination, \"trash\")\n\tdisksDir := path.Join(b.Destination, \"disks\")\n\terr = utils.ExistsMkdir(disksDir, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdisks, err := disk.GetAll(db, &bson.M{\n\t\t\"node\": b.node.Id,\n\t})\n\n\tdiskFilenames := set.NewSet()\n\tfor _, dsk := range disks {\n\t\tfilename := fmt.Sprintf(\"%s.qcow2\", dsk.Id.Hex())\n\t\tdiskFilenames.Add(filename)\n\n\t\tdestPath := path.Join(disksDir, filename)\n\n\t\terr = b.backupDisk(db, dsk, destPath)\n\t\tif err != nil {\n\t\t\tb.errorCount += 1\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t\t\"error\":   err,\n\t\t\t}).Error(\"qemu: Failed to backup disk\")\n\t\t} else {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"node_id\": b.node.Id.Hex(),\n\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t}).Info(\"backup: Disk exported\")\n\t\t}\n\t}\n\n\texportedDisks, err := ioutil.ReadDir(disksDir)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"backup: Failed to read disks directory\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttrashDiskDir := path.Join(trashDir, \"disks\")\n\tfor _, item := range exportedDisks {\n\t\tfilename := item.Name()\n\t\tdiskPath := path.Join(disksDir, filename)\n\t\tnewDiskPath := path.Join(trashDiskDir, filename)\n\t\tidStr := strings.Split(path.Base(filename), \".\")[0]\n\n\t\tif !diskFilenames.Contains(filename) {\n\t\t\terr = utils.ExistsMkdir(trashDiskDir, 0755)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = os.Rename(diskPath, newDiskPath)\n\t\t\tif err != nil {\n\t\t\t\tb.errorCount += 1\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"node_id\": b.node.Id.Hex(),\n\t\t\t\t\t\"disk_id\": idStr,\n\t\t\t\t\t\"error\":   err,\n\t\t\t\t}).Error(\"backup: Failed to move disk to trash\")\n\t\t\t} else {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"node_id\": b.node.Id.Hex(),\n\t\t\t\t\t\"disk_id\": idStr,\n\t\t\t\t}).Info(\"backup: Disk moved to trash\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *Backup) backupBackingDisks(db *database.Database) (err error) {\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"node_id\": b.node.Id.Hex(),\n\t}).Info(\"backup: Exporting backing disks\")\n\n\ttrashDir := path.Join(b.Destination, \"trash\")\n\tbackingDisksDir := path.Join(b.Destination, \"backing\")\n\tcurBackingDisksDir := path.Join(b.virtPath, \"backing\")\n\terr = utils.ExistsMkdir(backingDisksDir, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\texists, err := utils.Exists(curBackingDisksDir)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcurBackingDisks := []os.FileInfo{}\n\tif exists {\n\t\tcurBackingDisks, err = ioutil.ReadDir(curBackingDisksDir)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\terr,\n\t\t\t\t\t\"backup: Failed to read backing disks directory\",\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tbackingFilenames := set.NewSet()\n\tfor _, item := range curBackingDisks {\n\t\tfilename := item.Name()\n\t\tbackingFilenames.Add(filename)\n\n\t\tbackingPath := path.Join(curBackingDisksDir, filename)\n\t\tnewBackingPath := path.Join(backingDisksDir, filename)\n\n\t\t_ = os.Remove(newBackingPath)\n\n\t\terr = utils.Exec(\"\", \"cp\", backingPath, newBackingPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"node_id\":      b.node.Id.Hex(),\n\t\t\t\"backing_disk\": filename,\n\t\t}).Info(\"backup: Backing disk exported\")\n\t}\n\n\texportedBackingDisks, err := ioutil.ReadDir(backingDisksDir)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(\n\t\t\t\terr,\n\t\t\t\t\"backup: Failed to read backing disks directory\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\ttrashBackingDir := path.Join(trashDir, \"backing\")\n\tfor _, item := range exportedBackingDisks {\n\t\tfilename := item.Name()\n\t\tbackingPath := path.Join(backingDisksDir, filename)\n\t\tnewBackingPath := path.Join(trashBackingDir, filename)\n\n\t\tif !backingFilenames.Contains(filename) {\n\t\t\terr = utils.ExistsMkdir(trashBackingDir, 0755)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = os.Rename(backingPath, newBackingPath)\n\t\t\tif err != nil {\n\t\t\t\tb.errorCount += 1\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"node_id\":      b.node.Id.Hex(),\n\t\t\t\t\t\"backing_disk\": filename,\n\t\t\t\t\t\"error\":        err,\n\t\t\t\t}).Error(\"backup: Failed to move backing disk to trash\")\n\t\t\t} else {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"node_id\":      b.node.Id.Hex(),\n\t\t\t\t\t\"backing_disk\": filename,\n\t\t\t\t}).Info(\"backup: Backing disk moved to trash\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *Backup) Run() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tndeId, err := bson.ObjectIDFromHex(config.Config.NodeId)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"backup: Failed to parse ObjectId\"),\n\t\t}\n\t\treturn\n\t}\n\n\tnde, err := node.Get(db, ndeId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tb.node = nde\n\tb.virtPath = nde.GetVirtPath()\n\n\terr = b.backupDisks(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = b.backupBackingDisks(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif b.errorCount > 0 {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"backup: Backup encountered errors\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc New(dest string) *Backup {\n\treturn &Backup{\n\t\tDestination: dest,\n\t}\n}\n"
  },
  {
    "path": "balancer/balancer.go",
    "content": "package balancer\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Domain struct {\n\tDomain string `bson:\"domain\" json:\"domain\"`\n\tHost   string `bson:\"host\" json:\"host\"`\n}\n\ntype Backend struct {\n\tProtocol string `bson:\"protocol\" json:\"protocol\"`\n\tHostname string `bson:\"hostname\" json:\"hostname\"`\n\tPort     int    `bson:\"port\" json:\"port\"`\n}\n\ntype State struct {\n\tTimestamp   time.Time `bson:\"timestamp\" json:\"timestamp\"`\n\tRequests    int       `bson:\"requests\" json:\"requests\"`\n\tRetries     int       `bson:\"retries\" json:\"retries\"`\n\tWebSockets  int       `bson:\"websockets\" json:\"websockets\"`\n\tOnline      []string  `bson:\"online\" json:\"online\"`\n\tUnknownHigh []string  `bson:\"unknown_high\" json:\"unknown_high\"`\n\tUnknownMid  []string  `bson:\"unknown_mid\" json:\"unknown_mid\"`\n\tUnknownLow  []string  `bson:\"unknown_low\" json:\"unknown_low\"`\n\tOffline     []string  `bson:\"offline\" json:\"offline\"`\n}\n\ntype Balancer struct {\n\tId              bson.ObjectID     `bson:\"_id,omitempty\" json:\"id\"`\n\tName            string            `bson:\"name\" json:\"name\"`\n\tComment         string            `bson:\"comment\" json:\"comment\"`\n\tType            string            `bson:\"type\" json:\"type\"`\n\tState           bool              `bson:\"state\" json:\"state\"`\n\tOrganization    bson.ObjectID     `bson:\"organization\" json:\"organization\"`\n\tDatacenter      bson.ObjectID     `bson:\"datacenter\" json:\"datacenter\"`\n\tCertificates    []bson.ObjectID   `bson:\"certificates\" json:\"certificates\"`\n\tClientAuthority bson.ObjectID     `bson:\"client_authority\" json:\"client_authority\"`\n\tWebSockets      bool              `bson:\"websockets\" json:\"websockets\"`\n\tDomains         []*Domain         `bson:\"domains\" json:\"domains\"`\n\tBackends        []*Backend        `bson:\"backends\" json:\"backends\"`\n\tStates          map[string]*State `bson:\"states\" json:\"states\"`\n\tCheckPath       string            `bson:\"check_path\" json:\"check_path\"`\n}\n\nfunc (b *Balancer) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tb.Name = utils.FilterName(b.Name)\n\n\tif b.Type == \"\" {\n\t\tb.Type = Http\n\t}\n\n\tif b.Domains == nil {\n\t\tb.Domains = []*Domain{}\n\t}\n\n\tdomains := []*Domain{}\n\tfor _, domain := range b.Domains {\n\t\tdomain.Domain = utils.FilterDomain(domain.Domain)\n\t\tdomain.Host = utils.FilterDomain(domain.Host)\n\n\t\tif domain.Domain == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tdomains = append(domains, domain)\n\t}\n\tb.Domains = domains\n\n\tif b.Backends == nil {\n\t\tb.Backends = []*Backend{}\n\t}\n\n\tif b.Certificates == nil {\n\t\tb.Certificates = []bson.ObjectID{}\n\t}\n\n\tif b.States == nil {\n\t\tb.States = map[string]*State{}\n\t}\n\n\tfor _, backend := range b.Backends {\n\t\tif backend.Protocol != \"http\" && backend.Protocol != \"https\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"balancer_protocol_invalid\",\n\t\t\t\tMessage: \"Invalid balancer backend protocol\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif backend.Hostname == \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"balancer_hostname_invalid\",\n\t\t\t\tMessage: \"Invalid balancer backend hostname\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif backend.Port < 1 || backend.Port > 65535 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"balancer_port_invalid\",\n\t\t\t\tMessage: \"Invalid balancer backend port\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tip := net.ParseIP(backend.Hostname)\n\t\tif ip == nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError: \"balancer_hostname_invalid\",\n\t\t\t\tMessage: fmt.Sprintf(\"Balancer hostname '%s' must \"+\n\t\t\t\t\t\"match existing instance address\", backend.Hostname),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbackend.Hostname = ip.String()\n\n\t\texists, e := instance.ExistsIp(db, backend.Hostname)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif !exists {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError: \"balancer_hostname_not_found\",\n\t\t\t\tMessage: fmt.Sprintf(\"Balancer hostname '%s' must \"+\n\t\t\t\t\t\"match existing instance address\", backend.Hostname),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tif b.Organization.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"organization_required\",\n\t\t\tMessage: \"Missing required organization\",\n\t\t}\n\t\treturn\n\t}\n\n\tif b.Datacenter.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"datacenter_required\",\n\t\t\tMessage: \"Missing required datacenter\",\n\t\t}\n\t\treturn\n\t}\n\n\tif b.State {\n\t\tif len(b.Domains) == 0 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"domain_required\",\n\t\t\t\tMessage: \"Missing required domain\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif b.CheckPath == \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"check_path_required\",\n\t\t\t\tMessage: \"Missing required health check path\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif len(b.Backends) == 0 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"backend_required\",\n\t\t\t\tMessage: \"Missing required backend\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tdomains := []string{}\n\t\tfor _, domain := range b.Domains {\n\t\t\tdomains = append(domains, domain.Domain)\n\t\t}\n\n\t\tcoll := db.Balancers()\n\t\tcount, e := coll.CountDocuments(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$ne\": b.Id,\n\t\t\t},\n\t\t\t\"state\": true,\n\t\t\t\"domains.domain\": &bson.M{\n\t\t\t\t\"$in\": domains,\n\t\t\t},\n\t\t})\n\t\tif e != nil {\n\t\t\terr = database.ParseError(e)\n\t\t\treturn\n\t\t}\n\n\t\tif count > 0 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError: \"domain_conflict\",\n\t\t\t\tMessage: \"External domain conflicts with another \" +\n\t\t\t\t\t\"load balancer in same datacenter\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *Balancer) Json() {\n\tif b.States == nil || len(b.States) == 0 {\n\t\treturn\n\t}\n\n\tfor key, state := range b.States {\n\t\tif time.Since(state.Timestamp) > 1*time.Minute {\n\t\t\tdelete(b.States, key)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *Balancer) Clean(db *database.Database) (err error) {\n\tif b.States == nil || len(b.States) == 0 {\n\t\treturn\n\t}\n\n\tchanged := false\n\tfor key, state := range b.States {\n\t\tif time.Since(state.Timestamp) > 1*time.Minute {\n\t\t\tchanged = true\n\t\t\tdelete(b.States, key)\n\t\t}\n\t}\n\n\tif changed {\n\t\terr = b.CommitFields(db, set.NewSet(\"states\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *Balancer) CommitState(db *database.Database, state *State) (\n\terr error) {\n\n\tcoll := db.Balancers()\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\": b.Id,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"states.\" + node.Self.Id.Hex(): state,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Balancer) Commit(db *database.Database) (err error) {\n\tcoll := db.Balancers()\n\n\terr = coll.Commit(b.Id, b)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Balancer) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Balancers()\n\n\tif b.State && (fields.Contains(\"state\") || fields.Contains(\"domains\")) {\n\t\tdomains := []string{}\n\t\tfor _, domain := range b.Domains {\n\t\t\tdomains = append(domains, domain.Domain)\n\t\t}\n\n\t\tcoll := db.Balancers()\n\t\tcount, e := coll.CountDocuments(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$ne\": b.Id,\n\t\t\t},\n\t\t\t\"state\": true,\n\t\t\t\"domains.domain\": &bson.M{\n\t\t\t\t\"$in\": domains,\n\t\t\t},\n\t\t})\n\t\tif e != nil {\n\t\t\terr = database.ParseError(e)\n\t\t\treturn\n\t\t}\n\n\t\tif count > 0 {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.New(\"balancer: Datacenter domain conflict\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = coll.CommitFields(b.Id, b, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Balancer) Insert(db *database.Database) (err error) {\n\tcoll := db.Balancers()\n\n\tif !b.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"balancer: Balancer already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = coll.InsertOne(db, b)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "balancer/constants.go",
    "content": "package balancer\n\nconst (\n\tHttp = \"http\"\n)\n"
  },
  {
    "path": "balancer/utils.go",
    "content": "package balancer\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, balncId bson.ObjectID) (\n\tbalnc *Balancer, err error) {\n\n\tcoll := db.Balancers()\n\tbalnc = &Balancer{}\n\n\terr = coll.FindOneId(balncId, balnc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, balncId bson.ObjectID) (\n\tbalnc *Balancer, err error) {\n\n\tcoll := db.Balancers()\n\tbalnc = &Balancer{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          balncId,\n\t\t\"organization\": orgId,\n\t}).Decode(balnc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tbalncs []*Balancer, err error) {\n\n\tcoll := db.Balancers()\n\tbalncs = []*Balancer{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tbalnc := &Balancer{}\n\t\terr = cursor.Decode(balnc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tbalncs = append(balncs, balnc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (balncs []*Balancer, count int64, err error) {\n\n\tcoll := db.Balancers()\n\tbalncs = []*Balancer{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tbalnc := &Balancer{}\n\t\terr = cursor.Decode(balnc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tbalncs = append(balncs, balnc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, balncId bson.ObjectID) (err error) {\n\tcoll := db.Balancers()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": balncId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, balncId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Balancers()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          balncId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, balncIds []bson.ObjectID) (\n\terr error) {\n\tcoll := db.Balancers()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": balncIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tbalncIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Balancers()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": balncIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "block/block.go",
    "content": "package block\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Block struct {\n\tId       bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName     string        `bson:\"name\" json:\"name\"`\n\tComment  string        `bson:\"comment\" json:\"comment\"`\n\tType     string        `bson:\"type\" json:\"type\"`\n\tVlan     int           `bson:\"vlan\" json:\"vlan\"`\n\tSubnets  []string      `bson:\"subnets\" json:\"subnets\"`\n\tSubnets6 []string      `bson:\"subnets6\" json:\"subnets6\"`\n\tExcludes []string      `bson:\"excludes\" json:\"excludes\"`\n\tNetmask  string        `bson:\"netmask\" json:\"netmask\"`\n\tGateway  string        `bson:\"gateway\" json:\"gateway\"`\n\tGateway6 string        `bson:\"gateway6\" json:\"gateway6\"`\n}\n\ntype Completion struct {\n\tId   bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName string        `bson:\"name\" json:\"name\"`\n\tType string        `bson:\"type\" json:\"type\"`\n}\n\nfunc (b *Block) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tb.Name = utils.FilterName(b.Name)\n\n\tif b.Type == \"\" {\n\t\tb.Type = IPv4\n\t}\n\n\tif b.Vlan < 0 || b.Vlan > 4095 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_blan\",\n\t\t\tMessage: \"VLan is invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif b.Subnets == nil {\n\t\tb.Subnets = []string{}\n\t}\n\n\tif b.Subnets6 == nil {\n\t\tb.Subnets6 = []string{}\n\t}\n\n\tif b.Excludes == nil {\n\t\tb.Excludes = []string{}\n\t}\n\n\tif b.Type == IPv4 {\n\t\tb.Subnets6 = []string{}\n\n\t\tif b.Gateway != \"\" {\n\t\t\tgateway := net.ParseIP(b.Gateway)\n\t\t\tif gateway == nil || gateway.To4() == nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_gateway\",\n\t\t\t\t\tMessage: \"Gateway address is invalid\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tb.Gateway = gateway.String()\n\t\t}\n\n\t\tif b.Netmask != \"\" {\n\t\t\tnetmask := utils.ParseIpMask(b.Netmask)\n\t\t\tif netmask == nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_netmask\",\n\t\t\t\t\tMessage: \"Netmask is invalid\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tsubnets := []string{}\n\t\tfor _, subnet := range b.Subnets {\n\t\t\tif !strings.Contains(subnet, \"/\") {\n\t\t\t\tsubnet += \"/32\"\n\t\t\t}\n\n\t\t\t_, subnetNet, e := net.ParseCIDR(subnet)\n\t\t\tif e != nil || subnetNet.IP.To4() == nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_subnet\",\n\t\t\t\t\tMessage: \"Invalid subnet address\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tsubnets = append(subnets, subnetNet.String())\n\t\t}\n\t\tb.Subnets = subnets\n\n\t\texcludes := []string{}\n\t\tfor _, exclude := range b.Excludes {\n\t\t\tif !strings.Contains(exclude, \"/\") {\n\t\t\t\texclude += \"/32\"\n\t\t\t}\n\n\t\t\t_, excludeNet, e := net.ParseCIDR(exclude)\n\t\t\tif e != nil || excludeNet.IP.To4() == nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_exclude\",\n\t\t\t\t\tMessage: \"Invalid exclude address\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\texcludes = append(excludes, excludeNet.String())\n\t\t}\n\t\tb.Excludes = excludes\n\t} else if b.Type == IPv6 {\n\t\tb.Subnets = []string{}\n\t\tb.Excludes = []string{}\n\t\tb.Netmask = \"\"\n\t\tb.Gateway = \"\"\n\n\t\tif b.Gateway6 != \"\" {\n\t\t\tgateway6 := net.ParseIP(b.Gateway6)\n\t\t\tif gateway6 == nil || gateway6.To4() != nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_gateway6\",\n\t\t\t\t\tMessage: \"Gateway IPv6 address is invalid\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tb.Gateway6 = gateway6.String()\n\t\t}\n\n\t\tsubnets6 := []string{}\n\t\tfor _, subnet6 := range b.Subnets6 {\n\t\t\tif !strings.Contains(subnet6, \"/\") {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_subnet6\",\n\t\t\t\t\tMessage: \"Missing subnet6 cidr\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, subnetNet, e := net.ParseCIDR(subnet6)\n\t\t\tif e != nil || subnetNet.IP.To4() != nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_subnet6\",\n\t\t\t\t\tMessage: \"Invalid subnet6 address\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tsize, _ := subnetNet.Mask.Size()\n\t\t\tif size > 64 {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_subnet6_size\",\n\t\t\t\t\tMessage: \"Minimum subnet6 size 64 is required\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tsubnets6 = append(subnets6, subnetNet.String())\n\t\t}\n\t\tb.Subnets6 = subnets6\n\n\t\tif len(b.Subnets6) > 1 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_subnets6\",\n\t\t\t\tMessage: \"Currently only one IPv6 subnet is supported\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_type\",\n\t\t\tMessage: \"Block type is invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Block) Contains(blckIp *BlockIp) (contains bool, err error) {\n\tip := blckIp.GetIp()\n\n\tfor _, exclude := range b.Excludes {\n\t\t_, network, e := net.ParseCIDR(exclude)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"block: Failed to parse block exclude\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif network.Contains(ip) {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, subnet := range b.Subnets {\n\t\t_, network, e := net.ParseCIDR(subnet)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"block: Failed to parse block subnet\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif network.Contains(ip) {\n\t\t\tcontains = true\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *Block) GetGateway() net.IP {\n\treturn net.ParseIP(b.Gateway)\n}\n\nfunc (b *Block) GetGateway6() net.IP {\n\tif b.Gateway6 == \"\" {\n\t\treturn nil\n\t}\n\treturn net.ParseIP(b.Gateway6)\n}\n\nfunc (b *Block) GetMask() net.IPMask {\n\treturn utils.ParseIpMask(b.Netmask)\n}\n\nfunc (b *Block) GetGatewayCidr() string {\n\tstaticGateway := net.ParseIP(b.Gateway)\n\tstaticMask := utils.ParseIpMask(b.Netmask)\n\tif staticGateway == nil || staticMask == nil {\n\t\treturn \"\"\n\t}\n\n\tstaticSize, _ := staticMask.Size()\n\treturn fmt.Sprintf(\"%s/%d\", staticGateway.String(), staticSize)\n}\n\nfunc (b *Block) GetNetwork() (staticNet *net.IPNet, err error) {\n\tstaticMask := utils.ParseIpMask(b.Netmask)\n\tif staticMask == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"block: Invalid netmask\"),\n\t\t}\n\t\treturn\n\t}\n\tstaticSize, _ := staticMask.Size()\n\n\t_, staticNet, err = net.ParseCIDR(\n\t\tfmt.Sprintf(\"%s/%d\", b.Gateway, staticSize))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"block: Failed to parse network cidr\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Block) GetIps(db *database.Database) (blckIpsSet set.Set, err error) {\n\tcoll := db.BlocksIp()\n\n\tblckIps := []int64{}\n\terr = coll.Distinct(db, \"ip\", bson.M{\n\t\t\"block\": b.Id,\n\t}).Decode(&blckIps)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tblckIpsSet = set.NewSet()\n\tfor _, ipInt := range blckIps {\n\t\tif ipInt != 0 {\n\t\t\tblckIpsSet.Add(ipInt)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *Block) GetIpCount() (count int64, err error) {\n\tif b.Type != IPv4 {\n\t\treturn\n\t}\n\n\tgatewayIp := net.ParseIP(b.Gateway)\n\n\texcludes := []*net.IPNet{}\n\tfor _, exclude := range b.Excludes {\n\t\t_, network, e := net.ParseCIDR(exclude)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"block: Failed to parse block exclude\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\texcludes = append(excludes, network)\n\t}\n\n\tvar totalCount int64\n\tfor _, subnet := range b.Subnets {\n\t\t_, network, e := net.ParseCIDR(subnet)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"block: Failed to parse block subnet\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tones, bits := network.Mask.Size()\n\t\thostBits := bits - ones\n\t\tif hostBits <= 1 {\n\t\t\tif hostBits == 1 {\n\t\t\t\ttotalCount += 2\n\t\t\t} else {\n\t\t\t\ttotalCount += 1\n\t\t\t}\n\t\t} else {\n\t\t\ttotalCount += (1 << hostBits) - 2\n\t\t}\n\t}\n\n\tvar excludedCount int64\n\tfor _, excludeNet := range excludes {\n\t\tones, bits := excludeNet.Mask.Size()\n\t\thostBits := bits - ones\n\t\tif hostBits <= 1 {\n\t\t\tif hostBits == 1 {\n\t\t\t\texcludedCount += 2\n\t\t\t} else {\n\t\t\t\texcludedCount += 1\n\t\t\t}\n\t\t} else {\n\t\t\texcludedCount += (1 << hostBits) - 2\n\t\t}\n\t}\n\n\tgatewayInSubnet := false\n\tif gatewayIp != nil {\n\t\tfor _, subnet := range b.Subnets {\n\t\t\t_, network, e := net.ParseCIDR(subnet)\n\t\t\tif e != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif network.Contains(gatewayIp) {\n\t\t\t\tgatewayInSubnet = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tcount = totalCount - excludedCount\n\tif gatewayInSubnet {\n\t\tcount--\n\t}\n\n\tif count < 0 {\n\t\tcount = 0\n\t}\n\treturn\n}\n\nfunc (b *Block) GetIp(db *database.Database,\n\tinstId bson.ObjectID, typ string) (ip net.IP, err error) {\n\n\tblckIps, err := b.GetIps(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcoll := db.BlocksIp()\n\tgateway := net.ParseIP(b.Gateway)\n\tif gateway == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"block: Failed to parse block gateway\"),\n\t\t}\n\t\treturn\n\t}\n\n\tgatewaySize, _ := b.GetMask().Size()\n\t_, gatewayCidr, err := net.ParseCIDR(fmt.Sprintf(\"%s/%d\",\n\t\tgateway.String(), gatewaySize))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"block: Failed to parse block gateway cidr\"),\n\t\t}\n\t\treturn\n\t}\n\n\tbroadcast := utils.GetLastIpAddress(gatewayCidr)\n\n\texcludes := []*net.IPNet{}\n\tfor _, exclude := range b.Excludes {\n\t\t_, network, e := net.ParseCIDR(exclude)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"block: Failed to parse block exclude\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\texcludes = append(excludes, network)\n\t}\n\n\tfor _, subnet := range b.Subnets {\n\t\t_, network, e := net.ParseCIDR(subnet)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"block: Failed to parse block subnet\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfirst := true\n\t\tcurIp := utils.CopyIpAddress(network.IP)\n\t\tfor {\n\t\t\tif first {\n\t\t\t\tfirst = false\n\t\t\t} else {\n\t\t\t\tutils.IncIpAddress(curIp)\n\t\t\t}\n\t\t\tcurIpInt := utils.IpAddress2Int(curIp)\n\n\t\t\tif !network.Contains(curIp) {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif blckIps.Contains(curIpInt) || gatewayCidr.IP.Equal(curIp) ||\n\t\t\t\tgateway.Equal(curIp) || broadcast.Equal(curIp) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\texcluded := false\n\t\t\tfor _, exclude := range excludes {\n\t\t\t\tif exclude.Contains(curIp) {\n\t\t\t\t\texcluded = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif excluded {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tblckIp := &BlockIp{\n\t\t\t\tBlock:    b.Id,\n\t\t\t\tIp:       utils.IpAddress2Int(curIp),\n\t\t\t\tInstance: instId,\n\t\t\t\tType:     typ,\n\t\t\t}\n\n\t\t\t_, err = coll.InsertOne(db, blckIp)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tip = curIp\n\t\t\tbreak\n\t\t}\n\n\t\tif ip != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif ip == nil {\n\t\terr = &BlockFull{\n\t\t\terrors.New(\"block: Address pool full\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Block) GetIp6(db *database.Database,\n\tinstId bson.ObjectID, vlan int) (ip net.IP, cidr int, err error) {\n\n\tsubnets6 := b.Subnets6\n\tif subnets6 == nil || len(subnets6) < 1 {\n\t\treturn\n\t}\n\n\tsubnet6 := subnets6[0]\n\n\tif vlan == 0 || vlan > 4095 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"block: Failed to split subnet6\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, subnetNet, err := net.ParseCIDR(subnet6)\n\tif err != nil || subnetNet.IP.To4() != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"block: Invalid subnet6\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcidr, _ = subnetNet.Mask.Size()\n\n\tsubnet6 = subnetNet.String()\n\n\tsubnet6spl := strings.Split(subnet6, \":/\")\n\tif len(subnet6spl) != 2 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"block: Failed to split subnet6\"),\n\t\t}\n\t\treturn\n\t}\n\taddr6 := subnet6spl[0]\n\n\tif strings.Count(addr6, \":\") < 4 {\n\t\taddr6 += \":\"\n\t}\n\n\taddr6 += \"0\" + fmt.Sprintf(\"%03x\", vlan) + \":\"\n\n\thash := md5.New()\n\thash.Write([]byte(instId.Hex()))\n\tmacHash := fmt.Sprintf(\"%x\", hash.Sum(nil))\n\tmacHash = macHash[:12]\n\tmacBuf := bytes.Buffer{}\n\n\tfor i, run := range macHash {\n\t\tif i != 0 && i%4 == 0 {\n\t\t\tmacBuf.WriteRune(':')\n\t\t}\n\t\tmacBuf.WriteRune(run)\n\t}\n\n\taddr6 += macBuf.String() + fmt.Sprintf(\"/%d\", cidr)\n\n\tip, _, err = net.ParseCIDR(addr6)\n\tif err != nil || subnetNet.IP.To4() != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"block: Failed to parse address6\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Block) RemoveIp(db *database.Database,\n\tinstId bson.ObjectID) (err error) {\n\n\tcoll := db.BlocksIp()\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"instance\": instId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *Block) ValidateAddresses(db *database.Database,\n\tcommitFields set.Set) (err error) {\n\n\tcoll := db.Blocks()\n\tipColl := db.BlocksIp()\n\tinstColl := db.Instances()\n\n\tgateway := net.ParseIP(b.Gateway)\n\texcludes := []*net.IPNet{}\n\tfor _, exclude := range b.Excludes {\n\t\t_, network, e := net.ParseCIDR(exclude)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"block: Failed to parse block exclude\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\texcludes = append(excludes, network)\n\t}\n\n\tsubnets := []*net.IPNet{}\n\tfor _, subnet := range b.Subnets {\n\t\t_, network, e := net.ParseCIDR(subnet)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"block: Failed to parse block subnet\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tsubnets = append(subnets, network)\n\t}\n\n\tif commitFields != nil {\n\t\terr = coll.CommitFields(b.Id, b, commitFields)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tcursor, err := ipColl.Find(db, &bson.M{\n\t\t\"block\": b.Id,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tblckIp := &BlockIp{}\n\t\terr = cursor.Decode(blckIp)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tremove := false\n\t\tip := utils.Int2IpAddress(blckIp.Ip)\n\n\t\tif gateway != nil && gateway.Equal(ip) {\n\t\t\tremove = true\n\t\t}\n\n\t\tif !remove {\n\t\t\tfor _, exclude := range excludes {\n\t\t\t\tif exclude.Contains(ip) {\n\t\t\t\t\tremove = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !remove {\n\t\t\tmatch := false\n\t\t\tfor _, subnet := range subnets {\n\t\t\t\tif subnet.Contains(ip) {\n\t\t\t\t\tmatch = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !match {\n\t\t\t\tremove = true\n\t\t\t}\n\t\t}\n\n\t\tif remove {\n\t\t\t_, _ = instColl.UpdateOne(db, &bson.M{\n\t\t\t\t\"_id\": blckIp.Instance,\n\t\t\t}, &bson.M{\n\t\t\t\t\"$set\": &bson.M{\n\t\t\t\t\t\"restart_block_ip\": true,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\t_, err = ipColl.DeleteOne(db, &bson.M{\n\t\t\t\t\"_id\": blckIp.Id,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Block) Commit(db *database.Database) (err error) {\n\tcoll := db.Blocks()\n\n\terr = coll.Commit(b.Id, b)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Block) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\terr = b.ValidateAddresses(db, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (b *Block) Insert(db *database.Database) (err error) {\n\tcoll := db.Blocks()\n\n\tif !b.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"block: Block already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := coll.InsertOne(db, b)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tb.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  },
  {
    "path": "block/constants.go",
    "content": "package block\n\nconst (\n\tExternal = \"external\"\n\tHost     = \"host\"\n\tNodePort = \"node_port\"\n\tIPv4     = \"ipv4\"\n\tIPv6     = \"ipv6\"\n)\n"
  },
  {
    "path": "block/errortypes.go",
    "content": "package block\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype BlockFull struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "block/ip.go",
    "content": "package block\n\nimport (\n\t\"net\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype BlockIp struct {\n\tId       bson.ObjectID `bson:\"_id,omitempty\"`\n\tBlock    bson.ObjectID `bson:\"block\"`\n\tIp       int64         `bson:\"ip\"`\n\tInstance bson.ObjectID `bson:\"instance\"`\n\tType     string        `bson:\"type\"`\n}\n\nfunc (b *BlockIp) GetIp() net.IP {\n\treturn utils.Int2IpAddress(b.Ip)\n}\n"
  },
  {
    "path": "block/utils.go",
    "content": "package block\n\nimport (\n\t\"net\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc GetNodeBlock(ndeId bson.ObjectID) (blck *Block, err error) {\n\thostNetwork := settings.Hypervisor.HostNetwork\n\n\thostAddr, hostNet, err := net.ParseCIDR(hostNetwork)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"block: Failed to parse host network\"),\n\t\t}\n\t\treturn\n\t}\n\n\tutils.IncIpAddress(hostAddr)\n\n\tblck = &Block{\n\t\tId:   ndeId,\n\t\tName: \"host-block\",\n\t\tType: Host,\n\t\tSubnets: []string{\n\t\t\thostNetwork,\n\t\t},\n\t\tNetmask: net.IP(hostNet.Mask).String(),\n\t\tGateway: hostAddr.String(),\n\t}\n\n\treturn\n}\n\nfunc GetNodePortBlock(ndeId bson.ObjectID) (blck *Block, err error) {\n\tportNetwork := settings.Hypervisor.NodePortNetwork\n\n\tportAddr, portNet, err := net.ParseCIDR(portNetwork)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"block: Failed to parse node port network\"),\n\t\t}\n\t\treturn\n\t}\n\n\tutils.IncIpAddress(portAddr)\n\n\tblck = &Block{\n\t\tId:   ndeId,\n\t\tName: \"node-port-block\",\n\t\tType: NodePort,\n\t\tSubnets: []string{\n\t\t\tportNetwork,\n\t\t},\n\t\tNetmask: net.IP(portNet.Mask).String(),\n\t\tGateway: portAddr.String(),\n\t}\n\n\treturn\n}\n\nfunc GetNodePortGateway() (gateway string, err error) {\n\tportAddr, _, err := net.ParseCIDR(settings.Hypervisor.NodePortNetwork)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"block: Failed to parse node port network\"),\n\t\t}\n\t\treturn\n\t}\n\n\tutils.IncIpAddress(portAddr)\n\n\tgateway = portAddr.String()\n\treturn\n}\n\nfunc Get(db *database.Database, blockId bson.ObjectID) (\n\tblock *Block, err error) {\n\n\tcoll := db.Blocks()\n\tblock = &Block{}\n\n\terr = coll.FindOneId(blockId, block)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database) (blocks []*Block, err error) {\n\tcoll := db.Blocks()\n\tblocks = []*Block{}\n\n\topts := options.Find().\n\t\tSetSort(bson.D{{\"name\", 1}})\n\n\tcursor, err := coll.Find(db, bson.M{}, opts)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tblck := &Block{}\n\t\terr = cursor.Decode(blck)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tblocks = append(blocks, blck)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetInstanceHostIp(db *database.Database,\n\tinstId bson.ObjectID) (blckIp *BlockIp, err error) {\n\n\tcoll := db.BlocksIp()\n\tblckIp = &BlockIp{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"instance\": instId,\n\t\t\"type\":     Host,\n\t}).Decode(blckIp)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tblckIp = nil\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetInstanceNodePortIp(db *database.Database,\n\tinstId bson.ObjectID) (blckIp *BlockIp, err error) {\n\n\tcoll := db.BlocksIp()\n\tblckIp = &BlockIp{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"instance\": instId,\n\t\t\"type\":     NodePort,\n\t}).Decode(blckIp)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tblckIp = nil\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetInstanceIp(db *database.Database, instId bson.ObjectID,\n\ttyp string) (blck *Block, blckIp *BlockIp, err error) {\n\n\tcoll := db.BlocksIp()\n\tblckIp = &BlockIp{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"instance\": instId,\n\t\t\"type\":     typ,\n\t}).Decode(blckIp)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tblckIp = nil\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t}\n\t\treturn\n\t}\n\n\tblck, err = Get(db, blckIp.Block)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tRemoveIp(db, blckIp.Id)\n\t\t}\n\t\tblckIp = nil\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (blcks []*Block, count int64, err error) {\n\n\tcoll := db.Blocks()\n\tblcks = []*Block{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tblck := &Block{}\n\t\terr = cursor.Decode(blck)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tblcks = append(blcks, blck)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, blockId bson.ObjectID) (err error) {\n\tcoll := db.Blocks()\n\tipColl := db.BlocksIp()\n\tinstColl := db.Instances()\n\tnodeColl := db.Nodes()\n\n\tcursor, err := ipColl.Find(db, &bson.M{\n\t\t\"block\": blockId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tblckIp := &BlockIp{}\n\t\terr = cursor.Decode(blckIp)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\t_, _ = instColl.UpdateOne(db, &bson.M{\n\t\t\t\"_id\": blckIp.Instance,\n\t\t}, &bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"restart_block_ip\": true,\n\t\t\t},\n\t\t})\n\t}\n\n\t_, err = ipColl.DeleteMany(db, &bson.M{\n\t\t\"block\": blockId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\t_, err = nodeColl.UpdateMany(db, &bson.M{\n\t\t\"host_block\": blockId,\n\t}, &bson.M{\"$set\": &bson.M{\n\t\t\"host_block\": bson.NilObjectID,\n\t}})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": blockId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveIp(db *database.Database, blockIpId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.BlocksIp()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": blockIpId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveInstanceIps(db *database.Database, instId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.BlocksIp()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"instance\": instId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveInstanceIpsType(db *database.Database,\n\tinstId bson.ObjectID, typ string) (err error) {\n\n\tcoll := db.BlocksIp()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"instance\": instId,\n\t\t\"type\":     typ,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, blckIds []bson.ObjectID) (\n\terr error) {\n\tcoll := db.Blocks()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": blckIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "bridges/bridges.go",
    "content": "package bridges\n\nimport (\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/ip\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tbridges         = []ip.Interface{}\n\tlastBridgesSync time.Time\n\tcurAddr         = map[string]string{}\n\tcurAddr6        = map[string]string{}\n\tlastAddrSync    = map[string]time.Time{}\n)\n\nfunc ClearCache() {\n\tlastBridgesSync = time.Time{}\n\tbridges = []ip.Interface{}\n}\n\nfunc GetBridges() (brdgs []ip.Interface, err error) {\n\tif time.Since(lastBridgesSync) < 300*time.Second {\n\t\tbrdgs = bridges\n\t\treturn\n\t}\n\n\tbridgesNew := []ip.Interface{}\n\tbridgesSet := set.NewSet()\n\n\tifaces, err := iproute.IfaceGetBridges(\"\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tifacesData, err := ip.GetIfacesCached(\"\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, iface := range ifaces {\n\t\tif iface.Name == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tifaceData := ifacesData[iface.Name]\n\t\tif ifaceData != nil {\n\t\t\tbridgesNew = append(bridgesNew, ip.Interface{\n\t\t\t\tName:    iface.Name,\n\t\t\t\tAddress: ifaceData.GetAddress(),\n\t\t\t})\n\t\t} else {\n\t\t\tbridgesNew = append(bridgesNew, ip.Interface{\n\t\t\t\tName: iface.Name,\n\t\t\t})\n\t\t}\n\t\tbridgesSet.Add(iface.Name)\n\t}\n\n\texists, err := utils.ExistsDir(\"/etc/sysconfig/network-scripts\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif exists {\n\t\titems, e := ioutil.ReadDir(\"/etc/sysconfig/network-scripts\")\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"bridges: Failed to read network scripts\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfor _, item := range items {\n\t\t\tname := item.Name()\n\n\t\t\tif !strings.HasPrefix(name, \"ifcfg-\") ||\n\t\t\t\t!strings.Contains(name, \":\") {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tname = name[6:]\n\t\t\tnames := strings.Split(name, \":\")\n\t\t\tif len(names) != 2 || names[0] == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif bridgesSet.Contains(names[0]) && !bridgesSet.Contains(name) {\n\t\t\t\tbridgesNew = append(bridgesNew, ip.Interface{\n\t\t\t\t\tName: name,\n\t\t\t\t})\n\t\t\t\tbridgesSet.Add(name)\n\t\t\t}\n\t\t}\n\t}\n\n\tbridges = bridgesNew\n\tlastBridgesSync = time.Now()\n\tbrdgs = bridgesNew\n\n\treturn\n}\n\nfunc GetIpAddrs(iface string) (addr string, addr6 string, err error) {\n\tif time.Since(lastAddrSync[iface]) < 600*time.Second {\n\t\taddr = curAddr[iface]\n\t\taddr6 = curAddr6[iface]\n\t\treturn\n\t}\n\n\tif iface == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"bridges: Invalid external node interface\"),\n\t\t}\n\t\treturn\n\t}\n\n\taddress, address6, err := iproute.AddressGetIface(\"\", iface)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif address != nil {\n\t\taddr = address.Local\n\t}\n\n\tif address6 != nil {\n\t\taddr6 = address6.Local\n\t}\n\n\tcurAddr[iface] = addr\n\tcurAddr6[iface] = addr6\n\tlastAddrSync[iface] = time.Now()\n\n\treturn\n}\n"
  },
  {
    "path": "certificate/certificate.go",
    "content": "package certificate\n\nimport (\n\t\"crypto/md5\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Info struct {\n\tHash         string    `bson:\"hash\" json:\"hash\"`\n\tSignatureAlg string    `bson:\"signature_alg\" json:\"signature_alg\"`\n\tPublicKeyAlg string    `bson:\"public_key_alg\" json:\"public_key_alg\"`\n\tIssuer       string    `bson:\"issuer\" json:\"issuer\"`\n\tIssuedOn     time.Time `bson:\"issued_on\" json:\"issued_on\"`\n\tExpiresOn    time.Time `bson:\"expires_on\" json:\"expires_on\"`\n\tDnsNames     []string  `bson:\"dns_names\" json:\"dns_names\"`\n}\n\ntype Certificate struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tComment      string        `bson:\"comment\" json:\"comment\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tType         string        `bson:\"type\" json:\"type\"`\n\tKey          string        `bson:\"key\" json:\"key\"`\n\tCertificate  string        `bson:\"certificate\" json:\"certificate\"`\n\tInfo         *Info         `bson:\"info\" json:\"info\"`\n\tAcmeHash     string        `bson:\"acme_hash\" json:\"-\"`\n\tAcmeAccount  string        `bson:\"acme_account\" json:\"-\"`\n\tAcmeDomains  []string      `bson:\"acme_domains\" json:\"acme_domains\"`\n\tAcmeType     string        `bson:\"acme_type\" json:\"acme_type\"`\n\tAcmeAuth     string        `bson:\"acme_auth\" json:\"acme_auth\"`\n\tAcmeSecret   bson.ObjectID `bson:\"acme_secret\" json:\"acme_secret\"`\n}\n\ntype Completion struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tType         string        `bson:\"type\" json:\"type\"`\n}\n\nfunc (c *Certificate) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tc.Name = utils.FilterName(c.Name)\n\n\tif c.Type == \"\" {\n\t\tc.Type = Text\n\t}\n\n\tc.Key = strings.TrimSpace(c.Key)\n\tc.Certificate = strings.TrimSpace(c.Certificate)\n\n\tif c.Type == LetsEncrypt {\n\t\tswitch c.AcmeType {\n\t\tcase AcmeHTTP, \"\":\n\t\t\tc.AcmeType = AcmeHTTP\n\t\t\tbreak\n\t\tcase AcmeDNS:\n\t\t\tif c.AcmeSecret.IsZero() {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"acme_secret_invalid\",\n\t\t\t\t\tMessage: \"LetsEncrypt verification secret invalid\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"acme_type_invalid\",\n\t\t\t\tMessage: \"LetsEncrypt verification type invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tswitch c.AcmeAuth {\n\t\tcase AcmeAWS, \"\":\n\t\t\tc.AcmeAuth = AcmeAWS\n\t\t\tbreak\n\t\tcase AcmeCloudflare:\n\t\t\tbreak\n\t\tcase AcmeOracleCloud:\n\t\t\tbreak\n\t\tcase AcmeGoogleCloud:\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"acme_auth_invalid\",\n\t\t\t\tMessage: \"LetsEncrypt verification provider invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tc.AcmeAccount = \"\"\n\t\tc.AcmeDomains = []string{}\n\t\tc.AcmeType = \"\"\n\t\tc.AcmeAuth = \"\"\n\t\tc.AcmeSecret = bson.NilObjectID\n\t}\n\n\tif c.AcmeDomains == nil {\n\t\tc.AcmeDomains = []string{}\n\t}\n\n\tfor i, domain := range c.AcmeDomains {\n\t\tif strings.HasSuffix(domain, \".\") {\n\t\t\tc.AcmeDomains[i] = domain[:len(domain)-1]\n\t\t}\n\t}\n\n\tif c.Type == LetsEncrypt && len(c.AcmeDomains) == 0 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"missing_acme_domains\",\n\t\t\tMessage: \"Lets Encrypt domains required\",\n\t\t}\n\t\treturn\n\t}\n\n\terr = c.UpdateInfo()\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"certificate: Failed to update certificate info\")\n\t\terr = nil\n\t}\n\n\treturn\n}\n\nfunc (c *Certificate) UpdateInfo() (err error) {\n\thash := c.Hash()\n\n\tif c.Certificate == \"\" {\n\t\tc.Info = &Info{\n\t\t\tDnsNames: []string{},\n\t\t}\n\t\treturn\n\t}\n\n\tif c.Info != nil && hash == c.Info.Hash {\n\t\treturn\n\t}\n\n\tcertBlock, _ := pem.Decode([]byte(c.Certificate))\n\tif certBlock == nil {\n\t\tc.Info = nil\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"certificate: Failed to decode certificate\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcert, err := x509.ParseCertificate(certBlock.Bytes)\n\tif err != nil {\n\t\tc.Info = nil\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"certificate: Failed to parse certificate\"),\n\t\t}\n\t\treturn\n\t}\n\n\tpublicKeyAlg := \"\"\n\tswitch cert.PublicKeyAlgorithm {\n\tcase x509.RSA:\n\t\tpublicKeyAlg = \"RSA\"\n\t\tbreak\n\tcase x509.DSA:\n\t\tpublicKeyAlg = \"DSA\"\n\t\tbreak\n\tcase x509.ECDSA:\n\t\tpublicKeyAlg = \"ECDSA\"\n\t\tbreak\n\tdefault:\n\t\tpublicKeyAlg = \"Unknown\"\n\t}\n\n\tdnsNames := cert.DNSNames\n\tif len(dnsNames) == 0 && cert.Subject.CommonName != \"\" {\n\t\tdnsNames = append(dnsNames, cert.Subject.CommonName)\n\t}\n\n\tc.Info = &Info{\n\t\tHash:         hash,\n\t\tSignatureAlg: cert.SignatureAlgorithm.String(),\n\t\tPublicKeyAlg: publicKeyAlg,\n\t\tIssuer:       cert.Issuer.CommonName,\n\t\tIssuedOn:     cert.NotBefore,\n\t\tExpiresOn:    cert.NotAfter,\n\t\tDnsNames:     dnsNames,\n\t}\n\n\treturn\n}\n\nfunc (c *Certificate) Commit(db *database.Database) (err error) {\n\tcoll := db.Certificates()\n\n\terr = coll.Commit(c.Id, c)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Certificate) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Certificates()\n\n\terr = coll.CommitFields(c.Id, c, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Certificate) Insert(db *database.Database) (err error) {\n\tcoll := db.Certificates()\n\n\tif !c.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"certificate: Certificate already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := coll.InsertOne(db, c)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tc.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n\nfunc (c *Certificate) Hash() string {\n\thash := md5.New()\n\thash.Write([]byte(c.Type))\n\thash.Write([]byte(c.Key))\n\thash.Write([]byte(c.Certificate))\n\thash.Write([]byte(c.AcmeAccount))\n\tif c.AcmeDomains != nil {\n\t\tfor _, domain := range c.AcmeDomains {\n\t\t\tio.WriteString(hash, domain)\n\t\t}\n\t}\n\treturn fmt.Sprintf(\"%x\", hash.Sum(nil))\n}\n"
  },
  {
    "path": "certificate/constants.go",
    "content": "package certificate\n\nimport \"github.com/pritunl/mongo-go-driver/v2/bson\"\n\nconst (\n\tText        = \"text\"\n\tLetsEncrypt = \"lets_encrypt\"\n\n\tAcmeHTTP = \"acme_http\"\n\tAcmeDNS  = \"acme_dns\"\n\n\tAcmeAWS         = \"acme_aws\"\n\tAcmeCloudflare  = \"acme_cloudflare\"\n\tAcmeOracleCloud = \"acme_oracle_cloud\"\n\tAcmeGoogleCloud = \"acme_google_cloud\"\n)\n\nvar (\n\tGlobal = bson.NilObjectID\n)\n"
  },
  {
    "path": "certificate/utils.go",
    "content": "package certificate\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, certId bson.ObjectID) (\n\tcert *Certificate, err error) {\n\n\tcoll := db.Certificates()\n\tcert = &Certificate{}\n\n\terr = coll.FindOneId(certId, cert)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, certId bson.ObjectID) (\n\tcert *Certificate, err error) {\n\n\tcoll := db.Certificates()\n\tcert = &Certificate{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          certId,\n\t\t\"organization\": orgId,\n\t}).Decode(cert)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (cert *Certificate, err error) {\n\tcoll := db.Certificates()\n\tcert = &Certificate{}\n\n\terr = coll.FindOne(db, query).Decode(cert)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database,\n\tquery *bson.M) (certs []*Certificate, err error) {\n\n\tcoll := db.Certificates()\n\tcerts = []*Certificate{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tcert := &Certificate{}\n\t\terr = cursor.Decode(cert)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tcerts = append(certs, cert)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllOrg(db *database.Database, orgId bson.ObjectID) (\n\tcerts []*Certificate, err error) {\n\n\tcoll := db.Certificates()\n\tcerts = []*Certificate{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tcert := &Certificate{}\n\t\terr = cursor.Decode(cert)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tcerts = append(certs, cert)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNames(db *database.Database, query *bson.M) (\n\tcerts []*database.Named, err error) {\n\n\tcoll := db.Certificates()\n\tcerts = []*database.Named{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetProjection(bson.D{{\"name\", 1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tcrt := &database.Named{}\n\t\terr = cursor.Decode(crt)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tcerts = append(certs, crt)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (certs []*Certificate, count int64, err error) {\n\n\tcoll := db.Certificates()\n\tcerts = []*Certificate{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tcert := &Certificate{}\n\t\terr = cursor.Decode(cert)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tcerts = append(certs, cert)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, certId bson.ObjectID) (err error) {\n\tcoll := db.Certificates()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": certId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, certId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Certificates()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          certId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, certIds []bson.ObjectID) (\n\terr error) {\n\tcoll := db.Certificates()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": certIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tcertIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Certificates()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": certIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cloud/cloud.go",
    "content": "package cloud\n\ntype Subnet struct {\n\tId      string `bson:\"id\" json:\"id\"`\n\tVpcId   string `bson:\"vpc_id\" json:\"vpc_id\"`\n\tName    string `bson:\"name\" json:\"name\"`\n\tNetwork string `bson:\"network\" json:\"network\"`\n}\n\ntype Vpc struct {\n\tId      string    `bson:\"id\" json:\"id\"`\n\tName    string    `bson:\"name\" json:\"name\"`\n\tNetwork string    `bson:\"network\" json:\"network\"`\n\tSubnets []*Subnet `bson:\"subnets\" json:\"subnets\"`\n}\n"
  },
  {
    "path": "cloud/oracle.go",
    "content": "package cloud\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/oracle\"\n)\n\nvar (\n\tlastOracleSync time.Time\n\toracleVpcs     []*Vpc\n)\n\nfunc GetOracleVpcs(authPv oracle.AuthProvider) (vpcs []*Vpc, err error) {\n\tif time.Since(lastOracleSync) < 30*time.Second {\n\t\tvpcs = oracleVpcs\n\t\treturn\n\t}\n\n\tpv, err := oracle.NewProvider(authPv)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvcns, err := oracle.GetVcns(pv)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvpcs = []*Vpc{}\n\tfor _, ociVcn := range vcns {\n\t\tvpc := &Vpc{\n\t\t\tId:      ociVcn.Id,\n\t\t\tName:    ociVcn.Name,\n\t\t\tNetwork: ociVcn.Network,\n\t\t\tSubnets: []*Subnet{},\n\t\t}\n\n\t\tfor _, ociSubnet := range ociVcn.Subnets {\n\t\t\tsubnet := &Subnet{\n\t\t\t\tId:      ociSubnet.Id,\n\t\t\t\tVpcId:   ociSubnet.VcnId,\n\t\t\t\tName:    ociSubnet.Name,\n\t\t\t\tNetwork: ociSubnet.Network,\n\t\t\t}\n\n\t\t\tvpc.Subnets = append(vpc.Subnets, subnet)\n\t\t}\n\n\t\tvpcs = append(vpcs, vpc)\n\t}\n\n\tlastOracleSync = time.Now()\n\toracleVpcs = vpcs\n\n\treturn\n}\n"
  },
  {
    "path": "cloudinit/cloudinit.go",
    "content": "package cloudinit\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"net/textproto\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\nconst metaDataTmpl = `instance-id: %s\nlocal-hostname: %s`\n\nconst userDataTmpl = `Content-Type: multipart/mixed; boundary=\"%s\"\nMIME-Version: 1.0\n\n%s`\n\nconst netConfigTmpl = `version: 1\nconfig:\n  - type: physical\n    name: {{.Iface}}\n    mac_address: {{.Mac}}{{.Mtu}}\n    subnets:\n      - type: static\n        address: {{.Address}}\n        netmask: {{.Netmask}}\n        gateway: {{.Gateway}}\n        dns_nameservers:\n          - {{.Dns1}}\n          - {{.Dns2}}\n          - {{.Dns3}}\n      - type: static6\n        address: {{.AddressCidr6}}\n        gateway: {{.Gateway6}}\n`\n\nconst netConfigLegacyTmpl = `version: 1\nconfig:\n  - type: physical\n    name: {{.Iface}}\n    mac_address: {{.Mac}}{{.Mtu}}\n    subnets:\n      - type: static\n        address: {{.Address}}\n        netmask: {{.Netmask}}\n        network: {{.Network}}\n        gateway: {{.Gateway}}\n        dns_nameservers:\n          - {{.Dns1}}\n          - {{.Dns2}}\n          - {{.Dns3}}\n      - type: static\n        address: {{.AddressCidr6}}\n        gateway: {{.Gateway6}}\n`\n\nconst netConfig2Tmpl = `version: 2\nethernets:\n  {{.Iface}}:\n    match:\n      macaddress: {{.Mac}}{{.Mtu}}\n    addresses:\n      - {{.AddressCidr}}\n      - {{.AddressCidr6}}\n    gateway4: {{.Gateway}}\n    gateway6: {{.Gateway6}}\n    nameservers:\n      addresses:\n        - {{.Dns1}}\n        - {{.Dns2}}\n        - {{.Dns3}}\n`\n\nconst netMtu = `\n    mtu: %d`\n\nconst cloudConfigTmpl = `#cloud-config\nhostname: {{.Hostname}}\nssh_deletekeys: false\n{{if eq .RootPasswd \"\"}}disable_root: true{{else}}disable_root: false{{end}}\nssh_pwauth: no\nwrite_files:{{.WriteFiles}}\ngrowpart:\n    mode: auto\n    devices: [\"/\"]\n    ignore_growroot_disabled: false\nruncmd:\n  - 'systemctl restart sshd || true'\n  - [ {{.DeployRun}} ]\nusers:\n  - name: root\n    {{if eq .RootPasswd \"\"}}lock-passwd: true{{else}}lock-passwd: false\n    passwd: {{.RootPasswd}}\n    hashed_passwd: {{.RootPasswd}}{{end}}\n  - name: cloud\n    groups: adm, video, wheel, systemd-journal\n    selinux-user: staff_u\n    sudo: ALL=(ALL) NOPASSWD:ALL\n    lock-passwd: {{.LockPasswd}}\n    ssh-authorized-keys:\n{{- range .Keys}}\n      - {{.}}\n{{- end}}\nbootcmd:\n{{- range .Mounts}}\n  - [ \"mkdir\", \"-p\", \"{{.Path}}\" ]\n{{- end}}\n  - 'sysctl -w net.ipv4.conf.eth0.send_redirects=0 || true'\n  - [ sh, -c, '{{.DeployBoot}}' ]{{if .RunScript}}\n  - [ /etc/cloudinit-script ]{{end}}\n{{- if .HasMounts}}\nmounts:\n{{- range .Mounts}}\n  - [ \"{{.Tag}}\", \"{{.Path}}\", {{.Type}}, \"{{.Opts}}\", \"0\", \"{{.Fsck}}\" ]\n{{- end}}\n{{- end}}\n`\n\nconst cloudBsdConfigTmpl = `#cloud-config\nhostname: {{.Hostname}}\nssh_deletekeys: false\n{{if eq .RootPasswd \"\"}}disable_root: true{{else}}disable_root: false{{end}}\nssh_pwauth: no\nwrite_files:{{.WriteFiles}}\nruncmd:\n  - [ {{.DeployRun}} ]\nusers:\n  - name: root\n    {{if eq .RootPasswd \"\"}}lock-passwd: true{{else}}lock-passwd: false\n    passwd: {{.RootPasswd}}\n    hashed_passwd: {{.RootPasswd}}{{end}}\n  - name: cloud\n    groups: cloud, wheel\n    sudo: ALL=(ALL) NOPASSWD:ALL\n    lock-passwd: {{.LockPasswd}}\n    ssh-authorized-keys:\n{{- range .Keys}}\n      - {{.}}\n{{- end}}\nbootcmd:\n{{- range .Mounts}}\n  - [ \"mkdir\", \"-p\", \"{{.Path}}\" ]\n{{- end}}\n  - [ sysctl, net.inet.ip.redirect=0 ]\n  - [ sysctl, net.inet6.ip6.dad_count=0 ]\n  - [ sh, -c, '{{.DeployBoot}}' ]{{if .RunScript}}\n  - [ /etc/cloudinit-script ]{{end}}\n{{- if .HasMounts}}\nmounts:\n{{- range .Mounts}}\n  - [ \"{{.Tag}}\", \"{{.Path}}\", {{.Type}}, \"{{.Opts}}\", \"0\", \"{{.Fsck}}\" ]\n{{- end}}\n{{- end}}\n`\n\nconst deploymentScriptTmpl = `#!/bin/sh\nset -e\nmkdir -p /iso\nmount /dev/sr0 /iso\ncp /iso/pci %s\nsync\numount /iso\nrm -rf /iso\nrm -- \"$0\"%s\n`\n\nconst deploymentScriptBsdTmpl = `#!/bin/sh\nset -e\nmkdir -p /iso\nmount -t cd9660 /dev/cd0 /iso\ncp /iso/pci %s\nsync\numount /iso\nrm -rf /iso\nrm -- \"$0\"%s\n`\n\nvar (\n\tcloudConfig = template.Must(template.New(\n\t\t\"cloud\").Parse(cloudConfigTmpl))\n\tcloudBsdConfig = template.Must(template.New(\n\t\t\"cloud_bsd\").Parse(cloudBsdConfigTmpl))\n\tnetConfig = template.Must(template.New(\n\t\t\"net\").Parse(netConfigTmpl))\n\tnetConfigLegacy = template.Must(template.New(\n\t\t\"net\").Parse(netConfigLegacyTmpl))\n\tnetConfig2 = template.Must(template.New(\n\t\t\"net2\").Parse(netConfig2Tmpl))\n)\n\ntype netConfigData struct {\n\tIface        string\n\tMac          string\n\tMtu          string\n\tAddress      string\n\tAddressCidr  string\n\tNetmask      string\n\tNetwork      string\n\tGateway      string\n\tAddress6     string\n\tAddressCidr6 string\n\tGateway6     string\n\tDns1         string\n\tDns2         string\n\tDns3         string\n}\n\ntype cloudConfigData struct {\n\tHostname   string\n\tRootPasswd string\n\tLockPasswd string\n\tWriteFiles string\n\tRunScript  bool\n\tDeployRun  string\n\tDeployBoot string\n\tAddress6   string\n\tGateway6   string\n\tKeys       []string\n\tHasMounts  bool\n\tMounts     []cloudMount\n}\n\ntype cloudMount struct {\n\tTag  string\n\tPath string\n\tType string\n\tOpts string\n\tFsck string\n}\n\ntype imdsConfig struct {\n\tAddress string `json:\"address\"`\n\tPort    int    `json:\"port\"`\n\tSecret  string `json:\"secret\"`\n\tState   string `json:\"state\"`\n}\n\nfunc getUserData(db *database.Database, inst *instance.Instance,\n\tvirt *vm.VirtualMachine, dc *datacenter.Datacenter, zne *zone.Zone,\n\tvc *vpc.Vpc, deply *deployment.Deployment, deployUnit *unit.Unit,\n\tdeploySpec *spec.Spec, initial bool, addr6, gateway6 net.IP) (\n\tusrData string, err error) {\n\n\tauthrs, err := authority.GetOrgRoles(db, inst.Organization,\n\t\tinst.Roles)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttrusted := \"\"\n\tprincipals := \"\"\n\tauthorizedKeys := \"\"\n\twriteFiles := []*fileData{}\n\tinitGuestPath := utils.FilterPath(settings.Hypervisor.InitGuestPath)\n\tagentGuestPath := utils.FilterPath(settings.Hypervisor.AgentGuestPath)\n\tdnsCloud := strings.Split(settings.Hypervisor.ImdsAddress, \"/\")[0]\n\n\tdata := cloudConfigData{\n\t\tKeys:      []string{},\n\t\tHostname:  strings.Replace(inst.Name, \" \", \"_\", -1),\n\t\tAddress6:  addr6.String(),\n\t\tGateway6:  gateway6.String(),\n\t\tDeployRun: initGuestPath,\n\t\tMounts:    []cloudMount{},\n\t}\n\n\tif inst.RootEnabled {\n\t\tdata.RootPasswd, err = utils.GenerateShadow(inst.RootPasswd)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif !initial || !settings.Hypervisor.LockCloudPass {\n\t\tdata.LockPasswd = \"false\"\n\t} else {\n\t\tdata.LockPasswd = \"true\"\n\t}\n\n\towner := \"\"\n\tif virt.CloudType == instance.BSD {\n\t\towner = \"root:wheel\"\n\t} else {\n\t\towner = \"root:root\"\n\t}\n\n\tif virt.CloudType == instance.BSD ||\n\t\tvirt.SystemKind == instance.AlpineLinux {\n\n\t\tresolvConf := fmt.Sprintf(\"nameserver %s\\n\", dnsCloud)\n\n\t\tif inst.IsIpv6Only() {\n\t\t\tresolvConf += fmt.Sprintf(\"nameserver %s\\n\",\n\t\t\t\tzne.GetDnsServerPrimary6())\n\t\t\tresolvConf += fmt.Sprintf(\"nameserver %s\\n\",\n\t\t\t\tzne.GetDnsServerSecondary6())\n\t\t} else {\n\t\t\tresolvConf += fmt.Sprintf(\"nameserver %s\\n\",\n\t\t\t\tzne.GetDnsServerPrimary())\n\t\t\tresolvConf += fmt.Sprintf(\"nameserver %s\\n\",\n\t\t\t\tzne.GetDnsServerSecondary())\n\t\t}\n\n\t\twriteFiles = append(writeFiles, &fileData{\n\t\t\tContent:     resolvConf,\n\t\t\tOwner:       owner,\n\t\t\tPath:        \"/etc/resolv.conf\",\n\t\t\tPermissions: \"0644\",\n\t\t})\n\t}\n\n\tif inst.CloudScript != \"\" {\n\t\tdata.RunScript = true\n\t\twriteFiles = append(writeFiles, &fileData{\n\t\t\tContent:     inst.CloudScript,\n\t\t\tOwner:       owner,\n\t\t\tPath:        \"/etc/cloudinit-script\",\n\t\t\tPermissions: \"0755\",\n\t\t})\n\t}\n\n\tstateObjId, err := utils.RandObjectId()\n\tif err != nil {\n\t\treturn\n\t}\n\tstateId := stateObjId.Hex()\n\n\timdsConf := &imdsConfig{\n\t\tAddress: strings.Split(settings.Hypervisor.ImdsAddress, \"/\")[0],\n\t\tPort:    settings.Hypervisor.ImdsPort,\n\t\tSecret:  virt.ImdsClientSecret,\n\t}\n\n\timdsConfContent, err := json.Marshal(imdsConf)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"cloudinit: Failed to marshal imds conf\"),\n\t\t}\n\t\treturn\n\t}\n\n\twriteFiles = append(writeFiles, &fileData{\n\t\tContent:     string(imdsConfContent),\n\t\tOwner:       owner,\n\t\tPath:        \"/etc/pritunl-imds.json\",\n\t\tPermissions: \"0600\",\n\t})\n\n\tdeployScript := \"\"\n\tdeployScriptTmpl := \"\"\n\n\tif virt.CloudType == instance.BSD {\n\t\tdeployScriptTmpl = deploymentScriptBsdTmpl\n\t} else {\n\t\tdeployScriptTmpl = deploymentScriptTmpl\n\t}\n\n\tif deply != nil && deployUnit != nil && deploySpec != nil {\n\t\tif deply.Mounts != nil && len(deply.Mounts) > 0 {\n\t\t\tdata.HasMounts = true\n\t\t\tfor _, mnt := range deply.Mounts {\n\t\t\t\tdata.Mounts = append(data.Mounts, cloudMount{\n\t\t\t\t\tTag:  fmt.Sprintf(\"UUID=%s\", mnt.Uuid),\n\t\t\t\t\tPath: utils.FilterPath(mnt.Path),\n\t\t\t\t\tType: \"auto\",\n\t\t\t\t\tOpts: \"defaults\",\n\t\t\t\t\tFsck: \"2\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif deployUnit.Kind == deployment.Image {\n\t\t\tdeployScript = fmt.Sprintf(\n\t\t\t\tdeployScriptTmpl,\n\t\t\t\tagentGuestPath,\n\t\t\t\tfmt.Sprintf(\n\t\t\t\t\t\" && IMDS_STATE=\\\"run:%s\\\" %s --daemon engine image\",\n\t\t\t\t\tstateId,\n\t\t\t\t\tagentGuestPath,\n\t\t\t\t),\n\t\t\t)\n\t\t} else if initial {\n\t\t\tdeployScript = fmt.Sprintf(\n\t\t\t\tdeployScriptTmpl,\n\t\t\t\tagentGuestPath,\n\t\t\t\tfmt.Sprintf(\n\t\t\t\t\t\" && IMDS_STATE=\\\"run:%s\\\" %s --daemon engine initial\",\n\t\t\t\t\tstateId,\n\t\t\t\t\tagentGuestPath,\n\t\t\t\t),\n\t\t\t)\n\t\t} else {\n\t\t\tdeployScript = fmt.Sprintf(\n\t\t\t\tdeployScriptTmpl,\n\t\t\t\tagentGuestPath,\n\t\t\t\tfmt.Sprintf(\n\t\t\t\t\t\" && IMDS_STATE=\\\"run:%s\\\" %s --daemon engine post\",\n\t\t\t\t\tstateId,\n\t\t\t\t\tagentGuestPath,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\n\t\twriteFiles = append(writeFiles, &fileData{\n\t\t\tContent:     deploySpec.Data + \"\\n\",\n\t\t\tOwner:       owner,\n\t\t\tPath:        \"/etc/pritunl-deploy.md\",\n\t\t\tPermissions: \"0600\",\n\t\t})\n\n\t\tif virt.CloudType == instance.BSD {\n\t\t\tdata.DeployBoot = fmt.Sprintf(\n\t\t\t\t\"[ -f %s ] && ! pgrep -f \\\"%s --daemon\\\" && \"+\n\t\t\t\t\t\"IMDS_STATE=\\\"boot:%s\\\" %s --daemon engine post || true\",\n\t\t\t\tagentGuestPath,\n\t\t\t\tagentGuestPath,\n\t\t\t\tstateId,\n\t\t\t\tagentGuestPath,\n\t\t\t)\n\t\t} else {\n\t\t\tdata.DeployBoot = fmt.Sprintf(\n\t\t\t\t\"[ -f %s ] && ! pgrep -f \\\"^%s\\\" && \"+\n\t\t\t\t\t\"IMDS_STATE=\\\"boot:%s\\\" %s --daemon engine post || true\",\n\t\t\t\tagentGuestPath,\n\t\t\t\tagentGuestPath,\n\t\t\t\tstateId,\n\t\t\t\tagentGuestPath,\n\t\t\t)\n\t\t}\n\t} else {\n\t\tdeployScript = fmt.Sprintf(\n\t\t\tdeployScriptTmpl,\n\t\t\tagentGuestPath,\n\t\t\tfmt.Sprintf(\n\t\t\t\t\" && IMDS_STATE=\\\"run:%s\\\" %s --daemon agent\",\n\t\t\t\tstateId,\n\t\t\t\tagentGuestPath,\n\t\t\t),\n\t\t)\n\n\t\tif virt.CloudType == instance.BSD {\n\t\t\tdata.DeployBoot = fmt.Sprintf(\n\t\t\t\t\"[ -f %s ] && ! pgrep -f \\\"%s --daemon\\\" && \"+\n\t\t\t\t\t\"IMDS_STATE=\\\"boot:%s\\\" %s --daemon agent || true\",\n\t\t\t\tagentGuestPath,\n\t\t\t\tagentGuestPath,\n\t\t\t\tstateId,\n\t\t\t\tagentGuestPath,\n\t\t\t)\n\t\t} else {\n\t\t\tdata.DeployBoot = fmt.Sprintf(\n\t\t\t\t\"[ -f %s ] && ! pgrep -f \\\"^%s\\\" && \"+\n\t\t\t\t\t\"IMDS_STATE=\\\"boot:%s\\\" %s --daemon agent || true\",\n\t\t\t\tagentGuestPath,\n\t\t\t\tagentGuestPath,\n\t\t\t\tstateId,\n\t\t\t\tagentGuestPath,\n\t\t\t)\n\t\t}\n\t}\n\n\tfor _, mnt := range virt.Mounts {\n\t\tdata.HasMounts = true\n\n\t\tpth := utils.FilterPath(mnt.Path)\n\t\tif pth == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tdata.Mounts = append(data.Mounts, cloudMount{\n\t\t\tTag:  mnt.Name,\n\t\t\tPath: utils.FilterPath(mnt.Path),\n\t\t\tType: \"virtiofs\",\n\t\t\tOpts: \"defaults,_netdev\",\n\t\t\tFsck: \"0\",\n\t\t})\n\t}\n\n\twriteFiles = append(writeFiles, &fileData{\n\t\tContent:     deployScript,\n\t\tOwner:       owner,\n\t\tPath:        initGuestPath,\n\t\tPermissions: \"0755\",\n\t})\n\n\tfor _, authr := range authrs {\n\t\tswitch authr.Type {\n\t\tcase authority.SshKey:\n\t\t\tfor _, key := range strings.Split(authr.Key, \"\\n\") {\n\t\t\t\tdata.Keys = append(data.Keys, key)\n\t\t\t\tauthorizedKeys += key + \"\\n\"\n\t\t\t}\n\t\t\tbreak\n\t\tcase authority.SshCertificate:\n\t\t\ttrusted += authr.Certificate + \"\\n\"\n\t\t\tprincipals += strings.Join(authr.Principals, \"\\n\") + \"\\n\"\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif trusted == \"\" {\n\t\ttrusted = \"\\n\"\n\t}\n\tif principals == \"\" {\n\t\tprincipals = \"\\n\"\n\t}\n\n\twriteFiles = append(writeFiles, &fileData{\n\t\tContent:     trusted,\n\t\tOwner:       owner,\n\t\tPath:        \"/etc/ssh/trusted\",\n\t\tPermissions: \"0644\",\n\t})\n\twriteFiles = append(writeFiles, &fileData{\n\t\tContent:     principals,\n\t\tOwner:       owner,\n\t\tPath:        \"/etc/ssh/principals\",\n\t\tPermissions: \"0644\",\n\t})\n\twriteFiles = append(writeFiles, &fileData{\n\t\tContent:     authorizedKeys,\n\t\tOwner:       \"cloud:cloud\",\n\t\tPath:        \"/home/cloud/.ssh/authorized_keys\",\n\t\tPermissions: \"0600\",\n\t})\n\n\tdata.WriteFiles, err = generateWriteFiles(writeFiles)\n\tif err != nil {\n\t\treturn\n\t}\n\n\titems := []string{}\n\n\toutput := &bytes.Buffer{}\n\n\tvar templ *template.Template\n\tif virt.CloudType == instance.BSD {\n\t\ttempl = cloudBsdConfig\n\t} else {\n\t\ttempl = cloudConfig\n\t}\n\n\terr = templ.Execute(output, data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"cloudinit: Failed to exec cloud template\"),\n\t\t}\n\t\treturn\n\t}\n\titems = append(items, output.String())\n\n\tbuffer := &bytes.Buffer{}\n\tmessage := multipart.NewWriter(buffer)\n\tfor _, item := range items {\n\t\theader := textproto.MIMEHeader{}\n\n\t\theader.Set(\"Content-Transfer-Encoding\", \"base64\")\n\t\theader.Set(\"MIME-Version\", \"1.0\")\n\n\t\tif strings.HasPrefix(item, \"#!\") {\n\t\t\theader.Set(\"Content-Type\",\n\t\t\t\t\"text/x-shellscript; charset=\\\"utf-8\\\"\")\n\t\t} else {\n\t\t\theader.Set(\"Content-Type\",\n\t\t\t\t\"text/cloud-config; charset=\\\"utf-8\\\"\")\n\t\t}\n\n\t\tpart, e := message.CreatePart(header)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"cloudinit: Failed to create part\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t_, err = part.Write(\n\t\t\t[]byte(base64.StdEncoding.EncodeToString([]byte(item)) + \"\\n\"))\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(err, \"cloudinit: Failed to write part\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = message.Close()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"cloudinit: Failed to close message\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusrData = fmt.Sprintf(\n\t\tuserDataTmpl,\n\t\tmessage.Boundary(),\n\t\tbuffer.String(),\n\t)\n\n\treturn\n}\n\nfunc getNetData(db *database.Database, inst *instance.Instance,\n\tvirt *vm.VirtualMachine, dc *datacenter.Datacenter, zne *zone.Zone,\n\tvc *vpc.Vpc) (netData string, addr6, gateway6 net.IP, err error) {\n\n\tif len(virt.NetworkAdapters) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"cloudinit: Instance missing network adapters\"),\n\t\t}\n\t\treturn\n\t}\n\n\tadapter := virt.NetworkAdapters[0]\n\n\tif adapter.Vpc.IsZero() {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"cloudinit: Instance missing VPC\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif adapter.Subnet.IsZero() {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"cloudinit: Instance missing VPC subnet\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvcNet, err := vc.GetNetwork()\n\tif err != nil {\n\t\treturn\n\t}\n\n\taddr, gatewayAddr, err := vc.GetIp(db, inst.Subnet, inst.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcidr, _ := vcNet.Mask.Size()\n\n\taddr6 = vc.GetIp6(inst.Id)\n\tgateway6 = vc.GetGatewayIp6(inst.Id)\n\n\tdns1 := \"\"\n\tdns2 := \"\"\n\tdnsCloud := strings.Split(settings.Hypervisor.ImdsAddress, \"/\")[0]\n\tif inst.IsIpv6Only() {\n\t\tdns1 = utils.FilterIp(zne.GetDnsServerPrimary6())\n\t\tdns2 = utils.FilterIp(zne.GetDnsServerSecondary6())\n\t} else {\n\t\tdns1 = utils.FilterIp(zne.GetDnsServerPrimary())\n\t\tdns2 = utils.FilterIp(zne.GetDnsServerSecondary())\n\t}\n\n\tdata := netConfigData{\n\t\tMac:          adapter.MacAddress,\n\t\tAddress:      addr.String(),\n\t\tAddressCidr:  fmt.Sprintf(\"%s/%d\", addr.String(), cidr),\n\t\tNetmask:      net.IP(vcNet.Mask).String(),\n\t\tNetwork:      vcNet.IP.String(),\n\t\tGateway:      gatewayAddr.String(),\n\t\tAddress6:     addr6.String(),\n\t\tAddressCidr6: addr6.String() + \"/64\",\n\t\tGateway6:     gateway6.String(),\n\t\tDns1:         dnsCloud,\n\t\tDns2:         dns1,\n\t\tDns3:         dns2,\n\t}\n\n\tif virt.CloudType == instance.BSD {\n\t\tdata.Iface = \"vtnet0\"\n\t} else {\n\t\tdata.Iface = \"eth0\"\n\t}\n\n\tdata.Mtu = fmt.Sprintf(netMtu, dc.GetInstanceMtu())\n\n\toutput := &bytes.Buffer{}\n\n\tif settings.Hypervisor.CloudInitNetVer == 2 {\n\t\terr = netConfig2.Execute(output, data)\n\t} else {\n\t\tif virt.CloudType == instance.LinuxLegacy {\n\t\t\terr = netConfigLegacy.Execute(output, data)\n\t\t} else {\n\t\t\terr = netConfig.Execute(output, data)\n\t\t}\n\t}\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"cloudinit: Failed to exec cloud template\"),\n\t\t}\n\t\treturn\n\t}\n\n\tnetData = output.String()\n\n\treturn\n}\n\nfunc Write(db *database.Database, inst *instance.Instance,\n\tvirt *vm.VirtualMachine, dc *datacenter.Datacenter,\n\tzne *zone.Zone, vc *vpc.Vpc, initial bool) (err error) {\n\n\ttempDir := paths.GetTempDir()\n\tmetaPath := path.Join(tempDir, \"meta-data\")\n\tuserPath := path.Join(tempDir, \"user-data\")\n\tnetPath := path.Join(tempDir, \"network-config\")\n\tpciPath := path.Join(tempDir, \"pci\")\n\tinitPath := paths.GetInitPath(inst.Id)\n\n\tdefer os.RemoveAll(tempDir)\n\n\tvar deply *deployment.Deployment\n\tvar deployUnit *unit.Unit\n\tvar deploySpec *spec.Spec\n\tif !virt.Deployment.IsZero() {\n\t\tdeply, err = deployment.Get(db, virt.Deployment)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tdeploySpec, err = spec.Get(db, deply.Spec)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tdeployUnit, err = unit.Get(db, deply.Unit)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = utils.ExistsMkdir(paths.GetInitsPath(), 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(tempDir, 0700)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnetData, addr6, gateway6, err := getNetData(db, inst, virt, dc, zne, vc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tusrData, err := getUserData(db, inst, virt, dc, zne, vc,\n\t\tdeply, deployUnit, deploySpec, initial, addr6, gateway6)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tmetaData := fmt.Sprintf(metaDataTmpl,\n\t\tbson.NewObjectID().Hex(),\n\t\tstrings.Replace(inst.Name, \" \", \"_\", -1),\n\t)\n\n\terr = utils.CreateWrite(metaPath, metaData, 0644)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !virt.DhcpServer {\n\t\terr = utils.CreateWrite(netPath, netData, 0644)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = utils.CreateWrite(userPath, usrData, 0644)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif virt.CloudType == instance.BSD {\n\t\terr = utils.Exec(\"\", \"cp\",\n\t\t\tsettings.Hypervisor.AgentBsdHostPath, pciPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = utils.Exec(\"\", \"cp\",\n\t\t\tsettings.Hypervisor.AgentHostPath, pciPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\txorrisoPath, err := exec.LookPath(\"xorriso\")\n\tif err != nil {\n\t\txorrisoPath = \"\"\n\t\terr = nil\n\t}\n\n\tif xorrisoPath != \"\" {\n\t\targs := []string{\n\t\t\t\"-as\", \"mkisofs\",\n\t\t\t\"-output\", initPath,\n\t\t\t\"-volid\", \"cidata\",\n\t\t\t\"-joliet\",\n\t\t\t\"-rock\",\n\t\t\t\"user-data\",\n\t\t\t\"meta-data\",\n\t\t}\n\n\t\tif !virt.DhcpServer {\n\t\t\targs = append(args, \"network-config\")\n\t\t}\n\n\t\targs = append(args, pciPath)\n\n\t\t_, err = utils.ExecCombinedOutputLoggedDir(\n\t\t\tnil, tempDir,\n\t\t\t\"xorriso\",\n\t\t\targs...,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\targs := []string{\n\t\t\t\"-output\", initPath,\n\t\t\t\"-volid\", \"cidata\",\n\t\t\t\"-joliet\",\n\t\t\t\"-rock\",\n\t\t\t\"user-data\",\n\t\t\t\"meta-data\",\n\t\t}\n\n\t\tif !virt.DhcpServer {\n\t\t\targs = append(args, \"network-config\")\n\t\t}\n\n\t\targs = append(args, pciPath)\n\n\t\t_, err = utils.ExecCombinedOutputLoggedDir(\n\t\t\tnil, tempDir,\n\t\t\t\"genisoimage\",\n\t\t\targs...,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = utils.Chmod(initPath, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cloudinit/query.go",
    "content": "package cloudinit\n\nimport (\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/tools/commander\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype NetworkConfig struct {\n\tConfig  []NetworkInterface `json:\"config\"`\n\tVersion int                `json:\"version\"`\n}\n\ntype NetworkInterface struct {\n\tName           string   `json:\"name\"`\n\tMtu            int      `json:\"mtu,omitempty\"`\n\tType           string   `json:\"type\"`\n\tBondInterfaces []string `json:\"bond_interfaces,omitempty\"`\n\tSubnets        []Subnet `json:\"subnets,omitempty\"`\n\tVlanId         int      `json:\"vlan_id,omitempty\"`\n\tVlanLink       string   `json:\"vlan_link,omitempty\"`\n}\n\ntype Subnet struct {\n\tAddress string `json:\"address\"`\n\tGateway string `json:\"gateway,omitempty\"`\n\tType    string `json:\"type\"`\n}\n\ntype CloudConfig struct {\n\tCombinedCloudConfig CombinedCloudConfig `json:\"combined_cloud_config\"`\n\tMergedSystemConfig  MergedSystemConfig  `json:\"merged_system_cfg\"`\n}\n\ntype CombinedCloudConfig struct {\n\tNetwork NetworkConfig `json:\"network\"`\n}\n\ntype MergedSystemConfig struct {\n\tNetwork NetworkConfig `json:\"network\"`\n}\n\nfunc GetCloudConfig() (data *CloudConfig, err error) {\n\tret, err := commander.Exec(&commander.Opt{\n\t\tName: \"cloud-init\",\n\t\tArgs: []string{\n\t\t\t\"query\",\n\t\t\t\"--all\",\n\t\t},\n\t\tTimeout: 10 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tif ret != nil {\n\t\t\tlogrus.WithFields(ret.Map()).Warn(\n\t\t\t\t\"cloudinit: Cloud init query failed\")\n\t\t}\n\t\treturn\n\t}\n\n\tdata = &CloudConfig{}\n\terr = json.Unmarshal(ret.Output, &data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrapf(err, \"cloudinit: Failed to parse cloudinit query\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cloudinit/utils.go",
    "content": "package cloudinit\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"text/template\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype fileData struct {\n\tContent     string\n\tOwner       string\n\tPath        string\n\tPermissions string\n}\n\ntype writeFileData struct {\n\tFiles []*fileData\n}\n\nconst writeFileTmpl = `{{range .Files}}\n{{- if eq .Content \"\"}}\n  - content: \"\"\n{{- else}}\n  - encoding: base64\n    content: {{.Content}}\n{{- end}}\n    owner: {{.Owner}}\n    path: {{.Path}}\n    permissions: \"{{.Permissions}}\"\n{{- end}}`\n\nvar (\n\twriteFile = template.Must(template.New(\"write_file\").Parse(writeFileTmpl))\n)\n\nfunc generateWriteFiles(filesData []*fileData) (output string, err error) {\n\tfor _, file := range filesData {\n\t\tfile.Content = base64.StdEncoding.EncodeToString([]byte(file.Content))\n\t}\n\n\tdata := writeFileData{\n\t\tFiles: filesData,\n\t}\n\n\toutputBuf := &bytes.Buffer{}\n\n\terr = writeFile.Execute(outputBuf, data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"cloudinit: Failed to exec write file template\"),\n\t\t}\n\t\treturn\n\t}\n\n\toutput = outputBuf.String()\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/backup.go",
    "content": "package cmd\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/backup\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc Backup() (err error) {\n\tdest := flag.Arg(1)\n\n\tif dest == \"\" {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"cmd: Missing backup destination path\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfmt.Println(\"Feature comming soon\")\n\tos.Exit(1)\n\n\tback := backup.New(dest)\n\n\terr = back.Run()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/dhcp.go",
    "content": "package cmd\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/dhcpc\"\n\t\"github.com/pritunl/pritunl-cloud/dhcps\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc DhcpClient() (err error) {\n\terr = dhcpc.Main()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Dhcp4Server() (err error) {\n\tconfig := strings.Trim(os.Getenv(\"CONFIG\"), \"'\")\n\tserver4 := &dhcps.Server4{}\n\n\terr = json.Unmarshal([]byte(config), server4)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"cmd: Failed to parse DHCP4 configuration\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor {\n\t\terr = server4.Start()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"cmd: DHCP4 server error\")\n\t\t}\n\n\t\ttime.Sleep(3 * time.Second)\n\t}\n}\n\nfunc Dhcp6Server() (err error) {\n\tconfig := strings.Trim(os.Getenv(\"CONFIG\"), \"'\")\n\tserver6 := &dhcps.Server6{}\n\n\terr = json.Unmarshal([]byte(config), server6)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"cmd: Failed to parse DHCP6 configuration\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor {\n\t\terr = server6.Start()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"cmd: DHCP6 server error\")\n\t\t}\n\n\t\ttime.Sleep(3 * time.Second)\n\t}\n}\n\nfunc NdpServer() (err error) {\n\tconfig := strings.Trim(os.Getenv(\"CONFIG\"), \"'\")\n\tserverNdp := &dhcps.ServerNdp{}\n\n\terr = json.Unmarshal([]byte(config), serverNdp)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"cmd: Failed to parse NDP configuration\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor {\n\t\terr = serverNdp.Start()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"cmd: NDP server error\")\n\t\t}\n\n\t\ttime.Sleep(3 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "cmd/imds.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/imds/server\"\n)\n\nfunc ImdsServer() (err error) {\n\terr = server.Main()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/instance.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc StartInstance(name string) (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tinstances, err := instance.GetAll(db, &bson.M{\n\t\t\"name\": name,\n\t})\n\n\tfor _, inst := range instances {\n\t\tif inst.Action != instance.Start {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t}).Info(\"cmd: Starting instance\")\n\n\t\t\tinst.Action = instance.Start\n\t\t\terr = inst.CommitFields(db, set.NewSet(\"action\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc StopInstance(name string) (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tinstances, err := instance.GetAll(db, &bson.M{\n\t\t\"name\": name,\n\t})\n\n\tfor _, inst := range instances {\n\t\tif inst.Action != instance.Stop {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t}).Info(\"cmd: Stopping instance\")\n\n\t\t\tinst.Action = instance.Stop\n\t\t\terr = inst.CommitFields(db, set.NewSet(\"action\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/log.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/log\"\n)\n\nfunc ClearLogs() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\terr = log.Clear(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogrus.Info(\"cmd.log: Logs cleared\")\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/mtu.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/mtu\"\n)\n\nfunc MtuCheck() (err error) {\n\tchk := mtu.NewCheck()\n\n\terr = chk.Run()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/node.go",
    "content": "package cmd\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/config\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/defaults\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/relations/definitions\"\n\t\"github.com/pritunl/pritunl-cloud/router\"\n\t\"github.com/pritunl/pritunl-cloud/setup\"\n\t\"github.com/pritunl/pritunl-cloud/sync\"\n\t\"github.com/pritunl/pritunl-cloud/task\"\n\t\"github.com/pritunl/pritunl-cloud/upgrade\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc Node() (err error) {\n\terr = upgrade.Upgrade()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tobjId, err := bson.ObjectIDFromHex(config.Config.NodeId)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"cmd: Failed to parse ObjectId\"),\n\t\t}\n\t\treturn\n\t}\n\n\tnde := &node.Node{\n\t\tId: objId,\n\t}\n\terr = nde.Init()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefinitions.Init()\n\n\terr = setup.Iptables()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = defaults.Defaults()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsync.Init()\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"production\": constants.Production,\n\t\t\"types\":      nde.Types,\n\t}).Info(\"router: Starting node\")\n\n\troutr := &router.Router{}\n\troutr.Init()\n\n\terr = task.Init()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\terr = routr.Run()\n\t\tif err != nil && !constants.Shutdown {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\tsig := make(chan os.Signal, 2)\n\tsignal.Notify(sig, os.Interrupt, syscall.SIGTERM)\n\t<-sig\n\n\tlogrus.Info(\"cmd.node: Shutting down\")\n\n\tconstants.Shutdown = true\n\tgo routr.Shutdown()\n\n\tif constants.Production && !constants.FastExit {\n\t\ttime.Sleep(20 * time.Second)\n\t}\n\n\tconstants.Interrupt = true\n\n\tif !constants.Production || constants.FastExit {\n\t\ttime.Sleep(300 * time.Millisecond)\n\t} else {\n\t\ttime.Sleep(10 * time.Second)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/optimize.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc Optimize() (err error) {\n\terr = optimizeNested()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc optimizeNested() (err error) {\n\tresp, err := utils.ConfirmDefault(\n\t\t\"Enable nested virtualization\",\n\t\ttrue,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpth := \"/etc/modprobe.d/kvm-nested.conf\"\n\tif resp {\n\t\terr = utils.CreateWrite(\n\t\t\tpth,\n\t\t\t\"options kvm-intel nested=1\\noptions kvm-amd nested=1\\n\",\n\t\t\t0644,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"path\": pth,\n\t\t}).Info(\"sysctl: Nested virtualization enabled\")\n\t} else {\n\t\texists, e := utils.Exists(pth)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif exists {\n\t\t\terr = utils.Remove(pth)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"path\": pth,\n\t\t\t}).Info(\"sysctl: Nested virtualization disabled\")\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "cmd/settings.go",
    "content": "package cmd\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/config\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc Mongo() (err error) {\n\tmongodbUri := flag.Arg(1)\n\n\terr = config.Load()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tconfig.Config.MongoUri = mongodbUri\n\n\terr = config.Save()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"mongo_uri\": config.Config.MongoUri,\n\t}).Info(\"cmd: Set MongoDB URI\")\n\n\treturn\n}\n\nfunc ResetNodeWeb() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\terr = config.Load()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tndeId, err := bson.ObjectIDFromHex(config.Config.NodeId)\n\tif err != nil || ndeId.IsZero() {\n\t\terr = nil\n\t\tlogrus.Info(\"cmd: Node not initialized\")\n\t\treturn\n\t}\n\n\tcoll := db.Nodes()\n\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\": ndeId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"types\":              []string{\"admin\", \"hypervisor\"},\n\t\t\t\"port\":               443,\n\t\t\t\"protocol\":           \"https\",\n\t\t\t\"no_redirect_server\": false,\n\t\t\t\"admin_domain\":       \"\",\n\t\t\t\"user_domain\":        \"\",\n\t\t\t\"webauthn_domain\":    \"\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"node_id\": config.Config.NodeId,\n\t}).Info(\"cmd: Node web server reset\")\n\n\treturn\n}\n\nfunc DefaultPassword() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tusr, err := user.GetUsername(db, user.Local, \"pritunl\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif usr.DefaultPassword == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"cmd: No default password available\"),\n\t\t}\n\t\treturn\n\t}\n\n\tlogrus.Info(\"cmd: Get default password\")\n\n\tfmt.Println(\"Username: pritunl\")\n\tfmt.Println(\"Password: \" + usr.DefaultPassword)\n\n\treturn\n}\n\nfunc ResetPassword() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tcoll := db.Users()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"username\": \"pritunl\",\n\t})\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tusr := user.User{\n\t\tType:          user.Local,\n\t\tUsername:      \"pritunl\",\n\t\tAdministrator: \"super\",\n\t}\n\n\t_, err = usr.Validate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = usr.GenerateDefaultPassword()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = usr.Insert(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogrus.Info(\"cmd: Password reset\")\n\n\tfmt.Println(\"Username: pritunl\")\n\tfmt.Println(\"Password: \" + usr.DefaultPassword)\n\n\treturn\n}\n\nfunc DisablePolicies() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tcoll := db.Policies()\n\n\t_, err = coll.UpdateMany(db, &bson.M{}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"disabled\": true,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tlogrus.Info(\"cmd: Policies disabled\")\n\n\treturn\n}\n\nfunc DisableFirewall() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\terr = config.Load()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tndeId, err := bson.ObjectIDFromHex(config.Config.NodeId)\n\tif err != nil || ndeId.IsZero() {\n\t\terr = nil\n\t\tlogrus.Info(\"cmd: Node not initialized\")\n\t\treturn\n\t}\n\n\tcoll := db.Nodes()\n\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\": ndeId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"firewall\": false,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"node_id\": config.Config.NodeId,\n\t}).Info(\"cmd: Firewall disabled\")\n\n\treturn\n}\n\nfunc SettingsSet() (err error) {\n\tgroup := flag.Arg(1)\n\tkey := flag.Arg(2)\n\tval := flag.Arg(3)\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tvar valParsed interface{}\n\terr = json.Unmarshal([]byte(val), &valParsed)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"cmd.settings: Failed to parse value\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = settings.Set(db, group, key, valParsed)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc SettingsUnset() (err error) {\n\tgroup := flag.Arg(1)\n\tkey := flag.Arg(2)\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\terr = settings.Unset(db, group, key)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "colorize/colorize.go",
    "content": "package colorize\n\ntype Color string\n\nconst (\n\tNone       = \"\"\n\tBold       = \"\\033[1m\"\n\tBlack      = \"\\033[0;30m\"\n\tBlackBold  = \"\\033[1;30m\"\n\tRed        = \"\\033[0;31m\"\n\tRedBold    = \"\\033[1;31m\"\n\tGreen      = \"\\033[0;32m\"\n\tGreenBold  = \"\\033[1;32m\"\n\tYellow     = \"\\033[0;33m\"\n\tYellowBold = \"\\033[1;33m\"\n\tBlue       = \"\\033[0;34m\"\n\tBlueBold   = \"\\033[1;34m\"\n\tPurple     = \"\\033[0;35m\"\n\tPurpleBold = \"\\033[1;35m\"\n\tCyan       = \"\\033[0;36m\"\n\tCyanBold   = \"\\033[1;36m\"\n\tWhite      = \"\\033[0;37m\"\n\tWhiteBold  = \"\\033[1;37m\"\n\tBlackBg    = \"\\033[40m\"\n\tRedBg      = \"\\033[41m\"\n\tGreenBg    = \"\\033[42m\"\n\tYellowBg   = \"\\033[43m\"\n\tBlueBg     = \"\\033[44m\"\n\tPurpleBg   = \"\\033[45m\"\n\tCyanBg     = \"\\033[46m\"\n\tWhiteBg    = \"\\033[47m\"\n)\n\nfunc ColorString(input string, fg Color, bg Color) (str string) {\n\tstr = string(fg) + string(bg) + input + \"\\033[0m\"\n\treturn\n}\n"
  },
  {
    "path": "completion/completion.go",
    "content": "package completion\n\nimport (\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/plan\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\ntype Completion struct {\n\tOrganizations []*database.Named         `json:\"organizations\"`\n\tAuthorities   []*database.Named         `json:\"authorities\"`\n\tPolicies      []*database.Named         `json:\"policies\"`\n\tDomains       []*domain.Completion      `json:\"domains\"`\n\tVpcs          []*vpc.Completion         `json:\"vpcs\"`\n\tDatacenters   []*datacenter.Completion  `json:\"datacenters\"`\n\tBlocks        []*block.Completion       `json:\"blocks\"`\n\tNodes         []*node.Completion        `json:\"nodes\"`\n\tPools         []*pool.Completion        `json:\"pools\"`\n\tZones         []*zone.Completion        `json:\"zones\"`\n\tShapes        []*shape.Completion       `json:\"shapes\"`\n\tImages        []*image.Completion       `json:\"images\"`\n\tStorages      []*storage.Completion     `json:\"storages\"`\n\tBuilds        []*Build                  `json:\"builds\"`\n\tInstances     []*instance.Completion    `json:\"instances\"`\n\tPlans         []*plan.Completion        `json:\"plans\"`\n\tCertificates  []*certificate.Completion `json:\"certificates\"`\n\tSecrets       []*secret.Completion      `json:\"secrets\"`\n\tPods          []*pod.Completion         `json:\"pods\"`\n\tUnits         []*unit.Completion        `json:\"units\"`\n}\n\ntype Build struct {\n\tId           bson.ObjectID `json:\"id\"`\n\tName         string        `json:\"name\"`\n\tPod          bson.ObjectID `json:\"pod\"`\n\tOrganization bson.ObjectID `json:\"organization\"`\n\tTags         []*BuildTag   `json:\"tags\"`\n}\n\ntype BuildTag struct {\n\tTag       string    `json:\"tag\"`\n\tTimestamp time.Time `json:\"timestamp\"`\n}\n\nfunc get(db *database.Database, coll *database.Collection,\n\tquery bson.M, projection *bson.M, sort *bson.D, new func() interface{},\n\tadd func(interface{})) (err error) {\n\n\topts := options.Find().\n\t\tSetProjection(projection)\n\tif sort != nil {\n\t\topts.SetSort(sort)\n\t}\n\n\tcursor, err := coll.Find(db, query, opts)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\titem := new()\n\t\terr = cursor.Decode(item)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tadd(item)\n\t}\n\n\treturn\n}\n\nfunc GetCompletion(db *database.Database, orgId bson.ObjectID,\n\torgRoles []string) (cmpl *Completion, err error) {\n\n\tcmpl = &Completion{}\n\tquery := bson.M{}\n\tif !orgId.IsZero() {\n\t\tquery[\"organization\"] = orgId\n\t}\n\n\treleaseImages := map[string][]*image.Completion{}\n\totherImages := []*image.Completion{}\n\tunitsMap := map[bson.ObjectID]*unit.Completion{}\n\tbuildsMap := map[bson.ObjectID]*Build{}\n\tdeployments := []*deployment.Deployment{}\n\n\tvar wg sync.WaitGroup\n\terrChan := make(chan error, 16)\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar orgQuery bson.M\n\t\tif !orgId.IsZero() {\n\t\t\tif orgRoles == nil {\n\t\t\t\torgRoles = []string{}\n\t\t\t}\n\n\t\t\torgQuery = bson.M{\n\t\t\t\t\"roles\": bson.M{\n\t\t\t\t\t\"$in\": orgRoles,\n\t\t\t\t},\n\t\t\t}\n\t\t} else {\n\t\t\torgQuery = bson.M{}\n\t\t}\n\n\t\tvar orgs []*database.Named\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Organizations(),\n\t\t\torgQuery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":  1,\n\t\t\t\t\"name\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &database.Named{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\torgs = append(\n\t\t\t\t\torgs,\n\t\t\t\t\titem.(*database.Named),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Organizations = orgs\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar authrs []*database.Named\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Authorities(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":  1,\n\t\t\t\t\"name\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &database.Named{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tauthrs = append(\n\t\t\t\t\tauthrs,\n\t\t\t\t\titem.(*database.Named),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Authorities = authrs\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar domains []*domain.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Domains(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"organization\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &domain.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tdomains = append(\n\t\t\t\t\tdomains,\n\t\t\t\t\titem.(*domain.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Domains = domains\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar vpcs []*vpc.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Vpcs(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"organization\": 1,\n\t\t\t\t\"vpc_id\":       1,\n\t\t\t\t\"network\":      1,\n\t\t\t\t\"subnets\":      1,\n\t\t\t\t\"datacenter\":   1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &vpc.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tvpcs = append(\n\t\t\t\t\tvpcs,\n\t\t\t\t\titem.(*vpc.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Vpcs = vpcs\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar dcQuery bson.M\n\t\tif !orgId.IsZero() {\n\t\t\tdcQuery = bson.M{\n\t\t\t\t\"$or\": []bson.M{\n\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\"match_organizations\": false,\n\t\t\t\t\t},\n\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\"organizations\": orgId,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t} else {\n\t\t\tdcQuery = bson.M{}\n\t\t}\n\n\t\tvar datacenters []*datacenter.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Datacenters(),\n\t\t\tdcQuery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"network_mode\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &datacenter.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tdatacenters = append(\n\t\t\t\t\tdatacenters,\n\t\t\t\t\titem.(*datacenter.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Datacenters = datacenters\n\t}()\n\n\tif orgId.IsZero() {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\tvar blocks []*block.Completion\n\t\t\terr := get(\n\t\t\t\tdb,\n\t\t\t\tdb.Blocks(),\n\t\t\t\tquery,\n\t\t\t\t&bson.M{\n\t\t\t\t\t\"_id\":  1,\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t\t\"type\": 1,\n\t\t\t\t},\n\t\t\t\t&bson.D{\n\t\t\t\t\t{\"name\", 1},\n\t\t\t\t},\n\t\t\t\tfunc() interface{} {\n\t\t\t\t\treturn &block.Completion{}\n\t\t\t\t},\n\t\t\t\tfunc(item interface{}) {\n\t\t\t\t\tblocks = append(\n\t\t\t\t\t\tblocks,\n\t\t\t\t\t\titem.(*block.Completion),\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcmpl.Blocks = blocks\n\t\t}()\n\t}\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar nodes []*node.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Nodes(),\n\t\t\tbson.M{},\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":   1,\n\t\t\t\t\"name\":  1,\n\t\t\t\t\"zone\":  1,\n\t\t\t\t\"types\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &node.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tnde := item.(*node.Completion)\n\n\t\t\t\tif !nde.IsHypervisor() {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tnodes = append(\n\t\t\t\t\tnodes,\n\t\t\t\t\tnde,\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Nodes = nodes\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar pools []*pool.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Pools(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":  1,\n\t\t\t\t\"name\": 1,\n\t\t\t\t\"zone\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &pool.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tpools = append(\n\t\t\t\t\tpools,\n\t\t\t\t\titem.(*pool.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Pools = pools\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar zones []*zone.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Zones(),\n\t\t\tbson.M{},\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":        1,\n\t\t\t\t\"name\":       1,\n\t\t\t\t\"datacenter\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &zone.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tzones = append(\n\t\t\t\t\tzones,\n\t\t\t\t\titem.(*zone.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Zones = zones\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar shapes []*shape.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Shapes(),\n\t\t\tbson.M{},\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":        1,\n\t\t\t\t\"name\":       1,\n\t\t\t\t\"datacenter\": 1,\n\t\t\t\t\"flexible\":   1,\n\t\t\t\t\"memory\":     1,\n\t\t\t\t\"processors\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &shape.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tshapes = append(\n\t\t\t\t\tshapes,\n\t\t\t\t\titem.(*shape.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Shapes = shapes\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Images(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"release\":      1,\n\t\t\t\t\"build\":        1,\n\t\t\t\t\"organization\": 1,\n\t\t\t\t\"deployment\":   1,\n\t\t\t\t\"type\":         1,\n\t\t\t\t\"firmware\":     1,\n\t\t\t\t\"key\":          1,\n\t\t\t\t\"storage\":      1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &image.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\timg := item.(*image.Completion)\n\t\t\t\tif img.Release != \"\" {\n\t\t\t\t\treleaseImages[img.Release] = append(\n\t\t\t\t\t\treleaseImages[img.Release],\n\t\t\t\t\t\timg,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\totherImages = append(otherImages, img)\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar storages []*storage.Completion\n\t\tif orgId.IsZero() {\n\t\t\terr := get(\n\t\t\t\tdb,\n\t\t\t\tdb.Storages(),\n\t\t\t\tbson.M{},\n\t\t\t\t&bson.M{\n\t\t\t\t\t\"_id\":  1,\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t\t\"type\": 1,\n\t\t\t\t},\n\t\t\t\t&bson.D{\n\t\t\t\t\t{\"name\", 1},\n\t\t\t\t},\n\t\t\t\tfunc() interface{} {\n\t\t\t\t\treturn &storage.Completion{}\n\t\t\t\t},\n\t\t\t\tfunc(item interface{}) {\n\t\t\t\t\tstorages = append(\n\t\t\t\t\t\tstorages,\n\t\t\t\t\t\titem.(*storage.Completion),\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tcmpl.Storages = storages\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar instances []*instance.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Instances(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"organization\": 1,\n\t\t\t\t\"zone\":         1,\n\t\t\t\t\"vpc\":          1,\n\t\t\t\t\"subnet\":       1,\n\t\t\t\t\"node\":         1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &instance.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tinstances = append(\n\t\t\t\t\tinstances,\n\t\t\t\t\titem.(*instance.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Instances = instances\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar plans []*plan.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Plans(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"organization\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &plan.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tplans = append(\n\t\t\t\t\tplans,\n\t\t\t\t\titem.(*plan.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Plans = plans\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar certificates []*certificate.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Certificates(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"organization\": 1,\n\t\t\t\t\"type\":         1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &certificate.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tcertificates = append(\n\t\t\t\t\tcertificates,\n\t\t\t\t\titem.(*certificate.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Certificates = certificates\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar secrets []*secret.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Secrets(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"organization\": 1,\n\t\t\t\t\"type\":         1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &secret.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tsecrets = append(\n\t\t\t\t\tsecrets,\n\t\t\t\t\titem.(*secret.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Secrets = secrets\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar pods []*pod.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Pods(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"organization\": 1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &pod.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tpods = append(\n\t\t\t\t\tpods,\n\t\t\t\t\titem.(*pod.Completion),\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Pods = pods\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar units []*unit.Completion\n\t\terr := get(\n\t\t\tdb,\n\t\t\tdb.Units(),\n\t\t\tquery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"pod\":          1,\n\t\t\t\t\"organization\": 1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"kind\":         1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &unit.Completion{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tunt := item.(*unit.Completion)\n\n\t\t\t\tunits = append(\n\t\t\t\t\tunits,\n\t\t\t\t\tunt,\n\t\t\t\t)\n\n\t\t\t\tunitsMap[unt.Id] = unt\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\tcmpl.Units = units\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tvar deplyQuery bson.M\n\t\tif !orgId.IsZero() {\n\t\t\tdeplyQuery = bson.M{\n\t\t\t\t\"organizations\": orgId,\n\t\t\t\t\"kind\":          \"image\",\n\t\t\t}\n\t\t} else {\n\t\t\tdeplyQuery = bson.M{\n\t\t\t\t\"kind\": \"image\",\n\t\t\t}\n\t\t}\n\n\t\terr = get(\n\t\t\tdb,\n\t\t\tdb.Deployments(),\n\t\t\tdeplyQuery,\n\t\t\t&bson.M{\n\t\t\t\t\"_id\":          1,\n\t\t\t\t\"name\":         1,\n\t\t\t\t\"pod\":          1,\n\t\t\t\t\"unit\":         1,\n\t\t\t\t\"organization\": 1,\n\t\t\t\t\"kind\":         1,\n\t\t\t\t\"state\":        1,\n\t\t\t\t\"status\":       1,\n\t\t\t\t\"image\":        1,\n\t\t\t\t\"image_data\":   1,\n\t\t\t\t\"tags\":         1,\n\t\t\t},\n\t\t\t&bson.D{\n\t\t\t\t{\"timestamp\", -1},\n\t\t\t},\n\t\t\tfunc() interface{} {\n\t\t\t\treturn &deployment.Deployment{}\n\t\t\t},\n\t\t\tfunc(item interface{}) {\n\t\t\t\tdeployments = append(deployments, item.(*deployment.Deployment))\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t}()\n\n\twg.Wait()\n\tclose(errChan)\n\n\tfor e := range errChan {\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, imgs := range releaseImages {\n\t\ttags := []string{\"latest\"}\n\t\tvar latestImg *image.Completion\n\n\t\tfor _, img := range imgs {\n\t\t\ttags = append(tags, img.Build)\n\n\t\t\tif latestImg == nil {\n\t\t\t\tlatestImg = img\n\t\t\t} else if img.Build > latestImg.Build {\n\t\t\t\tlatestImg = img\n\t\t\t}\n\t\t}\n\n\t\tlatestImg.Name = latestImg.Release\n\t\tlatestImg.Tags = tags\n\t\tcmpl.Images = append(cmpl.Images, latestImg)\n\t}\n\tsort.Sort(image.CompletionsSort(cmpl.Images))\n\n\tcmpl.Images = append(\n\t\tcmpl.Images,\n\t\totherImages...,\n\t)\n\n\tfor _, deply := range deployments {\n\t\tif !deply.ImageReady() {\n\t\t\treturn\n\t\t}\n\n\t\tunt := unitsMap[deply.Unit]\n\t\tif unt == nil {\n\t\t\treturn\n\t\t}\n\n\t\tbuild := buildsMap[deply.Unit]\n\t\tif build == nil {\n\t\t\tbuild = &Build{\n\t\t\t\tId:           deply.Unit,\n\t\t\t\tName:         unt.Name,\n\t\t\t\tPod:          unt.Pod,\n\t\t\t\tOrganization: unt.Organization,\n\t\t\t\tTags: []*BuildTag{\n\t\t\t\t\t&BuildTag{\n\t\t\t\t\t\tTag:       \"latest\",\n\t\t\t\t\t\tTimestamp: deply.Timestamp,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\tbuildsMap[deply.Unit] = build\n\t\t}\n\n\t\tfor _, tag := range deply.Tags {\n\t\t\tbuild.Tags = append(build.Tags, &BuildTag{\n\t\t\t\tTag:       tag,\n\t\t\t\tTimestamp: deply.Timestamp,\n\t\t\t})\n\t\t}\n\t}\n\n\tfor _, build := range buildsMap {\n\t\tcmpl.Builds = append(cmpl.Builds, build)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "compositor/compositor.go",
    "content": "package compositor\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os/user\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nconst (\n\tProcDir = \"/proc\"\n)\n\nfunc GetEnv(username, driPath string, driPrime bool) (\n\tenvData string, err error) {\n\n\tdesktopEnv := settings.Hypervisor.DesktopEnv\n\n\tfiles, err := ioutil.ReadDir(ProcDir)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"compositor: Failed to read proc directory\"),\n\t\t}\n\t\treturn\n\t}\n\n\tunixUser, err := user.Lookup(username)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"compositor: Failed to find GUI user\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, file := range files {\n\t\tpidStr := file.Name()\n\n\t\t_, e := strconv.Atoi(pidStr)\n\t\tif e != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tcmdlinePath := path.Join(ProcDir, pidStr, \"cmdline\")\n\t\tenvironPath := path.Join(ProcDir, pidStr, \"environ\")\n\t\tloginuidPath := path.Join(ProcDir, pidStr, \"loginuid\")\n\n\t\texists, e := utils.Exists(cmdlinePath)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\texists, err = utils.Exists(environPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\texists, err = utils.Exists(loginuidPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tprocUid, e := ioutil.ReadFile(loginuidPath)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrapf(\n\t\t\t\t\te,\n\t\t\t\t\t\"compositor: Failed to read proc '%s' loginuid\",\n\t\t\t\t\tpidStr,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif strings.TrimSpace(string(procUid)) != unixUser.Uid {\n\t\t\tcontinue\n\t\t}\n\n\t\tprocCmd, e := ioutil.ReadFile(cmdlinePath)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrapf(\n\t\t\t\t\te,\n\t\t\t\t\t\"compositor: Failed to read proc '%s' cmdline\",\n\t\t\t\t\tpidStr,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif !strings.Contains(string(procCmd), desktopEnv) &&\n\t\t\t!strings.Contains(string(procCmd), \"xdg\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tprocEnv, e := ioutil.ReadFile(environPath)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrapf(\n\t\t\t\t\te,\n\t\t\t\t\t\"compositor: Failed to read proc '%s' environ\",\n\t\t\t\t\tpidStr,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tdisplayEnv := \"\"\n\t\twaylandDisplayEnv := \"\"\n\t\txauthEnv := \"\"\n\n\t\tenviron := strings.ReplaceAll(string(procEnv), \"\\000\", \"\\n\")\n\t\tfor _, env := range strings.Split(environ, \"\\n\") {\n\t\t\tenvSpl := strings.SplitN(env, \"=\", 2)\n\t\t\tif len(envSpl) != 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif strings.ToLower(envSpl[0]) == \"display\" {\n\t\t\t\tdisplayEnv = envSpl[1]\n\t\t\t} else if strings.ToLower(envSpl[0]) == \"wayland_display\" {\n\t\t\t\twaylandDisplayEnv = envSpl[1]\n\t\t\t} else if strings.ToLower(envSpl[0]) == \"xauthority\" {\n\t\t\t\txauthEnv = envSpl[1]\n\t\t\t}\n\t\t}\n\n\t\tif displayEnv == \"\" || xauthEnv == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tenvData += fmt.Sprintf(\"\\nEnvironment=\\\"DISPLAY=%s\\\"\", displayEnv)\n\t\tif waylandDisplayEnv != \"\" {\n\t\t\tenvData += fmt.Sprintf(\n\t\t\t\t\"\\nEnvironment=\\\"WAYLAND_DISPLAY=%s\\\"\", waylandDisplayEnv)\n\n\t\t\tenvData += \"\\nEnvironment=\\\"GDK_BACKEND=wayland\\\"\"\n\t\t\tenvData += \"\\nEnvironment=\\\"XDG_SESSION_TYPE=wayland\\\"\"\n\t\t\tenvData += fmt.Sprintf(\n\t\t\t\t\"\\nEnvironment=\\\"XDG_RUNTIME_DIR=/run/user/%s\\\"\", unixUser.Uid)\n\t\t\tenvData += \"\\nEnvironment=\\\"XDG_SESSION_CLASS=user\\\"\"\n\t\t}\n\n\t\tenvData += fmt.Sprintf(\"\\nEnvironment=\\\"XAUTHORITY=%s\\\"\", xauthEnv)\n\n\t\tif driPath != \"\" {\n\t\t\tenvData += fmt.Sprintf(\n\t\t\t\t\"\\nEnvironment=\\\"DRI_RENDER_DEVICE=%s\\\"\",\n\t\t\t\tdriPath,\n\t\t\t)\n\t\t}\n\t\tif driPrime {\n\t\t\tenvData += \"\\nEnvironment=\\\"DRI_PRIME=1\\\"\"\n\t\t\tenvData += \"\\nEnvironment=\\\"__NV_PRIME_RENDER_OFFLOAD=1\\\"\"\n\t\t\tenvData += \"\\nEnvironment=\\\"__GLX_VENDOR_LIBRARY_NAME=nvidia\\\"\"\n\t\t}\n\n\t\treturn\n\t}\n\n\terr = &errortypes.ReadError{\n\t\terrors.New(\"compositor: Failed to find X environment\"),\n\t}\n\treturn\n}\n"
  },
  {
    "path": "config/config.go",
    "content": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tConfig            = &ConfigData{}\n\tStaticRoot        = \"\"\n\tStaticTestingRoot = \"\"\n\tDefaultMongoUri   = \"mongodb://localhost:27017/pritunl-cloud\"\n)\n\ntype ConfigData struct {\n\tpath     string `json:\"-\"`\n\tloaded   bool   `json:\"-\"`\n\tMongoUri string `json:\"mongo_uri\"`\n\tNodeId   string `json:\"node_id\"`\n}\n\nfunc (c *ConfigData) Save() (err error) {\n\tif !c.loaded {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.New(\"config: Config file has not been loaded\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdata, err := json.MarshalIndent(c, \"\", \"\\t\")\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"config: File marshal error\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(filepath.Dir(constants.ConfPath), 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = ioutil.WriteFile(constants.ConfPath, data, 0600)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"config: File write error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Load() (err error) {\n\tdata := &ConfigData{}\n\n\t_, err = os.Stat(\"/cloud/pritunl-cloud.json\")\n\tif err == nil {\n\t\tconstants.ConfPath = \"/cloud/pritunl-cloud.json\"\n\t\tconstants.DefaultRoot = \"/cloud\"\n\t\tconstants.DefaultCache = \"/cloud/cache\"\n\t}\n\n\t_, err = os.Stat(constants.ConfPath)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\terr = nil\n\t\t\tdata.loaded = true\n\t\t\tConfig = data\n\t\t} else {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(err, \"config: File stat error\"),\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\tfile, err := ioutil.ReadFile(constants.ConfPath)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"config: File read error\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = json.Unmarshal(file, data)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"config: File unmarshal error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdata.loaded = true\n\n\tConfig = data\n\n\treturn\n}\n\nfunc Save() (err error) {\n\terr = Config.Save()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetModTime() (mod time.Time, err error) {\n\tstat, err := os.Stat(constants.ConfPath)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"config: Failed to stat conf file\"),\n\t\t}\n\t\treturn\n\t}\n\n\tmod = stat.ModTime()\n\n\treturn\n}\n\nfunc init() {\n\tmodule := requires.New(\"config\")\n\n\tmodule.Handler = func() (err error) {\n\t\tfor _, pth := range constants.StaticRoot {\n\t\t\texists, _ := utils.ExistsDir(pth)\n\t\t\tif exists {\n\t\t\t\tStaticRoot = pth\n\t\t\t}\n\t\t}\n\t\tif StaticRoot == \"\" {\n\t\t\tStaticRoot = constants.StaticRoot[len(constants.StaticRoot)-1]\n\t\t}\n\n\t\tfor _, pth := range constants.StaticTestingRoot {\n\t\t\texists, _ := utils.ExistsDir(pth)\n\t\t\tif exists {\n\t\t\t\tStaticTestingRoot = pth\n\t\t\t}\n\t\t}\n\t\tif StaticTestingRoot == \"\" {\n\t\t\tStaticTestingRoot = constants.StaticTestingRoot[len(\n\t\t\t\tconstants.StaticTestingRoot)-1]\n\t\t}\n\n\t\terr = Load()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tsave := false\n\n\t\tif Config.NodeId == \"\" {\n\t\t\tsave = true\n\t\t\tConfig.NodeId = bson.NewObjectID().Hex()\n\t\t}\n\n\t\tif Config.MongoUri == \"\" {\n\t\t\tsave = true\n\n\t\t\tdata, err := utils.ReadExists(\"/var/lib/mongo/credentials.txt\")\n\t\t\tif err != nil {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\tlines := strings.Split(string(data), \"\\n\")\n\t\t\t\tfor _, line := range lines {\n\t\t\t\t\tif strings.HasPrefix(strings.TrimSpace(line),\n\t\t\t\t\t\t\"mongodb://pritunl-cloud\") {\n\n\t\t\t\t\t\tConfig.MongoUri = strings.TrimSpace(line)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif Config.MongoUri == \"\" {\n\t\t\t\tConfig.MongoUri = DefaultMongoUri\n\t\t\t}\n\t\t}\n\n\t\tif save {\n\t\t\terr = Save()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "constants/constants.go",
    "content": "package constants\n\nimport (\n\t\"time\"\n)\n\nconst (\n\tVersion         = \"2.0.3665.99\"\n\tDatabaseVersion = 1\n\tLogPath         = \"/var/log/pritunl-cloud.log\"\n\tLogPath2        = \"/var/log/pritunl-cloud.log.1\"\n\tStaticCache     = true\n\tRetryDelay      = 3 * time.Second\n)\n\nvar (\n\tProduction   = true\n\tDebugWeb     = false\n\tFastExit     = false\n\tLockDebug    = false\n\tInterrupt    = false\n\tShutdown     = false\n\tConfPath     = \"/etc/pritunl-cloud.json\"\n\tDefaultRoot  = \"/var/lib/pritunl-cloud\"\n\tDefaultCache = \"/var/lib/pritunl-cloud/cache\"\n\tDefaultTemp  = \"/var/lib/pritunl-cloud/temp\"\n\tStaticRoot   = []string{\n\t\t\"www/dist\",\n\t\t\"/usr/share/pritunl-cloud/www\",\n\t}\n\tStaticTestingRoot = []string{\n\t\t\"/home/cloud/git/pritunl-cloud/www/dist-dev\",\n\t\t\"/home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/dist-dev\",\n\t\t\"/usr/share/pritunl-cloud/www\",\n\t}\n)\n\nvar PritunlKeyring = `-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQENBFu4Ww4BCACq/6Tc4wMhOIMEM9nUtWOZNfVAPt9NEVQ3+PDdSC6+dAarM0z2\ngeUByo1Qie4sAc1KwJJZ6t+X8mpxBZdqVwCeI4MksUfEnrL01JbAwC8Gw5nw6R1g\nl9yuXC/NeZozI4aIjcg3etvX871G7oDZRfdlcRaP6BceqIZRVJXno9wGB4gns+zv\n8RGtK+87YeCq3cFyuw1uvW3lPEcxdVdJMI6YLAiTjvM5RIpexjf6DYmb9uKpC9pu\nfbF8KNEi4C7usvSpdohkd747oQlL/JBJO8RBZi0bumbxVhjR1S0BPA+4Z//UyUEc\ndNvyxsTot8NeljWL8HBh/TROoCDq01URdf8nABEBAAG0HVByaXR1bmwgPGNvbnRh\nY3RAcHJpdHVubC5jb20+iQE4BBMBAgAiBQJbuFsOAhsDBgsJCAcDAgYVCAIJCgsE\nFgIDAQIeAQIXgAAKCRAK21I+BVwIpOFfB/0ZrE+OOsbWxwdC6jR7jEH0kS1e+HSV\nbFZxqXgBl8zsxtWF5xpD9o4iSRSudtwfWKdRUvoliiL8VOYWMgyl4aHOq/oR11pR\nes1Cy70qDyj+SuzxZjnhhLZMAYZnynbWCB7e9MP0rcmIOZImE2UNbFXWV85vjAHp\nzMXnDKrvDlz5eyUT/dfT6HgkiaVq4SyfubYJwXMj+vF3+hppbovFIEWYFl/A24YI\nql81EqfBXnzf2S9HDsJ2CAM5P33u+T7V0r8Q/HeX/1OlZGelmeyV3bumhg+PfTgg\n3sQyOSiST/stczt2gyw7UfiTWgwW0oYP/68FCBOzHC/kQxpk+kpf3Z8JuQENBFu4\nWw4BCAC0d2fgGm+2WRjdrYxZpBzKS9z8XNBQ6feNmliQECdJcrB+VHj/PNXgAgCM\naTM21eZCHm2t3pbwcEO4v3y2RIVbRl1PTGvULlzKK3ZvUcUINTqWFlERsSdQq2o1\nv996WN6Crc+P6txu17S74XwsMZcbCSYPG9N80cEkvFajuYYjIIf2Zww/wcbgGr0S\ndZnGPTZScBIfyWsxMCnzVLwWkIig6gEFqLgP5gcPhAhT8Rfbw3SYYIVogXTw5tyl\nnZZE+LrHAIN2XABVH3ho3XZQIjWquKd6ipzSenKyZi+Gry8QpG+r17ppCZmigsDj\ny3rgCIhRCl46VGSh2a0R51s5npR3ABEBAAGJAR8EGAECAAkFAlu4Ww4CGyAACgkQ\nCttSPgVcCKTiKQgAiSXywu5m61uFyRkWxYURrKqR2R/DMQ1C5Q4bFTqR67BRlxTD\nV8zKBPPCAPLbdnWYxohXYNYyyoT/xbmH312829AL2GmAtgysKJpdlG+bbvd+JAmb\nwdgfyXGs4//mGUA7MDIvVBr/4Vd2qle3//AZLgKyErM3tuESlWYm40CmUp+pnEMt\nnDDPbo8ypt6X02dTjPZ81UVLWemU+v3fsFichpo66dlE5N1cXJg9nkvJbfRxQgKf\njqqYVPMtU64wCwaZNFPuHXyWvU7G+WDWnw6RPzdONjN6QiyZSdSK34g86VsnduoW\nJ4x+1Z/v6ycqqq+t+niEDGV9YyEbeSHlr7MGbrkBDQRbuFsOAQgAyraB3isfso3/\nPivZnDGm7+Shmup9CbXD1JX6EL0AtKfWwSb9kPWTCw4Wr4aJmL5DNCxpEKjCz4yO\nHwZ4Qnn3OkclDem+lrEueXwvaGwvOPGBg0X44b2XNJkRGDCZQfFoePacp6SdhS3n\nEfd6HsRKMgG0Xo+gcYuqwFUJ4bvBi0dl6R1rPdbnRtbykCjrinNs56kiBH+Smzdh\nE1+wcivRuFOIIU6GZylVuTam+QNGZScKFxCB7FSp0QoxaQWmXZK7DH9vrKsNOC3y\nbMQcWRvir5SZ7GnoKl/H95FjX+3cgJoEIGMSc4EwCifnUqVNgEirKyfbzTOdGHVG\nzW8qaS7kewARAQABiQEfBBgBAgAJBQJbuFsOAhsMAAoJEArbUj4FXAikIjAH/jaL\n6kFewz071THtll1E3+OwCK389UXVJyh7p1fbWftRR7AhH7Xte3MnPeFGvW9PzRx+\nWY8VuQOMw3vDk2bGy4LEhZSMIFRLKYK2wzrrcom75cYSwqzopFVOukW8t0OjFThX\nWRJIk82EMo3wOsGlUXELAjOGNxvzJ8OIncH0hh/hbsxUMHRxJAHBWOEEmzdwc5po\nx8pCEDnXvIqL6mtQOQUmnVIXpMt1hui9dnE3JkyM/UY7rNvIcSU1A7pLmvoP4YlZ\nHHAgY88Wur0X2ksHdfQaISVxW0iZGnJIrGAbW1Ayw0UkRQGYjWglk4EVuvqW+Go7\n3FxR7SpBSsF/SmguI/w=\n=ohOM\n-----END PGP PUBLIC KEY BLOCK-----`\n"
  },
  {
    "path": "cookie/cookie.go",
    "content": "package cookie\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gorilla/securecookie\"\n\t\"github.com/gorilla/sessions\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/pritunl/pritunl-cloud/session\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nvar (\n\tAdminStore *sessions.CookieStore\n\tUserStore  *sessions.CookieStore\n)\n\ntype Cookie struct {\n\tId    bson.ObjectID\n\tstore *sessions.Session\n\tw     http.ResponseWriter\n\tr     *http.Request\n}\n\nfunc (c *Cookie) Get(key string) string {\n\tvalInf := c.store.Values[key]\n\tif valInf == nil {\n\t\treturn \"\"\n\t}\n\treturn valInf.(string)\n}\n\nfunc (c *Cookie) Set(key string, val string) {\n\tc.store.Values[key] = val\n}\n\nfunc (c *Cookie) GetSession(db *database.Database, r *http.Request,\n\ttyp string) (sess *session.Session, err error) {\n\n\tsessId := c.Get(\"id\")\n\tif sessId == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"cookie: Session not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsig := c.Get(\"signature\")\n\tif sig == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"cookie: Session signature not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsess, err = session.GetUpdate(db, sessId, r, typ, sig)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = &errortypes.NotFoundError{\n\t\t\t\terrors.New(\"cookie: Session not found\"),\n\t\t\t}\n\t\tdefault:\n\t\t\terr = &errortypes.UnknownError{\n\t\t\t\terrors.Wrap(err, \"cookie: Unknown session error\"),\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Cookie) NewSession(db *database.Database, r *http.Request,\n\tid bson.ObjectID, remember bool, typ string) (\n\tsess *session.Session, err error) {\n\n\tsess, sig, err := session.New(db, r, id, typ)\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"cookie: Unknown session error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tc.Set(\"id\", sess.Id)\n\tc.Set(\"signature\", sig)\n\tmaxAge := 0\n\n\tif remember {\n\t\tmaxAge = settings.Auth.CookieAge\n\t}\n\n\tc.store.Options.MaxAge = maxAge\n\n\terr = c.Save()\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"cookie: Unknown session error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Cookie) Remove(db *database.Database) (err error) {\n\tsessId := c.Get(\"id\")\n\tif sessId == \"\" {\n\t\treturn\n\t}\n\n\terr = session.Remove(db, sessId)\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"cookie: Unknown session error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tc.Set(\"id\", \"\")\n\tc.Set(\"signature\", \"\")\n\terr = c.Save()\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"cookie: Unknown session error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Cookie) Save() (err error) {\n\terr = c.store.Save(c.r, c.w)\n\treturn\n}\n\nfunc init() {\n\tmodule := requires.New(\"cookie\")\n\tmodule.After(\"settings\")\n\n\tmodule.Handler = func() (err error) {\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tadminCookieAuthKey := settings.System.AdminCookieAuthKey\n\t\tadminCookieCryptoKey := settings.System.AdminCookieCryptoKey\n\t\tuserCookieAuthKey := settings.System.UserCookieAuthKey\n\t\tuserCookieCryptoKey := settings.System.UserCookieCryptoKey\n\n\t\tif len(adminCookieAuthKey) == 0 || len(adminCookieCryptoKey) == 0 {\n\t\t\tadminCookieAuthKey = securecookie.GenerateRandomKey(64)\n\t\t\tadminCookieCryptoKey = securecookie.GenerateRandomKey(32)\n\t\t\tsettings.System.AdminCookieAuthKey = adminCookieAuthKey\n\t\t\tsettings.System.AdminCookieCryptoKey = adminCookieCryptoKey\n\n\t\t\tfields := set.NewSet(\n\t\t\t\t\"admin_cookie_auth_key\",\n\t\t\t\t\"admin_cookie_crypto_key\",\n\t\t\t)\n\n\t\t\terr = settings.Commit(db, settings.System, fields)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif len(userCookieAuthKey) == 0 || len(userCookieCryptoKey) == 0 {\n\t\t\tuserCookieAuthKey = securecookie.GenerateRandomKey(64)\n\t\t\tuserCookieCryptoKey = securecookie.GenerateRandomKey(32)\n\t\t\tsettings.System.UserCookieAuthKey = userCookieAuthKey\n\t\t\tsettings.System.UserCookieCryptoKey = userCookieCryptoKey\n\n\t\t\tfields := set.NewSet(\n\t\t\t\t\"user_cookie_auth_key\",\n\t\t\t\t\"user_cookie_crypto_key\",\n\t\t\t)\n\n\t\t\terr = settings.Commit(db, settings.System, fields)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tAdminStore = sessions.NewCookieStore(\n\t\t\tadminCookieAuthKey, adminCookieCryptoKey)\n\t\tAdminStore.Options.Secure = true\n\t\tAdminStore.Options.HttpOnly = true\n\n\t\tUserStore = sessions.NewCookieStore(\n\t\t\tuserCookieAuthKey, userCookieCryptoKey)\n\t\tUserStore.Options.Secure = true\n\t\tUserStore.Options.HttpOnly = true\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "cookie/utils.go",
    "content": "package cookie\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gorilla/securecookie\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc GetAdmin(w http.ResponseWriter, r *http.Request) (\n\tcook *Cookie, err error) {\n\n\tstore, err := AdminStore.New(r, \"pritunl-cloud-admin\")\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err.(securecookie.MultiError)[0],\n\t\t\t\t\"cookie: Unknown cookie error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcook = &Cookie{\n\t\tstore: store,\n\t\tw:     w,\n\t\tr:     r,\n\t}\n\n\treturn\n}\n\nfunc NewAdmin(w http.ResponseWriter, r *http.Request) (cook *Cookie) {\n\tstore, _ := AdminStore.New(r, \"pritunl-cloud-admin\")\n\n\tcook = &Cookie{\n\t\tstore: store,\n\t\tw:     w,\n\t\tr:     r,\n\t}\n\n\treturn\n}\n\nfunc CleanAdmin(w http.ResponseWriter, r *http.Request) {\n\tcook := &http.Cookie{\n\t\tName:     \"pritunl-cloud-admin\",\n\t\tPath:     \"/\",\n\t\tSecure:   true,\n\t\tHttpOnly: true,\n\t\tMaxAge:   -1,\n\t}\n\thttp.SetCookie(w, cook)\n\n\treturn\n}\n\nfunc GetUser(w http.ResponseWriter, r *http.Request) (\n\tcook *Cookie, err error) {\n\n\tstore, err := UserStore.New(r, \"pritunl-cloud-user\")\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err.(securecookie.MultiError)[0],\n\t\t\t\t\"cookie: Unknown cookie error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcook = &Cookie{\n\t\tstore: store,\n\t\tw:     w,\n\t\tr:     r,\n\t}\n\n\treturn\n}\n\nfunc NewUser(w http.ResponseWriter, r *http.Request) (cook *Cookie) {\n\tstore, _ := UserStore.New(r, \"pritunl-cloud-user\")\n\n\tcook = &Cookie{\n\t\tstore: store,\n\t\tw:     w,\n\t\tr:     r,\n\t}\n\n\treturn\n}\n\nfunc CleanUser(w http.ResponseWriter, r *http.Request) {\n\tcook := &http.Cookie{\n\t\tName:     \"pritunl-cloud-user\",\n\t\tPath:     \"/\",\n\t\tSecure:   true,\n\t\tHttpOnly: true,\n\t\tMaxAge:   -1,\n\t}\n\thttp.SetCookie(w, cook)\n\n\treturn\n}\n"
  },
  {
    "path": "crypto/crypto.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/sha512\"\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"golang.org/x/crypto/nacl/secretbox\"\n\t\"golang.org/x/crypto/nacl/sign\"\n)\n\ntype Message struct {\n\tNonce     string\n\tMessage   string\n\tSignature string\n}\n\ntype AsymNaclHmacKey struct {\n\tKey        string\n\tSecret     string\n\tPublicKey  string\n\tPrivateKey string\n}\n\ntype AsymNaclHmac struct {\n\tkey          *[32]byte\n\tsecret       *[32]byte\n\tpublicKey    *[32]byte\n\tprivateKey   *[64]byte\n\tnonceHandler func(nonce []byte) error\n}\n\nfunc (a *AsymNaclHmac) RegisterNonce(handler func(nonce []byte) error) {\n\ta.nonceHandler = handler\n}\n\nfunc (a *AsymNaclHmac) Seal(input any) (msg *Message, err error) {\n\tif a.key == nil || a.secret == nil || a.privateKey == nil {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"crypto: Private key and secret not loaded\"),\n\t\t}\n\t\treturn\n\t}\n\n\tnonce := new([24]byte)\n\t_, err = io.ReadFull(rand.Reader, nonce[:])\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to generate nonce\"),\n\t\t}\n\t\treturn\n\t}\n\tnonceStr := base64.StdEncoding.EncodeToString(nonce[:])\n\n\tdata, err := json.Marshal(input)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to marshal json data\"),\n\t\t}\n\t\treturn\n\t}\n\n\tencByt := secretbox.Seal(nil, data, nonce, a.key)\n\tsigEncByt := sign.Sign(nil, encByt, a.privateKey)\n\tsigEncStr := base64.StdEncoding.EncodeToString(sigEncByt)\n\n\thashFunc := hmac.New(sha512.New, a.secret[:])\n\thashFunc.Write([]byte(sigEncStr))\n\trawSignature := hashFunc.Sum(nil)\n\tsigStr := base64.StdEncoding.EncodeToString(rawSignature)\n\n\tmsg = &Message{\n\t\tNonce:     nonceStr,\n\t\tMessage:   sigEncStr,\n\t\tSignature: sigStr,\n\t}\n\n\treturn\n}\n\nfunc (a *AsymNaclHmac) SealJson(input any) (output string, err error) {\n\tmsg, err := a.Seal(input)\n\tif err != nil {\n\t\treturn\n\t}\n\n\toutputByt, err := json.Marshal(msg)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to marshal message\"),\n\t\t}\n\t\treturn\n\t}\n\n\toutput = string(outputByt)\n\treturn\n}\n\nfunc (a *AsymNaclHmac) Unseal(msg *Message, output any) (err error) {\n\tif a.key == nil || a.secret == nil || a.publicKey == nil {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"crypto: Private key and secret not loaded\"),\n\t\t}\n\t\treturn\n\t}\n\n\thashFunc := hmac.New(sha512.New, a.secret[:])\n\thashFunc.Write([]byte(msg.Message))\n\trawSignature := hashFunc.Sum(nil)\n\tsigStr := base64.StdEncoding.EncodeToString(rawSignature)\n\n\tif subtle.ConstantTimeCompare([]byte(sigStr), []byte(msg.Signature)) != 1 {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"crypto: Invalid message signature\"),\n\t\t}\n\t\treturn\n\t}\n\n\tnonceByt, err := base64.StdEncoding.DecodeString(msg.Nonce)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode nonce\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(nonceByt) != 24 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"crypto: Invalid nonce length\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif a.nonceHandler != nil {\n\t\terr = a.nonceHandler(nonceByt)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"crypto: Nonce validate failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tnonce := new([24]byte)\n\tcopy(nonce[:], nonceByt)\n\n\tsigEncByt, err := base64.StdEncoding.DecodeString(msg.Message)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode message\"),\n\t\t}\n\t\treturn\n\t}\n\n\tencByt, valid := sign.Open(nil, sigEncByt, a.publicKey)\n\tif !valid {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to verify message signature\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdecByt, ok := secretbox.Open(nil, encByt, nonce, a.key)\n\tif !ok {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"crypto: Failed to decrypt message\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = json.Unmarshal(decByt, output)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to unmarshal data\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (a *AsymNaclHmac) UnsealJson(input string, output any) (err error) {\n\tmsg := &Message{}\n\n\terr = json.Unmarshal([]byte(input), msg)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to unmarshal message\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = a.Unseal(msg, output)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (a *AsymNaclHmac) Export() AsymNaclHmacKey {\n\treturn AsymNaclHmacKey{\n\t\tKey:        base64.StdEncoding.EncodeToString(a.key[:]),\n\t\tSecret:     base64.StdEncoding.EncodeToString(a.secret[:]),\n\t\tPublicKey:  base64.StdEncoding.EncodeToString(a.publicKey[:]),\n\t\tPrivateKey: base64.StdEncoding.EncodeToString(a.privateKey[:]),\n\t}\n}\n\nfunc (a *AsymNaclHmac) Import(key AsymNaclHmacKey) (err error) {\n\tkeyByt, err := base64.StdEncoding.DecodeString(key.Key)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(keyByt) != 32 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"crypto: Invalid key length\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsecrByt, err := base64.StdEncoding.DecodeString(key.Secret)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode secret key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(secrByt) != 32 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"crypto: Invalid secret key length\"),\n\t\t}\n\t\treturn\n\t}\n\n\tpubKeyByt, err := base64.StdEncoding.DecodeString(\n\t\tkey.PublicKey)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode public key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(pubKeyByt) != 32 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"crypto: Invalid public key length\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif key.PrivateKey != \"\" {\n\t\tprivKeyByt, e := base64.StdEncoding.DecodeString(\n\t\t\tkey.PrivateKey)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"crypto: Failed to decode private key\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif len(privKeyByt) != 64 {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"crypto: Invalid private key length\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif a.privateKey == nil {\n\t\t\ta.privateKey = new([64]byte)\n\t\t}\n\t\tcopy(a.privateKey[:], privKeyByt)\n\t}\n\n\tif a.key == nil {\n\t\ta.key = new([32]byte)\n\t}\n\tif a.secret == nil {\n\t\ta.secret = new([32]byte)\n\t}\n\tif a.publicKey == nil {\n\t\ta.publicKey = new([32]byte)\n\t}\n\n\tcopy(a.key[:], keyByt)\n\tcopy(a.secret[:], secrByt)\n\tcopy(a.publicKey[:], pubKeyByt)\n\n\treturn\n}\n\nfunc (a *AsymNaclHmac) Generate() (err error) {\n\tkey := new([32]byte)\n\t_, err = io.ReadFull(rand.Reader, key[:])\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to generate key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsecKey := new([32]byte)\n\t_, err = io.ReadFull(rand.Reader, secKey[:])\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to generate secret key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsignPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to generate signing key\"),\n\t\t}\n\t\treturn\n\t}\n\n\ta.key = key\n\ta.secret = secKey\n\ta.publicKey = signPubKey\n\ta.privateKey = signPrivKey\n\n\treturn\n}\n"
  },
  {
    "path": "csrf/csrf.go",
    "content": "package csrf\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype CsrfToken struct {\n\tId        string    `bson:\"_id\"`\n\tSession   string    `bson:\"session\"`\n\tTimestamp time.Time `bson:\"timestamp\"`\n}\n\nfunc NewToken(db *database.Database, sessionId string) (\n\ttoken string, err error) {\n\n\tcoll := db.CsrfTokens()\n\n\ttkn, err := utils.RandStr(48)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdoc := &CsrfToken{\n\t\tId:        tkn,\n\t\tSession:   sessionId,\n\t\tTimestamp: time.Now(),\n\t}\n\n\t_, err = coll.InsertOne(db, doc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\ttoken = tkn\n\treturn\n}\n\nfunc ValidateToken(db *database.Database, sessionId, token string) (\n\tvalid bool, err error) {\n\n\tcoll := db.CsrfTokens()\n\n\tdoc := &CsrfToken{}\n\n\terr = coll.FindOneId(token, doc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif doc.Session == sessionId {\n\t\tvalid = true\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "data/disk.go",
    "content": "package data\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/lvm\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc createDiskQcow(db *database.Database, dsk *disk.Disk) (\n\tnewSize int, backingImage string, err error) {\n\n\tdiskPath := paths.GetDiskPath(dsk.Id)\n\n\tif !dsk.Image.IsZero() {\n\t\tnewSize, backingImage, err = writeImageQcow(db, dsk)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else if dsk.FileSystem != \"\" {\n\t\terr = writeFsQcow(db, dsk)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = utils.Exec(\"\", \"qemu-img\", \"create\",\n\t\t\t\"-f\", \"qcow2\", diskPath, fmt.Sprintf(\"%dG\", dsk.Size))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = utils.Chmod(diskPath, 0600)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc createDiskLvm(db *database.Database, dsk *disk.Disk) (\n\tnewSize int, err error) {\n\n\tpl, err := pool.Get(db, dsk.Pool)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = lvm.InitLock(pl.VgName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !dsk.Image.IsZero() {\n\t\tnewSize, err = writeImageLvm(db, dsk, pl)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else if dsk.FileSystem != \"\" {\n\t\terr = writeFsLvm(db, dsk, pl)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = lvm.CreateLv(pl.VgName, dsk.Id.Hex(), dsk.Size)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc CreateDisk(db *database.Database, dsk *disk.Disk) (\n\tnewSize int, backingImage string, err error) {\n\n\tswitch dsk.Type {\n\tcase disk.Lvm:\n\t\tnewSize, err = createDiskLvm(db, dsk)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase \"\", disk.Qcow2:\n\t\tnewSize, backingImage, err = createDiskQcow(db, dsk)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"data: Unknown disk type %s\", dsk.Type),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ActivateDisk(db *database.Database, dsk *disk.Disk) (err error) {\n\tif dsk.Type != disk.Lvm {\n\t\treturn\n\t}\n\n\tpl, err := pool.Get(db, dsk.Pool)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvgName := pl.VgName\n\tlvName := dsk.Id.Hex()\n\n\terr = lvm.ActivateLv(vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeactivateDisk(db *database.Database, dsk *disk.Disk) (err error) {\n\tif dsk.Type != disk.Lvm {\n\t\treturn\n\t}\n\n\tpl, err := pool.Get(db, dsk.Pool)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvgName := pl.VgName\n\tlvName := dsk.Id.Hex()\n\n\terr = lvm.DeactivateLv(vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "data/image.go",
    "content": "package data\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\tminio \"github.com/minio/minio-go/v7\"\n\t\"github.com/minio/minio-go/v7/pkg/credentials\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/lock\"\n\t\"github.com/pritunl/pritunl-cloud/lvm\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/qmp\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/crypto/openpgp\"\n)\n\nvar (\n\timageLock        = utils.NewMultiTimeoutLock(10 * time.Minute)\n\tbackingImageLock = utils.NewMultiTimeoutLock(5 * time.Minute)\n\tnbdLock          = sync.Mutex{}\n)\n\nfunc getImageS3(db *database.Database, store *storage.Storage,\n\tdsk *disk.Disk, img *image.Image) (tmpPth string, err error) {\n\n\ttmpPth = paths.GetImageTempPath()\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"image_id\":   img.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"key\":        img.Key,\n\t\t\"temp_path\":  tmpPth,\n\t}).Info(\"data: Downloading s3 image\")\n\n\tclient, err := minio.New(store.Endpoint, &minio.Options{\n\t\tCreds: credentials.NewStaticV4(\n\t\t\tstore.AccessKey,\n\t\t\tstore.SecretKey,\n\t\t\t\"\",\n\t\t),\n\t\tSecure: !store.Insecure,\n\t})\n\tif err != nil {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.Wrap(err, \"data: Failed to connect to storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\tstat, err := client.StatObject(context.Background(), store.Bucket,\n\t\timg.Key, minio.StatObjectOptions{})\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"data: Failed to stat s3 image\"),\n\t\t}\n\t\treturn\n\t}\n\n\tprog := NewProgressS3(db, dsk, img, tmpPth, stat.Size)\n\tprog.Start()\n\tdefer prog.Stop()\n\n\terr = client.FGetObject(context.Background(), store.Bucket,\n\t\timg.Key, tmpPth, minio.GetObjectOptions{})\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"data: Failed to download s3 image\"),\n\t\t}\n\t\treturn\n\t}\n\n\tprog.Stop()\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"image_id\":   img.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"key\":        img.Key,\n\t\t\"temp_path\":  tmpPth,\n\t}).Info(\"data: Downloaded s3 image\")\n\n\treturn\n}\n\ntype ProgressS3 struct {\n\tdb         *database.Database\n\tdisk       *disk.Disk\n\timg        *image.Image\n\tdone       chan bool\n\tstopOnce   sync.Once\n\tbaseDir    string\n\toutPrefix  string\n\tTotal      int64\n\tWrote      int64\n\tLastWrote  int64\n\tLastReport int\n\tLastTime   time.Time\n}\n\nfunc NewProgressS3(db *database.Database, dsk *disk.Disk, img *image.Image,\n\toutPath string, size int64) (prog *ProgressS3) {\n\n\tprog = &ProgressS3{\n\t\tdb:        db,\n\t\tdisk:      dsk,\n\t\timg:       img,\n\t\tdone:      make(chan bool),\n\t\tbaseDir:   filepath.Dir(outPath),\n\t\toutPrefix: filepath.Base(outPath),\n\t\tTotal:     size,\n\t\tLastTime:  time.Now(),\n\t}\n\n\treturn\n}\n\nfunc (p *ProgressS3) Start() {\n\tgo func() {\n\t\tticker := time.NewTicker(1 * time.Second)\n\t\tdefer ticker.Stop()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-p.done:\n\t\t\t\treturn\n\t\t\tcase <-ticker.C:\n\t\t\t\tp.calculateProgress()\n\t\t\t\tp.syncProgress()\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (p *ProgressS3) Stop() {\n\tp.stopOnce.Do(func() {\n\t\tp.done <- true\n\t\tclose(p.done)\n\t})\n}\n\nfunc (p *ProgressS3) calculateProgress() {\n\tvar totalBytes int64 = 0\n\n\tfiles, err := os.ReadDir(p.baseDir)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, file := range files {\n\t\tif !file.IsDir() && strings.HasPrefix(file.Name(), p.outPrefix) {\n\t\t\tinfo, err := file.Info()\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttotalBytes += info.Size()\n\t\t}\n\t}\n\n\tp.Wrote = totalBytes\n}\n\nfunc (p *ProgressS3) syncProgress() {\n\tpercent := int(float64(p.Wrote) / float64(p.Total) * 100)\n\tif percent > 100 {\n\t\tpercent = 100\n\t}\n\n\tif percent >= p.LastReport+10 {\n\t\tnow := time.Now()\n\t\telapsed := now.Sub(p.LastTime).Seconds()\n\n\t\tspeed := float64(p.Wrote-p.LastWrote) / elapsed\n\n\t\tp.LastTime = now\n\t\tp.LastWrote = p.Wrote\n\t\tp.LastReport = percent - (percent % 10)\n\n\t\tif p.disk != nil && !p.disk.Instance.IsZero() {\n\t\t\t_ = instance.SetDownloadProgress(\n\t\t\t\tp.db, p.disk.Instance, p.LastReport, speed/1_000_000.0)\n\t\t}\n\t}\n\n\treturn\n}\n\ntype Progress struct {\n\tdb         *database.Database\n\tdisk       *disk.Disk\n\timg        *image.Image\n\tTotal      int64\n\tWrote      int64\n\tLastWrote  int64\n\tLastReport int\n\tLastTime   time.Time\n}\n\nfunc humanReadableSpeed(bytesPerSecond float64) string {\n\tswitch {\n\tcase bytesPerSecond >= 1_000_000_000:\n\t\treturn fmt.Sprintf(\"%.2f GB/s\", bytesPerSecond/1_000_000_000)\n\tcase bytesPerSecond >= 1_000_000:\n\t\treturn fmt.Sprintf(\"%.2f MB/s\", bytesPerSecond/1_000_000)\n\tcase bytesPerSecond >= 1_000:\n\t\treturn fmt.Sprintf(\"%.2f KB/s\", bytesPerSecond/1_000)\n\tdefault:\n\t\treturn fmt.Sprintf(\"%.2f B/s\", bytesPerSecond)\n\t}\n}\n\nfunc (p *Progress) Write(data []byte) (n int, err error) {\n\tn = len(data)\n\tp.Wrote += int64(n)\n\n\tpercent := int(float64(p.Wrote) / float64(p.Total) * 100)\n\tif percent >= p.LastReport+10 {\n\t\tnow := time.Now()\n\t\telapsed := now.Sub(p.LastTime).Seconds()\n\n\t\tspeed := float64(p.Wrote-p.LastWrote) / elapsed\n\n\t\tp.LastTime = now\n\t\tp.LastWrote = p.Wrote\n\t\tp.LastReport = percent - (percent % 10)\n\n\t\tif p.disk != nil && !p.disk.Instance.IsZero() {\n\t\t\t_ = instance.SetDownloadProgress(\n\t\t\t\tp.db, p.disk.Instance, p.LastReport, speed/1_000_000.0)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc getImageWeb(db *database.Database, store *storage.Storage,\n\tdsk *disk.Disk, img *image.Image) (tmpPth string, err error) {\n\n\ttmpPth = paths.GetImageTempPath()\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"image_id\":   img.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"key\":        img.Key,\n\t\t\"temp_path\":  tmpPth,\n\t}).Info(\"data: Downloading web image\")\n\n\tu := store.GetWebUrl()\n\tu.Path += \"/\" + img.Key\n\n\treq, err := http.NewRequest(\"GET\", u.String(), nil)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"data: Failed to create file request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"pritunl-cloud\")\n\n\tresp, err := clientLarge.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"data: File request error\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"data: Bad status %d from file request\",\n\t\t\t\tresp.StatusCode,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tcontentLen, err := strconv.ParseInt(\n\t\tresp.Header.Get(\"Content-Length\"), 10, 64)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"data: Invalid content length from file request\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif contentLen <= 0 {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"data: Zero content length from file request\"),\n\t\t}\n\t\treturn\n\t}\n\n\tout, err := os.Create(tmpPth)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"data: Failed to create temporary file\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer out.Close()\n\n\tprog := &Progress{\n\t\tdb:       db,\n\t\tdisk:     dsk,\n\t\timg:      img,\n\t\tTotal:    contentLen,\n\t\tLastTime: time.Now(),\n\t}\n\n\t_, err = io.Copy(out, io.TeeReader(resp.Body, prog))\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"data: Failed to download file\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc checkImageSigS3(db *database.Database, store *storage.Storage,\n\timg *image.Image, tmpPth string) (err error) {\n\n\tsigPth := tmpPth + \".sig\"\n\tdefer os.Remove(sigPth)\n\n\tclient, err := minio.New(store.Endpoint, &minio.Options{\n\t\tCreds: credentials.NewStaticV4(\n\t\t\tstore.AccessKey,\n\t\t\tstore.SecretKey,\n\t\t\t\"\",\n\t\t),\n\t\tSecure: !store.Insecure,\n\t})\n\tif err != nil {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.Wrap(err, \"data: Failed to connect to storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = client.FGetObject(context.Background(), store.Bucket,\n\t\timg.Key+\".sig\", sigPth, minio.GetObjectOptions{})\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"data: Failed to download image signature\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsignature, err := os.Open(sigPth)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"data: Failed to open image signature\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer signature.Close()\n\n\ttmpImg, err := os.Open(tmpPth)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"data: Failed to open image\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer tmpImg.Close()\n\n\tkeyring, err := openpgp.ReadArmoredKeyRing(\n\t\tstrings.NewReader(constants.PritunlKeyring))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"data: Failed to parse Pritunl keyring\"),\n\t\t}\n\t\treturn\n\t}\n\n\tentity, err := openpgp.CheckArmoredDetachedSignature(\n\t\tkeyring, tmpImg, signature)\n\tif err != nil || entity == nil {\n\t\terr = &errortypes.VerificationError{\n\t\t\terrors.Wrap(err, \"data: Image signature verification failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\":         img.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"key\":        img.Key,\n\t}).Info(\"data: Image signature successfully validated\")\n\n\treturn\n}\n\nfunc checkImageSigWeb(db *database.Database, store *storage.Storage,\n\timg *image.Image, tmpPth string) (err error) {\n\n\tsigPth := tmpPth + \".sig\"\n\tdefer os.Remove(sigPth)\n\n\tu := store.GetWebUrl()\n\tu.Path += \"/\" + img.Key + \".sig\"\n\n\treq, err := http.NewRequest(\"GET\", u.String(), nil)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"data: Failed to create file request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"pritunl-cloud\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"data: File request error\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"data: Bad status %d from file request\",\n\t\t\t\tresp.StatusCode,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\ttmpImg, err := os.Open(tmpPth)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"data: Failed to open image\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer tmpImg.Close()\n\n\tkeyring, err := openpgp.ReadArmoredKeyRing(\n\t\tstrings.NewReader(constants.PritunlKeyring))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"data: Failed to parse Pritunl keyring\"),\n\t\t}\n\t\treturn\n\t}\n\n\tentity, err := openpgp.CheckArmoredDetachedSignature(\n\t\tkeyring, tmpImg, resp.Body)\n\tif err != nil || entity == nil {\n\t\terr = &errortypes.VerificationError{\n\t\t\terrors.Wrap(err, \"data: Image signature verification failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\":         img.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"key\":        img.Key,\n\t}).Info(\"data: Image signature successfully validated\")\n\n\treturn\n}\n\nfunc getImage(db *database.Database, dsk *disk.Disk, img *image.Image,\n\tpth string) (err error) {\n\n\tif imageLock.Locked(pth) {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"image_id\": img.Id.Hex(),\n\t\t\t\"key\":      img.Key,\n\t\t\t\"path\":     pth,\n\t\t}).Info(\"data: Waiting for image\")\n\t}\n\n\tlockId := imageLock.Lock(pth)\n\tdefer imageLock.Unlock(pth, lockId)\n\n\ttmpPth := \"\"\n\tdefer func() {\n\t\tif tmpPth != \"\" {\n\t\t\tutils.Remove(tmpPth)\n\t\t}\n\t}()\n\n\texists, err := utils.Exists(pth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif exists {\n\t\treturn\n\t}\n\n\tstore, err := storage.Get(db, img.Storage)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif img.Type == storage.Web {\n\t\ttmpPth, err = getImageWeb(db, store, dsk, img)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\ttmpPth, err = getImageS3(db, store, dsk, img)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif img.Signed || store.Endpoint == \"images.pritunl.com\" {\n\t\tif img.Type == storage.Web {\n\t\t\terr = checkImageSigWeb(db, store, img, tmpPth)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\terr = checkImageSigS3(db, store, img, tmpPth)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\thashed := false\n\tif img.Hash != \"\" {\n\t\thash, e := utils.FileSha256(tmpPth)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif hash != img.Hash {\n\t\t\terr = &errortypes.VerificationError{\n\t\t\t\terrors.Wrap(err, \"data: Image hash verification failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\thashed = true\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"image_id\":   img.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"key\":        img.Key,\n\t\t\"temp_path\":  tmpPth,\n\t\t\"path\":       pth,\n\t\t\"hashed\":     hashed,\n\t}).Info(\"data: Downloaded image\")\n\n\terr = utils.Exec(\"\", \"mv\", tmpPth, pth)\n\tif err != nil {\n\t\treturn\n\t}\n\ttmpPth = \"\"\n\n\treturn\n}\n\nfunc copyBackingImage(imagePth, backingImagePth string) (err error) {\n\tlockId := backingImageLock.Lock(backingImagePth)\n\tdefer backingImageLock.Unlock(backingImagePth, lockId)\n\n\texists, err := utils.Exists(backingImagePth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif exists {\n\t\treturn\n\t}\n\n\terr = utils.Exec(\"\", \"cp\", imagePth, backingImagePth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc writeFsQcow(db *database.Database, dsk *disk.Disk) (err error) {\n\tndbPath := settings.Hypervisor.NbdPath\n\n\tnbdLock.Lock()\n\tdefer func() {\n\t\tutils.Exec(\"\", \"sync\")\n\t\tutils.Exec(\"\", \"qemu-nbd\", \"--disconnect\", ndbPath)\n\t\tnbdLock.Unlock()\n\t}()\n\n\tdiskPath := paths.GetDiskPath(dsk.Id)\n\n\terr = utils.Exec(\"\", \"qemu-img\", \"create\",\n\t\t\"-f\", \"qcow2\", diskPath, fmt.Sprintf(\"%dG\", dsk.Size))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.Chmod(diskPath, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil, \"modprobe\", \"nbd\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil, \"qemu-nbd\", \"--disconnect\", ndbPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(300 * time.Millisecond)\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil, \"qemu-nbd\", \"--connect\", ndbPath, diskPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil, \"parted\", \"--script\", ndbPath, \"mklabel\", \"gpt\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(300 * time.Millisecond)\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil, \"parted\", \"--script\", ndbPath, \"mkpart\",\n\t\t\"primary\", \"1MiB\", \"100%\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\tdiskFs := \"\"\n\tdiskLvm := false\n\tswitch dsk.FileSystem {\n\tcase disk.Xfs:\n\t\tdiskFs = \"xfs\"\n\t\tdiskLvm = false\n\tcase disk.LvmXfs:\n\t\tdiskFs = \"xfs\"\n\t\tdiskLvm = true\n\tcase disk.Ext4:\n\t\tdiskFs = \"ext4\"\n\t\tdiskLvm = false\n\tcase disk.LvmExt4:\n\t\tdiskFs = \"ext4\"\n\t\tdiskLvm = true\n\tdefault:\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Newf(\"data: Invalid disk filesystem %s\", dsk.FileSystem),\n\t\t}\n\t\treturn\n\t}\n\n\tif diskLvm {\n\t\tvgName := GetVgName(dsk.Id, 0)\n\t\tlvName := GetLvName(dsk.Id, 0)\n\n\t\terr = utils.Exec(\"\", \"pvcreate\", ndbPath+\"p1\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = utils.Exec(\"\", \"vgcreate\", vgName, ndbPath+\"p1\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.LvSize == dsk.Size {\n\t\t\terr = utils.Exec(\"\", \"lvcreate\", \"-l\", \"100%\",\n\t\t\t\t\"-n\", lvName, vgName)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\terr = utils.Exec(\"\", \"lvcreate\", \"-L\",\n\t\t\t\tfmt.Sprintf(\"%dG\", dsk.LvSize), \"-n\", lvName, vgName)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr = utils.Exec(\"\", \"mkfs\", \"-t\", diskFs,\n\t\t\tfmt.Sprintf(\"/dev/%s/%s\", vgName, lvName))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\toutput, e := utils.ExecOutput(\"\", \"blkid\", \"-s\", \"UUID\",\n\t\t\t\"-o\", \"value\", fmt.Sprintf(\"/dev/%s/%s\", vgName, lvName))\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tdsk.Uuid = strings.TrimSpace(output)\n\n\t\terr = utils.Exec(\"\", \"lvchange\", \"-an\",\n\t\t\tfmt.Sprintf(\"/dev/%s/%s\", vgName, lvName))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\terr = utils.Exec(\"\", \"vgchange\", \"-an\", vgName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(50 * time.Millisecond)\n\t} else {\n\t\terr = utils.Exec(\"\", \"mkfs\", \"-t\", diskFs, ndbPath+\"p1\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\toutput, e := utils.ExecOutput(\"\", \"blkid\", \"-s\", \"UUID\",\n\t\t\t\"-o\", \"value\", ndbPath+\"p1\")\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tdsk.Uuid = strings.TrimSpace(output)\n\t}\n\n\terr = dsk.CommitFields(db, set.NewSet(\"uuid\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc writeImageQcow(db *database.Database, dsk *disk.Disk) (\n\tnewSize int, backingImageName string, err error) {\n\n\tsize := dsk.Size\n\tdiskPath := paths.GetDiskPath(dsk.Id)\n\tdiskTempPath := paths.GetDiskTempPath()\n\tdisksPath := paths.GetDisksPath()\n\tbackingPath := paths.GetBackingPath()\n\n\terr = utils.ExistsMkdir(disksPath, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(backingPath, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(paths.GetTempPath(), 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\timg, err := image.Get(db, dsk.Image)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlargeBase := strings.Contains(img.Key, \"fedora\")\n\n\tbackingImagePth := path.Join(\n\t\tbackingPath,\n\t\tfmt.Sprintf(\"image-%s-%s\", img.Id.Hex(), img.Etag),\n\t)\n\n\tbackingImageExists := false\n\tif dsk.Backing {\n\t\tbackingImageName = fmt.Sprintf(\"%s-%s\", img.Id.Hex(), img.Etag)\n\n\t\tbackingImageExists, err = utils.Exists(backingImagePth)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif img.Type == storage.Public || img.Type == storage.Web ||\n\t\t!img.Deployment.IsZero() {\n\n\t\tcacheDir := node.Self.GetCachePath()\n\n\t\timagePth := path.Join(\n\t\t\tcacheDir,\n\t\t\tfmt.Sprintf(\"image-%s-%s\", img.Id.Hex(), img.Etag),\n\t\t)\n\n\t\terr = utils.ExistsMkdir(cacheDir, 0755)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif !backingImageExists {\n\t\t\terr = getImage(db, dsk, img, imagePth)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\texists, e := utils.Exists(diskPath)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif exists {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"image_id\":   img.Id.Hex(),\n\t\t\t\t\"image_type\": img.Type,\n\t\t\t\t\"disk_id\":    dsk.Id.Hex(),\n\t\t\t\t\"key\":        img.Key,\n\t\t\t\t\"path\":       diskPath,\n\t\t\t}).Error(\"data: Blocking disk image overwrite\")\n\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(err, \"data: Image already exists\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tutils.Exec(\"\", \"touch\", imagePth)\n\n\t\tif dsk.Backing {\n\t\t\terr = copyBackingImage(imagePth, backingImagePth)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tutils.Exec(\"\", \"touch\", backingImagePth)\n\n\t\t\terr = utils.Chmod(backingImagePth, 0644)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif largeBase && size < 16 {\n\t\t\t\tsize = 16\n\t\t\t\tnewSize = 16\n\t\t\t} else if !largeBase && size < 10 {\n\t\t\t\tsize = 10\n\t\t\t\tnewSize = 10\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(nil, \"qemu-img\",\n\t\t\t\t\"create\", \"-f\", \"qcow2\", \"-F\", \"qcow2\",\n\t\t\t\t\"-o\", fmt.Sprintf(\"backing_file=%s\", backingImagePth),\n\t\t\t\tdiskTempPath,\n\t\t\t\tfmt.Sprintf(\"%dG\", size))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\terr = utils.Exec(\"\", \"cp\", imagePth, diskTempPath)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif largeBase && size < 16 {\n\t\t\t\tsize = 16\n\t\t\t\tnewSize = 16\n\t\t\t}\n\n\t\t\tif size > 8 {\n\t\t\t\tcurSize, e := getQcowSize(diskTempPath)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif curSize > size {\n\t\t\t\t\tsize = curSize\n\t\t\t\t\tnewSize = curSize\n\t\t\t\t}\n\n\t\t\t\tif size > curSize {\n\t\t\t\t\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\t\t\t\t\"qemu-img\", \"resize\", diskTempPath,\n\t\t\t\t\t\tfmt.Sprintf(\"%dG\", size))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\terr = utils.Chmod(diskTempPath, 0600)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = utils.Exec(\"\", \"mv\", diskTempPath, diskPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tif dsk.Backing {\n\t\t\terr = getImage(db, dsk, img, backingImagePth)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\terr = getImage(db, dsk, img, diskTempPath)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\texists, e := utils.Exists(diskPath)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif exists {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"image_id\":   img.Id.Hex(),\n\t\t\t\t\"image_type\": img.Type,\n\t\t\t\t\"disk_id\":    dsk.Id.Hex(),\n\t\t\t\t\"key\":        img.Key,\n\t\t\t\t\"path\":       diskPath,\n\t\t\t}).Error(\"data: Blocking disk image overwrite\")\n\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(err, \"data: Image already exists\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.Backing {\n\t\t\tutils.Exec(\"\", \"touch\", backingImagePth)\n\n\t\t\terr = utils.Chmod(backingImagePth, 0644)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif largeBase && size < 16 {\n\t\t\t\tsize = 16\n\t\t\t\tnewSize = 16\n\t\t\t} else if !largeBase && size < 10 {\n\t\t\t\tsize = 10\n\t\t\t\tnewSize = 10\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(nil, \"qemu-img\",\n\t\t\t\t\"create\", \"-f\", \"qcow2\", \"-F\", \"qcow2\",\n\t\t\t\t\"-o\", fmt.Sprintf(\"backing_file=%s\", backingImagePth),\n\t\t\t\tdiskTempPath,\n\t\t\t\tfmt.Sprintf(\"%dG\", size))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tif largeBase && size < 16 {\n\t\t\t\tsize = 16\n\t\t\t\tnewSize = 16\n\t\t\t}\n\n\t\t\tif size > 8 {\n\t\t\t\tcurSize, e := getQcowSize(diskTempPath)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif curSize > size {\n\t\t\t\t\tsize = curSize\n\t\t\t\t\tnewSize = curSize\n\t\t\t\t}\n\n\t\t\t\tif size > curSize {\n\t\t\t\t\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\t\t\t\t\"qemu-img\", \"resize\", diskTempPath,\n\t\t\t\t\t\tfmt.Sprintf(\"%dG\", size))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\terr = utils.Exec(\"\", \"mv\", diskTempPath, diskPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc writeFsLvm(db *database.Database, dsk *disk.Disk,\n\tpl *pool.Pool) (err error) {\n\n\tsize := dsk.Size\n\tvgName := pl.VgName\n\tlvName := dsk.Id.Hex()\n\tsourcePth := \"\"\n\tdiskTempPath := paths.GetDiskTempPath()\n\tdefer utils.Remove(diskTempPath)\n\n\tacquired, err := lock.LvmLock(db, vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !acquired {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.New(\"data: Failed to acquire LVM lock\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr2 := lock.LvmUnlock(db, vgName, lvName)\n\t\tif err2 != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err2,\n\t\t\t}).Error(\"data: Failed to unlock lvm\")\n\t\t}\n\t}()\n\n\tdiskFs := \"\"\n\tdiskLvm := false\n\tswitch dsk.FileSystem {\n\tcase disk.Xfs:\n\t\tdiskFs = \"xfs\"\n\t\tdiskLvm = false\n\tcase disk.LvmXfs:\n\t\tdiskFs = \"xfs\"\n\t\tdiskLvm = true\n\tcase disk.Ext4:\n\t\tdiskFs = \"ext4\"\n\t\tdiskLvm = false\n\tcase disk.LvmExt4:\n\t\tdiskFs = \"ext4\"\n\t\tdiskLvm = true\n\tdefault:\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Newf(\"data: Invalid disk filesystem %s\", dsk.FileSystem),\n\t\t}\n\t\treturn\n\t}\n\n\terr = lvm.CreateLv(vgName, lvName, size)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = lvm.ActivateLv(vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\terr = lvm.DeactivateLv(vgName, lvName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\terr = lvm.WriteLv(vgName, lvName, sourcePth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdiskPath := filepath.Join(\"/dev/mapper\",\n\t\tfmt.Sprintf(\"%s-%s\", vgName, lvName))\n\n\tif diskLvm {\n\t\tvgName := GetVgName(dsk.Id, 0)\n\t\tlvName := GetLvName(dsk.Id, 0)\n\n\t\terr = utils.Exec(\"\", \"pvcreate\", diskPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = utils.Exec(\"\", \"vgcreate\", vgName, diskPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.LvSize == dsk.Size {\n\t\t\terr = utils.Exec(\"\", \"lvcreate\", \"-l\", \"100%\",\n\t\t\t\t\"-n\", lvName, vgName)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\terr = utils.Exec(\"\", \"lvcreate\", \"-L\",\n\t\t\t\tfmt.Sprintf(\"%dG\", dsk.LvSize), \"-n\", lvName, vgName)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr = utils.Exec(\"\", \"mkfs\", \"-t\", diskFs,\n\t\t\tfmt.Sprintf(\"/dev/%s/%s\", vgName, lvName))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\toutput, e := utils.ExecOutput(\"\", \"blkid\", \"-s\", \"UUID\",\n\t\t\t\"-o\", \"value\", fmt.Sprintf(\"/dev/%s/%s\", vgName, lvName))\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tdsk.Uuid = strings.TrimSpace(output)\n\n\t\terr = utils.Exec(\"\", \"lvchange\", \"-an\",\n\t\t\tfmt.Sprintf(\"/dev/%s/%s\", vgName, lvName))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\terr = utils.Exec(\"\", \"vgchange\", \"-an\", vgName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(50 * time.Millisecond)\n\t} else {\n\t\terr = utils.Exec(\"\", \"mkfs\", \"-t\", diskFs, diskPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\toutput, e := utils.ExecOutput(\"\", \"blkid\", \"-s\", \"UUID\",\n\t\t\t\"-o\", \"value\", diskPath)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tdsk.Uuid = strings.TrimSpace(output)\n\t}\n\n\terr = dsk.CommitFields(db, set.NewSet(\"uuid\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc writeImageLvm(db *database.Database, dsk *disk.Disk,\n\tpl *pool.Pool) (newSize int, err error) {\n\n\tsize := dsk.Size\n\tvgName := pl.VgName\n\tlvName := dsk.Id.Hex()\n\tsourcePth := \"\"\n\tdiskTempPath := paths.GetDiskTempPath()\n\tdefer utils.Remove(diskTempPath)\n\n\tif dsk.Backing {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"data: Cannot create LVM disk with linked image\"),\n\t\t}\n\t\treturn\n\t}\n\n\timg, err := image.Get(db, dsk.Image)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlargeBase := strings.Contains(img.Key, \"fedora\")\n\n\tif img.Type == storage.Public || img.Type == storage.Web ||\n\t\t!img.Deployment.IsZero() {\n\n\t\tcacheDir := node.Self.GetCachePath()\n\n\t\timagePth := path.Join(\n\t\t\tcacheDir,\n\t\t\tfmt.Sprintf(\"image-%s-%s\", img.Id.Hex(), img.Etag),\n\t\t)\n\n\t\terr = utils.ExistsMkdir(cacheDir, 0755)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = getImage(db, dsk, img, imagePth)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tsourcePth = imagePth\n\n\t\tif largeBase && size < 16 {\n\t\t\tsize = 16\n\t\t\tnewSize = 16\n\t\t} else if !largeBase && size < 10 {\n\t\t\tsize = 10\n\t\t\tnewSize = 10\n\t\t}\n\t} else {\n\t\terr = getImage(db, dsk, img, diskTempPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tsourcePth = diskTempPath\n\n\t\tif largeBase && size < 16 {\n\t\t\tsize = 16\n\t\t\tnewSize = 16\n\t\t}\n\t}\n\n\tacquired, err := lock.LvmLock(db, vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !acquired {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.New(\"data: Failed to acquire LVM lock\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr2 := lock.LvmUnlock(db, vgName, lvName)\n\t\tif err2 != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err2,\n\t\t\t}).Error(\"data: Failed to unlock lvm\")\n\t\t}\n\t}()\n\n\terr = lvm.CreateLv(vgName, lvName, size)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = lvm.ActivateLv(vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\terr = lvm.DeactivateLv(vgName, lvName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\terr = lvm.WriteLv(vgName, lvName, sourcePth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeleteImage(db *database.Database, imgId bson.ObjectID) (\n\terr error) {\n\n\timg, err := image.Get(db, imgId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif img.Type == storage.Public || img.Type == storage.Web {\n\t\treturn\n\t}\n\n\tstore, err := storage.Get(db, img.Storage)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tclient, err := minio.New(store.Endpoint, &minio.Options{\n\t\tCreds:  credentials.NewStaticV4(store.AccessKey, store.SecretKey, \"\"),\n\t\tSecure: !store.Insecure,\n\t})\n\tif err != nil {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.Wrap(err, \"data: Failed to connect to storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = client.RemoveObject(context.Background(),\n\t\tstore.Bucket, img.Key, minio.RemoveObjectOptions{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = img.Remove(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc WriteImage(db *database.Database, dsk *disk.Disk) (\n\tnewSize int, backingImageName string, err error) {\n\n\tswitch dsk.Type {\n\tcase disk.Lvm:\n\t\tpl, e := pool.Get(db, dsk.Pool)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\terr = lvm.InitLock(pl.VgName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tnewSize, err = writeImageLvm(db, dsk, pl)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase \"\", disk.Qcow2:\n\t\tnewSize, backingImageName, err = writeImageQcow(db, dsk)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"data: Unknown disk type %s\", dsk.Type),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeleteImages(db *database.Database, imgIds []bson.ObjectID) (\n\terr error) {\n\n\tfor _, imgId := range imgIds {\n\t\terr = DeleteImage(db, imgId)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc DeleteImageOrg(db *database.Database, orgId, imgId bson.ObjectID) (\n\terr error) {\n\n\timg, err := image.GetOrg(db, orgId, imgId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif img.Type == storage.Public || img.Type == storage.Web {\n\t\treturn\n\t}\n\n\tstore, err := storage.Get(db, img.Storage)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tclient, err := minio.New(store.Endpoint, &minio.Options{\n\t\tCreds:  credentials.NewStaticV4(store.AccessKey, store.SecretKey, \"\"),\n\t\tSecure: !store.Insecure,\n\t})\n\tif err != nil {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.Wrap(err, \"data: Failed to connect to storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = client.RemoveObject(context.Background(),\n\t\tstore.Bucket, img.Key, minio.RemoveObjectOptions{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = img.Remove(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeleteImagesOrg(db *database.Database, orgId bson.ObjectID,\n\timgIds []bson.ObjectID) (err error) {\n\n\tfor _, imgId := range imgIds {\n\t\terr = DeleteImageOrg(db, orgId, imgId)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc CreateSnapshot(db *database.Database, dsk *disk.Disk,\n\tvirt *vm.VirtualMachine) (err error) {\n\n\tdskPth := paths.GetDiskPath(dsk.Id)\n\tcacheDir := node.Self.GetCachePath()\n\n\tnde, err := node.Get(db, dsk.Node)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzne, err := zone.Get(db, nde.Zone)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdc, err := datacenter.Get(db, zne.Datacenter)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif dc.PrivateStorage.IsZero() {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t}).Error(\"data: Cannot snapshot disk without private storage\")\n\t\treturn\n\t}\n\n\tstore, err := storage.Get(db, dc.PrivateStorage)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t}).Error(\"data: Cannot snapshot disk without private storage\")\n\t\t}\n\t\treturn\n\t}\n\n\tif store.Type != storage.Private {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.New(\"data: Cannot upload to non-private storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"disk_id\":    dsk.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"disk_path\":  dskPth,\n\t}).Info(\"data: Creating disk snapshot\")\n\n\terr = utils.ExistsMkdir(cacheDir, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\timgId := bson.NewObjectID()\n\ttmpPath := path.Join(cacheDir,\n\t\tfmt.Sprintf(\"snapshot-%s\", imgId.Hex()))\n\timg := &image.Image{\n\t\tId: imgId,\n\t\tName: fmt.Sprintf(\"%s-%s\", dsk.Name,\n\t\t\ttime.Now().Format(\"20060102-150405\")),\n\t\tOrganization: dsk.Organization,\n\t\tDeployment:   dsk.Deployment,\n\t\tType:         storage.Private,\n\t\tSystemType:   dsk.SystemType,\n\t\tSystemKind:   dsk.SystemKind,\n\t\tFirmware:     image.Unknown,\n\t\tStorage:      store.Id,\n\t\tKey:          fmt.Sprintf(\"snapshot/%s.qcow2\", imgId.Hex()),\n\t}\n\n\tdefer utils.Remove(tmpPath)\n\n\tavailable := false\n\tif virt != nil && virt.Running() {\n\t\terr = qmp.BackupDisk(virt.Id, dsk, tmpPath)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*qmp.DiskNotFound); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tavailable = true\n\t\t}\n\t}\n\n\tif !available {\n\t\terr = utils.Exec(\"\", \"cp\", dskPth, tmpPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = utils.Chmod(tmpPath, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\thash, err := utils.FileSha256(tmpPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"disk_id\":    dsk.Id.Hex(),\n\t\t\"disk_path\":  dskPth,\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"object_key\": img.Key,\n\t\t\"hash\":       hash,\n\t}).Info(\"data: Uploading disk snapshot\")\n\n\tclient, err := minio.New(store.Endpoint, &minio.Options{\n\t\tCreds:  credentials.NewStaticV4(store.AccessKey, store.SecretKey, \"\"),\n\t\tSecure: !store.Insecure,\n\t})\n\tif err != nil {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.Wrap(err, \"data: Failed to connect to storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\tputOpts := minio.PutObjectOptions{}\n\tstorageClass := storage.FormatStorageClass(dc.PrivateStorageClass)\n\tif storageClass != \"\" {\n\t\tputOpts.StorageClass = storageClass\n\t}\n\n\t_, err = client.FPutObject(context.Background(),\n\t\tstore.Bucket, img.Key, tmpPath, putOpts)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"data: Failed to write object\"),\n\t\t}\n\n\t\treturn\n\t}\n\n\ttime.Sleep(3 * time.Second)\n\n\tobj, err := client.StatObject(context.Background(),\n\t\tstore.Bucket, img.Key, minio.StatObjectOptions{})\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"data: Failed to stat object\"),\n\t\t}\n\t\treturn\n\t}\n\n\timg.Hash = hash\n\timg.Etag = image.GetEtag(obj)\n\timg.LastModified = obj.LastModified\n\n\tif store.IsOracle() {\n\t\timg.StorageClass = storage.ParseStorageClass(obj)\n\t} else {\n\t\timg.StorageClass = dc.BackupStorageClass\n\t}\n\n\terr = img.Upsert(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !dsk.Deployment.IsZero() {\n\t\tdeply, e := deployment.Get(db, dsk.Deployment)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tdeply.Image = img.Id\n\t\tdeply.SetImageState(deployment.Complete)\n\t\terr = deply.CommitFields(db, set.NewSet(\n\t\t\t\"image\", \"image_data.state\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = instance.Delete(db, deply.Instance)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"disk_id\":    dsk.Id.Hex(),\n\t\t\"disk_path\":  dskPth,\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"object_key\": img.Key,\n\t}).Info(\"data: Uploaded disk snapshot\")\n\n\tevent.PublishDispatch(db, \"image.change\")\n\n\treturn\n}\n\nfunc CreateBackup(db *database.Database, dsk *disk.Disk,\n\tvirt *vm.VirtualMachine) (err error) {\n\n\tdskPth := paths.GetDiskPath(dsk.Id)\n\tcacheDir := node.Self.GetCachePath()\n\n\tnde, err := node.Get(db, dsk.Node)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzne, err := zone.Get(db, nde.Zone)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdc, err := datacenter.Get(db, zne.Datacenter)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif dc.BackupStorage.IsZero() {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t}).Error(\"data: Cannot backup disk without backup storage\")\n\t\treturn\n\t}\n\n\tif dsk.BackingImage != \"\" {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t}).Error(\"data: Cannot backup disk with backing image\")\n\t\treturn\n\t}\n\n\tstore, err := storage.Get(db, dc.BackupStorage)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t}).Error(\"data: Cannot backup disk without backup storage\")\n\t\t}\n\t\treturn\n\t}\n\n\tif store.Type != storage.Private {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.New(\"data: Cannot upload to non-private storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"disk_id\":    dsk.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"disk_path\":  dskPth,\n\t}).Info(\"data: Creating disk backup\")\n\n\terr = utils.ExistsMkdir(cacheDir, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\timgId := bson.NewObjectID()\n\ttmpPath := path.Join(cacheDir,\n\t\tfmt.Sprintf(\"backup-%s\", imgId.Hex()))\n\timg := &image.Image{\n\t\tId:   imgId,\n\t\tDisk: dsk.Id,\n\t\tName: fmt.Sprintf(\"%s-%s\", dsk.Name,\n\t\t\ttime.Now().Format(\"20060102-150405\")),\n\t\tOrganization: dsk.Organization,\n\t\tType:         storage.Private,\n\t\tSystemType:   dsk.SystemType,\n\t\tSystemKind:   dsk.SystemKind,\n\t\tFirmware:     image.Unknown,\n\t\tStorage:      store.Id,\n\t\tKey:          fmt.Sprintf(\"backup/%s.qcow2\", imgId.Hex()),\n\t}\n\n\tdefer utils.Remove(tmpPath)\n\n\tavailable := false\n\tif virt != nil && virt.Running() {\n\t\terr = qmp.BackupDisk(virt.Id, dsk, tmpPath)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*qmp.DiskNotFound); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tavailable = true\n\t\t}\n\t}\n\n\tif !available {\n\t\terr = utils.Exec(\"\", \"cp\", dskPth, tmpPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = utils.Chmod(tmpPath, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\thash, err := utils.FileSha256(tmpPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"disk_id\":    dsk.Id.Hex(),\n\t\t\"disk_path\":  dskPth,\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"object_key\": img.Key,\n\t\t\"hash\":       hash,\n\t}).Info(\"data: Uploading disk backup\")\n\n\tclient, err := minio.New(store.Endpoint, &minio.Options{\n\t\tCreds:  credentials.NewStaticV4(store.AccessKey, store.SecretKey, \"\"),\n\t\tSecure: !store.Insecure,\n\t})\n\tif err != nil {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.Wrap(err, \"data: Failed to connect to storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\tputOpts := minio.PutObjectOptions{}\n\tstorageClass := storage.FormatStorageClass(dc.BackupStorageClass)\n\tif storageClass != \"\" {\n\t\tputOpts.StorageClass = storageClass\n\t}\n\n\t_, err = client.FPutObject(context.Background(),\n\t\tstore.Bucket, img.Key, tmpPath, putOpts)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"data: Failed to write object\"),\n\t\t}\n\n\t\treturn\n\t}\n\n\ttime.Sleep(3 * time.Second)\n\n\tobj, err := client.StatObject(context.Background(),\n\t\tstore.Bucket, img.Key, minio.StatObjectOptions{})\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"data: Failed to stat object\"),\n\t\t}\n\t\treturn\n\t}\n\n\timg.Hash = hash\n\timg.Etag = image.GetEtag(obj)\n\timg.LastModified = obj.LastModified\n\n\tif store.IsOracle() {\n\t\timg.StorageClass = storage.ParseStorageClass(obj)\n\t} else {\n\t\timg.StorageClass = dc.BackupStorageClass\n\t}\n\n\terr = img.Upsert(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"image.change\")\n\n\treturn\n}\n\nfunc RestoreBackup(db *database.Database, dsk *disk.Disk) (err error) {\n\tdskPth := paths.GetDiskPath(dsk.Id)\n\tcacheDir := node.Self.GetCachePath()\n\n\timg, err := image.Get(db, dsk.RestoreImage)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif img.Disk != dsk.Id {\n\t\terr = &errortypes.VerificationError{\n\t\t\terrors.Wrap(err, \"data: Restore image invalid\"),\n\t\t}\n\t\treturn\n\t}\n\n\tstore, err := storage.Get(db, img.Storage)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif store.Type != storage.Private {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.New(\"data: Cannot restore from non-private storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"disk_id\":    dsk.Id.Hex(),\n\t\t\"image_id\":   img.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"disk_path\":  dskPth,\n\t}).Info(\"data: Restoring disk backup\")\n\n\terr = utils.ExistsMkdir(cacheDir, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tclient, err := minio.New(store.Endpoint, &minio.Options{\n\t\tCreds:  credentials.NewStaticV4(store.AccessKey, store.SecretKey, \"\"),\n\t\tSecure: !store.Insecure,\n\t})\n\tif err != nil {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.Wrap(err, \"data: Failed to connect to storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\timgId := bson.NewObjectID()\n\ttmpPath := path.Join(cacheDir,\n\t\tfmt.Sprintf(\"restore-%s\", imgId.Hex()))\n\n\tdefer utils.Remove(tmpPath)\n\terr = client.FGetObject(context.Background(), store.Bucket,\n\t\timg.Key, tmpPath, minio.GetObjectOptions{})\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"data: Failed to download restore image\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = utils.Chmod(tmpPath, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\thashed := false\n\tif img.Hash != \"\" {\n\t\thash, e := utils.FileSha256(tmpPath)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif hash != img.Hash {\n\t\t\terr = &errortypes.VerificationError{\n\t\t\t\terrors.Wrap(err, \"data: Image hash verification failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\thashed = true\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"image_id\":   img.Id.Hex(),\n\t\t\"storage_id\": store.Id.Hex(),\n\t\t\"key\":        img.Key,\n\t\t\"temp_path\":  tmpPath,\n\t\t\"disk_path\":  dskPth,\n\t\t\"hashed\":     hashed,\n\t}).Info(\"data: Restored backup\")\n\n\terr = utils.Exec(\"\", \"mv\", \"-f\", tmpPath, dskPth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ImageAvailable(store *storage.Storage, img *image.Image) (\n\tavailable bool, err error) {\n\n\tif img.Type == storage.Web {\n\t\tavailable = true\n\t\treturn\n\t}\n\n\tif strings.Contains(strings.ToLower(store.Endpoint), \"oracle\") {\n\t\tclient, e := minio.New(store.Endpoint, &minio.Options{\n\t\t\tCreds: credentials.NewStaticV4(store.AccessKey,\n\t\t\t\tstore.SecretKey, \"\"),\n\t\t\tSecure: !store.Insecure,\n\t\t})\n\t\tif e != nil {\n\t\t\terr = &errortypes.ConnectionError{\n\t\t\t\terrors.Wrap(e, \"data: Failed to connect to storage\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tobj, e := client.StatObject(context.Background(),\n\t\t\tstore.Bucket, img.Key, minio.StatObjectOptions{})\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"data: Failed to stat object\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tarchivalState := strings.ToLower(obj.Metadata.Get(\"Archival-State\"))\n\t\tif archivalState != \"\" && archivalState != \"restored\" {\n\t\t\tavailable = false\n\t\t\treturn\n\t\t}\n\n\t\tavailable = true\n\t\treturn\n\t}\n\n\tswitch img.StorageClass {\n\tcase storage.AwsStandard:\n\t\tavailable = true\n\t\tbreak\n\tcase storage.AwsInfrequentAccess:\n\t\tavailable = true\n\t\tbreak\n\tcase storage.AwsGlacier:\n\t\tclient, e := minio.New(store.Endpoint, &minio.Options{\n\t\t\tCreds: credentials.NewStaticV4(store.AccessKey,\n\t\t\t\tstore.SecretKey, \"\"),\n\t\t\tSecure: !store.Insecure,\n\t\t})\n\t\tif e != nil {\n\t\t\terr = &errortypes.ConnectionError{\n\t\t\t\terrors.Wrap(e, \"data: Failed to connect to storage\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tobj, e := client.StatObject(context.Background(),\n\t\t\tstore.Bucket, img.Key, minio.StatObjectOptions{})\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"data: Failed to stat object\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\trestore := obj.Metadata.Get(\"x-amz-restore\")\n\t\tif strings.Contains(restore, \"ongoing-request=\\\"false\\\"\") &&\n\t\t\tstrings.Contains(restore, \"expiry-date\") {\n\n\t\t\tavailable = true\n\t\t} else {\n\t\t\tavailable = false\n\t\t}\n\t\tbreak\n\tdefault:\n\t\tavailable = true\n\t\tbreak\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "data/resize.go",
    "content": "package data\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/lock\"\n\t\"github.com/pritunl/pritunl-cloud/lvm\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype diskInfo struct {\n\tFilename    string `json:\"filename\"`\n\tFormat      string `json:\"format\"`\n\tActualSize  int    `json:\"actual-size\"`\n\tVirtualSize int    `json:\"virtual-size\"`\n}\n\nfunc getQcowSize(pth string) (size int, err error) {\n\toutput, err := utils.ExecOutput(\"\",\n\t\t\"qemu-img\", \"info\", \"--output=json\", pth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdiskInfo := &diskInfo{}\n\n\terr = json.Unmarshal([]byte(output), diskInfo)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"data: Failed to parse qemu disk info\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsize = diskInfo.VirtualSize / 1073741824\n\n\treturn\n}\n\nfunc getDiskSizeQcow(dsk *disk.Disk) (size int, err error) {\n\treturn getQcowSize(paths.GetDiskPath(dsk.Id))\n}\n\nfunc expandDiskQcow(db *database.Database, dsk *disk.Disk) (err error) {\n\tdskPth := paths.GetDiskPath(dsk.Id)\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"disk_id\":   dsk.Id.Hex(),\n\t\t\"disk_path\": dskPth,\n\t\t\"new_size\":  dsk.NewSize,\n\t}).Info(\"data: Expanding qcow disk\")\n\n\tcurSize, err := getDiskSizeQcow(dsk)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif curSize >= dsk.NewSize {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"disk_id\":      dsk.Id.Hex(),\n\t\t\t\"disk_path\":    dskPth,\n\t\t\t\"current_size\": curSize,\n\t\t\t\"new_size\":     dsk.NewSize,\n\t\t}).Warn(\"data: Disk size larger then new size\")\n\n\t\tdsk.Size = curSize\n\t\treturn\n\t}\n\n\texpandSize := dsk.NewSize - curSize\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"qemu-img\", \"resize\", dskPth, fmt.Sprintf(\"+%dG\", expandSize))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcurSize, err = getDiskSizeQcow(dsk)\n\tif err != nil {\n\t\treturn\n\t}\n\tdsk.Size = curSize\n\n\treturn\n}\n\nfunc expandDiskLvm(db *database.Database, dsk *disk.Disk) (err error) {\n\tpl, err := pool.Get(db, dsk.Pool)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvgName := pl.VgName\n\tlvName := dsk.Id.Hex()\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"disk_id\":  dsk.Id.Hex(),\n\t\t\"vg_name\":  vgName,\n\t\t\"lv_name\":  lvName,\n\t\t\"new_size\": dsk.NewSize,\n\t}).Info(\"data: Expanding lvm disk\")\n\n\tcurSize, err := lvm.GetSizeLv(vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif curSize >= dsk.NewSize {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"disk_id\":      dsk.Id.Hex(),\n\t\t\t\"vg_name\":      vgName,\n\t\t\t\"lv_name\":      lvName,\n\t\t\t\"current_size\": curSize,\n\t\t\t\"new_size\":     dsk.NewSize,\n\t\t}).Warn(\"data: Disk size larger then new size\")\n\n\t\tdsk.Size = curSize\n\t\treturn\n\t}\n\n\tacquired, err := lock.LvmLock(db, vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !acquired {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.New(\"data: Failed to acquire LVM lock\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr2 := lock.LvmUnlock(db, vgName, lvName)\n\t\tif err2 != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err2,\n\t\t\t}).Error(\"data: Failed to unlock lvm\")\n\t\t}\n\t}()\n\n\terr = lvm.ActivateLv(vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\terr = lvm.DeactivateLv(vgName, lvName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\texpandSize := dsk.NewSize - curSize\n\terr = lvm.ExtendLv(vgName, lvName, expandSize)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcurSize, err = lvm.GetSizeLv(vgName, lvName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdsk.Size = curSize\n\n\treturn\n}\n\nfunc ExpandDisk(db *database.Database, dsk *disk.Disk) (err error) {\n\tswitch dsk.Type {\n\tcase disk.Lvm:\n\t\terr = expandDiskLvm(db, dsk)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase \"\", disk.Qcow2:\n\t\terr = expandDiskQcow(db, dsk)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"data: Unknown disk type %s\", dsk.Type),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "data/sync.go",
    "content": "package data\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\tminio \"github.com/minio/minio-go/v7\"\n\t\"github.com/minio/minio-go/v7/pkg/credentials\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tsyncLock        = utils.NewMultiTimeoutLock(1 * time.Minute)\n\tclientTransport = &http.Transport{\n\t\tDisableKeepAlives:   true,\n\t\tTLSHandshakeTimeout: 10 * time.Second,\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tMinVersion: tls.VersionTLS12,\n\t\t\tMaxVersion: tls.VersionTLS13,\n\t\t},\n\t}\n\tclient = &http.Client{\n\t\tTransport: clientTransport,\n\t\tTimeout:   30 * time.Second,\n\t}\n\tclientLarge = &http.Client{\n\t\tTransport: clientTransport,\n\t\tTimeout:   30 * time.Minute,\n\t}\n)\n\nfunc getImagesS3(db *database.Database, store *storage.Storage) (\n\timages []*image.Image, err error) {\n\n\tclient, err := minio.New(store.Endpoint, &minio.Options{\n\t\tCreds:  credentials.NewStaticV4(store.AccessKey, store.SecretKey, \"\"),\n\t\tSecure: !store.Insecure,\n\t})\n\tif err != nil {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.Wrap(err, \"storage: Failed to connect to storage\"),\n\t\t}\n\t\treturn\n\t}\n\n\timages = []*image.Image{}\n\tsignedKeys := set.NewSet()\n\tfor object := range client.ListObjects(\n\t\tcontext.Background(),\n\t\tstore.Bucket, minio.ListObjectsOptions{\n\t\t\tRecursive: true,\n\t\t},\n\t) {\n\t\tif object.Err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(object.Err, \"storage: Failed to list objects\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif strings.HasSuffix(object.Key, \".qcow2.sig\") {\n\t\t\tsignedKeys.Add(strings.TrimRight(object.Key, \".sig\"))\n\t\t} else if strings.HasSuffix(object.Key, \".qcow2\") {\n\t\t\tetag := image.GetEtag(object)\n\n\t\t\timg := &image.Image{\n\t\t\t\tStorage:      store.Id,\n\t\t\t\tKey:          object.Key,\n\t\t\t\tFirmware:     image.Uefi,\n\t\t\t\tEtag:         etag,\n\t\t\t\tType:         store.Type,\n\t\t\t\tLastModified: object.LastModified,\n\t\t\t}\n\n\t\t\tif store.IsOracle() {\n\t\t\t\tobj, e := client.StatObject(context.Background(),\n\t\t\t\t\tstore.Bucket, object.Key, minio.StatObjectOptions{})\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = &errortypes.ReadError{\n\t\t\t\t\t\terrors.Wrap(e, \"storage: Failed to stat object\"),\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\timg.StorageClass = storage.ParseStorageClass(obj)\n\t\t\t} else {\n\t\t\t\timg.StorageClass = storage.ParseStorageClass(object)\n\t\t\t}\n\n\t\t\timages = append(images, img)\n\t\t}\n\t}\n\n\tfor _, img := range images {\n\t\timg.Signed = signedKeys.Contains(img.Key)\n\t}\n\n\treturn\n}\n\ntype Files struct {\n\tVersion int `json:\"version\"`\n\tFiles   []File\n}\n\ntype File struct {\n\tName         string    `json:\"name\"`\n\tSigned       bool      `json:\"signed\"`\n\tHash         string    `json:\"hash\"`\n\tLastModified time.Time `json:\"last_modified\"`\n}\n\nfunc getImagesWeb(db *database.Database, store *storage.Storage) (\n\timages []*image.Image, err error) {\n\n\tu := store.GetWebUrl()\n\tu.Path += \"/files.json\"\n\n\treq, e := http.NewRequest(\"GET\", u.String(), nil)\n\tif e != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(e, \"data: Failed to file listing request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"pritunl-cloud\")\n\n\tresp, e := client.Do(req)\n\tif e != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(e, \"data: File listing request error\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"data: Bad status %d from file listing request\",\n\t\t\t\tresp.StatusCode,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tfilesData := &Files{}\n\terr = json.NewDecoder(resp.Body).Decode(filesData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"data: Failed to unmarshal file listing\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\timages = []*image.Image{}\n\tfor _, object := range filesData.Files {\n\t\tif strings.HasSuffix(object.Name, \".qcow2\") {\n\t\t\timg := &image.Image{\n\t\t\t\tStorage:      store.Id,\n\t\t\t\tKey:          object.Name,\n\t\t\t\tFirmware:     image.Uefi,\n\t\t\t\tEtag:         object.Hash,\n\t\t\t\tType:         storage.Web,\n\t\t\t\tSigned:       object.Signed,\n\t\t\t\tLastModified: object.LastModified,\n\t\t\t}\n\n\t\t\timages = append(images, img)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Sync(db *database.Database, store *storage.Storage) (err error) {\n\tif store.Endpoint == \"\" {\n\t\treturn\n\t}\n\n\tlockId := syncLock.Lock(store.Id.Hex())\n\tdefer syncLock.Unlock(store.Id.Hex(), lockId)\n\n\tvar images []*image.Image\n\n\tif store.Type == storage.Web || store.Endpoint == \"images.pritunl.com\" {\n\t\timages, err = getImagesWeb(db, store)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\timages, err = getImagesS3(db, store)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tremoteKeys := set.NewSet()\n\tfor _, img := range images {\n\t\tremoteKeys.Add(img.Key)\n\n\t\tif img.Signed {\n\t\t\tif strings.Contains(img.Key, \"_bios\") {\n\t\t\t\timg.Firmware = image.Bios\n\t\t\t} else {\n\t\t\t\timg.Firmware = image.Uefi\n\t\t\t}\n\t\t}\n\n\t\terr = img.Sync(db)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*image.LostImageError); ok {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"bucket\": store.Bucket,\n\t\t\t\t\t\"key\":    img.Key,\n\t\t\t\t}).Error(\"data: Ignoring lost image\")\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tlocalKeys, err := image.Distinct(db, store.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tremoveKeysSet := set.NewSet()\n\tfor _, key := range localKeys {\n\t\tremoveKeysSet.Add(key)\n\t}\n\tremoveKeysSet.Subtract(remoteKeys)\n\n\tfor keyInf := range removeKeysSet.Iter() {\n\t\tkey := keyInf.(string)\n\n\t\timg, e := image.GetKey(db, store.Id, key)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"bucket\": store.Bucket,\n\t\t\t\"key\":    img.Key,\n\t\t}).Info(\"data: Remote image deleted, removing local\")\n\n\t\terr = img.Remove(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "data/utils.go",
    "content": "package data\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/base32\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n)\n\nfunc GetVgName(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:9]\n\treturn fmt.Sprintf(\"cvg_%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetLvName(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:9]\n\treturn fmt.Sprintf(\"clv_%s%d\", strings.ToLower(hashSum), n)\n}\n"
  },
  {
    "path": "database/base.go",
    "content": "package database\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n)\n\ntype Named struct {\n\tId   bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName string        `bson:\"name\" json:\"name\"`\n}\n"
  },
  {
    "path": "database/client.go",
    "content": "package database\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tglobalClient     atomic.Value\n\tglobalClientLock sync.Mutex\n\tDefaultDatabase  string\n)\n\nfunc getClient() *mongo.Client {\n\tval := globalClient.Load()\n\tif val == nil {\n\t\treturn nil\n\t}\n\treturn val.(*mongo.Client)\n}\n\nfunc setClient(client *mongo.Client) {\n\tglobalClientLock.Lock()\n\tcurClientInf := globalClient.Load()\n\tif curClientInf != nil {\n\t\tcurClient := curClientInf.(*mongo.Client)\n\t\tctx, cancel := context.WithTimeout(\n\t\t\tcontext.Background(),\n\t\t\t30*time.Second,\n\t\t)\n\t\tdefer cancel()\n\n\t\terr := curClient.Disconnect(ctx)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"database: Disconnect error\")\n\t\t}\n\t}\n\tglobalClient.Store(client)\n\tglobalClientLock.Unlock()\n}\n"
  },
  {
    "path": "database/collection.go",
    "content": "package database\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n)\n\ntype Collection struct {\n\tdb *Database\n\t*mongo.Collection\n}\n\nfunc (c *Collection) FindOneId(id interface{}, data interface{}) (err error) {\n\terr = c.FindOne(c.db, &bson.M{\n\t\t\"_id\": id,\n\t}).Decode(data)\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Collection) UpdateId(id interface{}, data interface{}) (err error) {\n\t_, err = c.UpdateOne(c.db, &bson.M{\n\t\t\"_id\": id,\n\t}, data)\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Collection) Commit(id interface{}, data interface{}) (err error) {\n\t_, err = c.UpdateOne(c.db, &bson.M{\n\t\t\"_id\": id,\n\t}, &bson.M{\n\t\t\"$set\": data,\n\t})\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Collection) CommitFields(id interface{}, data interface{},\n\tfields set.Set) (err error) {\n\n\t_, err = c.UpdateOne(c.db, &bson.M{\n\t\t\"_id\": id,\n\t}, SelectFieldsAll(data, fields))\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Collection) Upsert(query *bson.M, data interface{}) (err error) {\n\t_, err = c.UpdateOne(c.db, query, &bson.M{\n\t\t\"$set\": data,\n\t}, options.UpdateOne().SetUpsert(true))\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc SelectFields(obj interface{}, fields set.Set) (data bson.M) {\n\tval := reflect.ValueOf(obj).Elem()\n\tdata = bson.M{}\n\n\tn := val.NumField()\n\tfor i := 0; i < n; i++ {\n\t\tfield := val.Field(i)\n\t\ttyp := val.Type().Field(i)\n\n\t\tif typ.PkgPath != \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\ttag := typ.Tag.Get(\"bson\")\n\t\tif tag == \"\" || tag == \"-\" {\n\t\t\tcontinue\n\t\t}\n\n\t\ttag = strings.Split(tag, \",\")[0]\n\n\t\tif fields.Contains(tag) {\n\t\t\tval := field.Interface()\n\n\t\t\tswitch valTyp := val.(type) {\n\t\t\tcase bson.ObjectID:\n\t\t\t\tif valTyp.IsZero() {\n\t\t\t\t\tdata[tag] = nil\n\t\t\t\t} else {\n\t\t\t\t\tdata[tag] = val\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tdata[tag] = val\n\t\t\t}\n\t\t} else if (field.Kind() == reflect.Struct) ||\n\t\t\t(field.Kind() == reflect.Pointer &&\n\t\t\t\tfield.Elem().Kind() == reflect.Struct) {\n\n\t\t\tvar val reflect.Value\n\t\t\tif field.Kind() == reflect.Struct {\n\t\t\t\tval = field\n\t\t\t} else {\n\t\t\t\tval = reflect.ValueOf(field.Interface()).Elem()\n\t\t\t}\n\n\t\t\tx := val.NumField()\n\t\t\tfor j := 0; j < x; j++ {\n\t\t\t\tnestedField := val.Field(j)\n\t\t\t\tnestedTyp := val.Type().Field(j)\n\n\t\t\t\tif nestedTyp.PkgPath != \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnestedTag := nestedTyp.Tag.Get(\"bson\")\n\t\t\t\tif nestedTag == \"\" || nestedTag == \"-\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnestedTag = strings.Split(nestedTag, \",\")[0]\n\t\t\t\tnestedTag = tag + \".\" + nestedTag\n\n\t\t\t\tif fields.Contains(nestedTag) {\n\t\t\t\t\tnestedVal := nestedField.Interface()\n\n\t\t\t\t\tswitch nestedValTyp := nestedVal.(type) {\n\t\t\t\t\tcase bson.ObjectID:\n\t\t\t\t\t\tif nestedValTyp.IsZero() {\n\t\t\t\t\t\t\tdata[nestedTag] = bson.NilObjectID\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdata[nestedTag] = nestedVal\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdata[nestedTag] = nestedVal\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc SelectFieldsAll(obj interface{}, fields set.Set) (data bson.M) {\n\tval := reflect.ValueOf(obj).Elem()\n\n\tdataSet := bson.M{}\n\tdataUnset := bson.M{}\n\tdataUnseted := false\n\n\tn := val.NumField()\n\tfor i := 0; i < n; i++ {\n\t\tfield := val.Field(i)\n\t\ttyp := val.Type().Field(i)\n\n\t\tif typ.PkgPath != \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\ttag := typ.Tag.Get(\"bson\")\n\t\tif tag == \"\" || tag == \"-\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tomitempty := strings.Contains(tag, \"omitempty\")\n\t\ttag = strings.Split(tag, \",\")[0]\n\n\t\tif fields.Contains(tag) {\n\t\t\tval := field.Interface()\n\n\t\t\tswitch valTyp := val.(type) {\n\t\t\tcase bson.ObjectID:\n\t\t\t\tif valTyp.IsZero() {\n\t\t\t\t\tif omitempty {\n\t\t\t\t\t\tdataUnset[tag] = 1\n\t\t\t\t\t\tdataUnseted = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdataSet[tag] = bson.NilObjectID\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tdataSet[tag] = val\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tdataSet[tag] = val\n\t\t\t}\n\t\t} else if (field.Kind() == reflect.Struct) ||\n\t\t\t(field.Kind() == reflect.Pointer &&\n\t\t\t\tfield.Elem().Kind() == reflect.Struct) {\n\n\t\t\tvar val reflect.Value\n\t\t\tif field.Kind() == reflect.Struct {\n\t\t\t\tval = field\n\t\t\t} else {\n\t\t\t\tval = reflect.ValueOf(field.Interface()).Elem()\n\t\t\t}\n\n\t\t\tx := val.NumField()\n\t\t\tfor j := 0; j < x; j++ {\n\t\t\t\tnestedField := val.Field(j)\n\t\t\t\tnestedTyp := val.Type().Field(j)\n\n\t\t\t\tif nestedTyp.PkgPath != \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnestedTag := nestedTyp.Tag.Get(\"bson\")\n\t\t\t\tif nestedTag == \"\" || nestedTag == \"-\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tnestedOmitempty := strings.Contains(nestedTag, \"omitempty\")\n\t\t\t\tnestedTag = strings.Split(nestedTag, \",\")[0]\n\t\t\t\tnestedTag = tag + \".\" + nestedTag\n\n\t\t\t\tif fields.Contains(nestedTag) {\n\t\t\t\t\tnestedVal := nestedField.Interface()\n\n\t\t\t\t\tswitch nestedValTyp := nestedVal.(type) {\n\t\t\t\t\tcase bson.ObjectID:\n\t\t\t\t\t\tif nestedValTyp.IsZero() {\n\t\t\t\t\t\t\tif nestedOmitempty {\n\t\t\t\t\t\t\t\tdataUnset[nestedTag] = 1\n\t\t\t\t\t\t\t\tdataUnseted = true\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdataSet[nestedTag] = bson.NilObjectID\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdataSet[nestedTag] = nestedVal\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tdataSet[nestedTag] = nestedVal\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tdata = bson.M{\n\t\t\"$set\": dataSet,\n\t}\n\tif dataUnseted {\n\t\tdata[\"$unset\"] = dataUnset\n\t}\n\n\treturn\n}\n\ntype ArraySelectFields struct {\n\tcount       int\n\tsetFields   bson.M\n\tunsetFields bson.M\n\tfilters     []interface{}\n\tpush        []interface{}\n\tpull        []bson.ObjectID\n\trootKey     string\n\tidKey       string\n\tmodified    bool\n}\n\nfunc (a *ArraySelectFields) Modified() bool {\n\treturn a.modified\n}\n\nfunc (a *ArraySelectFields) Update(docId bson.ObjectID,\n\tupdate bson.M) {\n\n\ta.modified = true\n\n\tmatchKey := fmt.Sprintf(\"elem%d\", a.count)\n\ta.count += 1\n\n\tsetStr := fmt.Sprintf(\"%s.$[%s].\", a.rootKey, matchKey)\n\n\tfor key, val := range update {\n\t\ta.setFields[setStr+key] = val\n\t}\n\n\ta.filters = append(a.filters, bson.M{\n\t\tfmt.Sprintf(\"%s.%s\", matchKey, a.idKey): docId,\n\t})\n}\n\nfunc (a *ArraySelectFields) Push(doc interface{}) {\n\ta.modified = true\n\ta.push = append(a.push, doc)\n}\n\nfunc (a *ArraySelectFields) Delete(docId bson.ObjectID) {\n\ta.modified = true\n\ta.pull = append(a.pull, docId)\n}\n\nfunc (a *ArraySelectFields) GetQuery() (query bson.M, filters []interface{}) {\n\tquery = bson.M{}\n\tif len(a.setFields) > 0 {\n\t\tquery[\"$set\"] = a.setFields\n\t}\n\tif len(a.unsetFields) > 0 {\n\t\tquery[\"$unset\"] = a.unsetFields\n\t}\n\n\tfilters = a.filters\n\n\tif len(a.push) > 0 {\n\t\tquery[\"$push\"] = bson.M{\n\t\t\ta.rootKey: bson.M{\n\t\t\t\t\"$each\": &a.push,\n\t\t\t},\n\t\t}\n\t}\n\n\tif len(a.pull) > 0 {\n\t\tquery[\"$pull\"] = bson.M{\n\t\t\ta.rootKey: bson.M{\n\t\t\t\ta.idKey: bson.M{\n\t\t\t\t\t\"$in\": a.pull,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc NewArraySelectFields(obj interface{}, rootKey string, fields set.Set) (\n\tarraySel *ArraySelectFields) {\n\n\tselectFields := SelectFieldsAll(obj, fields)\n\tsetFields := selectFields[\"$set\"].(bson.M)\n\n\tvar unsetFields bson.M\n\tif _, exists := selectFields[\"$unset\"]; exists {\n\t\tunsetFields = selectFields[\"$unset\"].(bson.M)\n\t}\n\n\tarraySel = &ArraySelectFields{\n\t\tcount:       1,\n\t\tsetFields:   setFields,\n\t\tunsetFields: unsetFields,\n\t\tfilters:     []interface{}{},\n\t\tpush:        []interface{}{},\n\t\tpull:        []bson.ObjectID{},\n\t\trootKey:     rootKey,\n\t\tidKey:       \"id\",\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "database/database.go",
    "content": "package database\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/readconcern\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/writeconcern\"\n\t\"github.com/pritunl/mongo-go-driver/v2/x/mongo/driver/connstring\"\n\t\"github.com/pritunl/pritunl-cloud/config\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Database struct {\n\tctx      context.Context\n\tclient   *mongo.Client\n\tdatabase *mongo.Database\n}\n\nfunc (d *Database) Deadline() (time.Time, bool) {\n\tif d.ctx != nil {\n\t\treturn d.ctx.Deadline()\n\t}\n\treturn time.Time{}, false\n}\n\nfunc (d *Database) Done() <-chan struct{} {\n\tif d.ctx != nil {\n\t\treturn d.ctx.Done()\n\t}\n\treturn nil\n}\n\nfunc (d *Database) Err() error {\n\tif d.ctx != nil {\n\t\treturn d.ctx.Err()\n\t}\n\treturn nil\n}\n\nfunc (d *Database) Value(key interface{}) interface{} {\n\tif d.ctx != nil {\n\t\treturn d.ctx.Value(key)\n\t}\n\treturn nil\n}\n\nfunc (d *Database) String() string {\n\treturn \"context.database\"\n}\n\nfunc (d *Database) Close() {\n}\n\nfunc (d *Database) GetCollection(name string) (coll *Collection) {\n\tcoll = &Collection{\n\t\tdb:         d,\n\t\tCollection: d.database.Collection(name),\n\t}\n\treturn\n}\n\nfunc (d *Database) getCollectionWeak(name string) (coll *Collection) {\n\topts := options.Collection()\n\topts.SetWriteConcern(writeconcern.W1())\n\topts.SetReadConcern(readconcern.Local())\n\n\tcoll = &Collection{\n\t\tdb:         d,\n\t\tCollection: d.database.Collection(name, opts),\n\t}\n\treturn\n}\n\nfunc (d *Database) getCollectionStrong(name string) (coll *Collection) {\n\topts := options.Collection()\n\topts.SetWriteConcern(writeconcern.Majority())\n\topts.SetReadConcern(readconcern.Local())\n\n\tcoll = &Collection{\n\t\tdb:         d,\n\t\tCollection: d.database.Collection(name, opts),\n\t}\n\treturn\n}\n\nfunc (d *Database) Users() (coll *Collection) {\n\tcoll = d.GetCollection(\"users\")\n\treturn\n}\n\nfunc (d *Database) Policies() (coll *Collection) {\n\tcoll = d.GetCollection(\"policies\")\n\treturn\n}\n\nfunc (d *Database) Devices() (coll *Collection) {\n\tcoll = d.GetCollection(\"devices\")\n\treturn\n}\n\nfunc (d *Database) Alerts() (coll *Collection) {\n\tcoll = d.GetCollection(\"alerts\")\n\treturn\n}\n\nfunc (d *Database) AlertsEvent() (coll *Collection) {\n\tcoll = d.GetCollection(\"alerts_event\")\n\treturn\n}\n\nfunc (d *Database) AlertsEventLock() (coll *Collection) {\n\tcoll = d.GetCollection(\"alerts_event_lock\")\n\treturn\n}\n\nfunc (d *Database) Pods() (coll *Collection) {\n\tcoll = d.GetCollection(\"pods\")\n\treturn\n}\n\nfunc (d *Database) Units() (coll *Collection) {\n\tcoll = d.GetCollection(\"units\")\n\treturn\n}\n\nfunc (d *Database) Specs() (coll *Collection) {\n\tcoll = d.GetCollection(\"specs\")\n\treturn\n}\n\nfunc (d *Database) Deployments() (coll *Collection) {\n\tcoll = d.GetCollection(\"deployments\")\n\treturn\n}\n\nfunc (d *Database) Sessions() (coll *Collection) {\n\tcoll = d.GetCollection(\"sessions\")\n\treturn\n}\n\nfunc (d *Database) Tasks() (coll *Collection) {\n\tcoll = d.GetCollection(\"tasks\")\n\treturn\n}\n\nfunc (d *Database) Tokens() (coll *Collection) {\n\tcoll = d.GetCollection(\"tokens\")\n\treturn\n}\n\nfunc (d *Database) CsrfTokens() (coll *Collection) {\n\tcoll = d.GetCollection(\"csrf_tokens\")\n\treturn\n}\n\nfunc (d *Database) SecondaryTokens() (coll *Collection) {\n\tcoll = d.GetCollection(\"secondary_tokens\")\n\treturn\n}\n\nfunc (d *Database) Nonces() (coll *Collection) {\n\tcoll = d.GetCollection(\"nonces\")\n\treturn\n}\n\nfunc (d *Database) Rokeys() (coll *Collection) {\n\tcoll = d.GetCollection(\"rokeys\")\n\treturn\n}\n\nfunc (d *Database) Schedulers() (coll *Collection) {\n\tcoll = d.GetCollection(\"schedulers\")\n\treturn\n}\n\nfunc (d *Database) Settings() (coll *Collection) {\n\tcoll = d.GetCollection(\"settings\")\n\treturn\n}\n\nfunc (d *Database) Events() (coll *Collection) {\n\tcoll = d.getCollectionWeak(\"events\")\n\treturn\n}\n\nfunc (d *Database) Nodes() (coll *Collection) {\n\tcoll = d.GetCollection(\"nodes\")\n\treturn\n}\n\nfunc (d *Database) NodePorts() (coll *Collection) {\n\tcoll = d.GetCollection(\"node_ports\")\n\treturn\n}\n\nfunc (d *Database) Organizations() (coll *Collection) {\n\tcoll = d.GetCollection(\"organizations\")\n\treturn\n}\n\nfunc (d *Database) Storages() (coll *Collection) {\n\tcoll = d.GetCollection(\"storages\")\n\treturn\n}\n\nfunc (d *Database) Images() (coll *Collection) {\n\tcoll = d.GetCollection(\"images\")\n\treturn\n}\n\nfunc (d *Database) Datacenters() (coll *Collection) {\n\tcoll = d.GetCollection(\"datacenters\")\n\treturn\n}\n\nfunc (d *Database) Zones() (coll *Collection) {\n\tcoll = d.GetCollection(\"zones\")\n\treturn\n}\n\nfunc (d *Database) Shapes() (coll *Collection) {\n\tcoll = d.GetCollection(\"shapes\")\n\treturn\n}\n\nfunc (d *Database) Balancers() (coll *Collection) {\n\tcoll = d.GetCollection(\"balancers\")\n\treturn\n}\n\nfunc (d *Database) Advisories() (coll *Collection) {\n\tcoll = d.GetCollection(\"advisories\")\n\treturn\n}\n\nfunc (d *Database) Instances() (coll *Collection) {\n\tcoll = d.GetCollection(\"instances\")\n\treturn\n}\n\nfunc (d *Database) Pools() (coll *Collection) {\n\tcoll = d.GetCollection(\"pools\")\n\treturn\n}\n\nfunc (d *Database) Disks() (coll *Collection) {\n\tcoll = d.GetCollection(\"disks\")\n\treturn\n}\n\nfunc (d *Database) Blocks() (coll *Collection) {\n\tcoll = d.GetCollection(\"blocks\")\n\treturn\n}\n\nfunc (d *Database) BlocksIp() (coll *Collection) {\n\tcoll = d.GetCollection(\"blocks_ip\")\n\treturn\n}\n\nfunc (d *Database) LvmLock() (coll *Collection) {\n\tcoll = d.GetCollection(\"lvm_lock\")\n\treturn\n}\n\nfunc (d *Database) Journal() (coll *Collection) {\n\tcoll = d.getCollectionWeak(\"journal\")\n\treturn\n}\n\nfunc (d *Database) Firewalls() (coll *Collection) {\n\tcoll = d.GetCollection(\"firewalls\")\n\treturn\n}\n\nfunc (d *Database) Versions() (coll *Collection) {\n\tcoll = d.GetCollection(\"versions\")\n\treturn\n}\n\nfunc (d *Database) Plans() (coll *Collection) {\n\tcoll = d.GetCollection(\"plans\")\n\treturn\n}\n\nfunc (d *Database) Vpcs() (coll *Collection) {\n\tcoll = d.GetCollection(\"vpcs\")\n\treturn\n}\n\nfunc (d *Database) VpcsIp() (coll *Collection) {\n\tcoll = d.GetCollection(\"vpcs_ip\")\n\treturn\n}\n\nfunc (d *Database) Authorities() (coll *Collection) {\n\tcoll = d.GetCollection(\"authorities\")\n\treturn\n}\n\nfunc (d *Database) Certificates() (coll *Collection) {\n\tcoll = d.GetCollection(\"certificates\")\n\treturn\n}\n\nfunc (d *Database) Secrets() (coll *Collection) {\n\tcoll = d.GetCollection(\"secrets\")\n\treturn\n}\n\nfunc (d *Database) Domains() (coll *Collection) {\n\tcoll = d.GetCollection(\"domains\")\n\treturn\n}\n\nfunc (d *Database) DomainsRecords() (coll *Collection) {\n\tcoll = d.GetCollection(\"domains_records\")\n\treturn\n}\n\nfunc (d *Database) AcmeChallenges() (coll *Collection) {\n\tcoll = d.GetCollection(\"acme_challenges\")\n\treturn\n}\n\nfunc (d *Database) Logs() (coll *Collection) {\n\tcoll = d.getCollectionWeak(\"logs\")\n\treturn\n}\n\nfunc (d *Database) Audits() (coll *Collection) {\n\tcoll = d.GetCollection(\"audits\")\n\treturn\n}\n\nfunc (d *Database) Geo() (coll *Collection) {\n\tcoll = d.getCollectionWeak(\"geo\")\n\treturn\n}\n\nfunc Connect() (err error) {\n\tmongoUrl, err := connstring.ParseAndValidate(config.Config.MongoUri)\n\tif err != nil {\n\t\terr = &ConnectionError{\n\t\t\terrors.Wrap(err, \"database: Failed to parse mongo uri\"),\n\t\t}\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"mongodb_hosts\": mongoUrl.Hosts,\n\t}).Info(\"database: Connecting to MongoDB server\")\n\n\tif mongoUrl.Database != \"\" {\n\t\tDefaultDatabase = mongoUrl.Database\n\t}\n\n\topts := options.Client().ApplyURI(config.Config.MongoUri)\n\topts.SetRetryReads(true)\n\topts.SetRetryWrites(true)\n\topts.SetWriteConcern(writeconcern.Majority())\n\topts.SetReadConcern(readconcern.Local())\n\n\tclient, err := mongo.Connect(opts)\n\tif err != nil {\n\t\terr = &ConnectionError{\n\t\t\terrors.Wrap(err, \"database: Connection error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsetClient(client)\n\n\tversion, err := ValidateDatabase()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"mongodb_hosts\":   mongoUrl.Hosts,\n\t\t\"mongodb_version\": version,\n\t}).Info(\"database: Connected to MongoDB server\")\n\n\terr = addCollections()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = addIndexes()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcleanDb := GetDatabase()\n\tdefer cleanDb.Close()\n\n\terr = CleanIndexes(cleanDb)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ValidateDatabase() (version string, err error) {\n\tdb := GetDatabase()\n\tdefer db.Close()\n\n\tbuildInfo := bson.M{}\n\terr = db.database.RunCommand(\n\t\tdb,\n\t\tbson.D{{\"buildInfo\", 1}},\n\t).Decode(&buildInfo)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"database: Failed to get MongoDB version\")\n\t\terr = nil\n\t}\n\n\tversion, ok := buildInfo[\"version\"].(string)\n\tif version == \"\" || !ok {\n\t\tversion = \"unknown\"\n\t}\n\n\tcursor, err := db.database.ListCollections(\n\t\tdb, &bson.M{})\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\titem := &struct {\n\t\t\tName string `bson:\"name\"`\n\t\t}{}\n\t\terr = cursor.Decode(item)\n\t\tif err != nil {\n\t\t\terr = ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif item.Name == \"servers\" {\n\t\t\terr = &errortypes.DatabaseError{\n\t\t\t\terrors.New(\"database: Cannot connect to pritunl database\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc getDatabase(ctx context.Context, client *mongo.Client) *Database {\n\tif client == nil {\n\t\treturn nil\n\t}\n\n\tdatabase := client.Database(DefaultDatabase)\n\n\treturn &Database{\n\t\tctx:      ctx,\n\t\tclient:   client,\n\t\tdatabase: database,\n\t}\n}\n\nfunc GetDatabase() *Database {\n\treturn getDatabase(nil, getClient())\n}\n\nfunc GetDatabaseCtx(ctx context.Context) *Database {\n\treturn getDatabase(ctx, getClient())\n}\n\nfunc addIndexes() (err error) {\n\tdb := GetDatabase()\n\tdefer db.Close()\n\n\tindex := &Index{\n\t\tCollection: db.Users(),\n\t\tKeys: &bson.D{\n\t\t\t{\"username\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Users(),\n\t\tKeys: &bson.D{\n\t\t\t{\"type\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Users(),\n\t\tKeys: &bson.D{\n\t\t\t{\"roles\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Users(),\n\t\tKeys: &bson.D{\n\t\t\t{\"token\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Logs(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 4320 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Audits(),\n\t\tKeys: &bson.D{\n\t\t\t{\"user\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Policies(),\n\t\tKeys: &bson.D{\n\t\t\t{\"roles\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.CsrfTokens(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 168 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.SecondaryTokens(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 3 * time.Minute,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Nodes(),\n\t\tKeys: &bson.D{\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Nodes(),\n\t\tKeys: &bson.D{\n\t\t\t{\"pools\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.NodePorts(),\n\t\tKeys: &bson.D{\n\t\t\t{\"datacenter\", 1},\n\t\t\t{\"protocol\", 1},\n\t\t\t{\"port\", 1},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.NodePorts(),\n\t\tKeys: &bson.D{\n\t\t\t{\"resource\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Nonces(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 24 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Rokeys(),\n\t\tKeys: &bson.D{\n\t\t\t{\"type\", 1},\n\t\t\t{\"timeblock\", 1},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Rokeys(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 720 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Devices(),\n\t\tKeys: &bson.D{\n\t\t\t{\"user\", 1},\n\t\t\t{\"mode\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Devices(),\n\t\tKeys: &bson.D{\n\t\t\t{\"provider\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Alerts(),\n\t\tKeys: &bson.D{\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Alerts(),\n\t\tKeys: &bson.D{\n\t\t\t{\"roles\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.AlertsEvent(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 48 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.AlertsEvent(),\n\t\tKeys: &bson.D{\n\t\t\t{\"source\", 1},\n\t\t\t{\"resource\", 1},\n\t\t\t{\"timestamp\", -1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.AlertsEventLock(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 72 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Organizations(),\n\t\tKeys: &bson.D{\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Organizations(),\n\t\tKeys: &bson.D{\n\t\t\t{\"roles\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Images(),\n\t\tKeys: &bson.D{\n\t\t\t{\"key\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Images(),\n\t\tKeys: &bson.D{\n\t\t\t{\"organization\", 1},\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Images(),\n\t\tKeys: &bson.D{\n\t\t\t{\"storage\", 1},\n\t\t\t{\"key\", 1},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Images(),\n\t\tKeys: &bson.D{\n\t\t\t{\"disk\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.LvmLock(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 90 * time.Second,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Disks(),\n\t\tKeys: &bson.D{\n\t\t\t{\"instance\", 1},\n\t\t\t{\"index\", 1},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Disks(),\n\t\tKeys: &bson.D{\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Disks(),\n\t\tKeys: &bson.D{\n\t\t\t{\"organization\", 1},\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Disks(),\n\t\tKeys: &bson.D{\n\t\t\t{\"node\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Domains(),\n\t\tKeys: &bson.D{\n\t\t\t{\"last_update\", -1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.DomainsRecords(),\n\t\tKeys: &bson.D{\n\t\t\t{\"domain\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.DomainsRecords(),\n\t\tKeys: &bson.D{\n\t\t\t{\"domain\", 1},\n\t\t\t{\"sub_domain\", 1},\n\t\t\t{\"type\", 1},\n\t\t\t{\"value\", 1},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Datacenters(),\n\t\tKeys: &bson.D{\n\t\t\t{\"organization\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Datacenters(),\n\t\tKeys: &bson.D{\n\t\t\t{\"match_organizations\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.BlocksIp(),\n\t\tKeys: &bson.D{\n\t\t\t{\"block\", 1},\n\t\t\t{\"ip\", 1},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.BlocksIp(),\n\t\tKeys: &bson.D{\n\t\t\t{\"instance\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Journal(),\n\t\tKeys: &bson.D{\n\t\t\t{\"r\", 1},\n\t\t\t{\"k\", 1},\n\t\t\t{\"t\", 1},\n\t\t\t{\"c\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Journal(),\n\t\tKeys: &bson.D{\n\t\t\t{\"t\", 1},\n\t\t},\n\t\tExpire: 2160 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Vpcs(),\n\t\tKeys: &bson.D{\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Vpcs(),\n\t\tKeys: &bson.D{\n\t\t\t{\"organization\", 1},\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Vpcs(),\n\t\tKeys: &bson.D{\n\t\t\t{\"vpc_id\", 1},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Vpcs(),\n\t\tKeys: &bson.D{\n\t\t\t{\"datacenter\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.VpcsIp(),\n\t\tKeys: &bson.D{\n\t\t\t{\"vpc\", 1},\n\t\t\t{\"subnet\", 1},\n\t\t\t{\"ip\", 1},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.VpcsIp(),\n\t\tKeys: &bson.D{\n\t\t\t{\"vpc\", 1},\n\t\t\t{\"instance\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.VpcsIp(),\n\t\tKeys: &bson.D{\n\t\t\t{\"vpc\", 1},\n\t\t\t{\"subnet\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Sessions(),\n\t\tKeys: &bson.D{\n\t\t\t{\"user\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Sessions(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 4320 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Firewalls(),\n\t\tKeys: &bson.D{\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Firewalls(),\n\t\tKeys: &bson.D{\n\t\t\t{\"organization\", 1},\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Firewalls(),\n\t\tKeys: &bson.D{\n\t\t\t{\"roles\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Firewalls(),\n\t\tKeys: &bson.D{\n\t\t\t{\"roles\", 1},\n\t\t\t{\"organization\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Zones(),\n\t\tKeys: &bson.D{\n\t\t\t{\"datacenter\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Balancers(),\n\t\tKeys: &bson.D{\n\t\t\t{\"datacenter\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Authorities(),\n\t\tKeys: &bson.D{\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Authorities(),\n\t\tKeys: &bson.D{\n\t\t\t{\"organization\", 1},\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Authorities(),\n\t\tKeys: &bson.D{\n\t\t\t{\"roles\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Authorities(),\n\t\tKeys: &bson.D{\n\t\t\t{\"organization\", 1},\n\t\t\t{\"roles\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Instances(),\n\t\tKeys: &bson.D{\n\t\t\t{\"node\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Instances(),\n\t\tKeys: &bson.D{\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Instances(),\n\t\tKeys: &bson.D{\n\t\t\t{\"organization\", 1},\n\t\t\t{\"name\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Instances(),\n\t\tKeys: &bson.D{\n\t\t\t{\"node\", 1},\n\t\t\t{\"vnc_display\", 1},\n\t\t},\n\t\tPartial: &bson.M{\n\t\t\t\"vnc_display\": &bson.M{\n\t\t\t\t\"$gt\": 0,\n\t\t\t},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Instances(),\n\t\tKeys: &bson.D{\n\t\t\t{\"node\", 1},\n\t\t\t{\"spice_port\", 1},\n\t\t},\n\t\tPartial: &bson.M{\n\t\t\t\"spice_port\": &bson.M{\n\t\t\t\t\"$gt\": 0,\n\t\t\t},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Instances(),\n\t\tKeys: &bson.D{\n\t\t\t{\"unix_id\", 1},\n\t\t},\n\t\tPartial: &bson.M{\n\t\t\t\"unix_id\": &bson.M{\n\t\t\t\t\"$gt\": 0,\n\t\t\t},\n\t\t},\n\t\tUnique: true,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\tindex = &Index{\n\t\tCollection: db.Instances(),\n\t\tKeys: &bson.D{\n\t\t\t{\"node_ports.node_port\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Units(),\n\t\tKeys: &bson.D{\n\t\t\t{\"pod\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Tasks(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 48 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Events(),\n\t\tKeys: &bson.D{\n\t\t\t{\"channel\", 1},\n\t\t},\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.AcmeChallenges(),\n\t\tKeys: &bson.D{\n\t\t\t{\"timestamp\", 1},\n\t\t},\n\t\tExpire: 3 * time.Minute,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tindex = &Index{\n\t\tCollection: db.Geo(),\n\t\tKeys: &bson.D{\n\t\t\t{\"t\", 1},\n\t\t},\n\t\tExpire: 360 * time.Hour,\n\t}\n\terr = index.Create()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc addCollections() (err error) {\n\tdb := GetDatabase()\n\tdefer db.Close()\n\n\tcursor, err := db.database.ListCollections(\n\t\tdb, &bson.M{})\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\teventsExists := false\n\tisCapped := false\n\n\tfor cursor.Next(db) {\n\t\titem := &struct {\n\t\t\tName    string `bson:\"name\"`\n\t\t\tOptions bson.M `bson:\"options\"`\n\t\t}{}\n\t\terr = cursor.Decode(item)\n\t\tif err != nil {\n\t\t\terr = ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif item.Name == \"events\" {\n\t\t\teventsExists = true\n\t\t\tif options, ok := item.Options[\"capped\"]; ok {\n\t\t\t\tif cappedBool, ok := options.(bool); ok && cappedBool {\n\t\t\t\t\tisCapped = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\treturn\n\t}\n\n\tif eventsExists && !isCapped {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"collection\": \"events\",\n\t\t}).Warning(\"database: Correcting events capped collection\")\n\n\t\terr = db.database.Collection(\"events\").Drop(db)\n\t\tif err != nil {\n\t\t\terr = ParseError(err)\n\t\t\treturn\n\t\t}\n\t\teventsExists = false\n\t}\n\n\tif !eventsExists {\n\t\terr = db.database.RunCommand(\n\t\t\tdb,\n\t\t\tbson.D{\n\t\t\t\t{\"create\", \"events\"},\n\t\t\t\t{\"capped\", true},\n\t\t\t\t{\"max\", 5000},\n\t\t\t\t{\"size\", 20971520},\n\t\t\t},\n\t\t).Err()\n\t\tif err != nil {\n\t\t\terr = ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tmodule := requires.New(\"database\")\n\tmodule.After(\"config\")\n\n\tmodule.Handler = func() (err error) {\n\t\tfor {\n\t\t\te := Connect()\n\t\t\tif e != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": e,\n\t\t\t\t}).Error(\"database: Connection error\")\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttime.Sleep(constants.RetryDelay)\n\t\t}\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "database/errors.go",
    "content": "package database\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype ConnectionError struct {\n\terrors.DropboxError\n}\n\ntype IndexError struct {\n\terrors.DropboxError\n}\n\ntype NotFoundError struct {\n\terrors.DropboxError\n}\n\ntype ImmutableKeyError struct {\n\terrors.DropboxError\n}\n\ntype DuplicateKeyError struct {\n\terrors.DropboxError\n}\n\ntype UnknownError struct {\n\terrors.DropboxError\n}\n\ntype CertificateError struct {\n\terrors.DropboxError\n}\n\ntype IndexConflict struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "database/index.go",
    "content": "package database\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tindexes     = map[string]set.Set{}\n\tindexesLock = sync.Mutex{}\n)\n\ntype bsonIndex struct {\n\tName string `bson:\"name\"`\n}\n\ntype Index struct {\n\tCollection *Collection\n\tKeys       *bson.D\n\tUnique     bool\n\tPartial    interface{}\n\tExpire     time.Duration\n}\n\nfunc GenerateIndexName(doc bson.D) (indexName string, err error) {\n\tname := bytes.NewBufferString(\"\")\n\tfirst := true\n\n\tfor _, elem := range doc {\n\t\tif !first {\n\t\t\t_, err = name.WriteRune('_')\n\t\t\tif err != nil {\n\t\t\t\terr = &UnknownError{\n\t\t\t\t\terrors.Wrap(err, \"database: Write rune error\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t_, err = name.WriteString(elem.Key)\n\t\tif err != nil {\n\t\t\terr = &UnknownError{\n\t\t\t\terrors.Wrap(err, \"database: Write string error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t_, err = name.WriteRune('_')\n\t\tif err != nil {\n\t\t\terr = &UnknownError{\n\t\t\t\terrors.Wrap(err, \"database: Write rune error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tvalue := \"\"\n\t\tswitch val := elem.Value.(type) {\n\t\tcase int, int32, int64:\n\t\t\tvalue = fmt.Sprintf(\"%d\", val)\n\t\tcase string:\n\t\t\tvalue = val\n\t\tdefault:\n\t\t\terr = &UnknownError{\n\t\t\t\terrors.New(\"database: Invalid index value\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t_, err = name.WriteString(value)\n\t\tif err != nil {\n\t\t\terr = &UnknownError{\n\t\t\t\terrors.Wrap(err, \"database: Write string error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfirst = false\n\t}\n\n\tindexName = name.String()\n\treturn\n}\n\nfunc (i *Index) Create() (err error) {\n\topts := options.Index()\n\n\tif i.Unique {\n\t\topts.SetUnique(true)\n\t}\n\n\tif i.Partial != nil {\n\t\topts.SetPartialFilterExpression(i.Partial)\n\t}\n\n\tif i.Expire != 0 {\n\t\topts.SetExpireAfterSeconds(int32(i.Expire.Seconds()))\n\t}\n\n\tindexName, err := GenerateIndexName(*i.Keys)\n\tif err != nil {\n\t\treturn\n\t}\n\topts.SetName(indexName)\n\n\tname, err := i.Collection.Indexes().CreateOne(\n\t\tcontext.Background(),\n\t\tmongo.IndexModel{\n\t\t\tKeys:    i.Keys,\n\t\t\tOptions: opts,\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = ParseError(err)\n\t\tif _, ok := err.(*IndexConflict); ok {\n\t\t\terr = nil\n\n\t\t\terr = i.Collection.Indexes().DropOne(\n\t\t\t\tcontext.Background(),\n\t\t\t\tindexName,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tname, err = i.Collection.Indexes().CreateOne(\n\t\t\t\tcontext.Background(),\n\t\t\t\tmongo.IndexModel{\n\t\t\t\t\tKeys:    i.Keys,\n\t\t\t\t\tOptions: opts,\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\terr = ParseError(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tcollName := i.Collection.Name()\n\tindexesLock.Lock()\n\tcollIndexes, ok := indexes[collName]\n\tif !ok {\n\t\tcollIndexes = set.NewSet()\n\t\tindexes[collName] = collIndexes\n\t}\n\tcollIndexes.Add(name)\n\tindexesLock.Unlock()\n\n\treturn\n}\n\nfunc CleanIndexes(db *Database) (err error) {\n\tindexesLock.Lock()\n\tcurIndexes := indexes\n\tindexesLock.Unlock()\n\n\tfor collName, collIndexes := range curIndexes {\n\t\tcoll := db.GetCollection(collName)\n\n\t\tcursor, e := coll.Indexes().List(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor cursor.Next(db) {\n\t\t\tindex := &bsonIndex{}\n\n\t\t\terr = cursor.Decode(index)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"collection\": collName,\n\t\t\t\t\t\"error\":      err,\n\t\t\t\t}).Error(\"database: Failed to decode index\")\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif index.Name == \"_id\" || index.Name == \"_id_\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif collIndexes.Contains(index.Name) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"collection\": collName,\n\t\t\t\t\"index\":      index.Name,\n\t\t\t}).Info(\"database: Dropping unused index\")\n\n\t\t\terr = coll.Indexes().DropOne(\n\t\t\t\tdb,\n\t\t\t\tindex.Name,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tcursor.Close(db)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr = cursor.Err()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"collection\": collName,\n\t\t\t\t\"error\":      err,\n\t\t\t}).Error(\"database: Cursor error listing indexes\")\n\t\t}\n\n\t\tcursor.Close(db)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "database/utils.go",
    "content": "package database\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n)\n\nfunc FindProject(fields ...string) *options.FindOptionsBuilder {\n\tprcj := bson.D{}\n\n\tfor _, field := range fields {\n\t\tprcj = append(prcj, bson.E{Key: field, Value: 1})\n\t}\n\n\topts := options.Find()\n\topts.SetProjection(prcj)\n\n\treturn opts\n}\n\nfunc FindOneProject(fields ...string) *options.FindOneOptionsBuilder {\n\tprcj := bson.D{}\n\n\tfor _, field := range fields {\n\t\tprcj = append(prcj, bson.E{Key: field, Value: 1})\n\t}\n\n\topts := options.FindOne()\n\topts.SetProjection(prcj)\n\n\treturn opts\n}\n\nfunc GetErrorCodes(err error) (errCodes []int) {\n\tswitch err := err.(type) {\n\tcase mongo.CommandError:\n\t\terrCodes = []int{int(err.Code)}\n\t\tif strings.Contains(err.Name, \"Conflict\") {\n\t\t\terrCodes = append(errCodes, 85)\n\t\t}\n\t\tbreak\n\tcase mongo.WriteError:\n\t\terrCodes = []int{err.Code}\n\t\tbreak\n\tcase mongo.BulkWriteError:\n\t\terrCodes = []int{err.Code}\n\t\tbreak\n\tcase mongo.WriteConcernError:\n\t\terrCodes = []int{err.Code}\n\t\tbreak\n\tcase mongo.WriteException:\n\t\terrCodes = []int{}\n\t\tif err.WriteConcernError != nil {\n\t\t\terrCodes = append(errCodes, err.WriteConcernError.Code)\n\t\t}\n\t\tif err.WriteErrors != nil {\n\t\t\tfor _, e := range err.WriteErrors {\n\t\t\t\terrCodes = append(errCodes, e.Code)\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase mongo.WriteErrors:\n\t\terrCodes = []int{}\n\t\tfor _, e := range err {\n\t\t\teCodes := GetErrorCodes(e)\n\t\t\terrCodes = append(errCodes, eCodes...)\n\t\t}\n\t\tbreak\n\tcase *mongo.WriteError:\n\t\terrCodes = []int{err.Code}\n\t\tbreak\n\tcase *mongo.BulkWriteError:\n\t\terrCodes = []int{err.Code}\n\t\tbreak\n\tcase *mongo.WriteConcernError:\n\t\terrCodes = []int{err.Code}\n\t\tbreak\n\tcase *mongo.WriteException:\n\t\terrCodes = []int{}\n\t\tif err.WriteConcernError != nil {\n\t\t\terrCodes = append(errCodes, err.WriteConcernError.Code)\n\t\t}\n\t\tif err.WriteErrors != nil {\n\t\t\tfor _, e := range err.WriteErrors {\n\t\t\t\terrCodes = append(errCodes, e.Code)\n\t\t\t}\n\t\t}\n\t\tbreak\n\t}\n\n\treturn\n}\n\nfunc ParseError(err error) (newErr error) {\n\tif err == mongo.ErrNoDocuments {\n\t\tnewErr = &NotFoundError{\n\t\t\terrors.New(\"database: Not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\terrCodes := GetErrorCodes(err)\n\tfor _, errCode := range errCodes {\n\t\tswitch errCode {\n\t\tcase 66:\n\t\t\tnewErr = &ImmutableKeyError{\n\t\t\t\terrors.New(\"database: Immutable key\"),\n\t\t\t}\n\t\t\treturn\n\t\tcase 85:\n\t\t\tnewErr = &IndexConflict{\n\t\t\t\terrors.New(\"database: Index conflict\"),\n\t\t\t}\n\t\t\treturn\n\t\tcase 11000, 11001, 12582, 16460:\n\t\t\tnewErr = &DuplicateKeyError{\n\t\t\t\terrors.New(\"database: Duplicate key\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tnewErr = &UnknownError{\n\t\terrors.Wrap(err, fmt.Sprintf(\n\t\t\t\"database: Unknown error %v\", errCodes)),\n\t}\n\treturn\n}\n\nfunc IgnoreNotFoundError(err error) (newErr error) {\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *NotFoundError:\n\t\t\tnewErr = nil\n\t\t\tbreak\n\t\tdefault:\n\t\t\tnewErr = err\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "datacenter/constants.go",
    "content": "package datacenter\n\nconst (\n\tDefault     = \"default\"\n\tVxlanVlan   = \"vxlan_vlan\"\n\tWgVxlanVlan = \"wg_vxlan_vlan\"\n\n\tWg4 = \"wg4\"\n\tWg6 = \"wg6\"\n)\n"
  },
  {
    "path": "datacenter/datacenter.go",
    "content": "package datacenter\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Datacenter struct {\n\tId                  bson.ObjectID   `bson:\"_id,omitempty\" json:\"id\"`\n\tName                string          `bson:\"name\" json:\"name\"`\n\tComment             string          `bson:\"comment\" json:\"comment\"`\n\tMatchOrganizations  bool            `bson:\"match_organizations\" json:\"match_organizations\"`\n\tOrganizations       []bson.ObjectID `bson:\"organizations\" json:\"organizations\"`\n\tNetworkMode         string          `bson:\"network_mode\" json:\"network_mode\"`\n\tWgMode              string          `bson:\"wg_mode\" json:\"wg_mode\"`\n\tJumboMtu            int             `bson:\"jumbo_mtu\" json:\"jumbo_mtu\"`\n\tPublicStorages      []bson.ObjectID `bson:\"public_storages\" json:\"public_storages\"`\n\tPrivateStorage      bson.ObjectID   `bson:\"private_storage,omitempty\" json:\"private_storage\"`\n\tPrivateStorageClass string          `bson:\"private_storage_class\" json:\"private_storage_class\"`\n\tBackupStorage       bson.ObjectID   `bson:\"backup_storage,omitempty\" json:\"backup_storage\"`\n\tBackupStorageClass  string          `bson:\"backup_storage_class\" json:\"backup_storage_class\"`\n}\n\ntype Completion struct {\n\tId          bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName        string        `bson:\"name\" json:\"name\"`\n\tNetworkMode string        `bson:\"network_mode\" json:\"network_mode\"`\n}\n\nfunc (d *Datacenter) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\td.Name = utils.FilterName(d.Name)\n\n\tif d.Organizations == nil || !d.MatchOrganizations {\n\t\td.Organizations = []bson.ObjectID{}\n\t}\n\n\tif d.PublicStorages == nil {\n\t\td.PublicStorages = []bson.ObjectID{}\n\t}\n\n\tswitch d.NetworkMode {\n\tcase Default:\n\t\tbreak\n\tcase VxlanVlan:\n\t\tbreak\n\tcase WgVxlanVlan:\n\t\tbreak\n\tcase \"\":\n\t\td.NetworkMode = Default\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_network_mode\",\n\t\t\tMessage: \"Network mode invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.NetworkMode == WgVxlanVlan {\n\t\tswitch d.WgMode {\n\t\tcase Wg4:\n\t\t\tbreak\n\t\tcase Wg6:\n\t\t\tbreak\n\t\tcase \"\":\n\t\t\td.WgMode = Wg4\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_wg_mode\",\n\t\t\t\tMessage: \"WireGuard mode invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\td.WgMode = \"\"\n\t}\n\n\tif d.JumboMtu != 0 && (d.JumboMtu < 600 || d.JumboMtu > 65535) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_jumbo_mtu\",\n\t\t\tMessage: \"Jumbo MTU invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Datacenter) Vxlan() bool {\n\treturn d.NetworkMode == VxlanVlan || d.NetworkMode == WgVxlanVlan\n}\n\nfunc (d *Datacenter) GetBaseInternalMtu() (mtuSize int) {\n\tif node.Self.JumboFrames || node.Self.JumboFramesInternal {\n\t\tif d.JumboMtu != 0 {\n\t\t\tmtuSize = d.JumboMtu\n\t\t} else {\n\t\t\tmtuSize = settings.Hypervisor.JumboMtu\n\t\t}\n\t} else {\n\t\tmtuSize = settings.Hypervisor.NormalMtu\n\t}\n\treturn\n}\n\nfunc (d *Datacenter) GetBaseExternalMtu() (mtuSize int) {\n\tif node.Self.JumboFrames {\n\t\tif d.JumboMtu != 0 {\n\t\t\tmtuSize = d.JumboMtu\n\t\t} else {\n\t\t\tmtuSize = settings.Hypervisor.JumboMtu\n\t\t}\n\t} else {\n\t\tmtuSize = settings.Hypervisor.NormalMtu\n\t}\n\treturn\n}\n\nfunc (d *Datacenter) GetOverlayMtu() (mtuSize int) {\n\tif d.NetworkMode == WgVxlanVlan {\n\t\tif node.Self.JumboFrames {\n\t\t\tif d.JumboMtu != 0 {\n\t\t\t\tmtuSize = d.JumboMtu\n\t\t\t} else {\n\t\t\t\tmtuSize = settings.Hypervisor.JumboMtu\n\t\t\t}\n\t\t} else {\n\t\t\tmtuSize = settings.Hypervisor.NormalMtu\n\t\t}\n\n\t\tif d.WgMode == Wg6 {\n\t\t\tmtuSize -= 150\n\t\t} else {\n\t\t\tmtuSize -= 110\n\t\t}\n\t} else {\n\t\tif node.Self.JumboFrames || node.Self.JumboFramesInternal {\n\t\t\tif d.JumboMtu != 0 {\n\t\t\t\tmtuSize = d.JumboMtu\n\t\t\t} else {\n\t\t\t\tmtuSize = settings.Hypervisor.JumboMtu\n\t\t\t}\n\t\t} else {\n\t\t\tmtuSize = settings.Hypervisor.NormalMtu\n\t\t}\n\n\t\tif d.NetworkMode == VxlanVlan {\n\t\t\tmtuSize -= 50\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (d *Datacenter) GetInstanceMtu() (mtuSize int) {\n\tif d.NetworkMode == WgVxlanVlan {\n\t\tif node.Self.JumboFrames {\n\t\t\tif d.JumboMtu != 0 {\n\t\t\t\tmtuSize = d.JumboMtu\n\t\t\t} else {\n\t\t\t\tmtuSize = settings.Hypervisor.JumboMtu\n\t\t\t}\n\t\t} else {\n\t\t\tmtuSize = settings.Hypervisor.NormalMtu\n\t\t}\n\n\t\tif d.WgMode == Wg6 {\n\t\t\tmtuSize -= 154\n\t\t} else {\n\t\t\tmtuSize -= 114\n\t\t}\n\t} else {\n\t\tif node.Self.JumboFrames || node.Self.JumboFramesInternal {\n\t\t\tif d.JumboMtu != 0 {\n\t\t\t\tmtuSize = d.JumboMtu\n\t\t\t} else {\n\t\t\t\tmtuSize = settings.Hypervisor.JumboMtu\n\t\t\t}\n\t\t} else {\n\t\t\tmtuSize = settings.Hypervisor.NormalMtu\n\t\t}\n\n\t\tif d.NetworkMode == VxlanVlan {\n\t\t\tmtuSize -= 54\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (d *Datacenter) Commit(db *database.Database) (err error) {\n\tcoll := db.Datacenters()\n\n\terr = coll.Commit(d.Id, d)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Datacenter) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Datacenters()\n\n\terr = coll.CommitFields(d.Id, d, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Datacenter) Insert(db *database.Database) (err error) {\n\tcoll := db.Datacenters()\n\n\tif !d.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"datacenter: Datacenter already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := coll.InsertOne(db, d)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\td.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  },
  {
    "path": "datacenter/utils.go",
    "content": "package datacenter\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, dcId bson.ObjectID) (\n\tdc *Datacenter, err error) {\n\n\tcoll := db.Datacenters()\n\tdc = &Datacenter{}\n\n\terr = coll.FindOneId(dcId, dc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExistsOrg(db *database.Database, orgId, dcId bson.ObjectID) (\n\texists bool, err error) {\n\n\tcoll := db.Datacenters()\n\n\tcount, err := coll.CountDocuments(db, &bson.M{\n\t\t\"_id\": dcId,\n\t\t\"$or\": []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"match_organizations\": false,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"organizations\": orgId,\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif count > 0 {\n\t\texists = true\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database) (dcs []*Datacenter, err error) {\n\tcoll := db.Datacenters()\n\tdcs = []*Datacenter{}\n\n\tcursor, err := coll.Find(db, &bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdc := &Datacenter{}\n\t\terr = cursor.Decode(dc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdcs = append(dcs, dc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (dc *Datacenter, err error) {\n\tcoll := db.Datacenters()\n\tdc = &Datacenter{}\n\n\terr = coll.FindOne(db, query).Decode(dc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNamesOrg(db *database.Database, orgId bson.ObjectID) (\n\tdcs []*Completion, err error) {\n\n\tcoll := db.Datacenters()\n\tdcs = []*Completion{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"$or\": []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"match_organizations\": false,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"organizations\": orgId,\n\t\t\t},\n\t\t},\n\t}, options.Find().\n\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\tSetProjection(bson.D{\n\t\t\t{\"name\", 1},\n\t\t\t{\"network_mode\", 1},\n\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdc := &Completion{}\n\t\terr = cursor.Decode(dc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdcs = append(dcs, dc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNames(db *database.Database, query *bson.M) (\n\tdcs []*Completion, err error) {\n\n\tcoll := db.Certificates()\n\tdcs = []*Completion{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetProjection(bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"network_mode\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdc := &Completion{}\n\t\terr = cursor.Decode(dc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdcs = append(dcs, dc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (dc []*Datacenter, count int64, err error) {\n\n\tcoll := db.Datacenters()\n\tdc = []*Datacenter{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\td := &Datacenter{}\n\t\terr = cursor.Decode(d)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdc = append(dc, d)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DistinctOrg(db *database.Database, orgId bson.ObjectID) (\n\tids []bson.ObjectID, err error) {\n\n\tcoll := db.Datacenters()\n\tids = []bson.ObjectID{}\n\n\terr = coll.Distinct(db, \"_id\", &bson.M{\n\t\t\"$or\": []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"match_organizations\": false,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"organizations\": orgId,\n\t\t\t},\n\t\t},\n\t}).Decode(&ids)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, dcId bson.ObjectID) (err error) {\n\tcoll := db.Datacenters()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": dcId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, dcIds []bson.ObjectID) (\n\terr error) {\n\tcoll := db.Datacenters()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": dcIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tdcIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Datacenters()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": dcIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "defaults/defaults.go",
    "content": "package defaults\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"slices\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/cloudinit\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/organization\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc initStorage(db *database.Database) (err error) {\n\tstores, err := storage.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(stores) == 0 {\n\t\tstore := &storage.Storage{\n\t\t\tName:     \"pritunl-images\",\n\t\t\tType:     storage.Public,\n\t\t\tEndpoint: \"images.pritunl.com\",\n\t\t\tBucket:   \"stable\",\n\t\t\tInsecure: false,\n\t\t}\n\n\t\terrData, e := store.Validate(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"defaults: Storage validate error %s\",\n\t\t\t\t\terrData.Message,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = store.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"storage\": store.Id.Hex(),\n\t\t}).Info(\"defaults: Created default storage\")\n\n\t\tevent.PublishDispatch(db, \"storage.change\")\n\t}\n\n\treturn\n}\n\nfunc initOrganization(db *database.Database) (\n\tdefaultOrg bson.ObjectID, err error) {\n\n\torgs, err := organization.GetAll(db, &bson.M{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(orgs) == 0 {\n\t\torg := &organization.Organization{\n\t\t\tName:  \"org\",\n\t\t\tRoles: []string{\"org\"},\n\t\t}\n\n\t\terrData, e := org.Validate(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"defaults: Organization validate error %s\",\n\t\t\t\t\terrData.Message,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = org.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tdefaultOrg = org.Id\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"organization\": org.Id.Hex(),\n\t\t}).Info(\"defaults: Created default organization\")\n\n\t\tevent.PublishDispatch(db, \"organization.change\")\n\t} else {\n\t\tfor _, org := range orgs {\n\t\t\tif defaultOrg.IsZero() || org.Name == \"org\" {\n\t\t\t\tdefaultOrg = org.Id\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc initDatacenter(db *database.Database) (\n\tdefaultDc bson.ObjectID, err error) {\n\n\tdcs, err := datacenter.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(dcs) == 0 {\n\t\tstores, e := storage.GetAll(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tpublicStorages := []bson.ObjectID{}\n\t\tfor _, store := range stores {\n\t\t\tif store.Endpoint == \"images.pritunl.com\" &&\n\t\t\t\tstore.Bucket == \"stable\" {\n\n\t\t\t\tpublicStorages = append(publicStorages, store.Id)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tdc := &datacenter.Datacenter{\n\t\t\tName:           \"us-west-1\",\n\t\t\tNetworkMode:    datacenter.Default,\n\t\t\tPublicStorages: publicStorages,\n\t\t}\n\n\t\terrData, e := dc.Validate(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"defaults: Datacenter validate error %s\",\n\t\t\t\t\terrData.Message,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = dc.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tdefaultDc = dc.Id\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"datacenter\": dc.Id.Hex(),\n\t\t}).Info(\"defaults: Created default datacenter\")\n\n\t\tevent.PublishDispatch(db, \"datacenter.change\")\n\t} else {\n\t\tfor _, dc := range dcs {\n\t\t\tif defaultDc.IsZero() || dc.Name == \"us-west-1\" {\n\t\t\t\tdefaultDc = dc.Id\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc initZone(db *database.Database, defaultDc bson.ObjectID) (\n\terr error) {\n\n\tzones, err := zone.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(zones) == 0 {\n\t\tzne := &zone.Zone{\n\t\t\tName:       \"us-west-1a\",\n\t\t\tDatacenter: defaultDc,\n\t\t}\n\n\t\terrData, e := zne.Validate(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"defaults: Zone validate error %s\",\n\t\t\t\t\terrData.Message,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = zne.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"zone\": zne.Id.Hex(),\n\t\t}).Info(\"defaults: Created default zone\")\n\n\t\tevent.PublishDispatch(db, \"zone.change\")\n\t}\n\n\treturn\n}\n\nfunc initVpc(db *database.Database, defaultOrg,\n\tdefaultDc bson.ObjectID) (err error) {\n\n\tif defaultOrg.IsZero() {\n\t\treturn\n\t}\n\n\tvcs, err := vpc.GetAll(db, &bson.M{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(vcs) == 0 {\n\t\tstart, end, step := 100, 220, 4\n\t\trandomStep := rand.Intn((end-start)/step + 1)\n\t\tnetNum := start + (randomStep * step)\n\n\t\tvc := &vpc.Vpc{\n\t\t\tName:         \"vpc\",\n\t\t\tOrganization: defaultOrg,\n\t\t\tDatacenter:   defaultDc,\n\t\t\tVpcId:        utils.RandInt(1001, 3999),\n\t\t\tNetwork:      fmt.Sprintf(\"10.%d.0.0/14\", netNum),\n\t\t\tSubnets: []*vpc.Subnet{\n\t\t\t\t&vpc.Subnet{\n\t\t\t\t\tName:    \"primary\",\n\t\t\t\t\tNetwork: fmt.Sprintf(\"10.%d.1.0/24\", netNum),\n\t\t\t\t},\n\t\t\t\t&vpc.Subnet{\n\t\t\t\t\tName:    \"management\",\n\t\t\t\t\tNetwork: fmt.Sprintf(\"10.%d.2.0/24\", netNum),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tvc.InitVpc()\n\n\t\terrData, e := vc.Validate(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"defaults: VPC validate error %s\",\n\t\t\t\t\terrData.Message,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = vc.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"vpc\": vc.Id.Hex(),\n\t\t}).Info(\"defaults: Created default VPC\")\n\n\t\tevent.PublishDispatch(db, \"vpc.change\")\n\t}\n\n\treturn\n}\n\nfunc initFirewall(db *database.Database, defaultOrg bson.ObjectID) (\n\terr error) {\n\n\tif defaultOrg.IsZero() {\n\t\treturn\n\t}\n\n\tfires, err := firewall.GetAll(db, &bson.M{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(fires) == 0 {\n\t\tfire := &firewall.Firewall{\n\t\t\tName:         \"instance\",\n\t\t\tOrganization: defaultOrg,\n\t\t\tRoles: []string{\n\t\t\t\t\"instance\",\n\t\t\t},\n\t\t\tIngress: []*firewall.Rule{\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Icmp,\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\tPort:     \"22\",\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\tPort:     \"80\",\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\tPort:     \"443\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\terrData, e := fire.Validate(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"defaults: Firewall validate error %s\",\n\t\t\t\t\terrData.Message,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = fire.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tfire = &firewall.Firewall{\n\t\t\tName:         \"node\",\n\t\t\tOrganization: firewall.Global,\n\t\t\tComment: \"22/tcp - SSH\\n\" +\n\t\t\t\t\"80/tcp - HTTP\\n\" +\n\t\t\t\t\"443/tcp - HTTPS\\n\" +\n\t\t\t\t\"4789/udp - VXLAN cross-node\\n\" +\n\t\t\t\t\"20000-25000/tcp - VNC cross-node\\n\" +\n\t\t\t\t\"30000-32767/tcp - TCP NodePorts\\n\" +\n\t\t\t\t\"30000-32767/udp - UDP NodePorts\",\n\t\t\tRoles: []string{\n\t\t\t\t\"node-firewall\",\n\t\t\t},\n\t\t\tIngress: []*firewall.Rule{\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Icmp,\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\tPort:     \"22\",\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\tPort:     \"80\",\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\tPort:     \"443\",\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"10.0.0.0/8\",\n\t\t\t\t\t\t\"100.64.0.0/10\",\n\t\t\t\t\t\t\"172.16.0.0/12\",\n\t\t\t\t\t\t\"192.168.0.0/16\",\n\t\t\t\t\t\t\"198.18.0.0/15\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Udp,\n\t\t\t\t\tPort:     \"4789\",\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"10.0.0.0/8\",\n\t\t\t\t\t\t\"100.64.0.0/10\",\n\t\t\t\t\t\t\"172.16.0.0/12\",\n\t\t\t\t\t\t\"192.168.0.0/16\",\n\t\t\t\t\t\t\"198.18.0.0/15\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\tPort:     \"20000-25000\",\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Tcp,\n\t\t\t\t\tPort:     \"30000-32767\",\n\t\t\t\t},\n\t\t\t\t&firewall.Rule{\n\t\t\t\t\tSourceIps: []string{\n\t\t\t\t\t\t\"0.0.0.0/0\",\n\t\t\t\t\t\t\"::/0\",\n\t\t\t\t\t},\n\t\t\t\t\tProtocol: firewall.Udp,\n\t\t\t\t\tPort:     \"30000-32767\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\terrData, e = fire.Validate(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"defaults: Firewall validate error %s\",\n\t\t\t\t\terrData.Message,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = fire.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"firewall\": fire.Id.Hex(),\n\t\t}).Info(\"defaults: Created default firewall\")\n\n\t\tevent.PublishDispatch(db, \"firewall.change\")\n\t}\n\n\treturn\n}\n\nfunc initAuthority(db *database.Database, defaultOrg bson.ObjectID) (\n\terr error) {\n\n\tif defaultOrg.IsZero() {\n\t\treturn\n\t}\n\n\tauthrs, err := authority.GetAll(db, &bson.M{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(authrs) == 0 {\n\t\tauthr := &authority.Authority{\n\t\t\tName:         \"cloud\",\n\t\t\tType:         authority.SshKey,\n\t\t\tOrganization: defaultOrg,\n\t\t\tRoles: []string{\n\t\t\t\t\"instance\",\n\t\t\t},\n\t\t}\n\n\t\terrData, e := authr.Validate(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"defaults: Authority validate error %s\",\n\t\t\t\t\terrData.Message,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = authr.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"authority\": authr.Id.Hex(),\n\t\t}).Info(\"defaults: Created default authority\")\n\n\t\tevent.PublishDispatch(db, \"authority.change\")\n\t}\n\n\treturn\n}\n\nfunc initNode(db *database.Database, defaultOrg bson.ObjectID) (\n\terr error) {\n\n\tif defaultOrg.IsZero() {\n\t\treturn\n\t}\n\n\tif !node.Self.Zone.IsZero() {\n\t\treturn\n\t}\n\n\tdcs, err := datacenter.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzones, err := zone.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnodes, err := node.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(dcs) != 1 || len(zones) != 1 || len(nodes) != 1 {\n\t\treturn\n\t}\n\n\tdc := dcs[0]\n\tnode.Self.Datacenter = zones[0].Datacenter\n\tnode.Self.Zone = zones[0].Id\n\tnode.Self.Roles = []string{\n\t\t\"node-firewall\",\n\t\t\"shape-m2\",\n\t}\n\tnode.Self.HostNat = true\n\n\tfires, err := firewall.GetOrgRoles(db, firewall.Global,\n\t\t[]string{\"node-firewall\"})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(fires) > 0 {\n\t\tnode.Self.Firewall = true\n\t}\n\n\tlogrus.Info(\"defaults: Attempting to load network \" +\n\t\t\"configuration from cloudinit\")\n\n\tinternalIface := \"\"\n\tinternalJumbo := false\n\n\texternalIface := \"\"\n\texternalIp := \"\"\n\texternalNet := \"\"\n\texternalMask := \"\"\n\texternalGateway := \"\"\n\texternalJumbo := false\n\n\tcloudConf, err := cloudinit.GetCloudConfig()\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Warn(\"defaults: Failed to load cloudinit network config\")\n\t\terr = nil\n\t} else {\n\t\tfor _, iface := range cloudConf.CombinedCloudConfig.Network.Config {\n\t\t\tfor _, addrInfo := range iface.Subnets {\n\t\t\t\taddr := utils.ParseAddress(addrInfo.Address)\n\t\t\t\tif addr == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif internalIface == \"\" && addr.Private && !addr.Ip6 {\n\t\t\t\t\tinternalIface = iface.Name\n\t\t\t\t\tinternalJumbo = iface.Mtu >= 9000\n\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"iface\":   iface.Name,\n\t\t\t\t\t\t\"address\": addr.Address.String(),\n\t\t\t\t\t\t\"mode\":    addrInfo.Type,\n\t\t\t\t\t\t\"mtu\":     iface.Mtu,\n\t\t\t\t\t\t\"type\":    iface.Type,\n\t\t\t\t\t\t\"vlan\":    iface.VlanId,\n\t\t\t\t\t\t\"jumbo\":   internalJumbo,\n\t\t\t\t\t}).Info(\"defaults: Detected internal interface\")\n\t\t\t\t}\n\n\t\t\t\tif externalIface == \"\" && addr.Public && !addr.Ip6 &&\n\t\t\t\t\taddr.Network != nil {\n\n\t\t\t\t\texternalIface = iface.Name\n\t\t\t\t\texternalJumbo = iface.Mtu >= 9000\n\t\t\t\t\texternalIp = addr.Address.String()\n\t\t\t\t\texternalNet = addr.Network.String()\n\t\t\t\t\texternalMask = net.IP(addr.Network.Mask).String()\n\t\t\t\t\texternalGateway = addrInfo.Gateway\n\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"iface\":      iface.Name,\n\t\t\t\t\t\t\"address\":    externalIp,\n\t\t\t\t\t\t\"network\":    externalNet,\n\t\t\t\t\t\t\"netmask\":    externalMask,\n\t\t\t\t\t\t\"gateway\":    externalGateway,\n\t\t\t\t\t\t\"mode\":       addrInfo.Type,\n\t\t\t\t\t\t\"mtu\":        iface.Mtu,\n\t\t\t\t\t\t\"type\":       iface.Type,\n\t\t\t\t\t\t\"vlan\":       iface.VlanId,\n\t\t\t\t\t\t\"vlan_iface\": iface.VlanLink,\n\t\t\t\t\t\t\"jumbo\":      externalJumbo,\n\t\t\t\t\t}).Info(\"defaults: Detected external interface\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif internalIface != \"\" {\n\t\tnode.Self.InternalInterfaces = []string{internalIface}\n\t\tif internalJumbo {\n\t\t\tnode.Self.JumboFramesInternal = true\n\n\t\t\tdc.NetworkMode = datacenter.VxlanVlan\n\n\t\t\terrData, e := dc.Validate(db)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif errData != nil {\n\t\t\t\terr = errData.GetError()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = dc.CommitFields(db, set.NewSet(\"network_mode\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\tnode.Self.InternalInterfaces = []string{\n\t\t\tsettings.Hypervisor.HostNetworkName,\n\t\t}\n\t}\n\n\tif externalIface != \"\" {\n\t\tnode.Self.DefaultNoPublicAddress = true\n\t\tif externalJumbo && internalJumbo {\n\t\t\tnode.Self.JumboFrames = true\n\t\t\tnode.Self.JumboFramesInternal = true\n\t\t}\n\n\t\tblcks, e := block.GetAll(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tvar externalBlck *block.Block\n\t\tfor _, blck := range blcks {\n\t\t\tif blck.Netmask == externalMask {\n\t\t\t\tfor _, subnet := range blck.Subnets {\n\t\t\t\t\tif subnet == externalNet {\n\t\t\t\t\t\texternalBlck = blck\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif externalBlck != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif externalBlck != nil {\n\t\t\tif externalGateway != \"\" {\n\t\t\t\texternalBlck.Gateway = externalGateway\n\t\t\t}\n\n\t\t\texcludeIp := externalIp + \"/32\"\n\t\t\tif !slices.Contains(externalBlck.Excludes, excludeIp) {\n\t\t\t\texternalBlck.Excludes = append(\n\t\t\t\t\texternalBlck.Excludes, excludeIp)\n\t\t\t}\n\n\t\t\terr = externalBlck.CommitFields(\n\t\t\t\tdb, set.NewSet(\"gateway\", \"excludes\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\texternalBlck = &block.Block{\n\t\t\t\tName:     \"cloud-public\",\n\t\t\t\tType:     block.IPv4,\n\t\t\t\tSubnets:  []string{externalNet},\n\t\t\t\tExcludes: []string{externalIp + \"/32\"},\n\t\t\t\tNetmask:  externalMask,\n\t\t\t\tGateway:  externalGateway,\n\t\t\t}\n\n\t\t\terrData, e := externalBlck.Validate(db)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif errData != nil {\n\t\t\t\terr = errData.GetError()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = externalBlck.Insert(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tnode.Self.NetworkMode = node.Static\n\t\tnode.Self.Blocks = []*node.BlockAttachment{\n\t\t\t&node.BlockAttachment{\n\t\t\t\tInterface: externalIface,\n\t\t\t\tBlock:     externalBlck.Id,\n\t\t\t},\n\t\t}\n\t}\n\n\terrData, err := node.Self.Validate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\tif errData != nil {\n\t\terr = errData.GetError()\n\t\treturn\n\t}\n\n\terr = node.Self.Commit(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tshpe := &shape.Shape{\n\t\tName:       \"m2-small\",\n\t\tDatacenter: node.Self.Datacenter,\n\t\tMemory:     2048,\n\t\tProcessors: 1,\n\t\tFlexible:   true,\n\t\tRoles: []string{\n\t\t\t\"shape-m2\",\n\t\t},\n\t\tType:     shape.Instance,\n\t\tDiskType: shape.Qcow2,\n\t}\n\n\terrData, err = shpe.Validate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\tif errData != nil {\n\t\terr = errData.GetError()\n\t\treturn\n\t}\n\n\terr = shpe.Insert(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"node\": node.Self.Id.Hex(),\n\t}).Info(\"defaults: Configured default node\")\n\n\tevent.PublishDispatch(db, \"node.change\")\n\n\treturn\n}\n\nfunc Defaults() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\terr = initStorage(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefaultOrg, err := initOrganization(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefaultDc, err := initDatacenter(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initZone(db, defaultDc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initVpc(db, defaultOrg, defaultDc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initFirewall(db, defaultOrg)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initAuthority(db, defaultOrg)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initNode(db, defaultOrg)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "demo/alert.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/alert\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Alerts = []*alert.Alert{\n\t{\n\t\tId:           utils.ObjectIdHex(\"9cc278e67d0b4a3d173280c0\"),\n\t\tName:         \"cloud\",\n\t\tComment:      \"\",\n\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tRoles:        []string{\"instance\"},\n\t\tResource:     \"instance_offline\",\n\t\tLevel:        5,\n\t\tFrequency:    300,\n\t},\n}\n"
  },
  {
    "path": "demo/authority.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Authorities = []*authority.Authority{\n\t{\n\t\tId:           utils.ObjectIdHex(\"688ab80d1793930f821f4f3c\"),\n\t\tName:         \"cloud\",\n\t\tComment:      \"\",\n\t\tType:         \"ssh_key\",\n\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tRoles:        []string{\"instance\"},\n\t\tKey:          \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDE+MfxSp/77B/CphRquYSxnq7ee6EbcDASJHFeMx8MVoneQ0TRDdLsUJ3KwVKvx9VcXOzqOlV6SQnT/Zwfhz8NDeqePYWeN7tI4rMVRSmlg7wYj7affVpIWXmuMSfdmZytr/PCr4h/CwjA+KJdpIBqU9B0enosTt5+OwLmViL1VLk6oi6C9UNQyszx9btOZfYnEVZ+sm7iWVaqO4Z4An7cM4V9dzbT4GOI2F1AYp+RfdvktEccCcjzZSbyJkhRt5DkRx9q/PbNwF4bRNw9gKjAcxYt54BeJ0By1HUd1snTblftlN3CQskKNXlFI7fFqQfLpaO4csi8dWu8IH7Td5YV5MKlSt+ljoNhBE+5bntQWjGU209PS/DGRW62LTIF9tiCTvJhh+fmof0mKJlABH6Es1Qzr6iwKz22a8LunF9Sf/dm9Og8zZVuCPWJNWIyYjNCBhDAaGj7KSH7apGoSl5Ck9vAyiL2dv54c4tCX9dyswutZeK7+RgH91v+SsxKrp8= cloud@pritunl-demo\",\n\t\tPrincipals:   []string{},\n\t\tCertificate:  \"\",\n\t},\n\t{\n\t\tId:           utils.ObjectIdHex(\"688ab80d1793930f821f4f5f\"),\n\t\tName:         \"demo\",\n\t\tComment:      \"\",\n\t\tType:         \"ssh_key\",\n\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tRoles:        []string{\"instance\"},\n\t\tKey:          \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCtianWXiLSMZ5h53POvUd0v6oIEH7dw/KZryymxa1x+CEFSaeeACWNSalcWSx48D53v7S4OytXkqjjwdsxGjJ0jfYe9c06s3fCdp0to9Dz1Xk5jeW0ojZXit7Ta+4MQH7mbZkS5SOVWo09fvoUlRrYDD+dlpG1XmYqLVCY7Z2atTVArYSQ9xNQUbXU3TgljZ2yKPX25d20y50exJWxlJwEgXo8z2ZsUJO22KL23fLs3t5Dylj5uV7gD3lEKRe+v6VGESuY8QKFKLEuOy7F+xmZazkIxOixDT7bPOz1FHGzTFUUOanYD8F9zS8TZAHuW0B4yA3Uh90NQ+7mAqW1dcX1Qu78e8xuEtKzSVvLR02NMWqdD+/tebfb1QIB2ljz9PanHFpnT+Ht0RdONgNSqMIs4HORObXnNkvCYtMLtoy5acE9zhM4P+fyr6CMSGpiqhFxXFAT0x+Xws+KJjpO6kER/vmuOsTUCsxIPlfVntzeisVnMYomdeOipdQhMELt2yE= cloud@pritunl-demo\",\n\t\tPrincipals:   []string{},\n\t\tCertificate:  \"\",\n\t},\n}\n"
  },
  {
    "path": "demo/balancer.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/balancer\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Balancers = []*balancer.Balancer{\n\t{\n\t\tId:           utils.ObjectIdHex(\"61ba27ccf149d4c222b23247\"),\n\t\tName:         \"web-app\",\n\t\tComment:      \"\",\n\t\tType:         \"http\",\n\t\tState:        true,\n\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tCertificates: []bson.ObjectID{\n\t\t\tutils.ObjectIdHex(\"67b89ef24866ba90e6c459e8\"),\n\t\t},\n\t\tClientAuthority: bson.ObjectID{},\n\t\tWebSockets:      false,\n\t\tDomains: []*balancer.Domain{\n\t\t\t{\n\t\t\t\tDomain: \"demo.cloud.pritunl.com\",\n\t\t\t\tHost:   \"\",\n\t\t\t},\n\t\t},\n\t\tBackends: []*balancer.Backend{\n\t\t\t{\n\t\t\t\tProtocol: \"http\",\n\t\t\t\tHostname: \"10.234.10.22\",\n\t\t\t\tPort:     8000,\n\t\t\t},\n\t\t\t{\n\t\t\t\tProtocol: \"http\",\n\t\t\t\tHostname: \"10.234.10.24\",\n\t\t\t\tPort:     8000,\n\t\t\t},\n\t\t},\n\t\tStates: map[string]*balancer.State{\n\t\t\t\"65b5d7e1c2e9a21159765955\": {\n\t\t\t\tTimestamp:  time.Now(),\n\t\t\t\tRequests:   125,\n\t\t\t\tRetries:    0,\n\t\t\t\tWebSockets: 0,\n\t\t\t\tOnline: []string{\n\t\t\t\t\t\"10.234.10.22:8000\",\n\t\t\t\t\t\"10.234.10.24:8000\",\n\t\t\t\t},\n\t\t\t\tUnknownHigh: []string{},\n\t\t\t\tUnknownMid:  []string{},\n\t\t\t\tUnknownLow:  []string{},\n\t\t\t\tOffline:     []string{},\n\t\t\t},\n\t\t},\n\t\tCheckPath: \"/check\",\n\t},\n}\n"
  },
  {
    "path": "demo/block.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Blocks = []*aggregate.BlockAggregate{\n\t{\n\t\tBlock: block.Block{\n\t\t\tId:      utils.ObjectIdHex(\"689733b7a7a35eae0dbaea2f\"),\n\t\t\tName:    \"east-public\",\n\t\t\tComment: \"\",\n\t\t\tType:    \"ipv4\",\n\t\t\tVlan:    0,\n\t\t\tSubnets: []string{\n\t\t\t\t\"1.253.67.0/24\",\n\t\t\t},\n\t\t\tSubnets6: []string{},\n\t\t\tExcludes: []string{\n\t\t\t\t\"1.253.67.90/24\",\n\t\t\t\t\"1.253.67.91/24\",\n\t\t\t\t\"1.253.67.92/24\",\n\t\t\t\t\"1.253.67.93/24\",\n\t\t\t\t\"1.253.67.94/24\",\n\t\t\t\t\"1.253.67.95/24\",\n\t\t\t},\n\t\t\tNetmask:  \"255.255.255.0\",\n\t\t\tGateway:  \"1.253.67.1\",\n\t\t\tGateway6: \"\",\n\t\t},\n\t\tAvailable: 248,\n\t\tCapacity:  254,\n\t},\n\t{\n\t\tBlock: block.Block{\n\t\t\tId:      utils.ObjectIdHex(\"68973a47b5844593cf99cc7a\"),\n\t\t\tName:    \"east-public6\",\n\t\t\tComment: \"\",\n\t\t\tType:    \"ipv6\",\n\t\t\tVlan:    0,\n\t\t\tSubnets: []string{},\n\t\t\tSubnets6: []string{\n\t\t\t\t\"2001:db8:85a3:4d2f::/64\",\n\t\t\t},\n\t\t\tExcludes: []string{},\n\t\t\tNetmask:  \"\",\n\t\t\tGateway:  \"\",\n\t\t\tGateway6: \"2001:db8:85a3:4d2f::1\",\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "demo/certificate.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Certificates = []*certificate.Certificate{\n\t{\n\t\tId:           utils.ObjectIdHex(\"67b89ef24866ba90e6c459e8\"),\n\t\tName:         \"cloud-pritunl-com\",\n\t\tComment:      \"\",\n\t\tOrganization: bson.ObjectID{},\n\t\tType:         \"lets_encrypt\",\n\t\tKey: `-----BEGIN RSA PRIVATE KEY-----\nMIIJKQIBAAKCAgEAx9Y3Lk2AwV6ap7L/Sx9XC5mXaUf8hvMmDbLBqDZ1Y7xKJM2h\nzQ8Xm1rK9q0wzQC6qiL6xHmTpKWTzNVzGsQdM3/qNPLNA7W8PIYCzjkSe5X1YktY\nvxldBxYxPRJxXk5S9P8dFYVmFFKF2bvJ5pSMLq9w3z3nTm3TQtRPqWx2Vk3DqV2D\nQKmNtqJnhVqYvVKa3QpLLwz8xKqB1sPXLr4XqQ3bz3fLjLxPmYV5WxLhgdKLYZTv\nYxQPLPTJkX3Pw4XD4Qs4CrKLW5bYsqYKQ7kKDXgJmTxYzZLjZKf4vSqLxqV5bDPY\nrR2YxQ9TKLkYKVMpNtY5J9X2fWzyPSvXqXZfVx7D8xJzDY8YKPLXmvxKQZxLJxSx\nzxHQzYKJpX3YmVfqYYmfYxXYzLmYxDzSxXqLvKxVqXxQDsPxQVKfKqQx5KvxsVqD\n-----END RSA PRIVATE KEY-----`,\n\t\tCertificate: `-----BEGIN CERTIFICATE-----\nMIIGGTCCBQGgAwIBAgISBXx9YmN2KQm9g3Y5XmKbvx9YMA0GCSqGSIb3DQEBCwUA\nMDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD\nEwNSMTEwHhcNMjUwODA4MDY0NzI3WhcNMjUxMTA2MDY0NzI2WjAcMRowGAYDVQQD\nExFjbG91ZC5wcml0dW5sLnJlZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC\nggIBAMfWNy5NgMFemqey/0sfVwuZl2lH/IbzJg2ywag2dWO8SiTNoc0PF5tayvat\nMM0AuKoi+sR5k6Slk8zVcxrEHTN/6jTyzQO1vDyGAs45EnuV9WJLWL8ZXQcWMT0S\ncV5OUvT/HRWFZhRShdn5iQ2Sry6vcN8950Dt00LUT6lsdlZNw6ldg0CpjbaiZ4Va\nmL1Smt0KSy8M/MSqgdbD1y6+F6kN2893y4y8T5mFeVsS4YHSi2GU72MUDyz0yZF9\nz8OFw+ELOAqyi1uW2LKmCkO5Cg14CZk8WM2S42Sn+L0qi8aleWwz2K0dmMUPUyi5\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFBjCCAu6gAwIBAgIRAIp9PhPWLzDvI4a9KQdrNPgwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw\nWhcNMjcwMzEyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\nRW5jcnlwdDEMMAoGA1UEAxMDUjExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEAuoe8XBsAOcvKCs3UZxD5ATylTqVhyybKUvsVAbe5KPUoHu0nsyQYOWcJ\nDAjs4DqwO3cOvfPlOVRBDE6uQdaZdN5R2+97/1i9qLcT9t4x1fJyyXJqC4N0lZxG\nAGQUmfOx2SLZzaiSqhwmej/+71gFewiVgdtxD4774zEJuwm+UE1fj5F2PVqdnoPy\n-----END CERTIFICATE-----`,\n\t\tInfo: &certificate.Info{\n\t\t\tHash:         \"bba8a3941280c8466a6a2a723cc06f26\",\n\t\t\tSignatureAlg: \"SHA256-RSA\",\n\t\t\tPublicKeyAlg: \"RSA\",\n\t\t\tIssuer:       \"R11\",\n\t\t\tIssuedOn:     time.Now(),\n\t\t\tExpiresOn:    time.Now().Add(2160 * time.Hour),\n\t\t\tDnsNames: []string{\n\t\t\t\t\"cloud.pritunl.com\",\n\t\t\t\t\"user.cloud.pritunl.com\",\n\t\t\t},\n\t\t},\n\t\tAcmeDomains: []string{\n\t\t\t\"cloud.pritunl.com\",\n\t\t\t\"user.cloud.pritunl.com\",\n\t\t},\n\t\tAcmeType:   \"acme_dns\",\n\t\tAcmeAuth:   \"acme_cloudflare\",\n\t\tAcmeSecret: utils.ObjectIdHex(\"67b89e8d4866ba90e6c459ba\"),\n\t},\n}\n"
  },
  {
    "path": "demo/datacenter.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Datacenters = []*datacenter.Datacenter{\n\t{\n\t\tId:                 utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tName:               \"us-west-1\",\n\t\tComment:            \"\",\n\t\tMatchOrganizations: false,\n\t\tOrganizations:      []bson.ObjectID{},\n\t\tNetworkMode:        \"vxlan_vlan\",\n\t\tWgMode:             \"\",\n\t\tPublicStorages: []bson.ObjectID{\n\t\t\tutils.ObjectIdHex(\"689733b7a7a35eae0dbaea15\"),\n\t\t},\n\t\tPrivateStorage:      bson.ObjectID{},\n\t\tPrivateStorageClass: \"\",\n\t\tBackupStorage:       bson.ObjectID{},\n\t\tBackupStorageClass:  \"\",\n\t},\n}\n"
  },
  {
    "path": "demo/demo.go",
    "content": "package demo\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nfunc IsDemo() bool {\n\treturn settings.System.Demo\n}\n\nfunc Blocked(c *gin.Context) bool {\n\tif !IsDemo() {\n\t\treturn false\n\t}\n\n\terrData := &errortypes.ErrorData{\n\t\tError:   \"demo_unavailable\",\n\t\tMessage: \"Not available in demo mode\",\n\t}\n\tc.JSON(400, errData)\n\n\treturn true\n}\n\nfunc BlockedSilent(c *gin.Context) bool {\n\tif !IsDemo() {\n\t\treturn false\n\t}\n\n\tc.JSON(200, nil)\n\treturn true\n}\n"
  },
  {
    "path": "demo/disk.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Disks = []*aggregate.DiskAggregate{\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f00\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a00\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f01\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a01\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f02\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a02\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f03\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a03\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f04\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0e\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a04\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f05\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0f\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a05\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f06\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a06\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f07\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a07\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f08\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a08\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f09\"),\n\t\t\tName:         \"web-app\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a09\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f0a\"),\n\t\t\tName:         \"database\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0e\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0a\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         100,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f0b\"),\n\t\t\tName:         \"database\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0f\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0b\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         100,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f0c\"),\n\t\t\tName:         \"database\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0c\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         100,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f0d\"),\n\t\t\tName:         \"database\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0d\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         100,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f0e\"),\n\t\t\tName:         \"database\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0e\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         100,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f0f\"),\n\t\t\tName:         \"database\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0f\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         100,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f10\"),\n\t\t\tName:         \"search\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0e\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a10\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         200,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f11\"),\n\t\t\tName:         \"search\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0f\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a11\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         200,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f12\"),\n\t\t\tName:         \"vpn\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a12\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n\t{\n\t\tDisk: disk.Disk{\n\t\t\tId:           utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d34f13\"),\n\t\t\tName:         \"vpn\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"attached\",\n\t\t\tType:         \"qcow2\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\t\tZone:         utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\t\tNode:         utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a13\"),\n\t\t\tImage:        utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\t\tIndex:        \"0\",\n\t\t\tSize:         20,\n\t\t\tCreated:      time.Now(),\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "demo/domain.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Domains = []*domain.Domain{\n\t&domain.Domain{\n\t\tId:            utils.ObjectIdHex(\"67b8a1d24866ba90e6c45b5b\"),\n\t\tName:          \"pritunl-com\",\n\t\tComment:       \"\",\n\t\tOrganization:  utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tType:          \"cloudflare\",\n\t\tSecret:        utils.ObjectIdHex(\"67b89e8d4866ba90e6c459ba\"),\n\t\tRootDomain:    \"pritunl.com\",\n\t\tLockId:        bson.ObjectID{},\n\t\tLockTimestamp: time.Time{},\n\t\tLastUpdate:    time.Now(),\n\t\tRecords: []*domain.Record{\n\t\t\t{\n\t\t\t\tId:              utils.ObjectIdHex(\"68076c9f06fd0087c078dfdc\"),\n\t\t\t\tDomain:          utils.ObjectIdHex(\"67b8a1d24866ba90e6c45b5b\"),\n\t\t\t\tResource:        bson.ObjectID{},\n\t\t\t\tDeployment:      utils.ObjectIdHex(\"68076bb954e947708aa6d651\"),\n\t\t\t\tTimestamp:       time.Now(),\n\t\t\t\tDeleteTimestamp: time.Time{},\n\t\t\t\tSubDomain:       \"demo\",\n\t\t\t\tType:            \"A\",\n\t\t\t\tValue:           \"10.196.8.2\",\n\t\t\t\tOperation:       \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:              utils.ObjectIdHex(\"68076ca306fd0087c078dfdd\"),\n\t\t\t\tDomain:          utils.ObjectIdHex(\"67b8a1d24866ba90e6c45b5b\"),\n\t\t\t\tResource:        bson.ObjectID{},\n\t\t\t\tDeployment:      utils.ObjectIdHex(\"68076bb954e947708aa6d651\"),\n\t\t\t\tTimestamp:       time.Now(),\n\t\t\t\tDeleteTimestamp: time.Time{},\n\t\t\t\tSubDomain:       \"cloud\",\n\t\t\t\tType:            \"A\",\n\t\t\t\tValue:           \"10.196.8.12\",\n\t\t\t\tOperation:       \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:              utils.ObjectIdHex(\"68076ca406fd0087c078dfde\"),\n\t\t\t\tDomain:          utils.ObjectIdHex(\"67b8a1d24866ba90e6c45b5b\"),\n\t\t\t\tResource:        bson.ObjectID{},\n\t\t\t\tDeployment:      utils.ObjectIdHex(\"68076bb954e947708aa6d651\"),\n\t\t\t\tTimestamp:       time.Now(),\n\t\t\t\tDeleteTimestamp: time.Time{},\n\t\t\t\tSubDomain:       \"user.cloud\",\n\t\t\t\tType:            \"A\",\n\t\t\t\tValue:           \"10.196.8.12\",\n\t\t\t\tOperation:       \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:              utils.ObjectIdHex(\"6813705806fd0087c078dfe1\"),\n\t\t\t\tDomain:          utils.ObjectIdHex(\"67b8a1d24866ba90e6c45b5b\"),\n\t\t\t\tResource:        bson.ObjectID{},\n\t\t\t\tDeployment:      utils.ObjectIdHex(\"68136f7d43b4ac1351f54f0a\"),\n\t\t\t\tTimestamp:       time.Now(),\n\t\t\t\tDeleteTimestamp: time.Time{},\n\t\t\t\tSubDomain:       \"demo.cloud\",\n\t\t\t\tType:            \"A\",\n\t\t\t\tValue:           \"10.196.8.46\",\n\t\t\t\tOperation:       \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:              utils.ObjectIdHex(\"681e01394230fad44c6a5140\"),\n\t\t\t\tDomain:          utils.ObjectIdHex(\"67b8a1d24866ba90e6c45b5b\"),\n\t\t\t\tResource:        bson.ObjectID{},\n\t\t\t\tDeployment:      utils.ObjectIdHex(\"681e01308d67187e275a847a\"),\n\t\t\t\tTimestamp:       time.Now(),\n\t\t\t\tDeleteTimestamp: time.Time{},\n\t\t\t\tSubDomain:       \"forum\",\n\t\t\t\tType:            \"AAAA\",\n\t\t\t\tValue:           \"2001:db8:85a3:42:d5c:82ca:9ed4:854b\",\n\t\t\t\tOperation:       \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:              utils.ObjectIdHex(\"683e86d74230fad44c6a514d\"),\n\t\t\t\tDomain:          utils.ObjectIdHex(\"67b8a1d24866ba90e6c45b5b\"),\n\t\t\t\tResource:        bson.ObjectID{},\n\t\t\t\tDeployment:      utils.ObjectIdHex(\"683dcdf13249b43a9cc5ec70\"),\n\t\t\t\tTimestamp:       time.Now(),\n\t\t\t\tDeleteTimestamp: time.Time{},\n\t\t\t\tSubDomain:       \"docs\",\n\t\t\t\tType:            \"CNAME\",\n\t\t\t\tValue:           \"docs.pritunl.dev\",\n\t\t\t\tOperation:       \"\",\n\t\t\t},\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "demo/firewall.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Firewalls = []*firewall.Firewall{\n\t{\n\t\tId:           utils.ObjectIdHex(\"688ab80d1793930f821f4f39\"),\n\t\tName:         \"instance\",\n\t\tComment:      \"\",\n\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tRoles:        []string{\"instance\"},\n\t\tIngress: []*firewall.Rule{\n\t\t\t{\n\t\t\t\tSourceIps: []string{\"0.0.0.0/0\", \"::/0\"},\n\t\t\t\tProtocol:  \"icmp\",\n\t\t\t\tPort:      \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tSourceIps: []string{\"0.0.0.0/0\", \"::/0\"},\n\t\t\t\tProtocol:  \"tcp\",\n\t\t\t\tPort:      \"22\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tSourceIps: []string{\"0.0.0.0/0\", \"::/0\"},\n\t\t\t\tProtocol:  \"tcp\",\n\t\t\t\tPort:      \"80\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tSourceIps: []string{\"0.0.0.0/0\", \"::/0\"},\n\t\t\t\tProtocol:  \"tcp\",\n\t\t\t\tPort:      \"443\",\n\t\t\t},\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "demo/instance.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/telemetry\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Info = &instance.Info{\n\tNode:      \"node-name\",\n\tTimestamp: time.Now(),\n\tDisks: []string{\n\t\t\"instance-name\",\n\t},\n\tFirewallRules: map[string]string{\n\t\t\"icmp\":    \"0.0.0.0/0, ::/0\",\n\t\t\"tcp:22\":  \"0.0.0.0/0, ::/0\",\n\t\t\"tcp:80\":  \"0.0.0.0/0, ::/0\",\n\t\t\"tcp:443\": \"0.0.0.0/0, ::/0\",\n\t},\n}\n\nvar Instances = []*instance.Instance{\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a00\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.103\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:ac50:8355:bb57:e0f5\"},\n\t\tPrivateIps:       []string{\"10.196.1.163\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:58d5:f529:66f2:36ef\"},\n\t\tHostIps:          []string{\"198.18.84.140\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"ar16uommmvnkcx\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    43.9,\n\t\t\tLoad1:     33.84,\n\t\t\tLoad5:     39.55,\n\t\t\tLoad15:    41.69,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a01\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.195\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:5e1b:773a:2463:da58\"},\n\t\tPrivateIps:       []string{\"10.196.8.168\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:bf1a:d5e4:56a2:4b27\"},\n\t\tHostIps:          []string{\"198.18.84.29\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"3z3zjl0ftwopu9\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    41.16,\n\t\t\tLoad1:     31.86,\n\t\t\tLoad5:     38.61,\n\t\t\tLoad15:    43.48,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a02\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.201\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:27fe:0397:17fa:5d2e\"},\n\t\tPrivateIps:       []string{\"10.196.5.204\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:3d61:c9f7:d2d7:8b9b\"},\n\t\tHostIps:          []string{\"198.18.84.12\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"nyak3v7m6rxnqs\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    74.46,\n\t\t\tLoad1:     40.97,\n\t\t\tLoad5:     46.98,\n\t\t\tLoad15:    56.17,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a03\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.2\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:41b2:61e2:ad56:6cdc\"},\n\t\tPrivateIps:       []string{\"10.196.5.162\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:c166:fabc:4223:a974\"},\n\t\tHostIps:          []string{\"198.18.84.30\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"uln933asem6gxj\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    54.41,\n\t\t\tLoad1:     47.29,\n\t\t\tLoad5:     56.99,\n\t\t\tLoad15:    66.75,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a04\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.53\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:cb29:095b:a7f8:9a7e\"},\n\t\tPrivateIps:       []string{\"10.196.7.205\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:b2f4:9b35:700e:0b9a\"},\n\t\tHostIps:          []string{\"198.18.84.51\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0e\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"rgp5qyyx33b66o\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    78.56,\n\t\t\tLoad1:     45.93,\n\t\t\tLoad5:     50.45,\n\t\t\tLoad15:    58.1,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a05\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.36\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:126f:552b:77d0:010e\"},\n\t\tPrivateIps:       []string{\"10.196.2.94\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:c057:f8fa:ff43:a21a\"},\n\t\tHostIps:          []string{\"198.18.84.118\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0f\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"t0pt85ptxxat2p\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    78.37,\n\t\t\tLoad1:     56.02,\n\t\t\tLoad5:     65.95,\n\t\t\tLoad15:    70.14,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a06\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.90\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:aa28:1b64:c808:caeb\"},\n\t\tPrivateIps:       []string{\"10.196.7.203\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:9797:d44a:0c7e:cb9e\"},\n\t\tHostIps:          []string{\"198.18.84.216\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"5qbiaxk4w886zb\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    33.68,\n\t\t\tLoad1:     42.06,\n\t\t\tLoad5:     44.97,\n\t\t\tLoad15:    47.24,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a07\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.70\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:bf64:91d6:4050:eac0\"},\n\t\tPrivateIps:       []string{\"10.196.5.238\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:0dd4:8931:8c28:5465\"},\n\t\tHostIps:          []string{\"198.18.84.13\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"f218l27uoumg35\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    48.81,\n\t\t\tLoad1:     52.83,\n\t\t\tLoad5:     58.26,\n\t\t\tLoad15:    60.54,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a08\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.197\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:f5f3:98f7:82b5:ee87\"},\n\t\tPrivateIps:       []string{\"10.196.4.2\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:8d49:241d:4dd1:4663\"},\n\t\tHostIps:          []string{\"198.18.84.224\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"frb9erjro4vsnu\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    79.86,\n\t\t\tLoad1:     22.14,\n\t\t\tLoad5:     24.45,\n\t\t\tLoad15:    28.28,\n\t\t\tUpdates: []*telemetry.Update{\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:10371\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.20.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:10854\",\n\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.21.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:11428\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.22.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:12662\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.25.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a09\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.63\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:6e3a:29b0:639f:49d4\"},\n\t\tPrivateIps:       []string{\"10.196.6.18\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:ddb4:e207:cc09:d1e6\"},\n\t\tHostIps:          []string{\"198.18.84.22\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"web-app\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"e8iaefw40jrfmr\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    33.58,\n\t\t\tLoad1:     54.78,\n\t\t\tLoad5:     63.5,\n\t\t\tLoad15:    69.05,\n\t\t\tUpdates: []*telemetry.Update{\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:10371\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.20.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:10854\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.21.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:11428\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.22.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:12662\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.25.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0a\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93464\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.8\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:d943:7ff9:dfdc:4d68\"},\n\t\tPrivateIps:       []string{\"10.196.7.91\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:7ba9:bca3:5217:b534\"},\n\t\tHostIps:          []string{\"198.18.84.108\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0e\"),\n\t\tShape:            utils.ObjectIdHex(\"66f63282aac06d53e8c9c435\"),\n\t\tName:             \"database\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     100,\n\t\tMemory:           8192,\n\t\tProcessors:       4,\n\t\tNetworkNamespace: \"oe2641r0vib1bq\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    38.77,\n\t\t\tLoad1:     58.36,\n\t\t\tLoad5:     66.33,\n\t\t\tLoad15:    72.82,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0b\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93464\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.247\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:3e3e:0d9d:8669:2c89\"},\n\t\tPrivateIps:       []string{\"10.196.8.39\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:d0ff:8b42:1d9b:92fd\"},\n\t\tHostIps:          []string{\"198.18.84.45\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0f\"),\n\t\tShape:            utils.ObjectIdHex(\"66f63282aac06d53e8c9c435\"),\n\t\tName:             \"database\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     100,\n\t\tMemory:           8192,\n\t\tProcessors:       4,\n\t\tNetworkNamespace: \"zrz0kp1nydxe5t\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    34.16,\n\t\t\tLoad1:     22.62,\n\t\t\tLoad5:     30.79,\n\t\t\tLoad15:    34.31,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0c\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93464\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.148\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:74b0:8661:53c1:1d5b\"},\n\t\tPrivateIps:       []string{\"10.196.2.178\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:e7b4:670b:acf5:dfb4\"},\n\t\tHostIps:          []string{\"198.18.84.53\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\tShape:            utils.ObjectIdHex(\"66f63282aac06d53e8c9c435\"),\n\t\tName:             \"database\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     100,\n\t\tMemory:           8192,\n\t\tProcessors:       4,\n\t\tNetworkNamespace: \"tsusrdt4e4diyb\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    77.41,\n\t\t\tLoad1:     57.8,\n\t\t\tLoad5:     59.57,\n\t\t\tLoad15:    63.3,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0d\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93464\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.67\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:4b40:60d1:ed30:0b06\"},\n\t\tPrivateIps:       []string{\"10.196.5.113\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:5220:ac62:3c7c:7291\"},\n\t\tHostIps:          []string{\"198.18.84.60\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\tShape:            utils.ObjectIdHex(\"66f63282aac06d53e8c9c435\"),\n\t\tName:             \"database\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     100,\n\t\tMemory:           8192,\n\t\tProcessors:       4,\n\t\tNetworkNamespace: \"bvmmviq5cvx1zy\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    52.76,\n\t\t\tLoad1:     42.11,\n\t\t\tLoad5:     44.24,\n\t\t\tLoad15:    52.69,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0e\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93464\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.189\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:be8e:3013:d6f6:5396\"},\n\t\tPrivateIps:       []string{\"10.196.2.232\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:c924:41b8:22f3:5b3f\"},\n\t\tHostIps:          []string{\"198.18.84.33\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\tShape:            utils.ObjectIdHex(\"66f63282aac06d53e8c9c435\"),\n\t\tName:             \"database\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     100,\n\t\tMemory:           8192,\n\t\tProcessors:       4,\n\t\tNetworkNamespace: \"qbaoak9az3asxp\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    42.56,\n\t\t\tLoad1:     31.16,\n\t\t\tLoad5:     39.77,\n\t\t\tLoad15:    41.33,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0f\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93464\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.38\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:fff2:877d:227c:1c4a\"},\n\t\tPrivateIps:       []string{\"10.196.5.122\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:ae17:b804:32c5:956c\"},\n\t\tHostIps:          []string{\"198.18.84.141\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\tShape:            utils.ObjectIdHex(\"66f63282aac06d53e8c9c435\"),\n\t\tName:             \"database\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     100,\n\t\tMemory:           8192,\n\t\tProcessors:       4,\n\t\tNetworkNamespace: \"m4l3egi9yhfq9t\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    36.46,\n\t\t\tLoad1:     26.05,\n\t\t\tLoad5:     29.7,\n\t\t\tLoad15:    34.46,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a10\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93466\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.118\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:b5e8:fca8:79fa:a3b4\"},\n\t\tPrivateIps:       []string{\"10.196.4.224\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:83e0:cd9a:85c4:38da\"},\n\t\tHostIps:          []string{\"198.18.84.35\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0e\"),\n\t\tShape:            utils.ObjectIdHex(\"66f63282aac06d53e8c9c435\"),\n\t\tName:             \"search\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     200,\n\t\tMemory:           8192,\n\t\tProcessors:       4,\n\t\tNetworkNamespace: \"l6kiozfukehu7v\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    36.93,\n\t\t\tLoad1:     54.95,\n\t\t\tLoad5:     61.88,\n\t\t\tLoad15:    66.2,\n\t\t\tUpdates: []*telemetry.Update{\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:10371\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.20.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:10854\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.21.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:11428\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.22.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:12662\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.25.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a11\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93466\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.230\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:508e:01c4:330a:418c\"},\n\t\tPrivateIps:       []string{\"10.196.4.33\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:d6a1:6424:48d3:bdfc\"},\n\t\tHostIps:          []string{\"198.18.84.16\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0f\"),\n\t\tShape:            utils.ObjectIdHex(\"66f63282aac06d53e8c9c435\"),\n\t\tName:             \"search\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     200,\n\t\tMemory:           8192,\n\t\tProcessors:       4,\n\t\tNetworkNamespace: \"i0bt9vae2o4g8q\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    60.57,\n\t\t\tLoad1:     32.94,\n\t\t\tLoad5:     38.11,\n\t\t\tLoad15:    40.68,\n\t\t\tUpdates: []*telemetry.Update{\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:10371\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.20.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:10854\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.21.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:11428\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.22.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAdvisory: \"ALSA-2025:12662\",\n\t\t\t\t\tSeverity: \"important\",\n\t\t\t\t\tPackages: []string{\"kernel-6.12.0-55.25.1.el10_0.x86_64\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a12\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93467\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e1\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.74\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:7340:5e52:e94c:3d8a\"},\n\t\tPrivateIps:       []string{\"10.196.7.230\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:70a2:9db6:52a4:2ab8\"},\n\t\tHostIps:          []string{\"198.18.84.126\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"vpn\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"szy246vtwpjc3g\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    55.49,\n\t\t\tLoad1:     36.61,\n\t\t\tLoad5:     42.46,\n\t\t\tLoad15:    44.35,\n\t\t},\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a13\"),\n\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tVpc:              utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tSubnet:           utils.ObjectIdHex(\"66a076d5fafc270786e93467\"),\n\t\tImage:            utils.ObjectIdHex(\"650a2c36aed15f1f1f5e96e2\"),\n\t\tImageBacking:     false,\n\t\tStatus:           \"Running\",\n\t\tState:            \"running\",\n\t\tAction:           \"start\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{\"1.253.67.97\"},\n\t\tPublicIps6:       []string{\"2001:db8:85a3:4d2f:e4b2:fb8f:ca12:2da3\"},\n\t\tPrivateIps:       []string{\"10.196.8.7\"},\n\t\tPrivateIps6:      []string{\"fd97:30bf:d456:a3bc:8bed:8e24:e745:7657\"},\n\t\tHostIps:          []string{\"198.18.84.99\"},\n\t\tNode:             utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\tShape:            utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"vpn\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     20,\n\t\tMemory:           2048,\n\t\tProcessors:       2,\n\t\tNetworkNamespace: \"ub53x6q05snrd8\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest: &instance.GuestData{\n\t\t\tStatus:    \"running\",\n\t\t\tTimestamp: time.Now(),\n\t\t\tHeartbeat: time.Now(),\n\t\t\tMemory:    47.29,\n\t\t\tLoad1:     35.01,\n\t\t\tLoad5:     42.87,\n\t\t\tLoad15:    48.87,\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "demo/log.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/log\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Logs = []*log.Entry{\n\t&log.Entry{\n\t\tId:        utils.ObjectIdHex(\"5a18e6ae051a45ffac0e5b67\"),\n\t\tLevel:     log.Info,\n\t\tTimestamp: time.Unix(1498018860, 0),\n\t\tMessage:   \"router: Starting redirect server\",\n\t\tStack:     \"\",\n\t\tFields: map[string]interface{}{\n\t\t\t\"port\":       80,\n\t\t\t\"production\": true,\n\t\t\t\"protocol\":   \"http\",\n\t\t},\n\t},\n\t&log.Entry{\n\t\tId:        utils.ObjectIdHex(\"5a190b42051a45ffac129bbc\"),\n\t\tLevel:     log.Info,\n\t\tTimestamp: time.Unix(1498018860, 0),\n\t\tMessage:   \"router: Starting web server\",\n\t\tStack:     \"\",\n\t\tFields: map[string]interface{}{\n\t\t\t\"port\":       443,\n\t\t\t\"production\": true,\n\t\t\t\"protocol\":   \"https\",\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "demo/node.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/cloud\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/drive\"\n\t\"github.com/pritunl/pritunl-cloud/ip\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/pci\"\n\t\"github.com/pritunl/pritunl-cloud/usb\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Nodes = []*node.Node{\n\t{\n\t\tId:                   utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\tDatacenter:           utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:                 utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tName:                 \"pritunl-east0\",\n\t\tComment:              \"\",\n\t\tTypes:                []string{\"admin\", \"hypervisor\"},\n\t\tTimestamp:            time.Now(),\n\t\tPort:                 443,\n\t\tNoRedirectServer:     false,\n\t\tProtocol:             \"https\",\n\t\tHypervisor:           \"kvm\",\n\t\tVga:                  \"virtio\",\n\t\tVgaRender:            \"\",\n\t\tAvailableRenders:     []string{},\n\t\tGui:                  false,\n\t\tGuiUser:              \"\",\n\t\tGuiMode:              \"\",\n\t\tCertificates:         []bson.ObjectID{},\n\t\tAdminDomain:          \"\",\n\t\tUserDomain:           \"\",\n\t\tWebauthnDomain:       \"\",\n\t\tRequestsMin:          23,\n\t\tForwardedForHeader:   \"\",\n\t\tForwardedProtoHeader: \"\",\n\t\tExternalInterfaces:   []string{},\n\t\tExternalInterfaces6:  []string{},\n\t\tInternalInterfaces:   []string{\"bond0.2\"},\n\t\tAvailableInterfaces: []ip.Interface{\n\t\t\t{Name: \"bond0\", Address: \"\"},\n\t\t\t{Name: \"bond0.2\", Address: \"10.8.0.10\"},\n\t\t\t{Name: \"bond0.4\", Address: \"10.253.67.90\"},\n\t\t\t{Name: \"bonding_masters\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f0\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f1\", Address: \"\"},\n\t\t\t{Name: \"veth0\", Address: \"\"},\n\t\t},\n\t\tAvailableBridges: []ip.Interface{\n\t\t\t{Name: \"pritunlhost0\", Address: \"198.18.84.1\"},\n\t\t\t{Name: \"pritunlport0\", Address: \"198.19.96.1\"},\n\t\t},\n\t\tAvailableVpcs:    []*cloud.Vpc{},\n\t\tCloudSubnets:     []string{},\n\t\tDefaultInterface: \"bond0.4\",\n\t\tNetworkMode:      \"static\",\n\t\tNetworkMode6:     \"static\",\n\t\tBlocks: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"689733b7a7a35eae0dbaea2f\"),\n\t\t\t},\n\t\t},\n\t\tBlocks6: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"68973a47b5844593cf99cc7a\"),\n\t\t\t},\n\t\t},\n\t\tPools:  []bson.ObjectID{},\n\t\tShares: []*node.Share{},\n\t\tAvailableDrives: []*drive.Device{\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part3\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part3\"},\n\t\t},\n\t\tInstanceDrives:          []*drive.Device{},\n\t\tNoHostNetwork:           false,\n\t\tNoNodePortNetwork:       false,\n\t\tHostNat:                 true,\n\t\tDefaultNoPublicAddress:  true,\n\t\tDefaultNoPublicAddress6: false,\n\t\tJumboFrames:             true,\n\t\tJumboFramesInternal:     true,\n\t\tIscsi:                   false,\n\t\tLocalIsos:               nil,\n\t\tUsbPassthrough:          false,\n\t\tUsbDevices:              []*usb.Device{},\n\t\tPciPassthrough:          false,\n\t\tPciDevices:              []*pci.Device{},\n\t\tHugepages:               false,\n\t\tHugepagesSize:           0,\n\t\tFirewall:                false,\n\t\tRoles:                   []string{\"shape-m2\"},\n\t\tMemory:                  41.02,\n\t\tHugePagesUsed:           0,\n\t\tLoad1:                   43.17,\n\t\tLoad5:                   43.33,\n\t\tLoad15:                  43.83,\n\t\tCpuUnits:                128,\n\t\tMemoryUnits:             512,\n\t\tCpuUnitsRes:             68,\n\t\tMemoryUnitsRes:          198,\n\t\tPublicIps:               []string{\"10.253.67.90\"},\n\t\tPublicIps6: []string{\n\t\t\t\"2001:db8:85a3:4d2f:1319:8a2e:370:7348\",\n\t\t},\n\t\tPrivateIps: map[string]string{\n\t\t\t\"bond0.2\": \"10.8.0.10\",\n\t\t},\n\t\tSoftwareVersion: constants.Version,\n\t\tHostname:        \"pritunl-east0\",\n\t\tVirtPath:        \"/var/lib/pritunl-cloud\",\n\t\tCachePath:       \"/var/cache/pritunl-cloud\",\n\t\tTempPath:        \"\",\n\t\tOracleUser:      \"\",\n\t\tOracleTenancy:   \"\",\n\t\tOraclePublicKey: `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxWtYOIzsHsLlBI1jeepJ\nq8dyR1JH3QLdAJ2IFGZDtHCCi46Lvmx7hC8bAutj5s37qOfBrom6UOJf0f9zEP8K\ny8qTb2S4XOAWBHuGpaBqFEhtpW+vIxiy26vdZN85P3xzYle0uodr86+y2bVHMHKB\n0oEHnqu+CmH/r4GedBVFVBASo9C5iILsyISf4oep390V/u23RAXXNfcKvUYR4c2u\nfZBwlSVEDrK+X21ocJc+8VGbbLhXBvMEdqXzs1bbFzFHow8TjduxDNTbntIRpo6W\n0O7xMahUHxDWDro5fAkzvpk6wUBM6yWXXgwkDLLHW50dUnqgFJgOTIHXEtPSt4eU\n2wIDAQAB\n-----END PUBLIC KEY-----`,\n\t\tOperation: \"\",\n\t},\n\t{\n\t\tId:                   utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\tDatacenter:           utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:                 utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tName:                 \"pritunl-east1\",\n\t\tComment:              \"\",\n\t\tTypes:                []string{\"admin\", \"hypervisor\"},\n\t\tTimestamp:            time.Now(),\n\t\tPort:                 443,\n\t\tNoRedirectServer:     false,\n\t\tProtocol:             \"https\",\n\t\tHypervisor:           \"kvm\",\n\t\tVga:                  \"virtio\",\n\t\tVgaRender:            \"\",\n\t\tAvailableRenders:     []string{},\n\t\tGui:                  false,\n\t\tGuiUser:              \"\",\n\t\tGuiMode:              \"\",\n\t\tCertificates:         []bson.ObjectID{},\n\t\tAdminDomain:          \"\",\n\t\tUserDomain:           \"\",\n\t\tWebauthnDomain:       \"\",\n\t\tRequestsMin:          44,\n\t\tForwardedForHeader:   \"\",\n\t\tForwardedProtoHeader: \"\",\n\t\tExternalInterfaces:   []string{},\n\t\tExternalInterfaces6:  []string{},\n\t\tInternalInterfaces:   []string{\"bond0.2\"},\n\t\tAvailableInterfaces: []ip.Interface{\n\t\t\t{Name: \"bond0\", Address: \"\"},\n\t\t\t{Name: \"bond0.2\", Address: \"10.8.0.11\"},\n\t\t\t{Name: \"bond0.4\", Address: \"10.253.67.91\"},\n\t\t\t{Name: \"bonding_masters\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f0\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f1\", Address: \"\"},\n\t\t\t{Name: \"veth0\", Address: \"\"},\n\t\t},\n\t\tAvailableBridges: []ip.Interface{\n\t\t\t{Name: \"pritunlhost0\", Address: \"198.18.84.1\"},\n\t\t\t{Name: \"pritunlport0\", Address: \"198.19.96.1\"},\n\t\t},\n\t\tAvailableVpcs:    []*cloud.Vpc{},\n\t\tCloudSubnets:     []string{},\n\t\tDefaultInterface: \"bond0.4\",\n\t\tNetworkMode:      \"static\",\n\t\tNetworkMode6:     \"static\",\n\t\tBlocks: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"689733b7a7a35eae0dbaea2f\"),\n\t\t\t},\n\t\t},\n\t\tBlocks6: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"68973a47b5844593cf99cc7a\"),\n\t\t\t},\n\t\t},\n\t\tPools:  []bson.ObjectID{},\n\t\tShares: []*node.Share{},\n\t\tAvailableDrives: []*drive.Device{\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part3\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part3\"},\n\t\t},\n\t\tInstanceDrives:          []*drive.Device{},\n\t\tNoHostNetwork:           false,\n\t\tNoNodePortNetwork:       false,\n\t\tHostNat:                 true,\n\t\tDefaultNoPublicAddress:  true,\n\t\tDefaultNoPublicAddress6: false,\n\t\tJumboFrames:             true,\n\t\tJumboFramesInternal:     true,\n\t\tIscsi:                   false,\n\t\tLocalIsos:               nil,\n\t\tUsbPassthrough:          false,\n\t\tUsbDevices:              []*usb.Device{},\n\t\tPciPassthrough:          false,\n\t\tPciDevices:              []*pci.Device{},\n\t\tHugepages:               false,\n\t\tHugepagesSize:           0,\n\t\tFirewall:                false,\n\t\tRoles:                   []string{\"shape-m2\"},\n\t\tMemory:                  62.02,\n\t\tHugePagesUsed:           0,\n\t\tLoad1:                   67.12,\n\t\tLoad5:                   57.43,\n\t\tLoad15:                  55.23,\n\t\tCpuUnits:                128,\n\t\tMemoryUnits:             512,\n\t\tCpuUnitsRes:             78,\n\t\tMemoryUnitsRes:          324,\n\t\tPublicIps:               []string{\"10.253.67.91\"},\n\t\tPublicIps6: []string{\n\t\t\t\"2001:db8:85a3:4d2f:7c3a:9f42:8e5b:a234\",\n\t\t},\n\t\tPrivateIps: map[string]string{\n\t\t\t\"bond0.2\": \"10.8.0.11\",\n\t\t},\n\t\tSoftwareVersion: constants.Version,\n\t\tHostname:        \"pritunl-east0\",\n\t\tVirtPath:        \"/var/lib/pritunl-cloud\",\n\t\tCachePath:       \"/var/cache/pritunl-cloud\",\n\t\tTempPath:        \"\",\n\t\tOracleUser:      \"\",\n\t\tOracleTenancy:   \"\",\n\t\tOraclePublicKey: `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz4K8Lm3QvR7WxN5YdE2P\njX9TpQ6HgM1wV0nS4KaF3ZcB8LrY5UvO2JmN7XsPqI1AgK8EoH3RdWzM9LfY2VtN\nkP4QxGsJ7YnR8LwVmT3AqZ5HvK2NdP1XoS8JgR4LmW7YxQ3VnH5TsK9PpL2MdX8Rg\nvJ3KqN5WxT1LsM4HgY7RdP8NqV2JmK5XwL3TsR8YgN4HxP1LdK9VwQ2MsT3XpR7Y\nnL8KgJ5WdH3TmR9XsL2PqN7VxK4MgT3HdJ8YwP2LsK5RxT1NqM4JgY7PxR8WsL3T\nmK9XwN2HgJ5YdL3RsP8VqT2MxK4NhR3JdY8WwL2TsM5QxN1PqK4YgJ7RxP8VsT3M\nPwIDAQAB\n-----END PUBLIC KEY-----`,\n\t\tOperation: \"\",\n\t},\n\t{\n\t\tId:                   utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\tDatacenter:           utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:                 utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tName:                 \"pritunl-east2\",\n\t\tComment:              \"\",\n\t\tTypes:                []string{\"admin\", \"hypervisor\"},\n\t\tTimestamp:            time.Now(),\n\t\tPort:                 443,\n\t\tNoRedirectServer:     false,\n\t\tProtocol:             \"https\",\n\t\tHypervisor:           \"kvm\",\n\t\tVga:                  \"virtio\",\n\t\tVgaRender:            \"\",\n\t\tAvailableRenders:     []string{},\n\t\tGui:                  false,\n\t\tGuiUser:              \"\",\n\t\tGuiMode:              \"\",\n\t\tCertificates:         []bson.ObjectID{},\n\t\tAdminDomain:          \"\",\n\t\tUserDomain:           \"\",\n\t\tWebauthnDomain:       \"\",\n\t\tRequestsMin:          26,\n\t\tForwardedForHeader:   \"\",\n\t\tForwardedProtoHeader: \"\",\n\t\tExternalInterfaces:   []string{},\n\t\tExternalInterfaces6:  []string{},\n\t\tInternalInterfaces:   []string{\"bond0.2\"},\n\t\tAvailableInterfaces: []ip.Interface{\n\t\t\t{Name: \"bond0\", Address: \"\"},\n\t\t\t{Name: \"bond0.2\", Address: \"10.8.0.12\"},\n\t\t\t{Name: \"bond0.4\", Address: \"10.253.67.92\"},\n\t\t\t{Name: \"bonding_masters\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f0\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f1\", Address: \"\"},\n\t\t\t{Name: \"veth0\", Address: \"\"},\n\t\t},\n\t\tAvailableBridges: []ip.Interface{\n\t\t\t{Name: \"pritunlhost0\", Address: \"198.18.84.1\"},\n\t\t\t{Name: \"pritunlport0\", Address: \"198.19.96.1\"},\n\t\t},\n\t\tAvailableVpcs:    []*cloud.Vpc{},\n\t\tCloudSubnets:     []string{},\n\t\tDefaultInterface: \"bond0.4\",\n\t\tNetworkMode:      \"static\",\n\t\tNetworkMode6:     \"static\",\n\t\tBlocks: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"689733b7a7a35eae0dbaea2f\"),\n\t\t\t},\n\t\t},\n\t\tBlocks6: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"68973a47b5844593cf99cc7a\"),\n\t\t\t},\n\t\t},\n\t\tPools:  []bson.ObjectID{},\n\t\tShares: []*node.Share{},\n\t\tAvailableDrives: []*drive.Device{\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part3\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part3\"},\n\t\t},\n\t\tInstanceDrives:          []*drive.Device{},\n\t\tNoHostNetwork:           false,\n\t\tNoNodePortNetwork:       false,\n\t\tHostNat:                 true,\n\t\tDefaultNoPublicAddress:  true,\n\t\tDefaultNoPublicAddress6: false,\n\t\tJumboFrames:             true,\n\t\tJumboFramesInternal:     true,\n\t\tIscsi:                   false,\n\t\tLocalIsos:               nil,\n\t\tUsbPassthrough:          false,\n\t\tUsbDevices:              []*usb.Device{},\n\t\tPciPassthrough:          false,\n\t\tPciDevices:              []*pci.Device{},\n\t\tHugepages:               false,\n\t\tHugepagesSize:           0,\n\t\tFirewall:                false,\n\t\tRoles:                   []string{\"shape-m2\"},\n\t\tMemory:                  41.02,\n\t\tHugePagesUsed:           0,\n\t\tLoad1:                   76.53,\n\t\tLoad5:                   67.34,\n\t\tLoad15:                  67.87,\n\t\tCpuUnits:                128,\n\t\tMemoryUnits:             512,\n\t\tCpuUnitsRes:             56,\n\t\tMemoryUnitsRes:          286,\n\t\tPublicIps:               []string{\"10.253.67.92\"},\n\t\tPublicIps6: []string{\n\t\t\t\"2001:db8:85a3:4d2f:2e91:5cd8:f1a6:7b89\",\n\t\t},\n\t\tPrivateIps: map[string]string{\n\t\t\t\"bond0.2\": \"10.8.0.12\",\n\t\t},\n\t\tSoftwareVersion: constants.Version,\n\t\tHostname:        \"pritunl-east0\",\n\t\tVirtPath:        \"/var/lib/pritunl-cloud\",\n\t\tCachePath:       \"/var/cache/pritunl-cloud\",\n\t\tTempPath:        \"\",\n\t\tOracleUser:      \"\",\n\t\tOracleTenancy:   \"\",\n\t\tOraclePublicKey: `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3Fk7Tp4NwV8XmQ6LdJ9R\nhY2BqG8Vz0kL5HgM7Xa4FrC9NsU1YvQ3KnR8WxL6ZqE2DfH5JtX3YmVgB8KsNp4Q\nrL9XvT2HdQ8YwK5JmF3NxR7Pq4VsL8TgW6ZnM1KfY9DpX2RvQ8JwB3GtL5YxH4Nm\nsK7VpX3LdR9FwT8MqN2HxY5BgJ6TsL4RpW8NvK3YxH5QmD9LwT2JsN8KgR4XpM7V\nfY3BwL8TmN5KxQ2JgH7RsP9VwM4LnT8KpY3XgN5BwJ2HsR8TmL4VxQ9JpN3KgY7B\nwR5TnM8LpJ2XgH4VsN9BwK3TmQ5RxL8JpY7NwH2VsR4KgM9TnL5BxJ3QwN8VpH7Y\nKwIDAQAB\n-----END PUBLIC KEY-----`,\n\t\tOperation: \"\",\n\t},\n\t{\n\t\tId:                   utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\tDatacenter:           utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:                 utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tName:                 \"pritunl-east3\",\n\t\tComment:              \"\",\n\t\tTypes:                []string{\"admin\", \"hypervisor\"},\n\t\tTimestamp:            time.Now(),\n\t\tPort:                 443,\n\t\tNoRedirectServer:     false,\n\t\tProtocol:             \"https\",\n\t\tHypervisor:           \"kvm\",\n\t\tVga:                  \"virtio\",\n\t\tVgaRender:            \"\",\n\t\tAvailableRenders:     []string{},\n\t\tGui:                  false,\n\t\tGuiUser:              \"\",\n\t\tGuiMode:              \"\",\n\t\tCertificates:         []bson.ObjectID{},\n\t\tAdminDomain:          \"\",\n\t\tUserDomain:           \"\",\n\t\tWebauthnDomain:       \"\",\n\t\tRequestsMin:          56,\n\t\tForwardedForHeader:   \"\",\n\t\tForwardedProtoHeader: \"\",\n\t\tExternalInterfaces:   []string{},\n\t\tExternalInterfaces6:  []string{},\n\t\tInternalInterfaces:   []string{\"bond0.2\"},\n\t\tAvailableInterfaces: []ip.Interface{\n\t\t\t{Name: \"bond0\", Address: \"\"},\n\t\t\t{Name: \"bond0.2\", Address: \"10.8.0.13\"},\n\t\t\t{Name: \"bond0.4\", Address: \"10.253.67.93\"},\n\t\t\t{Name: \"bonding_masters\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f0\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f1\", Address: \"\"},\n\t\t\t{Name: \"veth0\", Address: \"\"},\n\t\t},\n\t\tAvailableBridges: []ip.Interface{\n\t\t\t{Name: \"pritunlhost0\", Address: \"198.18.84.1\"},\n\t\t\t{Name: \"pritunlport0\", Address: \"198.19.96.1\"},\n\t\t},\n\t\tAvailableVpcs:    []*cloud.Vpc{},\n\t\tCloudSubnets:     []string{},\n\t\tDefaultInterface: \"bond0.4\",\n\t\tNetworkMode:      \"static\",\n\t\tNetworkMode6:     \"static\",\n\t\tBlocks: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"689733b7a7a35eae0dbaea2f\"),\n\t\t\t},\n\t\t},\n\t\tBlocks6: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"68973a47b5844593cf99cc7a\"),\n\t\t\t},\n\t\t},\n\t\tPools:  []bson.ObjectID{},\n\t\tShares: []*node.Share{},\n\t\tAvailableDrives: []*drive.Device{\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part3\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part3\"},\n\t\t},\n\t\tInstanceDrives:          []*drive.Device{},\n\t\tNoHostNetwork:           false,\n\t\tNoNodePortNetwork:       false,\n\t\tHostNat:                 true,\n\t\tDefaultNoPublicAddress:  true,\n\t\tDefaultNoPublicAddress6: false,\n\t\tJumboFrames:             true,\n\t\tJumboFramesInternal:     true,\n\t\tIscsi:                   false,\n\t\tLocalIsos:               nil,\n\t\tUsbPassthrough:          false,\n\t\tUsbDevices:              []*usb.Device{},\n\t\tPciPassthrough:          false,\n\t\tPciDevices:              []*pci.Device{},\n\t\tHugepages:               false,\n\t\tHugepagesSize:           0,\n\t\tFirewall:                false,\n\t\tRoles:                   []string{\"shape-m2\"},\n\t\tMemory:                  41.02,\n\t\tHugePagesUsed:           0,\n\t\tLoad1:                   24.63,\n\t\tLoad5:                   23.32,\n\t\tLoad15:                  21.43,\n\t\tCpuUnits:                128,\n\t\tMemoryUnits:             512,\n\t\tCpuUnitsRes:             64,\n\t\tMemoryUnitsRes:          298,\n\t\tPublicIps:               []string{\"10.253.67.93\"},\n\t\tPublicIps6: []string{\n\t\t\t\"2001:db8:85a3:4d2f:9a47:3b6e:c829:4f16\",\n\t\t},\n\t\tPrivateIps: map[string]string{\n\t\t\t\"bond0.2\": \"10.8.0.13\",\n\t\t},\n\t\tSoftwareVersion: constants.Version,\n\t\tHostname:        \"pritunl-east0\",\n\t\tVirtPath:        \"/var/lib/pritunl-cloud\",\n\t\tCachePath:       \"/var/cache/pritunl-cloud\",\n\t\tTempPath:        \"\",\n\t\tOracleUser:      \"\",\n\t\tOracleTenancy:   \"\",\n\t\tOraclePublicKey: `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu7nP2Kx4YmRa5TgL9Hs3\ndW8JnF6QzV1MpC4BwX7Ka3Gt5RvN8YmL2Hs9Dx4TnQ6JpF8LwK3Vz5YmRs7NdT9H\ngX2BpL4KwR8TmQ5NvJ3YxF7DsH9LwK2VmT8RgJ4NpX5BwL3HsY7TmQ9KxF2DpN8V\nwJ3LsT4HgR9YmK5NxB2FwL7TsH8KpQ3XmJ4DgN9BwY2RsL5HxK7VmT3NpF8YwJ4D\ngR2TsL9KxH5BmN3VwQ8JpY7LsT4HgK2NxR9FmL3BwJ5TsN8YpK4VxH7DgQ2LmR9N\nwJ3BsT5HxL8KpN2YmF4DgR7VwQ3TsJ9KxH5BmL8NpY2FgT4RwK7VsH9JmQ3LxN8D\nFwIDAQAB\n-----END PUBLIC KEY-----`,\n\t\tOperation: \"\",\n\t},\n\t{\n\t\tId:                   utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0e\"),\n\t\tDatacenter:           utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:                 utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tName:                 \"pritunl-east4\",\n\t\tComment:              \"\",\n\t\tTypes:                []string{\"admin\", \"hypervisor\"},\n\t\tTimestamp:            time.Now(),\n\t\tPort:                 443,\n\t\tNoRedirectServer:     false,\n\t\tProtocol:             \"https\",\n\t\tHypervisor:           \"kvm\",\n\t\tVga:                  \"virtio\",\n\t\tVgaRender:            \"\",\n\t\tAvailableRenders:     []string{},\n\t\tGui:                  false,\n\t\tGuiUser:              \"\",\n\t\tGuiMode:              \"\",\n\t\tCertificates:         []bson.ObjectID{},\n\t\tAdminDomain:          \"\",\n\t\tUserDomain:           \"\",\n\t\tWebauthnDomain:       \"\",\n\t\tRequestsMin:          76,\n\t\tForwardedForHeader:   \"\",\n\t\tForwardedProtoHeader: \"\",\n\t\tExternalInterfaces:   []string{},\n\t\tExternalInterfaces6:  []string{},\n\t\tInternalInterfaces:   []string{\"bond0.2\"},\n\t\tAvailableInterfaces: []ip.Interface{\n\t\t\t{Name: \"bond0\", Address: \"\"},\n\t\t\t{Name: \"bond0.2\", Address: \"10.8.0.14\"},\n\t\t\t{Name: \"bond0.4\", Address: \"10.253.67.94\"},\n\t\t\t{Name: \"bonding_masters\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f0\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f1\", Address: \"\"},\n\t\t\t{Name: \"veth0\", Address: \"\"},\n\t\t},\n\t\tAvailableBridges: []ip.Interface{\n\t\t\t{Name: \"pritunlhost0\", Address: \"198.18.84.1\"},\n\t\t\t{Name: \"pritunlport0\", Address: \"198.19.96.1\"},\n\t\t},\n\t\tAvailableVpcs:    []*cloud.Vpc{},\n\t\tCloudSubnets:     []string{},\n\t\tDefaultInterface: \"bond0.4\",\n\t\tNetworkMode:      \"static\",\n\t\tNetworkMode6:     \"static\",\n\t\tBlocks: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"689733b7a7a35eae0dbaea2f\"),\n\t\t\t},\n\t\t},\n\t\tBlocks6: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"68973a47b5844593cf99cc7a\"),\n\t\t\t},\n\t\t},\n\t\tPools:  []bson.ObjectID{},\n\t\tShares: []*node.Share{},\n\t\tAvailableDrives: []*drive.Device{\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part3\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part3\"},\n\t\t},\n\t\tInstanceDrives:          []*drive.Device{},\n\t\tNoHostNetwork:           false,\n\t\tNoNodePortNetwork:       false,\n\t\tHostNat:                 true,\n\t\tDefaultNoPublicAddress:  true,\n\t\tDefaultNoPublicAddress6: false,\n\t\tJumboFrames:             true,\n\t\tJumboFramesInternal:     true,\n\t\tIscsi:                   false,\n\t\tLocalIsos:               nil,\n\t\tUsbPassthrough:          false,\n\t\tUsbDevices:              []*usb.Device{},\n\t\tPciPassthrough:          false,\n\t\tPciDevices:              []*pci.Device{},\n\t\tHugepages:               false,\n\t\tHugepagesSize:           0,\n\t\tFirewall:                false,\n\t\tRoles:                   []string{\"shape-m2\"},\n\t\tMemory:                  41.02,\n\t\tHugePagesUsed:           0,\n\t\tLoad1:                   88.45,\n\t\tLoad5:                   83.13,\n\t\tLoad15:                  72.34,\n\t\tCpuUnits:                128,\n\t\tMemoryUnits:             512,\n\t\tCpuUnitsRes:             45,\n\t\tMemoryUnitsRes:          242,\n\t\tPublicIps:               []string{\"10.253.67.94\"},\n\t\tPublicIps6: []string{\n\t\t\t\"2001:db8:85a3:4d2f:6f15:d8c3:2a94:e7b1\",\n\t\t},\n\t\tPrivateIps: map[string]string{\n\t\t\t\"bond0.2\": \"10.8.0.14\",\n\t\t},\n\t\tSoftwareVersion: constants.Version,\n\t\tHostname:        \"pritunl-east0\",\n\t\tVirtPath:        \"/var/lib/pritunl-cloud\",\n\t\tCachePath:       \"/var/cache/pritunl-cloud\",\n\t\tTempPath:        \"\",\n\t\tOracleUser:      \"\",\n\t\tOracleTenancy:   \"\",\n\t\tOraclePublicKey: `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx9SLk4Pm3VaRtN8Yw2Hf\njK5TgQ7BnM2XpL4DwV8Ha6FsR9YmJ3KxC5NtQ8LwP7DgH2VmF4TsK9XpN3BwJ8Rg\nmL5YxH7TsQ2NpK4VwF8DgJ3BmR9LxT5KsN2HwY7FpQ8VmJ4TgL3NxK9BwH5DsR2Y\nmF7KpT8LwQ3VgN4JxH9BsK2TmL5DwR8YpF3NxQ7KgJ4VsH9BmT2LwK8DxN5YpF3R\ngJ7TsL4KwH9VmQ2NxB8FpY3DgK5TsR9LwJ2HmN4BxF7VpQ8KgL3TsY9NwH5DmK4J\nxR2BgF8TpL7VsN3KwQ9YmH4DgJ5BxT2LsK8FpN3VwR7YgH4TmQ9KsL2BxJ5NpF8D\nRwIDAQAB\n-----END PUBLIC KEY-----`,\n\t\tOperation: \"\",\n\t},\n\t{\n\t\tId:                   utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0f\"),\n\t\tDatacenter:           utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:                 utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tName:                 \"pritunl-east5\",\n\t\tComment:              \"\",\n\t\tTypes:                []string{\"admin\", \"hypervisor\"},\n\t\tTimestamp:            time.Now(),\n\t\tPort:                 443,\n\t\tNoRedirectServer:     false,\n\t\tProtocol:             \"https\",\n\t\tHypervisor:           \"kvm\",\n\t\tVga:                  \"virtio\",\n\t\tVgaRender:            \"\",\n\t\tAvailableRenders:     []string{},\n\t\tGui:                  false,\n\t\tGuiUser:              \"\",\n\t\tGuiMode:              \"\",\n\t\tCertificates:         []bson.ObjectID{},\n\t\tAdminDomain:          \"\",\n\t\tUserDomain:           \"\",\n\t\tWebauthnDomain:       \"\",\n\t\tRequestsMin:          12,\n\t\tForwardedForHeader:   \"\",\n\t\tForwardedProtoHeader: \"\",\n\t\tExternalInterfaces:   []string{},\n\t\tExternalInterfaces6:  []string{},\n\t\tInternalInterfaces:   []string{\"bond0.2\"},\n\t\tAvailableInterfaces: []ip.Interface{\n\t\t\t{Name: \"bond0\", Address: \"\"},\n\t\t\t{Name: \"bond0.2\", Address: \"10.8.0.15\"},\n\t\t\t{Name: \"bond0.4\", Address: \"10.253.67.95\"},\n\t\t\t{Name: \"bonding_masters\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f0\", Address: \"\"},\n\t\t\t{Name: \"enp1s0f1\", Address: \"\"},\n\t\t\t{Name: \"veth0\", Address: \"\"},\n\t\t},\n\t\tAvailableBridges: []ip.Interface{\n\t\t\t{Name: \"pritunlhost0\", Address: \"198.18.84.1\"},\n\t\t\t{Name: \"pritunlport0\", Address: \"198.19.96.1\"},\n\t\t},\n\t\tAvailableVpcs:    []*cloud.Vpc{},\n\t\tCloudSubnets:     []string{},\n\t\tDefaultInterface: \"bond0.4\",\n\t\tNetworkMode:      \"static\",\n\t\tNetworkMode6:     \"static\",\n\t\tBlocks: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"689733b7a7a35eae0dbaea2f\"),\n\t\t\t},\n\t\t},\n\t\tBlocks6: []*node.BlockAttachment{\n\t\t\t{\n\t\t\t\tInterface: \"bond0.4\",\n\t\t\t\tBlock:     utils.ObjectIdHex(\"68973a47b5844593cf99cc7a\"),\n\t\t\t},\n\t\t},\n\t\tPools:  []bson.ObjectID{},\n\t\tShares: []*node.Share{},\n\t\tAvailableDrives: []*drive.Device{\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_27Z1P0FGN-part3\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part1\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part2\"},\n\t\t\t{Id: \"nvme-INTEL_42K1P0FGN-part3\"},\n\t\t},\n\t\tInstanceDrives:          []*drive.Device{},\n\t\tNoHostNetwork:           false,\n\t\tNoNodePortNetwork:       false,\n\t\tHostNat:                 true,\n\t\tDefaultNoPublicAddress:  true,\n\t\tDefaultNoPublicAddress6: false,\n\t\tJumboFrames:             true,\n\t\tJumboFramesInternal:     true,\n\t\tIscsi:                   false,\n\t\tLocalIsos:               nil,\n\t\tUsbPassthrough:          false,\n\t\tUsbDevices:              []*usb.Device{},\n\t\tPciPassthrough:          false,\n\t\tPciDevices:              []*pci.Device{},\n\t\tHugepages:               false,\n\t\tHugepagesSize:           0,\n\t\tFirewall:                false,\n\t\tRoles:                   []string{\"shape-m2\"},\n\t\tMemory:                  41.02,\n\t\tHugePagesUsed:           0,\n\t\tLoad1:                   53.44,\n\t\tLoad5:                   43.75,\n\t\tLoad15:                  43.93,\n\t\tCpuUnits:                128,\n\t\tMemoryUnits:             512,\n\t\tCpuUnitsRes:             76,\n\t\tMemoryUnitsRes:          346,\n\t\tPublicIps:               []string{\"10.253.67.95\"},\n\t\tPublicIps6: []string{\n\t\t\t\"2001:db8:85a3:4d2f:8d2c:1fa7:b653:9e48\",\n\t\t},\n\t\tPrivateIps: map[string]string{\n\t\t\t\"bond0.2\": \"10.8.0.15\",\n\t\t},\n\t\tSoftwareVersion: constants.Version,\n\t\tHostname:        \"pritunl-east0\",\n\t\tVirtPath:        \"/var/lib/pritunl-cloud\",\n\t\tCachePath:       \"/var/cache/pritunl-cloud\",\n\t\tTempPath:        \"\",\n\t\tOracleUser:      \"\",\n\t\tOracleTenancy:   \"\",\n\t\tOraclePublicKey: `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1pK7Gm4XwQ9RsT2NvH8Bf\njL5DgY3KpM2TwV8NxH4FsQ9BmL7YgK3VpT8DwR5NxJ2HmF4KgQ7TsL9BwY3VpN8R\nxK5DmH2FgT9LwJ4NsQ7BpV3KxY8TmL5HgR2DwN9FpK4VsT3BxJ7YmQ8LgH2NwK5D\npF9TxR3VsL4KgY7BmN2HwJ8FpQ5TxK9DsL3VgH4NmR7BwY2KpT8FxJ9LsQ3DgN5V\nmH7BwK4TpL2RxF9NsY3KgQ8DmJ5VwH7BpT2LxK4FgN9RsQ3TmL8YwJ5DpH7KxF2N\ngV4BsT9LmQ3KwR8YpN5DxH7FgJ2TsL4BwK9VmQ3NpY8RxT5LgH2DsK7BmF4JwN9Q\nTwIDAQAB\n-----END PUBLIC KEY-----`,\n\t\tOperation: \"\",\n\t},\n}\n"
  },
  {
    "path": "demo/organization.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/organization\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Organizations = []*organization.Organization{\n\t{\n\t\tId: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tRoles: []string{\n\t\t\t\"pritunl\",\n\t\t},\n\t\tName:    \"pritunl\",\n\t\tComment: \"\",\n\t},\n}\n"
  },
  {
    "path": "demo/plan.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/plan\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Plans = []*plan.Plan{\n\t{\n\t\tId:           utils.ObjectIdHex(\"66e8993f1fbc6db8e20819f8\"),\n\t\tName:         \"primary\",\n\t\tComment:      \"\",\n\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tStatements: []*plan.Statement{\n\t\t\t{\n\t\t\t\tId:        utils.ObjectIdHex(\"67c9bed42c125c5ddf24d0a1\"),\n\t\t\t\tStatement: \"IF instance.last_timestamp < 60 AND instance.last_heartbeat > 60 FOR 15 THEN 'stop'\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:        utils.ObjectIdHex(\"683d645e2956cdd93d3e08d2\"),\n\t\t\t\tStatement: \"IF instance.state != 'running' THEN 'start'\",\n\t\t\t},\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "demo/pod.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/journal\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Pods = []*aggregate.PodAggregate{\n\t{\n\t\tPod: pod.Pod{\n\t\t\tId:               utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\t\tName:             \"web-app\",\n\t\t\tComment:          \"\",\n\t\t\tOrganization:     utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\t\tDeleteProtection: false,\n\t\t\tDrafts:           []*pod.UnitDraft{},\n\t\t},\n\t\tUnits: Units,\n\t},\n}\n\nvar Units = []*unit.Unit{\n\t{\n\t\tId:           utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tPod:          utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tName:         \"web-app\",\n\t\tKind:         \"instance\",\n\t\tCount:        0,\n\t\tDeployments: []bson.ObjectID{\n\t\t\tutils.ObjectIdHex(\"688db9219da165ffad4e439c\"),\n\t\t\tutils.ObjectIdHex(\"688dbc759da165ffad4e4ab0\"),\n\t\t},\n\t\tSpec: \"```yaml\" + `\n---\nname: web-app\nkind: instance\nzone: +/zone/us-west-1a\nshape: +/shape/m2-small\nprocessors: 4\nmemory: 4096\nvpc: +/vpc/vpc\nsubnet: +/subnet/primary\nimage: +/image/almalinux9\nroles:\n    - instance\nnodePorts:\n    - protocol: tcp\n      externalPort: 32120\n      internalPort: 80\n\n---\nname: web-app-firewall\nkind: firewall\ningress:\n    - protocol: tcp\n      port: 22\n      source:\n        - 10.20.0.0/16\n` + \"```\" + `\n\n## Initialization\n\n* Update system\n* Install nginx\n\n` + \"```shell\" + `\ndnf -y update\ndnf install -y nginx\nsed -i \"s/Test Page/cloud-$(pci get +/instance/self/id)/\" /usr/share/nginx/html/index.html\n` + \"```\" + `\n\n## Configuration\n\n` + \"```python {phase=reload}\" + `\nimport string\nimport subprocess\n\ndef pci_get(query):\n    return subprocess.run(\n        [\"pci\", \"get\", query],\n        stdout=subprocess.PIPE,\n        text=True,\n        check=True,\n    ).stdout.strip()\n\nwith open(\"/etc/web.conf\", \"w\") as file:\n    file.write(pci_get(\"+/unit/database/private_ips\"))\n` + \"```\" + `\n\n## Startup\n\n` + \"```shell {phase=reboot}\" + `\nsystemctl start nginx\n` + \"```\",\n\t\tSpecIndex:  2,\n\t\tLastSpec:   utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tDeploySpec: utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tHash:       \"80309b44139a78378c9025d40535c73f52f9d71c\",\n\t},\n\t{\n\t\tId:           utils.ObjectIdHex(\"68b67d1aee12c08a1f39f88b\"),\n\t\tPod:          utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tOrganization: utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tName:         \"database\",\n\t\tKind:         \"instance\",\n\t\tCount:        0,\n\t\tDeployments:  []bson.ObjectID{},\n\t\tSpec: \"```yaml\" + `\n---\nname: database\nkind: instance\nzone: +/zone/us-west-1a\nshape: +/shape/m2-small\nprocessors: 4\nmemory: 4096\nvpc: +/vpc/vpc\nsubnet: +/subnet/primary\nimage: +/image/almalinux9\nroles:\n    - instance\n\n---\nname: web-app-firewall\nkind: firewall\ningress:\n    - protocol: tcp\n      port: 27017\n      source:\n        - +/unit/web-app\n` + \"```\" + `\n\n## Initialization\n\n* Update system\n* Install mongodb\n\n` + \"```shell\" + `\ndnf -y update\n\ntee /etc/yum.repos.d/mongodb-org.repo << EOF\n[mongodb-org-8.0]\nname=MongoDB Repository\nbaseurl=https://repo.mongodb.org/yum/redhat/9/mongodb-org/8.0/x86_64/\ngpgcheck=1\nenabled=1\ngpgkey=https://pgp.mongodb.com/server-8.0.asc\nEOF\n\ndnf -y install mongodb-org\n` + \"```\" + `\n\n## Startup\n\n` + \"```shell {phase=reboot}\" + `\nsudo systemctl start mongod\n` + \"```\",\n\t\tSpecIndex:  2,\n\t\tLastSpec:   utils.ObjectIdHex(\"688c7cde9da165ffad4b34f2\"),\n\t\tDeploySpec: utils.ObjectIdHex(\"688c7cde9da165ffad4b34f2\"),\n\t\tHash:       \"d0c176ab5dafce10956e0d3d5e1320b2e496acff\",\n\t},\n}\n\nvar Specs = []*spec.Spec{\n\t{\n\t\tId:        utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tUnit:      utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tIndex:     2,\n\t\tHash:      \"80309b44139a78378c9025d40535c73f52f9d71c\",\n\t\tTimestamp: time.Now().Add(-5 * time.Minute),\n\t\tData: \"```yaml\" + `\n---\nname: web-app\nkind: instance\nzone: +/zone/us-west-1a\nshape: +/shape/m2-small\nprocessors: 4\nmemory: 4096\nvpc: +/vpc/vpc\nsubnet: +/subnet/primary\nimage: +/image/almalinux9\nroles:\n    - instance\nnodePorts:\n    - protocol: tcp\n      externalPort: 32120\n      internalPort: 80\n\n---\nname: web-app-firewall\nkind: firewall\ningress:\n    - protocol: tcp\n      port: 22\n      source:\n        - 10.20.0.0/16\n` + \"```\" + `\n\n## Initialization\n\n* Update system\n* Install nginx\n\n` + \"```shell\" + `\ndnf -y update\ndnf install -y nginx\nsed -i \"s/Test Page/cloud-$(pci get +/instance/self/id)/\" /usr/share/nginx/html/index.html\n` + \"```\" + `\n\n## Configuration\n\n` + \"```python {phase=reload}\" + `\nimport string\nimport subprocess\n\ndef pci_get(query):\n    return subprocess.run(\n        [\"pci\", \"get\", query],\n        stdout=subprocess.PIPE,\n        text=True,\n        check=True,\n    ).stdout.strip()\n\nwith open(\"/etc/web.conf\", \"w\") as file:\n    file.write(pci_get(\"+/unit/database/private_ips\"))\n` + \"```\" + `\n\n## Startup\n\n` + \"```shell {phase=reboot}\" + `\nsystemctl start nginx\n` + \"```\",\n\t},\n\t{\n\t\tId:        utils.ObjectIdHex(\"68b67f44ee12c08a1f39fdbe\"),\n\t\tUnit:      utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tIndex:     1,\n\t\tHash:      \"7e4a9f2c8b3d6h5j1k9m0n2p4q8r3s7t5v8w2x34\",\n\t\tTimestamp: time.Now().Add(-5 * time.Hour),\n\t\tData: \"```yaml\" + `\n---\nname: web-app\nkind: instance\nzone: +/zone/us-west-1a\nshape: +/shape/m2-small\nprocessors: 4\nmemory: 4096\nvpc: +/vpc/vpc\nsubnet: +/subnet/primary\nimage: +/image/almalinux9\nroles:\n    - instance\nnodePorts:\n    - protocol: tcp\n      externalPort: 32120\n      internalPort: 80\n\n---\nname: web-app-firewall\nkind: firewall\ningress:\n    - protocol: tcp\n      port: 22\n      source:\n        - 10.20.0.0/16\n` + \"```\" + `\n\n## Initialization\n\n* Update system\n* Install nginx\n\n` + \"```shell\" + `\ndnf -y update\ndnf install -y nginx\nsed -i \"s/Test Page/cloud-$(pci get +/instance/self/id)/\" /usr/share/nginx/html/index.html\n` + \"```\" + `\n\n## Startup\n\n` + \"```shell {phase=reboot}\" + `\nsystemctl start nginx\n` + \"```\",\n\t},\n\t{\n\t\tId:        utils.ObjectIdHex(\"688c7cde9da165ffad4b34f2\"),\n\t\tUnit:      utils.ObjectIdHex(\"68b67d1aee12c08a1f39f88b\"),\n\t\tIndex:     2,\n\t\tHash:      \"d0c176ab5dafce10956e0d3d5e1320b2e496acff\",\n\t\tTimestamp: time.Now().Add(-10 * time.Minute),\n\t\tData: \"```yaml\" + `\n---\nname: database\nkind: instance\nzone: +/zone/us-west-1a\nshape: +/shape/m2-small\nprocessors: 4\nmemory: 4096\nvpc: +/vpc/vpc\nsubnet: +/subnet/primary\nimage: +/image/almalinux9\nroles:\n    - instance\n\n---\nname: web-app-firewall\nkind: firewall\ningress:\n    - protocol: tcp\n      port: 27017\n      source:\n        - +/unit/web-app\n` + \"```\" + `\n\n## Initialization\n\n* Update system\n* Install mongodb\n\n` + \"```shell\" + `\ndnf -y update\n\ntee /etc/yum.repos.d/mongodb-org.repo << EOF\n[mongodb-org-8.0]\nname=MongoDB Repository\nbaseurl=https://repo.mongodb.org/yum/redhat/9/mongodb-org/8.0/x86_64/\ngpgcheck=1\nenabled=1\ngpgkey=https://pgp.mongodb.com/server-8.0.asc\nEOF\n\ndnf -y install mongodb-org\n` + \"```\" + `\n\n## Startup\n\n` + \"```shell {phase=reboot}\" + `\nsudo systemctl start mongod\n` + \"```\",\n\t},\n\t{\n\t\tId:        utils.ObjectIdHex(\"68b67cb1ee12c08a1f39f78b\"),\n\t\tUnit:      utils.ObjectIdHex(\"68b67d1aee12c08a1f39f88b\"),\n\t\tIndex:     1,\n\t\tHash:      \"3c8f5a1d9e7b2k4m6n0p3q7r9s2t5v8w1x4y6z4d\",\n\t\tTimestamp: time.Now().Add(-6 * time.Hour),\n\t\tData: \"```yaml\" + `\n---\nname: database\nkind: instance\nzone: +/zone/us-west-1a\nshape: +/shape/m2-small\nprocessors: 4\nmemory: 4096\nvpc: +/vpc/vpc\nsubnet: +/subnet/primary\nimage: +/image/almalinux9\nroles:\n    - instance\n` + \"```\" + `\n\n## Initialization\n\n* Update system\n* Install mongodb\n\n` + \"```shell\" + `\ndnf -y update\n\ntee /etc/yum.repos.d/mongodb-org.repo << EOF\n[mongodb-org-8.0]\nname=MongoDB Repository\nbaseurl=https://repo.mongodb.org/yum/redhat/9/mongodb-org/8.0/x86_64/\ngpgcheck=1\nenabled=1\ngpgkey=https://pgp.mongodb.com/server-8.0.asc\nEOF\n\ndnf -y install mongodb-org\n` + \"```\" + `\n\n## Startup\n\n` + \"```shell {phase=reboot}\" + `\nsudo systemctl start mongod\n` + \"```\",\n\t},\n}\n\nvar SpecsNamed = []*spec.Named{\n\t{\n\t\tId:        Specs[0].Id,\n\t\tUnit:      Specs[0].Unit,\n\t\tIndex:     Specs[0].Index,\n\t\tTimestamp: Specs[0].Timestamp,\n\t},\n\t{\n\t\tId:        Specs[1].Id,\n\t\tUnit:      Specs[1].Unit,\n\t\tIndex:     Specs[1].Index,\n\t\tTimestamp: Specs[1].Timestamp,\n\t},\n\t{\n\t\tId:        Specs[2].Id,\n\t\tUnit:      Specs[2].Unit,\n\t\tIndex:     Specs[2].Index,\n\t\tTimestamp: Specs[2].Timestamp,\n\t},\n\t{\n\t\tId:        Specs[3].Id,\n\t\tUnit:      Specs[3].Unit,\n\t\tIndex:     Specs[3].Index,\n\t\tTimestamp: Specs[3].Timestamp,\n\t},\n}\n\nvar DeploymentLogs = []string{\n\t\"[2025-08-02 07:21:42] dnf install -y nginx\\n\",\n\t\"[2025-08-02 07:21:43] Waiting for process with pid 1024 to finish.\\n\",\n\t\"[2025-08-02 07:22:03] Last metadata expiration check: 0:00:02 ago on Sat 02 Aug 2025 07:22:01 AM UTC.\\n\",\n\t\"[2025-08-02 07:22:04] Dependencies resolved.\\n\",\n\t\"[2025-08-02 07:22:04] ================================================================================\\n\",\n\t\"[2025-08-02 07:22:04]  Package             Arch    Version                   Repository          Size\\n\",\n\t\"[2025-08-02 07:22:04] ================================================================================\\n\",\n\t\"[2025-08-02 07:22:04] Installing:\\n\",\n\t\"[2025-08-02 07:22:04]  nginx               x86_64  2:1.20.1-22.0.1.el9_6.3   ol9_appstream       49 k\\n\",\n\t\"[2025-08-02 07:22:04] Installing dependencies:\\n\",\n\t\"[2025-08-02 07:22:04]  nginx-core          x86_64  2:1.20.1-22.0.1.el9_6.3   ol9_appstream      589 k\\n\",\n\t\"[2025-08-02 07:22:04]  nginx-filesystem    noarch  2:1.20.1-22.0.1.el9_6.3   ol9_appstream      9.6 k\\n\",\n\t\"[2025-08-02 07:22:04]  oracle-logos-httpd  noarch  90.4-1.0.1.el9            ol9_baseos_latest   37 k\\n\",\n\t\"[2025-08-02 07:22:04] Transaction Summary\\n\",\n\t\"[2025-08-02 07:22:04] ================================================================================\\n\",\n\t\"[2025-08-02 07:22:04] Install  4 Packages\\n\",\n\t\"[2025-08-02 07:22:04] Total download size: 684 k\\n\",\n\t\"[2025-08-02 07:22:04] Installed size: 1.8 M\\n\",\n\t\"[2025-08-02 07:22:04] Downloading Packages:\\n\",\n\t\"[2025-08-02 07:22:04] (1/4): nginx-1.20.1-22.0.1.el9_6.3.x86_64.rpm   757 kB/s |  49 kB     00:00\\n\",\n\t\"[2025-08-02 07:22:04] (2/4): oracle-logos-httpd-90.4-1.0.1.el9.noarch 529 kB/s |  37 kB     00:00\\n\",\n\t\"[2025-08-02 07:22:04] (3/4): nginx-filesystem-1.20.1-22.0.1.el9_6.3.n 540 kB/s | 9.6 kB     00:00\\n\",\n\t\"[2025-08-02 07:22:04] (4/4): nginx-core-1.20.1-22.0.1.el9_6.3.x86_64. 5.7 MB/s | 589 kB     00:00\\n\",\n\t\"[2025-08-02 07:22:04] --------------------------------------------------------------------------------\\n\",\n\t\"[2025-08-02 07:22:04] Total                                           6.3 MB/s | 684 kB     00:00\\n\",\n\t\"[2025-08-02 07:22:05] Running transaction check\\n\",\n\t\"[2025-08-02 07:22:05] Transaction check succeeded.\\n\",\n\t\"[2025-08-02 07:22:05] Running transaction test\\n\",\n\t\"[2025-08-02 07:22:05] Transaction test succeeded.\\n\",\n\t\"[2025-08-02 07:22:05] Running transaction\\n\",\n\t\"[2025-08-02 07:22:05]   Preparing        :                                                        1/1\\n\",\n\t\"[2025-08-02 07:22:05]   Running scriptlet: nginx-filesystem-2:1.20.1-22.0.1.el9_6.3.noarch        1/4\\n\",\n\t\"[2025-08-02 07:22:05]   Installing       : nginx-filesystem-2:1.20.1-22.0.1.el9_6.3.noarch        1/4\\n\",\n\t\"[2025-08-02 07:22:05]   Installing       : nginx-core-2:1.20.1-22.0.1.el9_6.3.x86_64              2/4\\n\",\n\t\"[2025-08-02 07:22:05]   Installing       : oracle-logos-httpd-90.4-1.0.1.el9.noarch               3/4\\n\",\n\t\"[2025-08-02 07:22:05]   Installing       : nginx-2:1.20.1-22.0.1.el9_6.3.x86_64                   4/4\\n\",\n\t\"[2025-08-02 07:22:06]   Running scriptlet: nginx-2:1.20.1-22.0.1.el9_6.3.x86_64                   4/4\\n\",\n\t\"[2025-08-02 07:22:06]   Verifying        : oracle-logos-httpd-90.4-1.0.1.el9.noarch               1/4\\n\",\n\t\"[2025-08-02 07:22:06]   Verifying        : nginx-2:1.20.1-22.0.1.el9_6.3.x86_64                   2/4\\n\",\n\t\"[2025-08-02 07:22:06]   Verifying        : nginx-core-2:1.20.1-22.0.1.el9_6.3.x86_64              3/4\\n\",\n\t\"[2025-08-02 07:22:07]   Verifying        : nginx-filesystem-2:1.20.1-22.0.1.el9_6.3.noarch        4/4\\n\",\n\t\"[2025-08-02 07:22:07] Installed:\\n\",\n\t\"[2025-08-02 07:22:07]   nginx-2:1.20.1-22.0.1.el9_6.3.x86_64\\n\",\n\t\"[2025-08-02 07:22:07]   nginx-core-2:1.20.1-22.0.1.el9_6.3.x86_64\\n\",\n\t\"[2025-08-02 07:22:07]   nginx-filesystem-2:1.20.1-22.0.1.el9_6.3.noarch\\n\",\n\t\"[2025-08-02 07:22:07]   oracle-logos-httpd-90.4-1.0.1.el9.noarch\\n\",\n\t\"[2025-08-02 07:22:07] Complete!\\n\",\n\t\"[2025-08-02 07:22:07] systemctl start nginx\\n\",\n\t\"[2025-08-02 07:22:12] [INFO] ▶ agent: Queuing engine reload ◆ hash=2415297466 ◆ spec_len=618\",\n}\n\nvar Deployments = []*aggregate.Deployment{\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d00\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a00\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.178\"},\n\t\t\tPublicIps:   []string{\"1.253.67.10\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:ac50:8355:bb57:e0f5\"},\n\t\t\tPrivateIps:  []string{\"10.196.3.18\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:58d5:f529:66f2:36ef\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east0\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 50.52,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       35.43,\n\t\tInstanceLoad5:       44.56,\n\t\tInstanceLoad15:      51.32,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d01\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a01\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.82\"},\n\t\t\tPublicIps:   []string{\"1.253.67.103\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:5e1b:773a:2463:da58\"},\n\t\t\tPrivateIps:  []string{\"10.196.6.231\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:bf1a:d5e4:56a2:4b27\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east1\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 62.72,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       25.34,\n\t\tInstanceLoad5:       29.71,\n\t\tInstanceLoad15:      33.64,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d02\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a02\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.79\"},\n\t\t\tPublicIps:   []string{\"1.253.67.148\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:27fe:0397:17fa:5d2e\"},\n\t\t\tPrivateIps:  []string{\"10.196.3.251\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:3d61:c9f7:d2d7:8b9b\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east2\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 56.29,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       50.22,\n\t\tInstanceLoad5:       58.12,\n\t\tInstanceLoad15:      66.57,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d03\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a03\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.109\"},\n\t\t\tPublicIps:   []string{\"1.253.67.214\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:41b2:61e2:ad56:6cdc\"},\n\t\t\tPrivateIps:  []string{\"10.196.2.12\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:c166:fabc:4223:a974\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east3\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 32.21,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       54.49,\n\t\tInstanceLoad5:       59.36,\n\t\tInstanceLoad15:      64.21,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d04\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0e\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a04\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.97\"},\n\t\t\tPublicIps:   []string{\"1.253.67.129\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:cb29:095b:a7f8:9a7e\"},\n\t\t\tPrivateIps:  []string{\"10.196.6.229\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:b2f4:9b35:700e:0b9a\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east4\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 33.96,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       14.25,\n\t\tInstanceLoad5:       18.58,\n\t\tInstanceLoad15:      24.81,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d05\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0f\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a05\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.32\"},\n\t\t\tPublicIps:   []string{\"1.253.67.144\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:126f:552b:77d0:010e\"},\n\t\t\tPrivateIps:  []string{\"10.196.7.148\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:c057:f8fa:ff43:a21a\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east5\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 60.63,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       58.13,\n\t\tInstanceLoad5:       61.62,\n\t\tInstanceLoad15:      64.1,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d06\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a06\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.87\"},\n\t\t\tPublicIps:   []string{\"1.253.67.73\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:aa28:1b64:c808:caeb\"},\n\t\t\tPrivateIps:  []string{\"10.196.4.215\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:9797:d44a:0c7e:cb9e\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east0\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 48.03,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       53.4,\n\t\tInstanceLoad5:       60.79,\n\t\tInstanceLoad15:      68.75,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d07\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a07\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.151\"},\n\t\t\tPublicIps:   []string{\"1.253.67.65\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:bf64:91d6:4050:eac0\"},\n\t\t\tPrivateIps:  []string{\"10.196.5.97\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:0dd4:8931:8c28:5465\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east1\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 78.17,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       34.25,\n\t\tInstanceLoad5:       40.18,\n\t\tInstanceLoad15:      46.49,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d08\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a08\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.224\"},\n\t\t\tPublicIps:   []string{\"1.253.67.211\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:f5f3:98f7:82b5:ee87\"},\n\t\t\tPrivateIps:  []string{\"10.196.3.34\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:8d49:241d:4dd1:4663\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east2\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 53.78,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       35.24,\n\t\tInstanceLoad5:       38.31,\n\t\tInstanceLoad15:      43.56,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d09\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"688c716d9da165ffad4b3682\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b52e4\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a09\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.9\"},\n\t\t\tPublicIps:   []string{\"1.253.67.121\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:6e3a:29b0:639f:49d4\"},\n\t\t\tPrivateIps:  []string{\"10.196.2.187\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:ddb4:e207:cc09:d1e6\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east3\",\n\t\tInstanceName:        \"web-app\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      2048,\n\t\tInstanceProcessors:  2,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 42.23,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       56.09,\n\t\tInstanceLoad5:       57.62,\n\t\tInstanceLoad15:      65.05,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d0a\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"68b67d1aee12c08a1f39f88b\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b34f2\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0e\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0a\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.61\"},\n\t\t\tPublicIps:   []string{\"1.253.67.205\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:d943:7ff9:dfdc:4d68\"},\n\t\t\tPrivateIps:  []string{\"10.196.3.219\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:7ba9:bca3:5217:b534\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east4\",\n\t\tInstanceName:        \"database\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      8192,\n\t\tInstanceProcessors:  4,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 62.84,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       40.76,\n\t\tInstanceLoad5:       46.28,\n\t\tInstanceLoad15:      48.62,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d0b\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"68b67d1aee12c08a1f39f88b\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b34f2\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0f\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0b\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.221\"},\n\t\t\tPublicIps:   []string{\"1.253.67.155\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:3e3e:0d9d:8669:2c89\"},\n\t\t\tPrivateIps:  []string{\"10.196.8.253\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:d0ff:8b42:1d9b:92fd\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east5\",\n\t\tInstanceName:        \"database\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      8192,\n\t\tInstanceProcessors:  4,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 66.15,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       28.1,\n\t\tInstanceLoad5:       32.45,\n\t\tInstanceLoad15:      40.43,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d0c\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"68b67d1aee12c08a1f39f88b\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b34f2\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0a\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0c\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.99\"},\n\t\t\tPublicIps:   []string{\"1.253.67.59\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:74b0:8661:53c1:1d5b\"},\n\t\t\tPrivateIps:  []string{\"10.196.2.110\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:e7b4:670b:acf5:dfb4\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east0\",\n\t\tInstanceName:        \"database\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      8192,\n\t\tInstanceProcessors:  4,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 56.45,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       27.42,\n\t\tInstanceLoad5:       29.06,\n\t\tInstanceLoad15:      36.64,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d0d\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"68b67d1aee12c08a1f39f88b\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b34f2\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0b\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0d\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.208\"},\n\t\t\tPublicIps:   []string{\"1.253.67.39\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:4b40:60d1:ed30:0b06\"},\n\t\t\tPrivateIps:  []string{\"10.196.6.194\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:5220:ac62:3c7c:7291\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east1\",\n\t\tInstanceName:        \"database\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      8192,\n\t\tInstanceProcessors:  4,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 54.62,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       54.37,\n\t\tInstanceLoad5:       57.22,\n\t\tInstanceLoad15:      63.01,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d0e\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"68b67d1aee12c08a1f39f88b\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b34f2\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0c\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0e\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.210\"},\n\t\t\tPublicIps:   []string{\"1.253.67.19\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:be8e:3013:d6f6:5396\"},\n\t\t\tPrivateIps:  []string{\"10.196.6.223\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:c924:41b8:22f3:5b3f\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east2\",\n\t\tInstanceName:        \"database\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      8192,\n\t\tInstanceProcessors:  4,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 43.89,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       46.21,\n\t\tInstanceLoad5:       53.63,\n\t\tInstanceLoad15:      62.25,\n\t},\n\t{\n\t\tId:            utils.ObjectIdHex(\"651d8e7c4cf91e3b53d62d0f\"),\n\t\tPod:           utils.ObjectIdHex(\"688bf358d978631566998ffc\"),\n\t\tUnit:          utils.ObjectIdHex(\"68b67d1aee12c08a1f39f88b\"),\n\t\tSpec:          utils.ObjectIdHex(\"688c7cde9da165ffad4b34f2\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"689733b2a7a35eae0dbaea0d\"),\n\t\tInstance:      utils.ObjectIdHex(\"651d8e7c4cf9e2e3e4d56a0f\"),\n\t\tInstanceData: &deployment.InstanceData{\n\t\t\tHostIps:     []string{\"198.18.84.50\"},\n\t\t\tPublicIps:   []string{\"1.253.67.60\"},\n\t\t\tPublicIps6:  []string{\"2001:db8:85a3:4d2f:fff2:877d:227c:1c4a\"},\n\t\t\tPrivateIps:  []string{\"10.196.7.165\"},\n\t\t\tPrivateIps6: []string{\"fd97:30bf:d456:a3bc:ae17:b804:32c5:956c\"},\n\t\t},\n\t\tJournals: []*deployment.Journal{\n\t\t\t{\n\t\t\t\tIndex: journal.DeploymentAgent,\n\t\t\t\tKey:   \"agent\",\n\t\t\t\tType:  \"agent\",\n\t\t\t},\n\t\t},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"pritunl-east3\",\n\t\tInstanceName:        \"database\",\n\t\tInstanceRoles:       []string{\"instance\"},\n\t\tInstanceMemory:      8192,\n\t\tInstanceProcessors:  4,\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: 41.89,\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       24.24,\n\t\tInstanceLoad5:       25.81,\n\t\tInstanceLoad15:      30.23,\n\t},\n}\n"
  },
  {
    "path": "demo/policy.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/policy\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Policies = []*policy.Policy{\n\t{\n\t\tId:       utils.ObjectIdHex(\"67b8a03e4866ba90e6c45a8c\"),\n\t\tName:     \"policy\",\n\t\tComment:  \"\",\n\t\tDisabled: false,\n\t\tRoles: []string{\n\t\t\t\"pritunl\",\n\t\t},\n\t\tRules: map[string]*policy.Rule{\n\t\t\t\"location\": {\n\t\t\t\tType:    \"location\",\n\t\t\t\tDisable: false,\n\t\t\t\tValues: []string{\n\t\t\t\t\t\"US\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"whitelist_networks\": {\n\t\t\t\tType:    \"whitelist_networks\",\n\t\t\t\tDisable: false,\n\t\t\t\tValues: []string{\n\t\t\t\t\t\"10.0.0.0/8\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tAdminSecondary:       bson.ObjectID{},\n\t\tUserSecondary:        bson.ObjectID{},\n\t\tAdminDeviceSecondary: true,\n\t\tUserDeviceSecondary:  true,\n\t},\n}\n"
  },
  {
    "path": "demo/pool.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Pools = []*pool.Pool{\n\t{\n\t\tId:               utils.ObjectIdHex(\"67b89e8d4866ba90e6c459ba\"),\n\t\tName:             \"cloud-east\",\n\t\tComment:          \"\",\n\t\tDeleteProtection: false,\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tZone:             utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tType:             \"\",\n\t\tVgName:           \"cloud_east\",\n\t},\n}\n"
  },
  {
    "path": "demo/rand.go",
    "content": "package demo\n\nimport (\n\t\"sync\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tlock            = sync.Mutex{}\n\tipStore         = map[bson.ObjectID]string{}\n\tip6Store        = map[bson.ObjectID]string{}\n\tprivateIpStore  = map[bson.ObjectID]string{}\n\tprivateIp6Store = map[bson.ObjectID]string{}\n)\n\nfunc RandIp(instId bson.ObjectID) (addr string) {\n\tlock.Lock()\n\tdefer lock.Unlock()\n\n\taddr = ipStore[instId]\n\tif addr == \"\" {\n\t\taddr = utils.RandIp()\n\t\tipStore[instId] = addr\n\t}\n\n\treturn\n}\n\nfunc RandIp6(instId bson.ObjectID) (addr string) {\n\tlock.Lock()\n\tdefer lock.Unlock()\n\n\taddr = ip6Store[instId]\n\tif addr == \"\" {\n\t\taddr = utils.RandIp6()\n\t\tip6Store[instId] = addr\n\t}\n\n\treturn\n}\n\nfunc RandPrivateIp(instId bson.ObjectID) (addr string) {\n\tlock.Lock()\n\tdefer lock.Unlock()\n\n\taddr = privateIpStore[instId]\n\tif addr == \"\" {\n\t\taddr = utils.RandPrivateIp()\n\t\tprivateIpStore[instId] = addr\n\t}\n\n\treturn\n}\n\nfunc RandPrivateIp6(instId bson.ObjectID) (addr string) {\n\tlock.Lock()\n\tdefer lock.Unlock()\n\n\taddr = privateIp6Store[instId]\n\tif addr == \"\" {\n\t\taddr = utils.RandPrivateIp6()\n\t\tprivateIp6Store[instId] = addr\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "demo/secret.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Secrets = []*secret.Secret{\n\t{\n\t\tId:           utils.ObjectIdHex(\"67b89e8d4866ba90e6c459ba\"),\n\t\tName:         \"cloudflare-pritunl-com\",\n\t\tComment:      \"\",\n\t\tOrganization: bson.ObjectID{},\n\t\tType:         \"cloudflare\",\n\t\tKey:          \"a7kX9mN2vP8Q-4jL6wS3tR5Y-uH1gF7dZ0xC-vB8nM\",\n\t\tValue:        \"\",\n\t\tRegion:       \"\",\n\t\tPublicKey: `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz4K8Lm3QvR7WxN5YdE2P\njX9TpQ6HgM1wV0nS4KaF3ZcB8LrY5UvO2JmN7XsPqI1AgK8EoH3RdWzM9LfY2VtN\nkP4QxGsJ7YnR8LwVmT3AqZ5HvK2NdP1XoS8JgR4LmW7YxQ3VnH5TsK9PpL2MdX8Rg\nvJ3KqN5WxT1LsM4HgY7RdP8NqV2JmK5XwL3TsR8YgN4HxP1LdK9VwQ2MsT3XpR7Y\nnL8KgJ5WdH3TmR9XsL2PqN7VxK4MgT3HdJ8YwP2LsK5RxT1NqM4JgY7PxR8WsL3T\nmK9XwN2HgJ5YdL3RsP8VqT2MxK4NhR3JdY8WwL2TsM5QxN1PqK4YgJ7RxP8VsT3M\nPwIDAQAB\n-----END PUBLIC KEY-----`,\n\t\tData: \"\",\n\t},\n}\n"
  },
  {
    "path": "demo/shape.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Shapes = []*shape.Shape{\n\t{\n\t\tId:               utils.ObjectIdHex(\"65e6e303ceeebbb3dabaec96\"),\n\t\tName:             \"m2-small\",\n\t\tComment:          \"\",\n\t\tType:             \"instance\",\n\t\tDeleteProtection: false,\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tRoles: []string{\n\t\t\t\"shape-m2\",\n\t\t},\n\t\tFlexible:   true,\n\t\tDiskType:   \"qcow2\",\n\t\tDiskPool:   bson.ObjectID{},\n\t\tMemory:     2048,\n\t\tProcessors: 1,\n\t\tNodeCount:  1,\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"65e6e2ecceeebbb3dabaec79\"),\n\t\tName:             \"m2-medium\",\n\t\tComment:          \"\",\n\t\tType:             \"instance\",\n\t\tDeleteProtection: false,\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tRoles: []string{\n\t\t\t\"shape-m2\",\n\t\t},\n\t\tFlexible:   true,\n\t\tDiskType:   \"qcow2\",\n\t\tDiskPool:   bson.ObjectID{},\n\t\tMemory:     4096,\n\t\tProcessors: 2,\n\t\tNodeCount:  1,\n\t},\n\t{\n\t\tId:               utils.ObjectIdHex(\"66f63282aac06d53e8c9c435\"),\n\t\tName:             \"m2-large\",\n\t\tComment:          \"\",\n\t\tType:             \"instance\",\n\t\tDeleteProtection: false,\n\t\tDatacenter:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tRoles: []string{\n\t\t\t\"shape-m2\",\n\t\t},\n\t\tFlexible:   true,\n\t\tDiskType:   \"qcow2\",\n\t\tDiskPool:   bson.ObjectID{},\n\t\tMemory:     8192,\n\t\tProcessors: 4,\n\t\tNodeCount:  1,\n\t},\n}\n"
  },
  {
    "path": "demo/storage.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Storages = []*storage.Storage{\n\t{\n\t\tId:        utils.ObjectIdHex(\"689733b7a7a35eae0dbaea15\"),\n\t\tName:      \"pritunl-images\",\n\t\tComment:   \"\",\n\t\tType:      \"public\",\n\t\tEndpoint:  \"images.pritunl.com\",\n\t\tBucket:    \"stable\",\n\t\tAccessKey: \"\",\n\t\tSecretKey: \"\",\n\t\tInsecure:  false,\n\t},\n\t{\n\t\tId:        utils.ObjectIdHex(\"689733b7a7a35eae0dbaea16\"),\n\t\tName:      \"pritunl-storage\",\n\t\tComment:   \"\",\n\t\tType:      \"private\",\n\t\tEndpoint:  \"s3.amazonaws.com\",\n\t\tBucket:    \"pritunl-cloud-2943\",\n\t\tAccessKey: \"AKIAJTVJ15RORHDU7M1M\",\n\t\tSecretKey: \"VLBGHOVTKDP5SIRSEC8R4XFQWLCIYN4HK\",\n\t\tInsecure:  false,\n\t},\n}\n"
  },
  {
    "path": "demo/subscription.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/subscription\"\n)\n\nvar Subscription = &subscription.Subscription{\n\tActive:            true,\n\tStatus:            \"active\",\n\tPlan:              \"cloud\",\n\tQuantity:          1,\n\tAmount:            5000,\n\tPeriodEnd:         time.Unix(1893499200, 0),\n\tTrialEnd:          time.Time{},\n\tCancelAtPeriodEnd: false,\n\tBalance:           0,\n\tUrlKey:            \"demo\",\n}\n"
  },
  {
    "path": "demo/user.go",
    "content": "package demo\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/session\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/useragent\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Users = []*user.User{\n\t&user.User{\n\t\tId:            utils.ObjectIdHex(\"5b6cd11857e4a9a88cbf072e\"),\n\t\tType:          \"local\",\n\t\tProvider:      bson.ObjectID{},\n\t\tUsername:      \"demo\",\n\t\tToken:         \"\",\n\t\tSecret:        \"\",\n\t\tLastActive:    time.Now(),\n\t\tLastSync:      time.Now(),\n\t\tRoles:         []string{\"demo\"},\n\t\tAdministrator: \"super\",\n\t\tDisabled:      false,\n\t\tActiveUntil:   time.Time{},\n\t\tPermissions:   []string{},\n\t},\n\t&user.User{\n\t\tId:            utils.ObjectIdHex(\"5a7542190accad1a8a53b568\"),\n\t\tType:          \"local\",\n\t\tProvider:      bson.ObjectID{},\n\t\tUsername:      \"pritunl\",\n\t\tToken:         \"\",\n\t\tSecret:        \"\",\n\t\tLastActive:    time.Time{},\n\t\tLastSync:      time.Time{},\n\t\tRoles:         []string{},\n\t\tAdministrator: \"super\",\n\t\tDisabled:      false,\n\t\tActiveUntil:   time.Time{},\n\t\tPermissions:   []string{},\n\t},\n}\n\nvar Agent = &useragent.Agent{\n\tOperatingSystem: useragent.Linux,\n\tBrowser:         useragent.Chrome,\n\tIp:              \"8.8.8.8\",\n\tIsp:             \"Google\",\n\tContinent:       \"North America\",\n\tContinentCode:   \"NA\",\n\tCountry:         \"United States\",\n\tCountryCode:     \"US\",\n\tRegion:          \"Washington\",\n\tRegionCode:      \"WA\",\n\tCity:            \"Seattle\",\n\tLatitude:        47.611,\n\tLongitude:       -122.337,\n}\n\nvar Audits = []*audit.Audit{\n\t&audit.Audit{\n\t\tId:        utils.ObjectIdHex(\"5a17f9bf051a45ffacf2b352\"),\n\t\tTimestamp: time.Unix(1498018860, 0),\n\t\tType:      \"admin_login\",\n\t\tFields: audit.Fields{\n\t\t\t\"method\": \"local\",\n\t\t},\n\t\tAgent: Agent,\n\t},\n}\n\nvar Sessions = []*session.Session{\n\t&session.Session{\n\t\tId:         \"jhgRu4n3oY0iXRYmLb77Ql5jNs2o7uWM\",\n\t\tType:       session.User,\n\t\tTimestamp:  time.Unix(1498018860, 0),\n\t\tLastActive: time.Unix(1498018860, 0),\n\t\tRemoved:    false,\n\t\tAgent:      Agent,\n\t},\n}\n"
  },
  {
    "path": "demo/vpc.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\nvar Vpcs = []*vpc.Vpc{\n\t{\n\t\tId:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea23\"),\n\t\tName:     \"production\",\n\t\tComment:  \"\",\n\t\tVpcId:    2996,\n\t\tNetwork:  \"10.196.0.0/14\",\n\t\tNetwork6: \"fd97:30bf:d456:a3bc::/64\",\n\t\tSubnets: []*vpc.Subnet{\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"66a076d5fafc270786e93461\"),\n\t\t\t\tName:    \"primary\",\n\t\t\t\tNetwork: \"10.196.1.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"66a076d5fafc270786e93462\"),\n\t\t\t\tName:    \"management\",\n\t\t\t\tNetwork: \"10.196.2.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"66a076d5fafc270786e93463\"),\n\t\t\t\tName:    \"link\",\n\t\t\t\tNetwork: \"10.196.3.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"66a076d5fafc270786e93464\"),\n\t\t\t\tName:    \"database\",\n\t\t\t\tNetwork: \"10.196.4.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"66a076d5fafc270786e93465\"),\n\t\t\t\tName:    \"web\",\n\t\t\t\tNetwork: \"10.196.5.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"66a076d5fafc270786e93466\"),\n\t\t\t\tName:    \"search\",\n\t\t\t\tNetwork: \"10.196.6.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"66a076d5fafc270786e93467\"),\n\t\t\t\tName:    \"vpn\",\n\t\t\t\tNetwork: \"10.196.7.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"66a076d5fafc270786e93468\"),\n\t\t\t\tName:    \"balancer\",\n\t\t\t\tNetwork: \"10.196.8.0/24\",\n\t\t\t},\n\t\t},\n\t\tOrganization:  utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:    utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tIcmpRedirects: false,\n\t\tRoutes: []*vpc.Route{\n\t\t\t&vpc.Route{\n\t\t\t\tDestination: \"10.24.0.0/16\",\n\t\t\t\tTarget:      \"10.196.7.2\",\n\t\t\t},\n\t\t},\n\t\tMaps:             []*vpc.Map{},\n\t\tArps:             []*vpc.Arp{},\n\t\tDeleteProtection: false,\n\t},\n\t{\n\t\tId:       utils.ObjectIdHex(\"689733b7a7a35eae0dbaea24\"),\n\t\tName:     \"testing\",\n\t\tComment:  \"\",\n\t\tVpcId:    2732,\n\t\tNetwork:  \"10.224.0.0/14\",\n\t\tNetwork6: \"fd97:30bf:d456:a3bc::/64\",\n\t\tSubnets: []*vpc.Subnet{\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"689733b7a7a35eae0dbaea61\"),\n\t\t\t\tName:    \"primary\",\n\t\t\t\tNetwork: \"10.224.1.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"689733b7a7a35eae0dbaea62\"),\n\t\t\t\tName:    \"management\",\n\t\t\t\tNetwork: \"10.224.2.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"689733b7a7a35eae0dbaea63\"),\n\t\t\t\tName:    \"link\",\n\t\t\t\tNetwork: \"10.224.3.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"689733b7a7a35eae0dbaea64\"),\n\t\t\t\tName:    \"database\",\n\t\t\t\tNetwork: \"10.224.4.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"689733b7a7a35eae0dbaea65\"),\n\t\t\t\tName:    \"web\",\n\t\t\t\tNetwork: \"10.224.5.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"689733b7a7a35eae0dbaea66\"),\n\t\t\t\tName:    \"search\",\n\t\t\t\tNetwork: \"10.224.6.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"689733b7a7a35eae0dbaea67\"),\n\t\t\t\tName:    \"vpn\",\n\t\t\t\tNetwork: \"10.224.7.0/24\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tId:      utils.ObjectIdHex(\"689733b7a7a35eae0dbaea68\"),\n\t\t\t\tName:    \"balancer\",\n\t\t\t\tNetwork: \"10.224.8.0/24\",\n\t\t\t},\n\t\t},\n\t\tOrganization:  utils.ObjectIdHex(\"5a3245a50accad1a8a53bc82\"),\n\t\tDatacenter:    utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tIcmpRedirects: false,\n\t\tRoutes: []*vpc.Route{\n\t\t\t&vpc.Route{\n\t\t\t\tDestination: \"10.36.0.0/16\",\n\t\t\t\tTarget:      \"10.224.7.2\",\n\t\t\t},\n\t\t},\n\t\tMaps:             []*vpc.Map{},\n\t\tArps:             []*vpc.Arp{},\n\t\tDeleteProtection: false,\n\t},\n}\n"
  },
  {
    "path": "demo/zone.go",
    "content": "package demo\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\nvar Zones = []*zone.Zone{\n\t{\n\t\tId:          utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1e\"),\n\t\tDatacenter:  utils.ObjectIdHex(\"689733b7a7a35eae0dbaea1b\"),\n\t\tName:        \"us-west-1a\",\n\t\tComment:     \"\",\n\t\tDnsServers:  []string{},\n\t\tDnsServers6: []string{},\n\t},\n}\n"
  },
  {
    "path": "deploy/deploy.go",
    "content": "package deploy\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n)\n\nfunc Deploy(stat *state.State, runtimes *state.Runtimes) (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tstart := time.Now()\n\tnetwork := NewNetwork(stat)\n\terr = network.Deploy()\n\tif err != nil {\n\t\treturn\n\t}\n\truntimes.Network = time.Since(start)\n\n\tstart = time.Now()\n\tipset := NewIpset(stat)\n\terr = ipset.Deploy()\n\tif err != nil {\n\t\treturn\n\t}\n\truntimes.Ipset = time.Since(start)\n\n\tstart = time.Now()\n\tiptables := NewIptables(stat)\n\terr = iptables.Deploy()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = ipset.Clean()\n\tif err != nil {\n\t\treturn\n\t}\n\truntimes.Iptables = time.Since(start)\n\n\tstart = time.Now()\n\tdisks := NewDisks(stat)\n\terr = disks.Deploy(db)\n\tif err != nil {\n\t\treturn\n\t}\n\truntimes.Disks = time.Since(start)\n\n\tstart = time.Now()\n\tinstances := NewInstances(stat)\n\terr = instances.Deploy(db)\n\tif err != nil {\n\t\treturn\n\t}\n\truntimes.Instances = time.Since(start)\n\n\tstart = time.Now()\n\tnamespaces := NewNamespace(stat)\n\terr = namespaces.Deploy(db)\n\tif err != nil {\n\t\treturn\n\t}\n\truntimes.Namespaces = time.Since(start)\n\n\tstart = time.Now()\n\tpods := NewPods(stat)\n\terr = pods.Deploy(db)\n\tif err != nil {\n\t\treturn\n\t}\n\truntimes.Pods = time.Since(start)\n\n\tstart = time.Now()\n\tdeployments := NewDeployments(stat)\n\terr = deployments.Deploy(db)\n\tif err != nil {\n\t\treturn\n\t}\n\truntimes.Deployments = time.Since(start)\n\n\tstart = time.Now()\n\timds := NewImds(stat)\n\terr = imds.Deploy(db)\n\tif err != nil {\n\t\treturn\n\t}\n\truntimes.Imds = time.Since(start)\n\n\tstart = time.Now()\n\tstat.Wait()\n\truntimes.Wait = time.Since(start)\n\n\treturn\n}\n"
  },
  {
    "path": "deploy/deployments.go",
    "content": "package deploy\n\nimport (\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/imds\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tdeploymentsLock = utils.NewMultiTimeoutLock(5 * time.Minute)\n)\n\ntype Deployments struct {\n\tstat *state.State\n}\n\nfunc (d *Deployments) migrate(deply *deployment.Deployment) {\n\tnde := d.stat.Node()\n\tnodeId := d.stat.Node().Id\n\n\tacquired, lockId := deploymentsLock.LockOpenTimeout(\n\t\tdeply.Id.Hex(), 3*time.Minute)\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tdeploymentsLock.Unlock(deply.Id.Hex(), lockId)\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif deply.Node != nodeId {\n\t\t\treturn\n\t\t}\n\n\t\tinst, err := instance.Get(db, deply.Instance)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\"new_spec_id\":   deply.NewSpec.Hex(),\n\t\t\t\t\"error\":         err,\n\t\t\t}).Error(\"deploy: Failed to get instance\")\n\t\t\treturn\n\t\t}\n\n\t\tinst.PreCommit()\n\n\t\tcurSpec, err := spec.Get(db, deply.Spec)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\"new_spec_id\":   deply.NewSpec.Hex(),\n\t\t\t\t\"error\":         err,\n\t\t\t}).Error(\"deploy: Failed to get current spec\")\n\t\t\treturn\n\t\t}\n\n\t\tnewSpec, err := spec.Get(db, deply.NewSpec)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t\t\t\"error\":         err,\n\t\t\t}).Error(\"deploy: Failed to get new spec\")\n\t\t\treturn\n\t\t}\n\n\t\terrData, err := curSpec.CanMigrate(db, deply, newSpec)\n\t\tif err != nil || errData != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t\t\t\"error\":         err,\n\t\t\t\t\"error_data\":    errData,\n\t\t\t}).Error(\"deploy: Incompatible migrate\")\n\n\t\t\tdeply.State = deployment.Deployed\n\t\t\tdeply.NewSpec = bson.NilObjectID\n\t\t\terr = deply.CommitFields(db, set.NewSet(\"state\", \"new_spec\"))\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t\t\t\t\"error\":         err,\n\t\t\t\t}).Error(\"deploy: Failed to commit deployment\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\tinstFields := set.NewSet()\n\n\t\tif curSpec.Instance.Uefi != newSpec.Instance.Uefi {\n\t\t\tif newSpec.Instance.Uefi != nil {\n\t\t\t\tinstFields.Add(\"uefi\")\n\t\t\t\tinst.Uefi = *newSpec.Instance.Uefi\n\t\t\t}\n\t\t}\n\t\tif curSpec.Instance.SecureBoot != newSpec.Instance.SecureBoot {\n\t\t\tif newSpec.Instance.SecureBoot != nil {\n\t\t\t\tinstFields.Add(\"secure_boot\")\n\t\t\t\tinst.SecureBoot = *newSpec.Instance.SecureBoot\n\t\t\t}\n\t\t}\n\t\tif curSpec.Instance.CloudType != newSpec.Instance.CloudType {\n\t\t\tif newSpec.Instance.CloudType != \"\" {\n\t\t\t\tinstFields.Add(\"cloud_type\")\n\t\t\t\tinst.CloudType = newSpec.Instance.CloudType\n\t\t\t}\n\t\t}\n\t\tif curSpec.Instance.Tpm != newSpec.Instance.Tpm {\n\t\t\tinstFields.Add(\"tpm\")\n\t\t\tinst.Tpm = newSpec.Instance.Tpm\n\t\t}\n\t\tif curSpec.Instance.Vnc != newSpec.Instance.Vnc {\n\t\t\tinstFields.Add(\"vnc\")\n\t\t\tinst.Vnc = newSpec.Instance.Vnc\n\t\t}\n\t\tif curSpec.Instance.DeleteProtection !=\n\t\t\tnewSpec.Instance.DeleteProtection {\n\n\t\t\tinstFields.Add(\"delete_protection\")\n\t\t\tinst.DeleteProtection = newSpec.Instance.DeleteProtection\n\t\t}\n\t\tif curSpec.Instance.SkipSourceDestCheck !=\n\t\t\tnewSpec.Instance.SkipSourceDestCheck {\n\n\t\t\tinstFields.Add(\"skip_source_dest_check\")\n\t\t\tinst.SkipSourceDestCheck = newSpec.Instance.SkipSourceDestCheck\n\t\t}\n\t\tif curSpec.Instance.Gui != newSpec.Instance.Gui {\n\t\t\tinstFields.Add(\"gui\")\n\t\t\tinst.Gui = newSpec.Instance.Gui\n\t\t}\n\t\tif curSpec.Instance.HostAddress != newSpec.Instance.HostAddress {\n\t\t\tif newSpec.Instance.HostAddress != nil {\n\t\t\t\tinstFields.Add(\"no_host_address\")\n\t\t\t\tinst.NoHostAddress = !*newSpec.Instance.HostAddress\n\t\t\t}\n\t\t}\n\t\tif curSpec.Instance.PublicAddress != newSpec.Instance.PublicAddress {\n\t\t\tif newSpec.Instance.PublicAddress != nil {\n\t\t\t\tinstFields.Add(\"no_public_address\")\n\t\t\t\tinst.NoPublicAddress = !*newSpec.Instance.PublicAddress\n\t\t\t} else {\n\t\t\t\tinstFields.Add(\"no_public_address\")\n\t\t\t\tinst.NoPublicAddress = nde.DefaultNoPublicAddress\n\t\t\t}\n\t\t}\n\t\tif curSpec.Instance.PublicAddress6 != newSpec.Instance.PublicAddress6 {\n\t\t\tif newSpec.Instance.PublicAddress6 != nil {\n\t\t\t\tinstFields.Add(\"no_public_address6\")\n\t\t\t\tinst.NoPublicAddress6 = !*newSpec.Instance.PublicAddress6\n\t\t\t} else {\n\t\t\t\tinstFields.Add(\"no_public_address6\")\n\t\t\t\tinst.NoPublicAddress6 = nde.DefaultNoPublicAddress6\n\t\t\t}\n\t\t}\n\t\tif curSpec.Instance.DhcpServer != newSpec.Instance.DhcpServer {\n\t\t\tinstFields.Add(\"dhcp_server\")\n\t\t\tinst.DhcpServer = newSpec.Instance.DhcpServer\n\t\t}\n\n\t\tif curSpec.Instance.Shape != newSpec.Instance.Shape ||\n\t\t\tcurSpec.Instance.Processors != newSpec.Instance.Processors ||\n\t\t\tcurSpec.Instance.Memory != newSpec.Instance.Memory {\n\n\t\t\tif !newSpec.Instance.Shape.IsZero() {\n\t\t\t\tshpe, e := shape.Get(db, newSpec.Instance.Shape)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t\t\t\t\t\"error\":         err,\n\t\t\t\t\t}).Error(\"deploy: Failed to get spec shape\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif inst != nil {\n\t\t\t\t\tinst.Processors = shpe.Processors\n\t\t\t\t\tinstFields.Add(\"processors\")\n\t\t\t\t\tinst.Memory = shpe.Memory\n\t\t\t\t\tinstFields.Add(\"memory\")\n\n\t\t\t\t\tif shpe.Flexible {\n\t\t\t\t\t\tif newSpec.Instance.Processors != 0 {\n\t\t\t\t\t\t\tinst.Processors = newSpec.Instance.Processors\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif newSpec.Instance.Memory != 0 {\n\t\t\t\t\t\t\tinst.Memory = newSpec.Instance.Memory\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if inst != nil {\n\t\t\t\tinst.Processors = newSpec.Instance.Processors\n\t\t\t\tinstFields.Add(\"processors\")\n\t\t\t\tinst.Memory = newSpec.Instance.Memory\n\t\t\t\tinstFields.Add(\"memory\")\n\t\t\t}\n\t\t}\n\n\t\tif !utils.CompareStringSlicesUnsorted(curSpec.Instance.Roles,\n\t\t\tnewSpec.Instance.Roles) {\n\n\t\t\tinstFields.Add(\"roles\")\n\t\t\tinst.Roles = newSpec.Instance.Roles\n\t\t}\n\n\t\tif curSpec.Instance.DiffNodePorts(newSpec.Instance.NodePorts) {\n\t\t\tinstFields.Add(\"node_ports\")\n\n\t\t\tnewNodePorts := []*nodeport.Mapping{}\n\t\t\tfor _, ndePort := range newSpec.Instance.NodePorts {\n\t\t\t\tnewNodePorts = append(newNodePorts, &nodeport.Mapping{\n\t\t\t\t\tProtocol:     ndePort.Protocol,\n\t\t\t\t\tExternalPort: ndePort.ExternalPort,\n\t\t\t\t\tInternalPort: ndePort.InternalPort,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tinst.UpsertNodePorts(newNodePorts)\n\t\t}\n\n\t\tinst.Mounts = []*instance.Mount{}\n\t\tfor _, mount := range newSpec.Instance.Mounts {\n\t\t\tif mount.Type != spec.HostPath {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tinst.Mounts = append(inst.Mounts, &instance.Mount{\n\t\t\t\tName:     mount.Name,\n\t\t\t\tType:     instance.HostPath,\n\t\t\t\tPath:     mount.Path,\n\t\t\t\tHostPath: mount.HostPath,\n\t\t\t})\n\t\t}\n\t\tinstFields.Add(\"mounts\")\n\n\t\tif instFields.Len() > 0 {\n\t\t\terrData, err = inst.Validate(db)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t\t\t\t\"error\":         err,\n\t\t\t\t\t\"error_data\":    errData,\n\t\t\t\t}).Error(\"deploy: Migrate failed, invalid instance options\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdskChange, err := inst.PostCommit(db)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t\t\t\t\"error\":         err,\n\t\t\t\t\t\"error_data\":    errData,\n\t\t\t\t}).Error(\"deploy: Migrate failed, instance post commit error\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = inst.CommitFields(db, instFields)\n\t\t\tif err != nil {\n\t\t\t\t_ = inst.Cleanup(db)\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t\t\t\t\"error\":         err,\n\t\t\t\t}).Error(\"deploy: Failed to migrate instance\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = inst.Cleanup(db)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t\t\t\t\"error\":         err,\n\t\t\t\t}).Error(\"deploy: Failed to cleanup instance\")\n\t\t\t\terr = nil\n\t\t\t}\n\n\t\t\tevent.PublishDispatch(db, \"instance.change\")\n\t\t\tif dskChange {\n\t\t\t\tevent.PublishDispatch(db, \"disk.change\")\n\t\t\t}\n\t\t}\n\n\t\tjrnls := []*deployment.Journal{}\n\t\tif newSpec.Journal != nil {\n\t\t\tfor _, input := range newSpec.Journal.Inputs {\n\t\t\t\tjrnls = append(jrnls, &deployment.Journal{\n\t\t\t\t\tIndex: input.Index,\n\t\t\t\t\tKey:   input.Key,\n\t\t\t\t\tType:  input.Type,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tdeply.Journals = jrnls\n\n\t\tdeply.Action = \"\"\n\t\tdeply.Spec = newSpec.Id\n\t\tdeply.NewSpec = bson.NilObjectID\n\t\terr = deply.CommitFields(db, set.NewSet(\n\t\t\t\"action\", \"spec\", \"new_spec\", \"journals\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t\t\t\"error\":         err,\n\t\t\t}).Error(\"deploy: Failed to commit deployment\")\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\"cur_spec_id\":   curSpec.Id.Hex(),\n\t\t\t\"new_spec_id\":   newSpec.Id.Hex(),\n\t\t}).Info(\"deploy: Migrated deployment\")\n\n\t\treturn\n\t}()\n}\n\nfunc (d *Deployments) destroy(deply *deployment.Deployment) {\n\tacquired, lockId := deploymentsLock.LockOpenTimeout(\n\t\tdeply.Id.Hex(), 3*time.Minute)\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tdeploymentsLock.Unlock(deply.Id.Hex(), lockId)\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif deply.Node != d.stat.Node().Id {\n\t\t\treturn\n\t\t}\n\n\t\tif deply.Kind == deployment.Image && !deply.Image.IsZero() {\n\t\t\timg, err := image.Get(db, deply.Image)\n\t\t\tif err != nil {\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\timg = nil\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\t\"error\":         err,\n\t\t\t\t\t}).Error(\"deploy: Failed to get image\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif img != nil {\n\t\t\t\terr = data.DeleteImage(db, img.Id)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\t\"error\":         err,\n\t\t\t\t\t}).Error(\"deploy: Failed to remove deployment image\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tevent.PublishDispatch(db, \"image.change\")\n\t\t\t}\n\t\t}\n\n\t\tinst := d.stat.GetInstace(deply.Instance)\n\t\tif inst != nil {\n\t\t\tif inst.DeleteProtection {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment\": deply.Id.Hex(),\n\t\t\t\t\t\"instance\":   inst.Id.Hex(),\n\t\t\t\t}).Warning(\"deploy: Cannot destroy deployment with \" +\n\t\t\t\t\t\"instance delete protection, archiving...\")\n\n\t\t\t\tdeply.Action = deployment.Archive\n\t\t\t\terr := deply.CommitFields(db, set.NewSet(\"action\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\t\"error\":         err,\n\t\t\t\t\t}).Error(\"deploy: Failed to commit deployment\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif inst.Action != instance.Destroy {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment\": deply.Id.Hex(),\n\t\t\t\t\t\"instance\":   inst.Id.Hex(),\n\t\t\t\t}).Info(\"deploy: Delete deployment instance\")\n\n\t\t\t\terr := instance.Delete(db, inst.Id)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif _, ok := err.(*database.NotFoundError); !ok {\n\t\t\t\t\t\terr = nil\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\t\t\"error\":         err,\n\t\t\t\t\t\t}).Error(\"deploy: Failed to delete instance\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\terr := deployment.Remove(db, deply.Id)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\"error\":         err,\n\t\t\t\t}).Error(\"deploy: Failed to remove deployment\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tevent.PublishDispatch(db, \"pod.change\")\n\t\t}\n\n\t\treturn\n\t}()\n}\n\nfunc (d *Deployments) archive(deply *deployment.Deployment) (err error) {\n\tinst := d.stat.GetInstace(deply.Instance)\n\tdisks := d.stat.GetDeploymentDisks(deply.Id)\n\tspc := d.stat.Spec(deply.Spec)\n\tnodeId := d.stat.Node().Id\n\n\tacquired, lockId := deploymentsLock.LockOpenTimeout(\n\t\tdeply.Id.Hex(), 3*time.Minute)\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tdeploymentsLock.Unlock(deply.Id.Hex(), lockId)\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif deply.Node != nodeId {\n\t\t\treturn\n\t\t}\n\n\t\tif !inst.IsActive() {\n\t\t\tif len(disks) > 0 {\n\t\t\t\tspecDisks := set.NewSet()\n\t\t\t\tfor _, mount := range spc.Instance.Mounts {\n\t\t\t\t\tif mount.Type != spec.Disk {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, dskId := range mount.Disks {\n\t\t\t\t\t\tspecDisks.Add(dskId)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor _, dsk := range disks {\n\t\t\t\t\tif !specDisks.Contains(dsk.Id) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\terr = dsk.Unreserve(db, inst.Id, deply.Id)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tevent.PublishDispatch(db, \"disk.change\")\n\t\t\t}\n\n\t\t\tdeply.State = deployment.Archived\n\t\t\tdeply.Action = \"\"\n\t\t\terr = deply.CommitFields(db, set.NewSet(\"state\", \"action\"))\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\"error\":         err,\n\t\t\t\t}).Error(\"deploy: Failed to commit deployment\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\tif inst.Action != instance.Stop {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t}).Info(\"deploy: Stopping instance for deployment archive\")\n\n\t\t\terr = instance.SetAction(db, inst.Id, instance.Stop)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"deploy: Failed to set instance state\")\n\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (d *Deployments) restore(deply *deployment.Deployment) (err error) {\n\tinst := d.stat.GetInstace(deply.Instance)\n\tinstDisks := d.stat.GetInstaceDisks(deply.Instance)\n\tspc := d.stat.Spec(deply.Spec)\n\tnodeId := d.stat.Node().Id\n\n\tacquired, lockId := deploymentsLock.LockOpenTimeout(\n\t\tdeply.Id.Hex(), 3*time.Minute)\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tdeploymentsLock.Unlock(deply.Id.Hex(), lockId)\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif deply.Node != nodeId {\n\t\t\treturn\n\t\t}\n\n\t\tif inst.IsActive() {\n\t\t\tdeply.State = deployment.Deployed\n\t\t\tdeply.Action = \"\"\n\t\t\terr = deply.CommitFields(db, set.NewSet(\"state\", \"action\"))\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\"error\":         err,\n\t\t\t\t}).Error(\"deploy: Failed to commit deployment\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\tindex := 0\n\t\tcurDisks := set.NewSet()\n\t\tfor _, dsk := range instDisks {\n\t\t\tdskIndex, _ := strconv.Atoi(dsk.Index)\n\t\t\tindex = max(index, dskIndex)\n\t\t\tcurDisks.Add(dsk.Id)\n\t\t}\n\n\t\tif inst.Action != instance.Start {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t}).Info(\"deploy: Starting instance for deployment restore\")\n\n\t\t\treservedDisks := []*disk.Disk{}\n\t\t\tdeplyMounts := []*deployment.Mount{}\n\n\t\t\tfor _, mount := range spc.Instance.Mounts {\n\t\t\t\tif mount.Type != spec.Disk {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tindex += 1\n\t\t\t\tdiskReserved := false\n\n\t\t\t\tfor _, dskId := range mount.Disks {\n\t\t\t\t\tif curDisks.Contains(dskId) {\n\t\t\t\t\t\tdiskReserved = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif !diskReserved {\n\t\t\t\t\tfor _, dskId := range mount.Disks {\n\t\t\t\t\t\tdsk, e := disk.Get(db, dskId)\n\t\t\t\t\t\tif e != nil {\n\t\t\t\t\t\t\terr = e\n\n\t\t\t\t\t\t\tfor _, dsk := range reservedDisks {\n\t\t\t\t\t\t\t\terr = dsk.Unreserve(db, inst.Id, deply.Id)\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif dsk.Node != node.Self.Id || !dsk.Instance.IsZero() {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdiskReserved, err = dsk.Reserve(\n\t\t\t\t\t\t\tdb, inst.Id, index, deply.Id)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tfor _, dsk := range reservedDisks {\n\t\t\t\t\t\t\t\terr = dsk.Unreserve(db, inst.Id, deply.Id)\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif !diskReserved {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdeplyMounts = append(deplyMounts, &deployment.Mount{\n\t\t\t\t\t\t\tDisk: dsk.Id,\n\t\t\t\t\t\t\tPath: mount.Path,\n\t\t\t\t\t\t\tUuid: dsk.Uuid,\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\treservedDisks = append(reservedDisks, dsk)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif !diskReserved {\n\t\t\t\t\tfor _, dsk := range reservedDisks {\n\t\t\t\t\t\terr = dsk.Unreserve(db, inst.Id, deply.Id)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"mount_path\": mount.Path,\n\t\t\t\t\t}).Error(\"deploy: Failed to reserve disk for mount\")\n\n\t\t\t\t\tdeply.State = deployment.Archived\n\t\t\t\t\tdeply.Action = \"\"\n\t\t\t\t\terr = deply.CommitFields(db, set.NewSet(\"state\", \"action\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\t\t\"error\":         err,\n\t\t\t\t\t\t}).Error(\"deploy: Failed to commit deployment\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(reservedDisks) > 0 {\n\t\t\t\tevent.PublishDispatch(db, \"disk.change\")\n\t\t\t}\n\n\t\t\terr = instance.SetAction(db, inst.Id, instance.Start)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"deploy: Failed to set instance state\")\n\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (d *Deployments) imageShutdown(db *database.Database,\n\tspc *spec.Spec, deply *deployment.Deployment, virt *vm.VirtualMachine) {\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"instance_id\": virt.Id.Hex(),\n\t}).Info(\"deploy: Stopping instance for deployment image\")\n\n\tjournals := types.NewJournals(spc)\n\n\ttime.Sleep(3 * time.Second)\n\n\terr := imds.Pull(db, virt.Id, virt.Deployment,\n\t\tvirt.ImdsHostSecret, journals)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\"error\":       err,\n\t\t}).Error(\"deploy: Failed to pull imds state for shutdown\")\n\t}\n\n\terr = instance.SetAction(db, virt.Id, instance.Stop)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\"error\":       err,\n\t\t}).Error(\"deploy: Failed to set instance state\")\n\n\t\treturn\n\t}\n\n\tif deply.GetImageState() == \"\" {\n\t\tdeply.SetImageState(deployment.Ready)\n\t\terr = deply.CommitFields(db, set.NewSet(\"image_data.state\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to commit deployment state\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n}\n\nfunc (d *Deployments) image(deply *deployment.Deployment) (err error) {\n\tacquired, lockId := deploymentsLock.LockOpenTimeout(\n\t\tdeply.Id.Hex(), 5*time.Minute)\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tdeploymentsLock.Unlock(deply.Id.Hex(), lockId)\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif deply.Node != d.stat.Node().Id {\n\t\t\treturn\n\t\t}\n\n\t\tinst := d.stat.GetInstace(deply.Instance)\n\t\tif inst == nil {\n\t\t\treturn\n\t\t}\n\n\t\tvirt := d.stat.GetVirt(inst.Id)\n\t\tspc := d.stat.Spec(deply.Spec)\n\n\t\tif inst.Guest == nil {\n\t\t\treturn\n\t\t}\n\n\t\tif inst.IsActive() && inst.Guest.Status == types.Imaged &&\n\t\t\tinst.Action != instance.Stop {\n\n\t\t\td.imageShutdown(db, spc, deply, virt)\n\t\t\treturn\n\t\t}\n\n\t\tif deply.State == deployment.Deployed &&\n\t\t\tdeply.GetImageState() == deployment.Ready &&\n\t\t\t!inst.IsActive() && inst.Guest.Status == types.Imaged {\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t}).Info(\"deploy: Creating deployment image\")\n\n\t\t\tdsk, e := disk.GetInstanceIndex(db, inst.Id, \"0\")\n\t\t\tif e != nil {\n\t\t\t\tif _, ok := e.(*database.NotFoundError); ok {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\t\"error\":       err,\n\t\t\t\t\t}).Error(\"deploy: Failed to find instance disk for image\")\n\n\t\t\t\t\tdeply.SetImageState(deployment.Failed)\n\t\t\t\t\terr = deply.CommitFields(db, set.NewSet(\"image_data.state\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tdsk = nil\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdsk.Action = disk.Snapshot\n\t\t\terr = dsk.CommitFields(db, set.NewSet(\"action\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdeply.SetImageState(deployment.Snapshot)\n\t\t\terr = deply.CommitFields(db, set.NewSet(\"image_data.state\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (d *Deployments) domainCommit(deply *deployment.Deployment,\n\tdomn *domain.Domain, newRecs []*domain.Record) {\n\n\tacquired, lockId := deploymentsLock.LockOpenTimeout(\n\t\tdeply.Id.Hex(), 3*time.Minute)\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tdeploymentsLock.Unlock(deply.Id.Hex(), lockId)\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\ttime.Sleep(500 * time.Millisecond)\n\n\t\tdeply, err := deployment.Get(db, deply.Id)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif deply.State != deployment.Deployed {\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"domain_id\": domn.Id.Hex(),\n\t\t}).Info(\"deploy: Committing domain records\")\n\n\t\trecs := []*deployment.RecordData{}\n\t\tfor _, rec := range newRecs {\n\t\t\trecs = append(recs, &deployment.RecordData{\n\t\t\t\tDomain: rec.SubDomain + \".\" + domn.RootDomain,\n\t\t\t\tValue:  rec.Value,\n\t\t\t})\n\t\t}\n\t\tdomnData := &deployment.DomainData{\n\t\t\tRecords: recs,\n\t\t}\n\n\t\terr = domn.CommitRecords(db)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"domain_id\": domn.Id.Hex(),\n\t\t\t\t\"error\":     err,\n\t\t\t}).Error(\"deploy: Failed to commit domain records\")\n\t\t\treturn\n\t\t}\n\n\t\tdeply.DomainData = domnData\n\t\terr = deply.CommitFields(db, set.NewSet(\"domain_data\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"domain.change\")\n\t\tevent.PublishDispatch(db, \"pod.change\")\n\n\t\ttime.Sleep(500 * time.Millisecond)\n\n\t\tnewDeply, err := deployment.Get(db, deply.Id)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\tnewDeply = nil\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif newDeply == nil || newDeply.State != deployment.Deployed {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"domain_id\": domn.Id.Hex(),\n\t\t\t}).Info(\"deploy: Undo domains commit for deactivated deployment\")\n\n\t\t\terr = deployment.RemoveDomains(db, deply.Id)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (d *Deployments) domain(db *database.Database,\n\tdeply *deployment.Deployment, spc *spec.Spec) (err error) {\n\n\tif spc.Domain != nil && deply.InstanceData != nil {\n\t\tnewRecs := map[bson.ObjectID][]*domain.Record{}\n\n\t\tfor _, specRec := range spc.Domain.Records {\n\t\t\tdomn := d.stat.SpecDomain(specRec.Domain)\n\t\t\tif domn == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch specRec.Type {\n\t\t\tcase spec.Public:\n\t\t\t\tfor _, val := range deply.InstanceData.PublicIps {\n\t\t\t\t\trec := &domain.Record{\n\t\t\t\t\t\tDomain:     specRec.Domain,\n\t\t\t\t\t\tSubDomain:  specRec.Name,\n\t\t\t\t\t\tDeployment: deply.Id,\n\t\t\t\t\t\tType:       domain.A,\n\t\t\t\t\t\tValue:      val,\n\t\t\t\t\t}\n\n\t\t\t\t\terrData, e := rec.Validate(db)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif errData != nil {\n\t\t\t\t\t\terr = errData.GetError()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tnewRecs[domn.Id] = append(newRecs[domn.Id], rec)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase spec.Public6:\n\t\t\t\tfor _, val := range deply.InstanceData.PublicIps6 {\n\t\t\t\t\trec := &domain.Record{\n\t\t\t\t\t\tDomain:     specRec.Domain,\n\t\t\t\t\t\tSubDomain:  specRec.Name,\n\t\t\t\t\t\tDeployment: deply.Id,\n\t\t\t\t\t\tType:       domain.AAAA,\n\t\t\t\t\t\tValue:      val,\n\t\t\t\t\t}\n\n\t\t\t\t\terrData, e := rec.Validate(db)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif errData != nil {\n\t\t\t\t\t\terr = errData.GetError()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tnewRecs[domn.Id] = append(newRecs[domn.Id], rec)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase spec.Host:\n\t\t\t\tfor _, val := range deply.InstanceData.HostIps {\n\t\t\t\t\trec := &domain.Record{\n\t\t\t\t\t\tDomain:     specRec.Domain,\n\t\t\t\t\t\tSubDomain:  specRec.Name,\n\t\t\t\t\t\tDeployment: deply.Id,\n\t\t\t\t\t\tType:       domain.A,\n\t\t\t\t\t\tValue:      val,\n\t\t\t\t\t}\n\n\t\t\t\t\terrData, e := rec.Validate(db)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif errData != nil {\n\t\t\t\t\t\terr = errData.GetError()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tnewRecs[domn.Id] = append(newRecs[domn.Id], rec)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase spec.Private:\n\t\t\t\tfor _, val := range deply.InstanceData.PrivateIps {\n\t\t\t\t\trec := &domain.Record{\n\t\t\t\t\t\tDomain:     specRec.Domain,\n\t\t\t\t\t\tSubDomain:  specRec.Name,\n\t\t\t\t\t\tDeployment: deply.Id,\n\t\t\t\t\t\tType:       domain.A,\n\t\t\t\t\t\tValue:      val,\n\t\t\t\t\t}\n\n\t\t\t\t\terrData, e := rec.Validate(db)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif errData != nil {\n\t\t\t\t\t\terr = errData.GetError()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tnewRecs[domn.Id] = append(newRecs[domn.Id], rec)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase spec.Private6:\n\t\t\t\tfor _, val := range deply.InstanceData.PrivateIps6 {\n\t\t\t\t\trec := &domain.Record{\n\t\t\t\t\t\tDomain:     specRec.Domain,\n\t\t\t\t\t\tSubDomain:  specRec.Name,\n\t\t\t\t\t\tDeployment: deply.Id,\n\t\t\t\t\t\tType:       domain.AAAA,\n\t\t\t\t\t\tValue:      val,\n\t\t\t\t\t}\n\n\t\t\t\t\terrData, e := rec.Validate(db)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif errData != nil {\n\t\t\t\t\t\terr = errData.GetError()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tnewRecs[domn.Id] = append(newRecs[domn.Id], rec)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase spec.CloudPublic:\n\t\t\t\tfor _, val := range deply.InstanceData.CloudPublicIps {\n\t\t\t\t\trec := &domain.Record{\n\t\t\t\t\t\tDomain:     specRec.Domain,\n\t\t\t\t\t\tSubDomain:  specRec.Name,\n\t\t\t\t\t\tDeployment: deply.Id,\n\t\t\t\t\t\tType:       domain.A,\n\t\t\t\t\t\tValue:      val,\n\t\t\t\t\t}\n\n\t\t\t\t\terrData, e := rec.Validate(db)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif errData != nil {\n\t\t\t\t\t\terr = errData.GetError()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tnewRecs[domn.Id] = append(newRecs[domn.Id], rec)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase spec.CloudPublic6:\n\t\t\t\tfor _, val := range deply.InstanceData.CloudPublicIps6 {\n\t\t\t\t\trec := &domain.Record{\n\t\t\t\t\t\tDomain:     specRec.Domain,\n\t\t\t\t\t\tSubDomain:  specRec.Name,\n\t\t\t\t\t\tDeployment: deply.Id,\n\t\t\t\t\t\tType:       domain.AAAA,\n\t\t\t\t\t\tValue:      val,\n\t\t\t\t\t}\n\n\t\t\t\t\terrData, e := rec.Validate(db)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif errData != nil {\n\t\t\t\t\t\terr = errData.GetError()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tnewRecs[domn.Id] = append(newRecs[domn.Id], rec)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase spec.CloudPrivate:\n\t\t\t\tfor _, val := range deply.InstanceData.CloudPrivateIps {\n\t\t\t\t\trec := &domain.Record{\n\t\t\t\t\t\tDomain:     specRec.Domain,\n\t\t\t\t\t\tSubDomain:  specRec.Name,\n\t\t\t\t\t\tDeployment: deply.Id,\n\t\t\t\t\t\tType:       domain.A,\n\t\t\t\t\t\tValue:      val,\n\t\t\t\t\t}\n\n\t\t\t\t\terrData, e := rec.Validate(db)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif errData != nil {\n\t\t\t\t\t\terr = errData.GetError()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tnewRecs[domn.Id] = append(newRecs[domn.Id], rec)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tfor domnId, domnNewRecs := range newRecs {\n\t\t\tdomn := d.stat.SpecDomain(domnId)\n\t\t\tif domn == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tchangedDomn := domn.MergeRecords(deply.Id, domnNewRecs)\n\t\t\tif changedDomn != nil {\n\t\t\t\td.domainCommit(deply, changedDomn, domnNewRecs)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (d *Deployments) Deploy(db *database.Database) (err error) {\n\tactiveDeployments := d.stat.DeploymentsDeployed()\n\tinactiveDeployments := d.stat.DeploymentsInactive()\n\n\tfor _, deply := range inactiveDeployments {\n\t\tswitch deply.Action {\n\t\tcase deployment.Migrate:\n\t\t\td.migrate(deply)\n\t\t\tbreak\n\t\tcase deployment.Destroy:\n\t\t\td.destroy(deply)\n\t\t\tbreak\n\t\tcase deployment.Archive:\n\t\t\terr = d.archive(deply)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase deployment.Restore:\n\t\t\terr = d.restore(deply)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\tfor _, deply := range activeDeployments {\n\t\tif deply.Action == deployment.Migrate {\n\t\t\td.migrate(deply)\n\t\t\tbreak\n\t\t}\n\n\t\tif deply.State == deployment.Deployed &&\n\t\t\tdeply.Kind == deployment.Instance {\n\n\t\t\tspec := d.stat.Spec(deply.Spec)\n\t\t\tif spec != nil && spec.Domain != nil {\n\t\t\t\terr = d.domain(db, deply, spec)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif deply.Kind == deployment.Image {\n\t\t\terr = d.image(deply)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc NewDeployments(stat *state.State) *Deployments {\n\treturn &Deployments{\n\t\tstat: stat,\n\t}\n}\n"
  },
  {
    "path": "deploy/disks.go",
    "content": "package deploy\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tdisksLock     = utils.NewMultiTimeoutLock(5 * time.Minute)\n\tbackupLimiter = utils.NewLimiter(3)\n)\n\ntype Disks struct {\n\tstat *state.State\n}\n\nfunc (d *Disks) provision(dsk *disk.Disk) {\n\tacquired, lockId := disksLock.LockOpen(dsk.Id.Hex())\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer disksLock.Unlock(dsk.Id.Hex(), lockId)\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif constants.Interrupt {\n\t\t\treturn\n\t\t}\n\n\t\tdsk, err := disk.Get(db, dsk.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.State != disk.Provision {\n\t\t\treturn\n\t\t}\n\n\t\tnewSize, backingImage, err := data.CreateDisk(db, dsk)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed to provision disk\")\n\t\t\treturn\n\t\t}\n\n\t\tfields := set.NewSet(\"state\", \"backing_image\")\n\n\t\tdsk.State = disk.Available\n\t\tdsk.BackingImage = backingImage\n\n\t\tif newSize != 0 {\n\t\t\tfields.Add(\"size\")\n\t\t\tdsk.Size = newSize\n\t\t}\n\n\t\terr = dsk.CommitFields(db, fields)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}()\n}\n\nfunc (d *Disks) snapshot(dsk *disk.Disk) {\n\tacquired, lockId := disksLock.LockOpen(dsk.Id.Hex())\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer disksLock.Unlock(dsk.Id.Hex(), lockId)\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif constants.Interrupt {\n\t\t\treturn\n\t\t}\n\n\t\tdsk, err := disk.Get(db, dsk.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.Action != disk.Snapshot {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.Type != disk.Qcow2 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\":   dsk.Id.Hex(),\n\t\t\t\t\"disk_type\": dsk.Type,\n\t\t\t}).Error(\"deploy: Disk type does not support snapshot\")\n\t\t} else {\n\t\t\tvirt := d.stat.GetVirt(dsk.Instance)\n\t\t\tif virt == nil {\n\t\t\t\terr := &errortypes.ReadError{\n\t\t\t\t\terrors.New(\"deploy: Failed to load virt\"),\n\t\t\t\t}\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t\t\t\"error\":   err,\n\t\t\t\t}).Error(\"deploy: Failed to load virt\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr := data.CreateSnapshot(db, dsk, virt)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"deploy: Failed to snapshot disk\")\n\t\t\t}\n\t\t}\n\n\t\tdsk.Action = \"\"\n\t\terr = dsk.CommitFields(db, set.NewSet(\"action\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\":   dsk.Id.Hex(),\n\t\t\t\t\"disk_type\": dsk.Type,\n\t\t\t\t\"error\":     err,\n\t\t\t}).Error(\"deploy: Failed update disk state\")\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}()\n}\n\nfunc (d *Disks) expand(dsk *disk.Disk) {\n\tacquired, lockId := disksLock.LockOpen(dsk.Id.Hex())\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer disksLock.Unlock(dsk.Id.Hex(), lockId)\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif constants.Interrupt {\n\t\t\treturn\n\t\t}\n\n\t\tdsk, err := disk.Get(db, dsk.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.Action != disk.Expand {\n\t\t\treturn\n\t\t}\n\n\t\tinst := d.stat.GetInstace(dsk.Instance)\n\t\tif inst != nil {\n\t\t\tif inst.Action != instance.Stop {\n\t\t\t\tinst.Action = instance.Stop\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"disk_id\":     dsk.Id.Hex(),\n\t\t\t\t}).Info(\"deploy: Stopping instance for resize\")\n\n\t\t\t\terr := inst.CommitFields(db, set.NewSet(\"action\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"error\": err,\n\t\t\t\t\t}).Error(\"deploy: Failed to commit instance state\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvirt := d.stat.GetVirt(inst.Id)\n\t\t\tif virt != nil && virt.State != vm.Stopped &&\n\t\t\t\tvirt.State != vm.Failed {\n\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr = data.ExpandDisk(db, dsk)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed to expand disk\")\n\t\t}\n\n\t\tdsk.Action = \"\"\n\t\tdsk.NewSize = 0\n\t\terr = dsk.CommitFields(db, set.NewSet(\"action\", \"size\", \"new_size\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed update disk state\")\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}()\n}\n\nfunc (d *Disks) backup(dsk *disk.Disk) {\n\tif !backupLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := disksLock.LockOpen(dsk.Id.Hex())\n\tif !acquired {\n\t\tbackupLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tdisksLock.Unlock(dsk.Id.Hex(), lockId)\n\t\t\tbackupLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif constants.Interrupt {\n\t\t\treturn\n\t\t}\n\n\t\tdsk, err := disk.Get(db, dsk.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.Action != disk.Backup {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.Type != disk.Qcow2 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\":   dsk.Id.Hex(),\n\t\t\t\t\"disk_type\": dsk.Type,\n\t\t\t}).Error(\"deploy: Disk type does not support backup\")\n\t\t} else {\n\t\t\tvirt := d.stat.GetVirt(dsk.Instance)\n\t\t\tif virt == nil {\n\t\t\t\terr := &errortypes.ReadError{\n\t\t\t\t\terrors.New(\"deploy: Failed to load virt\"),\n\t\t\t\t}\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t\t\t\"error\":   err,\n\t\t\t\t}).Error(\"deploy: Failed to load virt\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr := data.CreateBackup(db, dsk, virt)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"deploy: Failed to backup disk\")\n\t\t\t}\n\t\t}\n\n\t\tdsk.Action = \"\"\n\t\terr = dsk.CommitFields(db, set.NewSet(\"action\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed update disk state\")\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t\tevent.PublishDispatch(db, \"image.change\")\n\t}()\n}\n\nfunc (d *Disks) restore(dsk *disk.Disk) {\n\tif !backupLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := disksLock.LockOpen(dsk.Id.Hex())\n\tif !acquired {\n\t\tbackupLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tdisksLock.Unlock(dsk.Id.Hex(), lockId)\n\t\t\tbackupLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif constants.Interrupt {\n\t\t\treturn\n\t\t}\n\n\t\tdsk, err := disk.Get(db, dsk.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.Action != disk.Restore {\n\t\t\treturn\n\t\t}\n\n\t\tinst := d.stat.GetInstace(dsk.Instance)\n\t\tif inst != nil {\n\t\t\tif inst.Action != instance.Stop {\n\t\t\t\tinst.Action = instance.Stop\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"disk_id\":     dsk.Id.Hex(),\n\t\t\t\t}).Info(\"deploy: Stopping instance for restore\")\n\n\t\t\t\terr := inst.CommitFields(db, set.NewSet(\"state\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"error\": err,\n\t\t\t\t\t}).Error(\"deploy: Failed to commit instance state\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvirt := d.stat.GetVirt(inst.Id)\n\t\t\tif virt != nil && virt.State != vm.Stopped &&\n\t\t\t\tvirt.State != vm.Failed {\n\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif dsk.Type != disk.Qcow2 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\":   dsk.Id.Hex(),\n\t\t\t\t\"disk_type\": dsk.Type,\n\t\t\t}).Error(\"deploy: Disk type does not support restore\")\n\t\t} else {\n\t\t\terr := data.RestoreBackup(db, dsk)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t\t\t\"error\":   err,\n\t\t\t\t}).Error(\"deploy: Failed to restore disk\")\n\t\t\t}\n\t\t}\n\n\t\tdsk.Action = \"\"\n\t\terr = dsk.CommitFields(db, set.NewSet(\"action\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t\t\"error\":   err,\n\t\t\t}).Error(\"deploy: Failed update disk state\")\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}()\n}\n\nfunc (d *Disks) destroy(db *database.Database, dsk *disk.Disk) {\n\tvar inst *instance.Instance\n\tif !dsk.Instance.IsZero() {\n\t\tinst = d.stat.GetInstace(dsk.Instance)\n\t}\n\n\tif d.stat.DiskInUse(dsk.Instance, dsk.Id) {\n\t\treturn\n\t}\n\n\tacquired, lockId := disksLock.LockOpen(dsk.Id.Hex())\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer disksLock.Unlock(dsk.Id.Hex(), lockId)\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif constants.Interrupt {\n\t\t\treturn\n\t\t}\n\n\t\tdsk, err := disk.Get(db, dsk.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.Action != disk.Destroy {\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.DeleteProtection {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t}).Info(\"deploy: Delete protection ignore disk destroy\")\n\n\t\t\tdsk.Action = \"\"\n\t\t\t_ = dsk.CommitFields(db, set.NewSet(\"action\"))\n\n\t\t\tevent.PublishDispatch(db, \"disk.change\")\n\n\t\t\treturn\n\t\t}\n\n\t\tif inst != nil && inst.DeleteProtection {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t}).Info(\"deploy: Instance delete protection ignore disk destroy\")\n\n\t\t\tdsk.Action = \"\"\n\t\t\t_ = dsk.CommitFields(db, set.NewSet(\"action\"))\n\n\t\t\tevent.PublishDispatch(db, \"disk.change\")\n\n\t\t\treturn\n\t\t}\n\n\t\terr = dsk.Destroy(db)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed to destroy disk\")\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}()\n}\n\nfunc (d *Disks) scheduleBackup(dsk *disk.Disk) {\n\tif time.Since(dsk.LastBackup) < 24*time.Hour {\n\t\treturn\n\t}\n\n\tif !backupLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := disksLock.LockOpen(dsk.Id.Hex())\n\tif !acquired {\n\t\tbackupLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tdisksLock.Unlock(dsk.Id.Hex(), lockId)\n\t\t\tbackupLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif constants.Interrupt {\n\t\t\treturn\n\t\t}\n\n\t\tif !dsk.IsActive() || dsk.Action != \"\" {\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t}).Info(\"deploy: Scheduling automatic disk backup\")\n\n\t\tdsk.Action = disk.Backup\n\t\tdsk.LastBackup = time.Now()\n\t\terr := dsk.CommitFields(db, set.NewSet(\"action\", \"last_backup\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed update disk state\")\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\n\t\tvirt := d.stat.GetVirt(dsk.Instance)\n\t\tif virt == nil {\n\t\t\terr := &errortypes.ReadError{\n\t\t\t\terrors.New(\"deploy: Failed to load virt\"),\n\t\t\t}\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t\t\"error\":   err,\n\t\t\t}).Error(\"deploy: Failed to load virt\")\n\t\t\treturn\n\t\t}\n\n\t\terr = data.CreateBackup(db, dsk, virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed to backup disk\")\n\t\t}\n\n\t\tdsk.Action = \"\"\n\t\terr = dsk.CommitFields(db, set.NewSet(\"action\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed update disk state\")\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t\tevent.PublishDispatch(db, \"image.change\")\n\t}()\n}\n\nfunc (d *Disks) Deploy(db *database.Database) (err error) {\n\tdisks := d.stat.Disks()\n\n\tbackupHour := settings.System.DiskBackupTime\n\tbackupWindow := settings.System.DiskBackupWindow\n\tutcHour := time.Now().UTC().Hour()\n\tbackupActive := false\n\tif utcHour >= backupHour && utcHour <= (backupHour+backupWindow) {\n\t\tbackupActive = true\n\t}\n\n\tfor _, dsk := range disks {\n\t\tif dsk.State == disk.Provision {\n\t\t\td.provision(dsk)\n\t\t} else if dsk.IsActive() {\n\t\t\tswitch dsk.Action {\n\t\t\tcase disk.Snapshot:\n\t\t\t\td.snapshot(dsk)\n\t\t\t\tbreak\n\t\t\tcase disk.Backup:\n\t\t\t\td.backup(dsk)\n\t\t\t\tbreak\n\t\t\tcase disk.Restore:\n\t\t\t\td.restore(dsk)\n\t\t\t\tbreak\n\t\t\tcase disk.Expand:\n\t\t\t\td.expand(dsk)\n\t\t\t\tbreak\n\t\t\tcase disk.Destroy:\n\t\t\t\td.destroy(db, dsk)\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tif backupActive && dsk.Backup {\n\t\t\t\t\td.scheduleBackup(dsk)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc NewDisks(stat *state.State) *Disks {\n\treturn &Disks{\n\t\tstat: stat,\n\t}\n}\n"
  },
  {
    "path": "deploy/imds.go",
    "content": "package deploy\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/imds\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\nvar (\n\tHashes = map[bson.ObjectID]uint32{}\n)\n\ntype Imds struct {\n\tstat *state.State\n}\n\nfunc (s *Imds) buildInstance(db *database.Database,\n\tinst *instance.Instance, virt *vm.VirtualMachine) (\n\tconf *types.Config, err error) {\n\n\tvc := s.stat.Vpc(inst.Vpc)\n\n\tvar subnet *vpc.Subnet\n\tif vc != nil {\n\t\tsubnet = vc.GetSubnet(inst.Subnet)\n\t}\n\n\tconf, err = imds.BuildConfig(\n\t\tinst, virt, nil, nil,\n\t\tvc, subnet,\n\t\t[]*pod.Pod{},\n\t\tmap[bson.ObjectID][]*unit.Unit{},\n\t\tmap[bson.ObjectID]*deployment.Deployment{},\n\t\t[]*secret.Secret{},\n\t\t[]*certificate.Certificate{},\n\t\ts.stat.GetDomains(inst.Organization),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = conf.ComputeHash()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Imds) buildDeployInstance(db *database.Database,\n\tinst *instance.Instance, virt *vm.VirtualMachine) (\n\tconf *types.Config, err error) {\n\n\tvc := s.stat.Vpc(inst.Vpc)\n\n\tvar subnet *vpc.Subnet\n\tif vc != nil {\n\t\tsubnet = vc.GetSubnet(inst.Subnet)\n\t}\n\n\tdeply := s.stat.Deployment(virt.Deployment)\n\tif deply == nil {\n\t\tprintln(\"**************************************************1\")\n\t\tprintln(inst.Id.Hex())\n\t\tprintln(\"**************************************************1\")\n\t\treturn\n\t}\n\n\tspc := s.stat.Spec(deply.Spec)\n\tif spc == nil {\n\t\tprintln(\"**************************************************2\")\n\t\tprintln(inst.Id.Hex())\n\t\tprintln(\"**************************************************2\")\n\t\treturn\n\t}\n\n\tcerts := []*certificate.Certificate{}\n\tfor _, certId := range spc.Instance.Certificates {\n\t\tcert := s.stat.SpecCert(certId)\n\t\tif cert == nil || cert.Organization != inst.Organization {\n\t\t\tcontinue\n\t\t}\n\n\t\tcerts = append(certs, cert)\n\t}\n\n\tsecrs := []*secret.Secret{}\n\tfor _, secrId := range spc.Instance.Secrets {\n\t\tsecr := s.stat.SpecSecret(secrId)\n\t\tif secr == nil || secr.Organization != inst.Organization {\n\t\t\tcontinue\n\t\t}\n\n\t\tsecrs = append(secrs, secr)\n\t}\n\n\tpods := []*pod.Pod{}\n\tpodUnitsMap := map[bson.ObjectID][]*unit.Unit{}\n\n\tinstPd := s.stat.Pod(deply.Pod)\n\tif instPd != nil {\n\t\tpods = append(pods, instPd)\n\t}\n\n\tinstUnt := s.stat.Unit(deply.Unit)\n\tif instUnt != nil {\n\t\tpodUnitsMap[deply.Pod] = append(podUnitsMap[deply.Pod], instUnt)\n\t}\n\n\tfor _, podId := range spc.Instance.Pods {\n\t\tpd := s.stat.SpecPod(podId)\n\t\tif pd == nil || pd.Organization != inst.Organization {\n\t\t\tcontinue\n\t\t}\n\n\t\tpods = append(pods, pd)\n\n\t\tpodUnits := s.stat.SpecPodUnits(podId)\n\t\tif podUnits != nil {\n\t\t\tpodUnitsMap[podId] = podUnits\n\t\t}\n\t}\n\n\tconf, err = imds.BuildConfig(\n\t\tinst, virt, instUnt, spc,\n\t\tvc, subnet,\n\t\tpods,\n\t\tpodUnitsMap,\n\t\ts.stat.DeploymentsDeployed(),\n\t\tsecrs,\n\t\tcerts,\n\t\ts.stat.GetDomains(inst.Organization),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = conf.ComputeHash()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Imds) Deploy(db *database.Database) (err error) {\n\tinstances := s.stat.Instances()\n\n\tconfs := map[bson.ObjectID]*types.Config{}\n\tfor _, inst := range instances {\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tvirt := s.stat.GetVirt(inst.Id)\n\t\tif virt == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif virt.ImdsVersion < 1 {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar conf *types.Config\n\t\tif inst.Deployment.IsZero() {\n\t\t\tconf, err = s.buildInstance(db, inst, virt)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tconf, err = s.buildDeployInstance(db, inst, virt)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif conf != nil {\n\t\t\tconfs[inst.Id] = conf\n\t\t}\n\t}\n\n\timds.SetConfigs(confs)\n\n\treturn\n}\n\nfunc NewImds(stat *state.State) *Imds {\n\treturn &Imds{\n\t\tstat: stat,\n\t}\n}\n"
  },
  {
    "path": "deploy/instances.go",
    "content": "package deploy\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/arp\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/info\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/netconf\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/permission\"\n\t\"github.com/pritunl/pritunl-cloud/qemu\"\n\t\"github.com/pritunl/pritunl-cloud/qmp\"\n\t\"github.com/pritunl/pritunl-cloud/qms\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/store\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tinstancesLock = utils.NewMultiTimeoutLock(5 * time.Minute)\n\tcreateLimiter = utils.NewLimiter(3)\n\tstartLimiter  = utils.NewLimiter(3)\n\tstopLimiter   = utils.NewLimiter(3)\n\tdiskLimiter   = utils.NewLimiter(3)\n\tusbLimiter    = utils.NewLimiter(1)\n)\n\ntype Instances struct {\n\tstat *state.State\n}\n\nfunc (s *Instances) create(inst *instance.Instance) {\n\tif !createLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpenTimeout(\n\t\tinst.Id.Hex(), 10*time.Minute)\n\tif !acquired {\n\t\tcreateLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tcreateLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\terr := qemu.Create(db, inst, inst.Virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to create instance\")\n\n\t\t\terr = instance.SetAction(db, inst.Id, instance.Stop)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"deploy: Failed to set instance state\")\n\n\t\t\t\tqemu.PowerOff(db, inst.Virt)\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t}()\n}\n\nfunc (s *Instances) start(inst *instance.Instance) {\n\tif !startLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\tstartLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tstartLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif inst.Restart || inst.RestartBlockIp {\n\t\t\tinst.Restart = false\n\t\t\tinst.RestartBlockIp = false\n\t\t\terr := inst.CommitFields(db,\n\t\t\t\tset.NewSet(\"restart\", \"restart_block_ip\"))\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"deploy: Failed to commit instance\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr := qemu.PowerOn(db, inst, inst.Virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to start instance\")\n\n\t\t\terr = instance.SetAction(db, inst.Id, instance.Stop)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"deploy: Failed to set instance state\")\n\n\t\t\t\tqemu.PowerOff(db, inst.Virt)\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t}()\n}\n\nfunc (s *Instances) cleanup(inst *instance.Instance) {\n\tif !stopLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\tstopLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tstopLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tqemu.Cleanup(db, inst.Virt)\n\n\t\terr := instance.SetAction(db, inst.Id, instance.Stop)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to update instance\")\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t}()\n}\n\nfunc (s *Instances) stop(inst *instance.Instance) {\n\tif !stopLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\tstopLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tstopLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\terr := qemu.PowerOff(db, inst.Virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to stop instance\")\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t}()\n}\n\nfunc (s *Instances) restart(inst *instance.Instance) {\n\tif !stopLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\tstopLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tstopLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\terr := qemu.PowerOff(db, inst.Virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to restart instance\")\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\n\t\tif inst.Restart || inst.RestartBlockIp {\n\t\t\tinst.Restart = false\n\t\t\tinst.RestartBlockIp = false\n\t\t\terr = inst.CommitFields(db,\n\t\t\t\tset.NewSet(\"restart\", \"restart_block_ip\"))\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"deploy: Failed to commit instance\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr = qemu.PowerOn(db, inst, inst.Virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to restart instance\")\n\n\t\t\terr = instance.SetAction(db, inst.Id, instance.Stop)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"deploy: Failed to set instance state\")\n\n\t\t\t\tqemu.PowerOff(db, inst.Virt)\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\tinst.Action = instance.Start\n\t\terr = inst.CommitFields(db, set.NewSet(\"action\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to commit instance\")\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t}()\n}\n\nfunc (s *Instances) destroy(inst *instance.Instance) {\n\tif !stopLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\tstopLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tstopLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\t_, err := instance.Get(db, inst.Id)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = qemu.Destroy(db, inst.Virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to power off instance\")\n\t\t\treturn\n\t\t}\n\n\t\terr = netconf.Destroy(db, inst.Virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to destroy netconf\")\n\t\t\treturn\n\t\t}\n\n\t\terr = instance.Remove(db, inst.Id)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"deploy: Failed to remove instance\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}()\n}\n\nfunc (s *Instances) diskAdd(inst *instance.Instance,\n\tvirt *vm.VirtualMachine, addDisks vm.SortDisks) {\n\n\tif !diskLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\tdiskLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tdiskLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tsort.Sort(addDisks)\n\n\t\tfor _, dsk := range addDisks {\n\t\t\terr := permission.InitDisk(virt, dsk)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = qmp.AddDisk(inst.Id, dsk)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"disk_id\":     dsk.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"sync: Failed to add disk\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\ttime.Sleep(200 * time.Millisecond)\n\n\t\terr := qemu.UpdateVmDisk(virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"sync: Failed to update vm disk state\")\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}()\n}\n\nfunc (s *Instances) diskRemove(inst *instance.Instance,\n\tvirt *vm.VirtualMachine, remDisks vm.SortDisks) {\n\n\tif !diskLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\tdiskLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tdiskLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tsort.Sort(sort.Reverse(remDisks))\n\n\t\tfor _, dsk := range remDisks {\n\t\t\te := qmp.RemoveDisk(inst.Id, dsk)\n\t\t\tif e != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"disk_id\":     dsk.Id.Hex(),\n\t\t\t\t\t\"error\":       e,\n\t\t\t\t}).Error(\"sync: Failed to remove disk\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\ttime.Sleep(200 * time.Millisecond)\n\n\t\terr := qemu.UpdateVmDisk(virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"sync: Failed to update vm disk state\")\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}()\n}\n\nfunc (s *Instances) usbAdd(inst *instance.Instance, virt *vm.VirtualMachine,\n\taddUsbs []*vm.UsbDevice) {\n\n\tif !usbLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\tusbLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tusbLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tfor _, device := range addUsbs {\n\t\t\te := qms.AddUsb(virt, device)\n\t\t\tif e != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"usb_address\": device.Address,\n\t\t\t\t\t\"usb_bus\":     device.Bus,\n\t\t\t\t\t\"usb_product\": device.Product,\n\t\t\t\t\t\"usb_vendor\":  device.Vendor,\n\t\t\t\t\t\"error\":       e,\n\t\t\t\t}).Error(\"sync: Failed to add usb\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\ttime.Sleep(200 * time.Millisecond)\n\n\t\terr := qemu.UpdateVmUsb(virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"sync: Failed to update vm usb state\")\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t}()\n}\n\nfunc (s *Instances) usbRemove(inst *instance.Instance,\n\tvirt *vm.VirtualMachine, remUsbs []*vm.UsbDevice) {\n\n\tif !usbLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\tusbLimiter.Release()\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t\tusbLimiter.Release()\n\t\t}()\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tfor _, device := range remUsbs {\n\t\t\terr := qms.RemoveUsb(virt, device)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"usb_address\": device.Address,\n\t\t\t\t\t\"usb_bus\":     device.Bus,\n\t\t\t\t\t\"usb_product\": device.Product,\n\t\t\t\t\t\"usb_vendor\":  device.Vendor,\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"sync: Failed to remove usb\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\ttime.Sleep(200 * time.Millisecond)\n\n\t\terr := qemu.UpdateVmUsb(virt)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"sync: Failed to update vm usb state\")\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\t}()\n}\n\nfunc (s *Instances) diff(db *database.Database,\n\tinst *instance.Instance) (err error) {\n\n\tcurVirt := s.stat.GetVirt(inst.Id)\n\tif curVirt == nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.New(\"deploy: Failed to load virt\"),\n\t\t}\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\"error\":       err,\n\t\t}).Error(\"deploy: Failed to load virt\")\n\t\terr = nil\n\t\treturn\n\t}\n\n\tchanged, reason := inst.Changed(curVirt)\n\taddDisks, remDisks := inst.DiskChanged(curVirt)\n\taddUsbs, remUsbs := inst.UsbChanged(curVirt)\n\n\tif instancesLock.Locked(inst.Id.Hex()) {\n\t\treturn\n\t}\n\n\tif changed && !inst.Restart {\n\t\tinst.Restart = true\n\t\tinst.RestartReason = reason\n\t\terr = inst.CommitFields(db, set.NewSet(\"restart\", \"restart_reason\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else if !changed && inst.Restart {\n\t\tinst.Restart = false\n\t\tinst.RestartReason = \"\"\n\t\terr = inst.CommitFields(db, set.NewSet(\"restart\", \"restart_reason\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif len(remDisks) > 0 {\n\t\ts.diskRemove(inst, curVirt, remDisks)\n\t}\n\n\tif len(addDisks) > 0 {\n\t\ts.diskAdd(inst, curVirt, addDisks)\n\t}\n\n\tif len(remUsbs) > 0 {\n\t\ts.usbRemove(inst, curVirt, remUsbs)\n\t}\n\n\tif len(addUsbs) > 0 {\n\t\ts.usbAdd(inst, curVirt, addUsbs)\n\t}\n\n\treturn\n}\n\nfunc (s *Instances) check(inst *instance.Instance, namespaces set.Set) (\n\tvalid bool, err error) {\n\n\tnamespace := vm.GetNamespace(inst.Id, 0)\n\tif !namespaces.Contains(namespace) {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\":   inst.Id.Hex(),\n\t\t\t\"net_namespace\": namespace,\n\t\t}).Error(\"deploy: Instance missing namespace\")\n\t\treturn\n\t}\n\n\tvalid = true\n\n\treturn\n}\n\nfunc (s *Instances) routes(inst *instance.Instance) (err error) {\n\tacquired, lockId := instancesLock.LockOpen(inst.Id.Hex())\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\tdefer func() {\n\t\t\tinstancesLock.Unlock(inst.Id.Hex(), lockId)\n\t\t}()\n\n\t\tnamespace := vm.GetNamespace(inst.Id, 0)\n\n\t\tvc := s.stat.Vpc(inst.Vpc)\n\t\tif vc == nil {\n\t\t\terr = &errortypes.NotFoundError{\n\t\t\t\terrors.New(\"deploy: Instance vpc not found\"),\n\t\t\t}\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\":   inst.Id.Hex(),\n\t\t\t\t\"net_namespace\": namespace,\n\t\t\t\t\"error\":         err,\n\t\t\t}).Error(\"deploy: Failed to deploy instance routes\")\n\t\t\treturn\n\t\t}\n\n\t\tcurRoutes := set.NewSet()\n\t\tcurRoutes6 := set.NewSet()\n\t\tnewRoutes := set.NewSet()\n\t\tnewRoutes6 := set.NewSet()\n\n\t\tvar icmpRedirects bool\n\t\tvar routes []vpc.Route\n\t\tvar routes6 []vpc.Route\n\n\t\troutesStore, ok := store.GetRoutes(inst.Id)\n\t\tif !ok {\n\t\t\ticmpRedirects, routes, routes6, err = qemu.GetRoutes(inst.Id)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\":   inst.Id.Hex(),\n\t\t\t\t\t\"net_namespace\": namespace,\n\t\t\t\t\t\"error\":         err,\n\t\t\t\t}).Error(\"deploy: Failed to deploy instance routes\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif routes == nil || routes6 == nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tstore.SetRoutes(inst.Id, icmpRedirects, routes, routes6)\n\t\t} else {\n\t\t\ticmpRedirects = routesStore.IcmpRedirects\n\t\t\troutes = routesStore.Routes\n\t\t\troutes6 = routesStore.Routes6\n\t\t}\n\n\t\tfor _, route := range routes {\n\t\t\tcurRoutes.Add(route)\n\t\t}\n\n\t\tfor _, route := range routes6 {\n\t\t\tcurRoutes6.Add(route)\n\t\t}\n\n\t\tif vc.Routes != nil {\n\t\t\tfor _, route := range vc.Routes {\n\t\t\t\tif !strings.Contains(route.Destination, \":\") {\n\t\t\t\t\tnewRoutes.Add(*route)\n\t\t\t\t} else {\n\t\t\t\t\tnewRoutes6.Add(*route)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tchanged := false\n\t\taddRoutes := newRoutes.Copy()\n\t\taddRoutes6 := newRoutes6.Copy()\n\t\tremRoutes := curRoutes.Copy()\n\t\tremRoutes6 := curRoutes6.Copy()\n\n\t\taddRoutes.Subtract(curRoutes)\n\t\taddRoutes6.Subtract(curRoutes6)\n\t\tremRoutes.Subtract(newRoutes)\n\t\tremRoutes6.Subtract(newRoutes6)\n\n\t\tif icmpRedirects != vc.IcmpRedirects {\n\t\t\tchanged = true\n\t\t\ticmpRedirectsCtl := 0\n\t\t\tif vc.IcmpRedirects {\n\t\t\t\ticmpRedirectsCtl = 1\n\t\t\t}\n\t\t\tutils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\"sysctl\", \"-w\",\n\t\t\t\tfmt.Sprintf(\"net.ipv4.conf.br0.send_redirects=%d\",\n\t\t\t\t\ticmpRedirectsCtl),\n\t\t\t)\n\t\t}\n\n\t\tfor routeInf := range remRoutes.Iter() {\n\t\t\troute := routeInf.(vpc.Route)\n\t\t\tchanged = true\n\n\t\t\tutils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\"ip\", \"route\",\n\t\t\t\t\"del\", route.Destination,\n\t\t\t\t\"via\", route.Target,\n\t\t\t\t\"metric\", \"97\",\n\t\t\t)\n\t\t}\n\n\t\tfor routeInf := range remRoutes6.Iter() {\n\t\t\troute := routeInf.(vpc.Route)\n\t\t\tchanged = true\n\n\t\t\tutils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\"ip\", \"-6\", \"route\",\n\t\t\t\t\"del\", route.Destination,\n\t\t\t\t\"via\", route.Target,\n\t\t\t\t\"metric\", \"97\",\n\t\t\t)\n\t\t}\n\n\t\tfor routeInf := range addRoutes.Iter() {\n\t\t\troute := routeInf.(vpc.Route)\n\t\t\tchanged = true\n\n\t\t\tutils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"File exists\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\"ip\", \"route\",\n\t\t\t\t\"add\", route.Destination,\n\t\t\t\t\"via\", route.Target,\n\t\t\t\t\"metric\", \"97\",\n\t\t\t)\n\t\t}\n\n\t\tfor routeInf := range addRoutes6.Iter() {\n\t\t\troute := routeInf.(vpc.Route)\n\t\t\tchanged = true\n\n\t\t\tutils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"File exists\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\"ip\", \"-6\", \"route\",\n\t\t\t\t\"add\", route.Destination,\n\t\t\t\t\"via\", route.Target,\n\t\t\t\t\"metric\", \"97\",\n\t\t\t)\n\t\t}\n\n\t\tif changed {\n\t\t\tstore.RemRoutes(inst.Id)\n\t\t}\n\n\t\tvar curRecords set.Set\n\n\t\trecordsStore, ok := store.GetArp(inst.Id)\n\t\tif !ok {\n\t\t\tcurRecords, err = arp.GetRecords(namespace)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Error(\"deploy: Failed to deploy instance arp table\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif routes == nil || routes6 == nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tstore.SetArp(inst.Id, curRecords)\n\t\t} else {\n\t\t\tcurRecords = recordsStore.Records\n\t\t}\n\n\t\tnewRecords := s.stat.ArpRecords(namespace)\n\n\t\tif curRecords == nil || newRecords == nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"cur_records_nil\": curRecords == nil,\n\t\t\t\t\"new_records_nil\": newRecords == nil,\n\t\t\t}).Error(\"deploy: Missing arp records\")\n\t\t\treturn\n\t\t}\n\n\t\tchanged, err = arp.ApplyState(namespace, curRecords, newRecords)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Error(\"deploy: Failed to deploy instance arp table\")\n\t\t\treturn\n\t\t}\n\n\t\tif changed {\n\t\t\tstore.RemArp(inst.Id)\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (s *Instances) Deploy(db *database.Database) (err error) {\n\tinstances := s.stat.Instances()\n\tnamespaces := s.stat.Namespaces()\n\n\tnamespacesSet := set.NewSet()\n\tfor _, namespace := range namespaces {\n\t\tnamespacesSet.Add(namespace)\n\t}\n\n\tcpuUnits := 0\n\tmemoryUnits := 0.0\n\n\tnow := time.Now()\n\tinfoTtl := time.Duration(settings.Hypervisor.InfoTtl) * time.Second\n\tfor _, inst := range instances {\n\t\tvirt := s.stat.GetVirt(inst.Id)\n\t\tif virt != nil {\n\t\t\tif inst.State == vm.Running &&\n\t\t\t\t(virt.State == vm.Stopped || virt.State == vm.Failed) {\n\n\t\t\t\tinst.Action = instance.Cleanup\n\n\t\t\t\ts.stat.WaitAdd()\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\t\t\t\tdefer s.stat.WaitDone()\n\t\t\t\t\terr := virt.CommitState(db, instance.Cleanup)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"error\": err,\n\t\t\t\t\t\t}).Error(\"qemu: Failed to commit instance state\")\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t} else {\n\t\t\t\ts.stat.WaitAdd()\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\t\t\t\tdefer s.stat.WaitDone()\n\t\t\t\t\terr := virt.Commit(db)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"error\": err,\n\t\t\t\t\t\t}).Error(\"qemu: Failed to commit instance state\")\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t}\n\n\t\tif inst.Info == nil || now.Sub(inst.Info.Timestamp) > infoTtl {\n\t\t\ts.stat.WaitAdd()\n\t\t\tgo func(inst *instance.Instance) {\n\t\t\t\tdefer utils.RecoverLog(\"deploy: Panic in instance action\")\n\t\t\t\tdefer s.stat.WaitDone()\n\n\t\t\t\tinst.Info = info.NewInstance(s.stat, inst)\n\t\t\t\terr := inst.CommitFields(db, set.NewSet(\"info\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"error\": err,\n\t\t\t\t\t}).Error(\"qemu: Failed to commit instance info\")\n\t\t\t\t}\n\t\t\t}(inst)\n\t\t}\n\t}\n\n\tinstIds := set.NewSet()\n\tfor _, inst := range instances {\n\t\tvirt := s.stat.GetVirt(inst.Id)\n\t\tinstIds.Add(inst.Id)\n\n\t\tif inst.Action == instance.Destroy {\n\t\t\tif inst.DeleteProtection {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t}).Info(\"deploy: Delete protection ignore instance destroy\")\n\n\t\t\t\tif virt != nil && virt.State == vm.Running {\n\t\t\t\t\tinst.Action = instance.Start\n\t\t\t\t} else {\n\t\t\t\t\tinst.Action = instance.Stop\n\t\t\t\t}\n\t\t\t\terr = inst.CommitFields(db, set.NewSet(\"action\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tevent.PublishDispatch(db, \"instance.change\")\n\t\t\t} else {\n\t\t\t\ts.destroy(inst)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tcpuUnits += inst.Processors\n\t\tmemoryUnits += float64(inst.Memory) / float64(1024)\n\n\t\tif virt == nil {\n\t\t\tif inst.Action == instance.Start {\n\t\t\t\ts.create(inst)\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch inst.Action {\n\t\tcase instance.Start:\n\t\t\tif virt.State == vm.Stopped || virt.State == vm.Failed {\n\t\t\t\tdsks := s.stat.GetInstaceDisks(inst.Id)\n\n\t\t\t\tfor _, dsk := range dsks {\n\t\t\t\t\tif !dsk.IsActive() || dsk.Action != \"\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ts.start(inst)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvalid, e := s.check(inst, namespacesSet)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !valid {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\terr = s.diff(db, inst)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = s.routes(inst)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tbreak\n\t\tcase instance.Cleanup:\n\t\t\ts.cleanup(inst)\n\t\t\tcontinue\n\t\tcase instance.Stop:\n\t\t\tif virt.State == vm.Running {\n\t\t\t\ts.stop(inst)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\tcase instance.Restart:\n\t\t\tif virt.State == vm.Running {\n\t\t\t\tdsks := s.stat.GetInstaceDisks(inst.Id)\n\n\t\t\t\tfor _, dsk := range dsks {\n\t\t\t\t\tif !dsk.IsActive() || dsk.Action != \"\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ts.restart(inst)\n\t\t\t\tcontinue\n\t\t\t} else if virt.State == vm.Stopped ||\n\t\t\t\tvirt.State == vm.Failed {\n\n\t\t\t\tinst.Action = instance.Start\n\t\t\t\terr = inst.CommitFields(db, set.NewSet(\"action\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\tvirts := s.stat.VirtsMap()\n\tfor _, virt := range virts {\n\t\tif !instIds.Contains(virt.Id) {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"id\": virt.Id.Hex(),\n\t\t\t}).Info(\"sync: Unknown instance\")\n\t\t}\n\t}\n\n\tnode.Self.CpuUnitsRes = cpuUnits\n\tnode.Self.MemoryUnitsRes = memoryUnits\n\n\treturn\n}\n\nfunc NewInstances(stat *state.State) *Instances {\n\treturn &Instances{\n\t\tstat: stat,\n\t}\n}\n"
  },
  {
    "path": "deploy/ipset.go",
    "content": "package deploy\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/ipset\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n)\n\ntype Ipset struct {\n\tstat *state.State\n}\n\nfunc (t *Ipset) Deploy() (err error) {\n\tinstaces := t.stat.Instances()\n\tnamespaces := t.stat.Namespaces()\n\tnodeFirewall := t.stat.NodeFirewall()\n\tfirewalls := t.stat.Firewalls()\n\n\terr = ipset.UpdateState(instaces, namespaces, nodeFirewall, firewalls)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (t *Ipset) Clean() (err error) {\n\tinstaces := t.stat.Instances()\n\tnodeFirewall := t.stat.NodeFirewall()\n\tfirewalls := t.stat.Firewalls()\n\n\terr = ipset.UpdateNamesState(instaces, nodeFirewall, firewalls)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc NewIpset(stat *state.State) *Ipset {\n\treturn &Ipset{\n\t\tstat: stat,\n\t}\n}\n"
  },
  {
    "path": "deploy/iptables.go",
    "content": "package deploy\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/iptables\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n)\n\ntype Iptables struct {\n\tstat *state.State\n}\n\nfunc (t *Iptables) Deploy() (err error) {\n\tnodeSelf := t.stat.Node()\n\tvpcs := t.stat.Vpcs()\n\tinstaces := t.stat.Instances()\n\tnamespaces := t.stat.Namespaces()\n\tnodeFirewall := t.stat.NodeFirewall()\n\tfirewalls := t.stat.Firewalls()\n\tfirewallMaps := t.stat.FirewallMaps()\n\n\tiptables.UpdateStateRecover(nodeSelf, vpcs, instaces, namespaces,\n\t\tnodeFirewall, firewalls, firewallMaps)\n\n\treturn\n}\n\nfunc NewIptables(stat *state.State) *Iptables {\n\treturn &Iptables{\n\t\tstat: stat,\n\t}\n}\n"
  },
  {
    "path": "deploy/namespace.go",
    "content": "package deploy\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/interfaces\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar (\n\tfirstRun         = true\n\tnamespaceLock    = utils.NewMultiTimeoutLock(5 * time.Minute)\n\tnamespaceLimiter = utils.NewLimiter(5)\n)\n\ntype Namespace struct {\n\tstat *state.State\n}\n\nfunc (n *Namespace) Deploy(db *database.Database) (err error) {\n\tinstances := n.stat.Instances()\n\tnamespaces := n.stat.Namespaces()\n\tifaces := n.stat.Interfaces()\n\n\tcurNamespaces := set.NewSet()\n\tcurVirtIfaces := set.NewSet()\n\tcurExternalIfaces := set.NewSet()\n\n\tnodeNetworkMode := node.Self.NetworkMode\n\tif nodeNetworkMode == \"\" {\n\t\tnodeNetworkMode = node.Dhcp\n\t}\n\n\tnodeNetworkMode6 := node.Self.NetworkMode6\n\tif nodeNetworkMode6 == \"\" {\n\t\tnodeNetworkMode6 = node.Dhcp\n\t}\n\n\texternalNetwork := false\n\tif (nodeNetworkMode != node.Disabled &&\n\t\tnodeNetworkMode != node.Cloud) ||\n\t\t(nodeNetworkMode6 != node.Disabled &&\n\t\t\tnodeNetworkMode6 != node.Cloud) {\n\n\t\texternalNetwork = true\n\t}\n\n\tfor _, inst := range instances {\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tcurNamespaces.Add(vm.GetNamespace(inst.Id, 0))\n\t\tif externalNetwork {\n\t\t\tcurVirtIfaces.Add(vm.GetIfaceNodeExternal(inst.Id, 0))\n\t\t}\n\t\tcurVirtIfaces.Add(vm.GetIfaceNodeInternal(inst.Id, 0))\n\t\tcurVirtIfaces.Add(vm.GetIfaceHost(inst.Id, 0))\n\t\tif externalNetwork {\n\t\t\tcurExternalIfaces.Add(vm.GetIfaceExternal(inst.Id, 0))\n\t\t}\n\t\tcurVirtIfaces.Add(vm.GetIfaceNodePort(inst.Id, 0))\n\t}\n\n\tfirstRun = false\n\n\tfor _, iface := range ifaces {\n\t\tif len(iface) != 14 || !(strings.HasPrefix(iface, \"j\") ||\n\t\t\tstrings.HasPrefix(iface, \"r\") ||\n\t\t\tutils.HasPreSuf(iface, \"h\", \"0\") ||\n\t\t\tutils.HasPreSuf(iface, \"m\", \"0\")) {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif !curVirtIfaces.Contains(iface) {\n\t\t\tutils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"Cannot find device\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"link\", \"del\", iface,\n\t\t\t)\n\t\t\tinterfaces.RemoveVirtIface(iface)\n\t\t}\n\t}\n\n\tfor _, namespace := range namespaces {\n\t\tif len(namespace) != 14 || !strings.HasPrefix(namespace, \"n\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !curNamespaces.Contains(namespace) {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"No such file\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"netns\", \"del\", namespace,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc NewNamespace(stat *state.State) *Namespace {\n\treturn &Namespace{\n\t\tstat: stat,\n\t}\n}\n"
  },
  {
    "path": "deploy/network.go",
    "content": "package deploy\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/bridges\"\n\t\"github.com/pritunl/pritunl-cloud/hnetwork\"\n\t\"github.com/pritunl/pritunl-cloud/interfaces\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vxlan\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tnodePortInitialized = false\n\tnodePortCurGateway  = \"\"\n)\n\ntype Network struct {\n\tstat *state.State\n}\n\nfunc (d *Network) Deploy() (err error) {\n\terr = hnetwork.ApplyState(d.stat)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = NodePortApplyState(d.stat)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = vxlan.ApplyState(d.stat)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tinterfaces.SyncIfaces(d.stat.VxLan())\n\n\treturn\n}\n\nfunc NewNetwork(stat *state.State) *Network {\n\treturn &Network{\n\t\tstat: stat,\n\t}\n}\n\nfunc nodePortCreate() (err error) {\n\terr = iproute.BridgeAdd(\"\", settings.Hypervisor.NodePortNetworkName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"link\", \"set\",\n\t\t\"dev\", settings.Hypervisor.NodePortNetworkName, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tbridges.ClearCache()\n\n\treturn\n}\n\nfunc nodePortGetAddr() (addr string, err error) {\n\taddress, _, err := iproute.AddressGetIface(\n\t\t\"\", settings.Hypervisor.NodePortNetworkName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif address != nil {\n\t\taddr = address.Local + fmt.Sprintf(\"/%d\", address.Prefix)\n\t}\n\n\treturn\n}\n\nfunc nodePortSetAddr(addr string) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"link\", \"set\",\n\t\t\"dev\", settings.Hypervisor.NodePortNetworkName, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"addr\", \"flush\",\n\t\t\"dev\", settings.Hypervisor.NodePortNetworkName,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"addr\", \"add\", addr,\n\t\t\"dev\", settings.Hypervisor.NodePortNetworkName,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc nodePortClearAddr() (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"link\", \"set\",\n\t\t\"dev\", settings.Hypervisor.NodePortNetworkName, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"addr\", \"flush\",\n\t\t\"dev\", settings.Hypervisor.NodePortNetworkName,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc nodePortRemoveNetwork(stat *state.State) (err error) {\n\tif nodePortCurGateway != \"\" || stat.HasInterfaces(\n\t\tsettings.Hypervisor.HostNetworkName) {\n\n\t\terr = nodePortClearAddr()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tnodePortCurGateway = \"\"\n\t}\n\n\treturn\n}\n\nfunc NodePortApplyState(stat *state.State) (err error) {\n\tinitializeInst := false\n\tnodeNetName := settings.Hypervisor.NodePortNetworkName\n\n\tif !nodePortInitialized {\n\t\taddr, e := nodePortGetAddr()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tinitializeInst = true\n\t\tnodePortInitialized = true\n\t\tnodePortCurGateway = addr\n\t}\n\n\tif !stat.HasInterfaces(nodeNetName) {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"iface\": nodeNetName,\n\t\t}).Info(\"nodeport: Creating node port interface\")\n\n\t\terr = nodePortCreate()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tnodePortBlock, err := block.GetNodePortBlock(stat.Node().Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tgatewayCidr := nodePortBlock.GetGatewayCidr()\n\tif gatewayCidr == \"\" {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"node_port_block\": nodePortBlock.Id.Hex(),\n\t\t}).Error(\"nodeport: Node port network block gateway is invalid\")\n\n\t\terr = nodePortRemoveNetwork(stat)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t}\n\n\tif nodePortCurGateway != gatewayCidr {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"node_port_block\":         nodePortBlock.Id.Hex(),\n\t\t\t\"node_port_block_gateway\": gatewayCidr,\n\t\t}).Info(\"nodeport: Updating node port network bridge\")\n\n\t\terr = nodePortSetAddr(gatewayCidr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tnodePortCurGateway = gatewayCidr\n\t\tinitializeInst = true\n\t}\n\n\tif initializeInst {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"node_port_block\": nodePortBlock.Id.Hex(),\n\t\t}).Info(\"nodeport: Updating instance nodeport network\")\n\n\t\tinstances := stat.Instances()\n\t\tfor _, inst := range instances {\n\t\t\tutils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"Cannot find device\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"link\", \"set\",\n\t\t\t\tvm.GetIfaceNodePort(inst.Id, 0), \"master\", nodeNetName,\n\t\t\t)\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "deploy/services.go",
    "content": "package deploy\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n\t\"github.com/pritunl/pritunl-cloud/scheduler\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tpodsLock    = utils.NewMultiTimeoutLock(3 * time.Minute)\n\tpodsLimiter = utils.NewLimiter(50)\n)\n\ntype Pods struct {\n\tstat *state.State\n}\n\nfunc (s *Pods) processSchedule(schd *scheduler.Scheduler) {\n\tif !podsLimiter.Acquire() {\n\t\treturn\n\t}\n\n\tacquired, lockId := podsLock.LockOpen(schd.Id.Hex())\n\tif !acquired {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tpodsLock.Unlock(schd.Id.Hex(), lockId)\n\t\t\tpodsLimiter.Release()\n\t\t}()\n\n\t\terr := s.deploySchedule(schd)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"unit\":  schd.Id.Hex(),\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Unit deploy failed\")\n\t\t\treturn\n\t\t}\n\t}()\n}\n\nfunc (s *Pods) deploySchedule(schd *scheduler.Scheduler) (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tunt, err := unit.Get(db, schd.Id)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"unit\": schd.Id.Hex(),\n\t\t\t}).Warn(\"deploy: Canceling deployment on unit not found\")\n\n\t\t\terr = schd.ClearTickets(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\tspc, err := spec.Get(db, schd.Spec)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"unit\": schd.Id.Hex(),\n\t\t\t}).Warn(\"deploy: Canceling deployment on spec not found\")\n\n\t\t\terr = schd.ClearTickets(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\ttickets := schd.Tickets[s.stat.Node().Id]\n\tif len(tickets) > 0 {\n\t\tnow := time.Now()\n\t\tfor _, ticket := range tickets {\n\t\t\tstart := schd.Created.Add(\n\t\t\t\ttime.Duration(ticket.Offset) * time.Second)\n\t\t\tif now.After(start) {\n\t\t\t\texists, e := schd.Refresh(db)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif !exists {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"pod\":  unt.Pod.Hex(),\n\t\t\t\t\t\t\"unit\": unt.Id.Hex(),\n\t\t\t\t\t}).Info(\"deploy: Pod deploy schedule lost\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif schd.Consumed >= schd.Count {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif !schd.Ready() {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"pod\":  unt.Pod.Hex(),\n\t\t\t\t\t\t\"unit\": unt.Id.Hex(),\n\t\t\t\t\t}).Info(\"deploy: Reached maximum schedule attempts\")\n\n\t\t\t\t\terr = schd.ClearTickets(db)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\treserved, e := s.DeploySpec(db, schd, unt, spc)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\n\t\t\t\t\tlimit, _ := schd.Failure(db)\n\t\t\t\t\tif limit {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"pod\":  unt.Pod.Hex(),\n\t\t\t\t\t\t\t\"unit\": unt.Id.Hex(),\n\t\t\t\t\t\t}).Info(\"deploy: Reached maximum schedule attempts\")\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif reserved {\n\t\t\t\t\terr = schd.Consume(db)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tlimit, e := schd.Failure(db)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif limit {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"pod\":  unt.Pod.Hex(),\n\t\t\t\t\t\t\t\"unit\": unt.Id.Hex(),\n\t\t\t\t\t\t}).Info(\"deploy: Reached maximum schedule attempts\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (s *Pods) DeploySpec(db *database.Database,\n\tschd *scheduler.Scheduler, unt *unit.Unit,\n\tspc *spec.Spec) (reserved bool, err error) {\n\n\timg, err := image.Get(db, spc.Instance.Image)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tjrnls := []*deployment.Journal{}\n\tif spc.Journal != nil {\n\t\tfor _, input := range spc.Journal.Inputs {\n\t\t\tjrnls = append(jrnls, &deployment.Journal{\n\t\t\t\tIndex: input.Index,\n\t\t\t\tKey:   input.Key,\n\t\t\t\tType:  input.Type,\n\t\t\t})\n\t\t}\n\t}\n\n\tdeply := &deployment.Deployment{\n\t\tPod:          unt.Pod,\n\t\tUnit:         unt.Id,\n\t\tOrganization: unt.Organization,\n\t\tTimestamp:    time.Now(),\n\t\tSpec:         spc.Id,\n\t\tDatacenter:   node.Self.Datacenter,\n\t\tZone:         node.Self.Zone,\n\t\tNode:         node.Self.Id,\n\t\tKind:         unt.Kind,\n\t\tState:        deployment.Reserved,\n\t\tJournals:     jrnls,\n\t}\n\n\terrData, err := spc.Refresh(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = errData.GetError()\n\t\treturn\n\t}\n\n\terrData, err = deply.Validate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = errData.GetError()\n\t\treturn\n\t}\n\n\terr = deply.Insert(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\te := deployment.Remove(db, deply.Id)\n\t\t\tif e != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": e,\n\t\t\t\t}).Error(\"deploy: Failed to cleanup deployment\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\terr = unt.Refresh(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treserved, err = unt.Reserve(db, deply.Id, schd.OverrideCount)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !reserved {\n\t\terr = deployment.Remove(db, deply.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\tinst := &instance.Instance{\n\t\tOrganization:        unt.Organization,\n\t\tZone:                spc.Instance.Zone,\n\t\tVpc:                 spc.Instance.Vpc,\n\t\tSubnet:              spc.Instance.Subnet,\n\t\tShape:               spc.Instance.Shape,\n\t\tNode:                node.Self.Id,\n\t\tImage:               spc.Instance.Image,\n\t\tUefi:                true,\n\t\tTpm:                 spc.Instance.Tpm,\n\t\tVnc:                 spc.Instance.Vnc,\n\t\tDhcpServer:          spc.Instance.DhcpServer,\n\t\tCloudScript:         \"\",\n\t\tDeleteProtection:    spc.Instance.DeleteProtection,\n\t\tSkipSourceDestCheck: spc.Instance.SkipSourceDestCheck,\n\t\tGui:                 spc.Instance.Gui,\n\t\tName:                spc.Name,\n\t\tComment:             \"\",\n\t\tInitDiskSize:        10,\n\t\tMemory:              spc.Instance.Memory,\n\t\tProcessors:          spc.Instance.Processors,\n\t\tRoles:               spc.Instance.Roles,\n\t\tSystemKind:          img.GetSystemKind(),\n\t\tNoPublicAddress:     false,\n\t\tNoPublicAddress6:    false,\n\t\tNoHostAddress:       false,\n\t\tDeployment:          deply.Id,\n\t}\n\n\tswitch img.GetSystemType() {\n\tcase image.Bsd:\n\t\tinst.CloudType = instance.BSD\n\t\tinst.SecureBoot = false\n\tcase image.LinuxUnsigned:\n\t\tinst.CloudType = instance.Linux\n\t\tinst.SecureBoot = false\n\tcase image.LinuxLegacy:\n\t\tinst.CloudType = instance.LinuxLegacy\n\t\tinst.SecureBoot = true\n\tdefault:\n\t\tinst.CloudType = instance.Linux\n\t\tinst.SecureBoot = true\n\t}\n\n\tif spc.Instance.Uefi != nil {\n\t\tinst.Uefi = *spc.Instance.Uefi\n\t}\n\tif spc.Instance.SecureBoot != nil {\n\t\tinst.SecureBoot = *spc.Instance.SecureBoot\n\t}\n\tif spc.Instance.CloudType != \"\" {\n\t\tinst.CloudType = spc.Instance.CloudType\n\t}\n\tif spc.Instance.HostAddress != nil {\n\t\tinst.NoHostAddress = !*spc.Instance.HostAddress\n\t}\n\tif spc.Instance.PublicAddress != nil {\n\t\tinst.NoPublicAddress = !*spc.Instance.PublicAddress\n\t} else {\n\t\tinst.NoPublicAddress = node.Self.DefaultNoPublicAddress\n\t}\n\tif spc.Instance.PublicAddress6 != nil {\n\t\tinst.NoPublicAddress6 = !*spc.Instance.PublicAddress6\n\t} else {\n\t\tinst.NoPublicAddress6 = node.Self.DefaultNoPublicAddress6\n\t}\n\tif spc.Instance.DiskSize != 0 {\n\t\tinst.InitDiskSize = spc.Instance.DiskSize\n\t}\n\n\tif len(spc.Instance.NodePorts) > 0 {\n\t\tfor _, ndePort := range spc.Instance.NodePorts {\n\t\t\tinst.NodePorts = append(inst.NodePorts, &nodeport.Mapping{\n\t\t\t\tProtocol:     ndePort.Protocol,\n\t\t\t\tExternalPort: ndePort.ExternalPort,\n\t\t\t\tInternalPort: ndePort.InternalPort,\n\t\t\t})\n\t\t}\n\t}\n\n\tfor _, mount := range spc.Instance.Mounts {\n\t\tif mount.Type != spec.HostPath {\n\t\t\tcontinue\n\t\t}\n\n\t\tinst.Mounts = append(inst.Mounts, &instance.Mount{\n\t\t\tName:     mount.Name,\n\t\t\tType:     instance.HostPath,\n\t\t\tPath:     mount.Path,\n\t\t\tHostPath: mount.HostPath,\n\t\t})\n\t}\n\n\terr = inst.GenerateId()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terrData, err = inst.Validate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\treserved = false\n\t\terr = errData.GetError()\n\t\treturn\n\t}\n\n\tif len(inst.NodePorts) > 0 {\n\t\terr = inst.SyncNodePorts(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tindex := 0\n\treservedDisks := []*disk.Disk{}\n\tdeplyMounts := []*deployment.Mount{}\n\n\tfor _, mount := range spc.Instance.Mounts {\n\t\tif mount.Type != spec.Disk {\n\t\t\tcontinue\n\t\t}\n\n\t\tindex += 1\n\t\tdiskReserved := false\n\n\t\tfor _, dskId := range mount.Disks {\n\t\t\tdsk, e := disk.Get(db, dskId)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\n\t\t\t\tfor _, dsk := range reservedDisks {\n\t\t\t\t\terr = dsk.Unreserve(db, inst.Id, deply.Id)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif dsk.Node != node.Self.Id || !dsk.Instance.IsZero() {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdiskReserved, err = dsk.Reserve(db, inst.Id, index, deply.Id)\n\t\t\tif err != nil {\n\t\t\t\tfor _, dsk := range reservedDisks {\n\t\t\t\t\terr = dsk.Unreserve(db, inst.Id, deply.Id)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !diskReserved {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdeplyMounts = append(deplyMounts, &deployment.Mount{\n\t\t\t\tDisk: dsk.Id,\n\t\t\t\tPath: mount.Path,\n\t\t\t\tUuid: dsk.Uuid,\n\t\t\t})\n\n\t\t\treservedDisks = append(reservedDisks, dsk)\n\t\t\tbreak\n\t\t}\n\n\t\tif !diskReserved {\n\t\t\tfor _, dsk := range reservedDisks {\n\t\t\t\terr = dsk.Unreserve(db, inst.Id, deply.Id)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"mount_path\": mount.Path,\n\t\t\t}).Error(\"deploy: Failed to reserve disk for mount\")\n\n\t\t\terr = deployment.Remove(db, deply.Id)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treserved = false\n\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = inst.Insert(db)\n\tif err != nil {\n\t\t_ = inst.Cleanup(db)\n\n\t\tfor _, dsk := range reservedDisks {\n\t\t\terr = dsk.Unreserve(db, inst.Id, deply.Id)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t}\n\n\terr = inst.Cleanup(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdeply.State = deployment.Deployed\n\tdeply.Instance = inst.Id\n\tdeply.Mounts = deplyMounts\n\n\terr = deply.CommitFields(db, set.NewSet(\"state\", \"instance\", \"mounts\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\n\treturn\n}\n\nfunc (s *Pods) Deploy(db *database.Database) (err error) {\n\tschds := s.stat.Schedulers()\n\n\tfor _, schd := range schds {\n\t\tif schd.Kind != scheduler.InstanceUnitKind {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(schd.Tickets) == 0 {\n\t\t\tdeleted, e := scheduler.Remove(db, schd.Id)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif deleted {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"unit\": schd.Id.Hex(),\n\t\t\t\t}).Error(\"deploy: All nodes failed to schedule deployment\")\n\t\t\t}\n\t\t}\n\n\t\ttickets := schd.Tickets[s.stat.Node().Id]\n\t\tif tickets != nil && len(tickets) > 0 {\n\t\t\tnow := time.Now()\n\t\t\tfor _, ticket := range tickets {\n\t\t\t\tstart := schd.Created.Add(\n\t\t\t\t\ttime.Duration(ticket.Offset) * time.Second)\n\t\t\t\tif now.After(start) {\n\t\t\t\t\ts.processSchedule(schd)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc NewPods(stat *state.State) *Pods {\n\treturn &Pods{\n\t\tstat: stat,\n\t}\n}\n"
  },
  {
    "path": "deployment/constants.go",
    "content": "package deployment\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\nconst (\n\tProvision = \"provision\"\n\tReserved  = \"reserved\"\n\tDeployed  = \"deployed\"\n\tArchived  = \"archived\"\n\n\tDestroy = \"destroy\"\n\tArchive = \"archive\"\n\tMigrate = \"migrate\"\n\tRestore = \"restore\"\n\n\tReady    = \"ready\"\n\tSnapshot = \"snapshot\"\n\tComplete = \"complete\"\n\tFailed   = \"failed\"\n\n\tInstance = \"instance\"\n\tImage    = \"image\"\n\tFirewall = \"firewall\"\n\tDomain   = \"domain\"\n\n\tHealthy   = \"healthy\"\n\tUnknown   = \"unknown\"\n\tUnhealthy = \"unhealthy\"\n\n\tThresholdMin = 10\n\tActionLimit  = 1 * time.Minute\n)\n\nvar (\n\tValidStates = set.NewSet(\n\t\tProvision,\n\t\tReserved,\n\t\tDeployed,\n\t\tArchived,\n\t)\n\tValidActions = set.NewSet(\n\t\t\"\",\n\t\tDestroy,\n\t\tArchive,\n\t\tMigrate,\n\t\tRestore,\n\t)\n)\n"
  },
  {
    "path": "deployment/deployment.go",
    "content": "package deployment\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Deployment struct {\n\tId           bson.ObjectID             `bson:\"_id,omitempty\" json:\"id\"`\n\tPod          bson.ObjectID             `bson:\"pod\" json:\"pod\"`\n\tUnit         bson.ObjectID             `bson:\"unit\" json:\"unit\"`\n\tOrganization bson.ObjectID             `bson:\"organization\" json:\"organization\"`\n\tTimestamp    time.Time                 `bson:\"timestamp\" json:\"timestamp\"`\n\tTags         []string                  `bson:\"tags\" json:\"tags\"`\n\tSpec         bson.ObjectID             `bson:\"spec\" json:\"spec\"`\n\tNewSpec      bson.ObjectID             `bson:\"new_spec\" json:\"new_spec\"`\n\tKind         string                    `bson:\"kind\" json:\"kind\"`\n\tState        string                    `bson:\"state\" json:\"state\"`\n\tAction       string                    `bson:\"action\" json:\"action\"`\n\tStatus       string                    `bson:\"status\" json:\"status\"`\n\tDatacenter   bson.ObjectID             `bson:\"datacenter\" json:\"datacenter\"`\n\tZone         bson.ObjectID             `bson:\"zone\" json:\"zone\"`\n\tNode         bson.ObjectID             `bson:\"node\" json:\"node\"`\n\tInstance     bson.ObjectID             `bson:\"instance\" json:\"instance\"`\n\tImage        bson.ObjectID             `bson:\"image\" json:\"image\"`\n\tMounts       []*Mount                  `bson:\"mounts\" json:\"mounts\"`\n\tJournals     []*Journal                `bson:\"journals\" json:\"journals\"`\n\tInstanceData *InstanceData             `bson:\"instance_data,omitempty\" json:\"instance_data\"`\n\tImageData    *ImageData                `bson:\"image_data,omitempty\" json:\"image_data\"`\n\tDomainData   *DomainData               `bson:\"domain_data,omitempty\" json:\"domain_data\"`\n\tActions      map[bson.ObjectID]*Action `bson:\"actions,omitempty\" json:\"actions\"`\n}\n\ntype InstanceData struct {\n\tHostIps         []string `bson:\"host_ips\" json:\"host_ips\"`\n\tPublicIps       []string `bson:\"public_ips\" json:\"public_ips\"`\n\tPublicIps6      []string `bson:\"public_ips6\" json:\"public_ips6\"`\n\tPrivateIps      []string `bson:\"private_ips\" json:\"private_ips\"`\n\tPrivateIps6     []string `bson:\"private_ips6\" json:\"private_ips6\"`\n\tCloudPrivateIps []string `bson:\"cloud_private_ips\" json:\"cloud_private_ips\"`\n\tCloudPublicIps  []string `bson:\"cloud_public_ips\" json:\"cloud_public_ips\"`\n\tCloudPublicIps6 []string `bson:\"cloud_public_ips6\" json:\"cloud_public_ips6\"`\n}\n\ntype DomainData struct {\n\tRecords []*RecordData `bson:\"records\" json:\"records\"`\n}\n\ntype RecordData struct {\n\tDomain string `bson:\"domain\" json:\"domain\"`\n\tValue  string `bson:\"value\" json:\"value\"`\n}\n\ntype ImageData struct {\n\tState string `bson:\"state\" json:\"state\"`\n}\n\ntype Mount struct {\n\tDisk bson.ObjectID `bson:\"disk\" json:\"disk\"`\n\tPath string        `bson:\"path\" json:\"path\"`\n\tUuid string        `bson:\"uuid\" json:\"uuid\"`\n}\n\ntype Journal struct {\n\tIndex int32  `bson:\"index\" json:\"index\"`\n\tKey   string `bson:\"key\" json:\"key\"`\n\tType  string `bson:\"type\" json:\"type\"`\n}\n\ntype Action struct {\n\tStatement bson.ObjectID `bson:\"statement\" json:\"statement\"`\n\tSince     time.Time     `bson:\"since\" json:\"since\"`\n\tExecuted  time.Time     `bson:\"executed\" json:\"executed\"`\n\tAction    string        `bson:\"action\" json:\"action\"`\n}\n\nfunc (d *Deployment) IsHealthy() bool {\n\treturn d.Status == Healthy\n}\n\nfunc (d *Deployment) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif d.Timestamp.IsZero() {\n\t\td.Timestamp = time.Now()\n\t}\n\n\tif d.Tags == nil {\n\t\td.Tags = []string{}\n\t}\n\n\ttags := []string{}\n\tfor _, tag := range d.Tags {\n\t\ttag = utils.FilterName(tag)\n\t\tif tag == \"\" || tag == \"latest\" {\n\t\t\tcontinue\n\t\t}\n\n\t\ttags = append(tags, tag)\n\t}\n\td.Tags = tags\n\n\tif d.Actions == nil {\n\t\td.Actions = map[bson.ObjectID]*Action{}\n\t}\n\n\tif !ValidStates.Contains(d.State) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_state\",\n\t\t\tMessage: \"Invalid deployment state\",\n\t\t}\n\t\treturn\n\t}\n\n\tif !ValidActions.Contains(d.Action) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_action\",\n\t\t\tMessage: \"Invalid deployment action\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch d.Status {\n\tcase Healthy:\n\t\tbreak\n\tcase Unhealthy:\n\t\tbreak\n\tcase Unknown:\n\t\tbreak\n\tcase \"\":\n\t\td.Status = Unhealthy\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_status\",\n\t\t\tMessage: \"Deployment status is invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch d.Kind {\n\tcase Instance:\n\t\tbreak\n\tcase Image:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_kind\",\n\t\t\tMessage: \"Deployment kind is invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Deployment) HandleStatement(db *database.Database,\n\tstatementId bson.ObjectID, thresholdSec int, action string) (\n\tnewAction string, err error) {\n\n\tthresholdSec = utils.Max(ThresholdMin, thresholdSec)\n\tthreshold := time.Duration(thresholdSec) * time.Second\n\n\tif action != \"\" {\n\t\tcurAction := d.Actions[statementId]\n\t\tif curAction == nil {\n\t\t\terr = d.CommitAction(db, &Action{\n\t\t\t\tStatement: statementId,\n\t\t\t\tSince:     time.Now(),\n\t\t\t\tAction:    action,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnewAction = \"\"\n\t\t\treturn\n\t\t} else if curAction.Action != action {\n\t\t\tif !curAction.Executed.IsZero() && time.Since(\n\t\t\t\tcurAction.Executed) < ActionLimit {\n\n\t\t\t\tnewAction = \"\"\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcurAction.Since = time.Now()\n\t\t\tcurAction.Executed = time.Time{}\n\t\t\tcurAction.Action = action\n\n\t\t\terr = d.CommitAction(db, curAction)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnewAction = \"\"\n\t\t\treturn\n\t\t} else if time.Since(curAction.Since) >= threshold {\n\t\t\tif !curAction.Executed.IsZero() && time.Since(\n\t\t\t\tcurAction.Executed) < ActionLimit {\n\n\t\t\t\tnewAction = \"\"\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcurAction.Executed = time.Now()\n\n\t\t\terr = d.CommitAction(db, curAction)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnewAction = action\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcurAction := d.Actions[statementId]\n\t\tif curAction != nil {\n\t\t\tif !curAction.Executed.IsZero() && time.Since(\n\t\t\t\tcurAction.Executed) < ActionLimit {\n\n\t\t\t\tnewAction = \"\"\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = d.RemoveAction(db, curAction)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tnewAction = \"\"\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Deployment) SetImageState(state string) {\n\tif d.ImageData == nil {\n\t\td.ImageData = &ImageData{}\n\t}\n\td.ImageData.State = state\n}\n\nfunc (d *Deployment) GetImageState() string {\n\tif d.ImageData == nil {\n\t\treturn \"\"\n\t}\n\treturn d.ImageData.State\n}\n\nfunc (d *Deployment) ImageReady() bool {\n\treturn !d.Image.IsZero() && d.ImageData != nil &&\n\t\td.ImageData.State == Complete\n}\n\nfunc (d *Deployment) CommitAction(db *database.Database,\n\taction *Action) (err error) {\n\n\tcoll := db.Deployments()\n\n\t_, err = coll.UpdateOne(db, bson.M{\n\t\t\"_id\": d.Id,\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\tfmt.Sprintf(\"actions.%s\", action.Statement.Hex()): action,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Deployment) RemoveAction(db *database.Database,\n\taction *Action) (err error) {\n\n\tcoll := db.Deployments()\n\n\t_, err = coll.UpdateOne(db, bson.M{\n\t\t\"_id\": d.Id,\n\t}, bson.M{\n\t\t\"$unset\": bson.M{\n\t\t\tfmt.Sprintf(\"actions.%s\", action.Statement.Hex()): \"\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Deployment) Commit(db *database.Database) (err error) {\n\tcoll := db.Deployments()\n\n\terr = coll.Commit(d.Id, d)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Deployment) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Deployments()\n\n\terr = coll.CommitFields(d.Id, d, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Deployment) Insert(db *database.Database) (err error) {\n\tcoll := db.Deployments()\n\n\tresp, err := coll.InsertOne(db, d)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\td.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  },
  {
    "path": "deployment/utils.go",
    "content": "package deployment\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/journal\"\n)\n\nfunc Get(db *database.Database, deplyId bson.ObjectID) (\n\tdeply *Deployment, err error) {\n\n\tcoll := db.Deployments()\n\tdeply = &Deployment{}\n\n\terr = coll.FindOneId(deplyId, deply)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetUnit(db *database.Database, unitId, deplyId bson.ObjectID) (\n\tdeply *Deployment, err error) {\n\n\tcoll := db.Deployments()\n\tdeply = &Deployment{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":  deplyId,\n\t\t\"unit\": unitId,\n\t}).Decode(deply)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, unitId bson.ObjectID) (\n\tdeply *Deployment, err error) {\n\n\tcoll := db.Deployments()\n\tdeply = &Deployment{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          unitId,\n\t\t\"organization\": orgId,\n\t}).Decode(deply)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetUnitOrg(db *database.Database,\n\torgId, unitId, deplyId bson.ObjectID) (\n\tdeply *Deployment, err error) {\n\n\tcoll := db.Deployments()\n\tdeply = &Deployment{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          deplyId,\n\t\t\"unit\":         unitId,\n\t\t\"organization\": orgId,\n\t}).Decode(deply)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tdeplys []*Deployment, err error) {\n\n\tcoll := db.Deployments()\n\tdeplys = []*Deployment{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdeply := &Deployment{}\n\t\terr = cursor.Decode(deply)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdeplys = append(deplys, deply)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllSorted(db *database.Database, query *bson.M) (\n\tdeplys []*Deployment, err error) {\n\n\tcoll := db.Deployments()\n\tdeplys = []*Deployment{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"timestamp\", -1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdeply := &Deployment{}\n\t\terr = cursor.Decode(deply)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdeplys = append(deplys, deply)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllActiveIds(db *database.Database) (deplyIds set.Set, err error) {\n\tcoll := db.Deployments()\n\tdeplyIds = set.NewSet()\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tbson.M{\n\t\t\t\"state\": bson.M{\n\t\t\t\t\"$in\": []string{\n\t\t\t\t\tReserved,\n\t\t\t\t\tDeployed,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\toptions.Find().\n\t\t\tSetProjection(bson.M{\n\t\t\t\t\"_id\": 1,\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdeply := &Deployment{}\n\t\terr = cursor.Decode(deply)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdeplyIds.Add(deply.Id)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllStates(db *database.Database) (\n\tdeplysMap map[bson.ObjectID]*Deployment, err error) {\n\n\tcoll := db.Deployments()\n\tdeplysMap = map[bson.ObjectID]*Deployment{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tbson.M{},\n\t\toptions.Find().\n\t\t\tSetProjection(bson.M{\n\t\t\t\t\"_id\":    1,\n\t\t\t\t\"state\":  1,\n\t\t\t\t\"action\": 1,\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdeply := &Deployment{}\n\t\terr = cursor.Decode(deply)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdeplysMap[deply.Id] = deply\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveDomains(db *database.Database, deplyId bson.ObjectID) (\n\terr error) {\n\n\trecs, err := domain.GetRecordAll(db, &bson.M{\n\t\t\"deployment\": deplyId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdomnIdsSet := set.NewSet()\n\tfor _, rec := range recs {\n\t\tdomnIdsSet.Add(rec.Domain)\n\t}\n\n\tdomnIds := []bson.ObjectID{}\n\tfor domnIdInf := range domnIdsSet.Iter() {\n\t\tdomnIds = append(domnIds, domnIdInf.(bson.ObjectID))\n\t}\n\n\tif len(domnIds) > 0 {\n\t\tdomns, e := domain.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": domnIds,\n\t\t\t},\n\t\t})\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, domn := range domns {\n\t\t\terr = domn.LoadRecords(db, false)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdomn.PreCommit()\n\n\t\t\tchanged := false\n\t\t\tfor _, rec := range domn.Records {\n\t\t\t\tif rec.Deployment == deplyId {\n\t\t\t\t\tchanged = true\n\t\t\t\t\trec.Operation = domain.DELETE\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif changed {\n\t\t\t\terr = domn.CommitRecords(db)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\n\treturn\n}\n\nfunc Remove(db *database.Database, deplyId bson.ObjectID) (err error) {\n\tcoll := db.Deployments()\n\n\terr = journal.RemoveAll(db, deplyId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = RemoveDomains(db, deplyId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": deplyId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, unitId bson.ObjectID,\n\tdeplyIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Deployments()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": deplyIds,\n\t\t},\n\t\t\"unit\": unitId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Destroy,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ArchiveMulti(db *database.Database, unitId bson.ObjectID,\n\tdeplyIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Deployments()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": deplyIds,\n\t\t},\n\t\t\"unit\":  unitId,\n\t\t\"state\": Deployed,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Archive,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RestoreMulti(db *database.Database, unitId bson.ObjectID,\n\tdeplyIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Deployments()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": deplyIds,\n\t\t},\n\t\t\"unit\":  unitId,\n\t\t\"state\": Archived,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Restore,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "device/constants.go",
    "content": "package device\n\nconst (\n\tU2f       = \"u2f\"\n\tWebAuthn  = \"webauthn\"\n\tSecondary = \"secondary\"\n\tPhone     = \"phone\"\n\tCall      = \"call\"\n\tMessage   = \"message\"\n\tLow       = 1\n\tMedium    = 5\n\tHigh      = 10\n)\n"
  },
  {
    "path": "device/device.go",
    "content": "package device\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/x509\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/go-webauthn/webauthn/webauthn\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Device struct {\n\tId                 bson.ObjectID           `bson:\"_id,omitempty\" json:\"id\"`\n\tUser               bson.ObjectID           `bson:\"user\" json:\"user\"`\n\tName               string                  `bson:\"name\" json:\"name\"`\n\tType               string                  `bson:\"type\" json:\"type\"`\n\tMode               string                  `bson:\"mode\" json:\"mode\"`\n\tTimestamp          time.Time               `bson:\"timestamp\" json:\"timestamp\"`\n\tDisabled           bool                    `bson:\"disabled\" json:\"disabled\"`\n\tActiveUntil        time.Time               `bson:\"activeactive_until_until\" json:\"active_until\"`\n\tLastActive         time.Time               `bson:\"last_active\" json:\"last_active\"`\n\tAlertLevels        []int                   `bson:\"alert_levels\" json:\"alert_levels\"`\n\tNumber             string                  `bson:\"number\" json:\"number\"`\n\tU2fRaw             []byte                  `bson:\"u2f_raw\" json:\"-\"`\n\tU2fCounter         uint32                  `bson:\"u2f_counter\" json:\"-\"`\n\tU2fKeyHandle       []byte                  `bson:\"u2f_key_handle\" json:\"-\"`\n\tU2fPublicKey       []byte                  `bson:\"u2f_public_key\" json:\"-\"`\n\tWanId              []byte                  `bson:\"wan_id\" json:\"-\"`\n\tWanPublicKey       []byte                  `bson:\"wan_public_key\" json:\"-\"`\n\tWanAttestationType string                  `bson:\"wan_attestation_type\" json:\"-\"`\n\tWanAuthenticator   *webauthn.Authenticator `bson:\"wan_authenticator\" json:\"-\"`\n\tWanRpId            string                  `bson:\"wan_rp_id\" json:\"wan_rp_id\"`\n}\n\nfunc (d *Device) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\td.Name = utils.FilterStr(d.Name, 32)\n\n\tif len(d.Name) == 0 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"device_name_missing\",\n\t\t\tMessage: \"Device name is required\",\n\t\t}\n\t\treturn\n\t}\n\n\tif len(d.Name) > 22 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"device_name_invalid\",\n\t\t\tMessage: \"Device name is too long\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch d.Mode {\n\tcase Secondary:\n\t\tif d.Type != U2f && d.Type != WebAuthn {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"device_type_invalid\",\n\t\t\t\tMessage: \"Device type is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase Phone:\n\t\tif d.Type != Call && d.Type != Message {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"device_type_invalid\",\n\t\t\t\tMessage: \"Device type is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif len(d.Number) == 10 {\n\t\t\td.Number = \"+1\" + d.Number\n\t\t}\n\n\t\tif len(d.Number) < 10 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"device_number_invalid\",\n\t\t\t\tMessage: \"Device phone number invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"device_mode_invalid\",\n\t\t\tMessage: \"Device mode is invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.AlertLevels == nil {\n\t\td.AlertLevels = []int{}\n\t}\n\tfor _, level := range d.AlertLevels {\n\t\tswitch level {\n\t\tcase Low, Medium, High:\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"device_alert_level_invalid\",\n\t\t\t\tMessage: \"Device alert level is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (d *Device) SetActive(db *database.Database) (err error) {\n\td.LastActive = time.Now()\n\terr = d.CommitFields(db, set.NewSet(\"last_active\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Device) MarshalWebauthn(cred *webauthn.Credential) {\n\tif d.Type == U2f {\n\t\td.U2fCounter = cred.Authenticator.SignCount\n\t} else {\n\t\td.WanId = cred.ID\n\t\td.WanPublicKey = cred.PublicKey\n\t\td.WanAttestationType = cred.AttestationType\n\t\td.WanAuthenticator = &cred.Authenticator\n\t}\n\n\treturn\n}\n\nfunc (d *Device) UnmarshalWebauthn() (cred webauthn.Credential, err error) {\n\tif d.Type == U2f {\n\t\tpubKeyItf, e := x509.ParsePKIXPublicKey(d.U2fPublicKey)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"device: Failed to parse device public key\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tpubKey, ok := pubKeyItf.(*ecdsa.PublicKey)\n\t\tif !ok {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"device: Device public key invalid type\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tpubKeyByte := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)\n\n\t\tcred = webauthn.Credential{\n\t\t\tID:              d.U2fKeyHandle,\n\t\t\tPublicKey:       pubKeyByte,\n\t\t\tAttestationType: \"fido-u2f\",\n\t\t\tAuthenticator: webauthn.Authenticator{\n\t\t\t\tAAGUID:    d.U2fRaw,\n\t\t\t\tSignCount: d.U2fCounter,\n\t\t\t},\n\t\t}\n\t\treturn\n\t}\n\n\tcred = webauthn.Credential{\n\t\tID:              d.WanId,\n\t\tPublicKey:       d.WanPublicKey,\n\t\tAttestationType: d.WanAttestationType,\n\t\tAuthenticator:   *d.WanAuthenticator,\n\t}\n\treturn\n}\n\nfunc (d *Device) CheckLevel(level int) bool {\n\tif d.AlertLevels == nil {\n\t\treturn false\n\t}\n\n\tfor _, lvl := range d.AlertLevels {\n\t\tif level == lvl {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (d *Device) Commit(db *database.Database) (err error) {\n\tcoll := db.Devices()\n\n\terr = coll.Commit(d.Id, d)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Device) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Devices()\n\n\terr = coll.CommitFields(d.Id, d, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Device) Insert(db *database.Database) (err error) {\n\tcoll := db.Devices()\n\n\t_, err = coll.InsertOne(db, d)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "device/facet.go",
    "content": "package device\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\ntype FacetVersion struct {\n\tMajor int `json:\"major\"`\n\tMinor int `json:\"minor\"`\n}\n\ntype TrustedFacet struct {\n\tIds     []string      `json:\"ids\"`\n\tVersion *FacetVersion `json:\"version\"`\n}\n\ntype Facets struct {\n\tTrustedFacets []*TrustedFacet `json:\"trustedFacets\"`\n}\n\nfunc GetFacets() (facets *Facets) {\n\treturn &Facets{\n\t\tTrustedFacets: []*TrustedFacet{\n\t\t\t&TrustedFacet{\n\t\t\t\tIds: settings.Local.Facets,\n\t\t\t\tVersion: &FacetVersion{\n\t\t\t\t\tMajor: 1,\n\t\t\t\t\tMinor: 0,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "device/utils.go",
    "content": "package device\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nfunc Get(db *database.Database, devcId bson.ObjectID) (\n\tdevc *Device, err error) {\n\n\tcoll := db.Devices()\n\tdevc = &Device{}\n\n\terr = coll.FindOneId(devcId, devc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetUser(db *database.Database, devcId bson.ObjectID,\n\tuserId bson.ObjectID) (devc *Device, err error) {\n\n\tcoll := db.Devices()\n\tdevc = &Device{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":  devcId,\n\t\t\"user\": userId,\n\t}).Decode(devc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, userId bson.ObjectID) (\n\tdevices []*Device, err error) {\n\n\tcoll := db.Devices()\n\tdevices = []*Device{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"user\": userId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdevc := &Device{}\n\t\terr = cursor.Decode(devc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdevices = append(devices, devc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllSorted(db *database.Database, userId bson.ObjectID) (\n\tdevices []*Device, err error) {\n\n\tcoll := db.Devices()\n\tdevices = []*Device{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"user\": userId,\n\t}, options.Find().\n\t\tSetSort(bson.D{\n\t\t\t{\"mode\", 1},\n\t\t\t{\"name\", 1},\n\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdevc := &Device{}\n\t\terr = cursor.Decode(devc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdevices = append(devices, devc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllMode(db *database.Database, userId bson.ObjectID,\n\tmode string) (devices []*Device, err error) {\n\n\tcoll := db.Devices()\n\tdevices = []*Device{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"user\": userId,\n\t\t\"mode\": mode,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdevc := &Device{}\n\t\terr = cursor.Decode(devc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdevices = append(devices, devc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc CountSecondary(db *database.Database, userId bson.ObjectID) (\n\tcount int64, err error) {\n\n\tcoll := db.Devices()\n\n\tcount, err = coll.CountDocuments(db, &bson.M{\n\t\t\"user\": userId,\n\t\t\"mode\": Secondary,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc New(userId bson.ObjectID, typ, mode string) (devc *Device) {\n\tdevc = &Device{\n\t\tId:         bson.NewObjectID(),\n\t\tType:       typ,\n\t\tMode:       mode,\n\t\tUser:       userId,\n\t\tTimestamp:  time.Now(),\n\t\tLastActive: time.Now(),\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, id bson.ObjectID) (err error) {\n\tcoll := db.Devices()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": id,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveUser(db *database.Database, id bson.ObjectID,\n\tuserId bson.ObjectID) (err error) {\n\n\tcoll := db.Devices()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":  id,\n\t\t\"user\": userId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveAll(db *database.Database, userId bson.ObjectID) (err error) {\n\tcoll := db.Devices()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"user\": userId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dhcpc/constants.go",
    "content": "package dhcpc\n\nimport (\n\t\"time\"\n)\n\nconst (\n\tMaxMessageSize  = 1500\n\tDefaultInterval = 60 * time.Second\n\tDhcpTimeout     = 10 * time.Second\n\tDhcpRetries     = 3\n)\n"
  },
  {
    "path": "dhcpc/dhcpc.go",
    "content": "package dhcpc\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/tools/logger\"\n)\n\ntype Dhcpc struct {\n\tInterval    time.Duration\n\tImdsAddress string\n\tImdsPort    int\n\tImdsSecret  string\n\tDhcpIface   string\n\tDhcpIface6  string\n\tDhcpIp      *net.IPNet\n\tDhcpIp6     *net.IPNet\n\tlease       *Lease\n\tsyncTrigger chan struct{}\n}\n\nfunc (d *Dhcpc) startSync() {\n\tim := &Imds{\n\t\tAddress: d.ImdsAddress,\n\t\tPort:    d.ImdsPort,\n\t\tSecret:  d.ImdsSecret,\n\t}\n\n\tticker := time.NewTicker(60 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\te := im.Sync(d.lease)\n\t\tif e != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": e,\n\t\t\t}).Error(\"dhcpc: Failed to sync lease with imds\")\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\"address\":   d.lease.Address.String(),\n\t\t\t\"gateway\":   d.lease.Gateway.String(),\n\t\t\t\"server\":    d.lease.ServerAddress.String(),\n\t\t\t\"time\":      d.lease.LeaseTime.String(),\n\t\t}).Info(\"dhcpc: Synced\")\n\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\tcase <-d.syncTrigger:\n\t\t\tticker.Reset(60 * time.Second)\n\t\t}\n\t}\n}\n\nfunc (d *Dhcpc) sync() {\n\tselect {\n\tcase d.syncTrigger <- struct{}{}:\n\tdefault:\n\t}\n}\n\nfunc (d *Dhcpc) run4() (err error) {\n\td.lease.Gateway = nil\n\td.lease.ServerAddress = nil\n\td.lease.LeaseTime = 0\n\td.lease.TransactionId = \"\"\n\n\tfor {\n\t\tfor {\n\t\t\tok, e := d.lease.Exchange4()\n\t\t\tif e != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\t\t\"address\":   d.lease.Address.String(),\n\t\t\t\t\t\"error\":     e,\n\t\t\t\t}).Error(\"dhcpc: Failed to exchange lease4\")\n\t\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !ok {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\t}).Error(\"dhcpc: Failed to receive lease4\")\n\t\t\t\ttime.Sleep(1000 * time.Millisecond)\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\"address\":   d.lease.Address.String(),\n\t\t\t\"gateway\":   d.lease.Gateway.String(),\n\t\t\t\"server\":    d.lease.ServerAddress.String(),\n\t\t\t\"time\":      d.lease.LeaseTime.String(),\n\t\t}).Info(\"dhcpc: Exchanged ipv4\")\n\n\t\td.sync()\n\n\t\tready4 := false\n\t\tfor i := 0; i < 20; i++ {\n\t\t\tready4, _ = d.lease.IfaceReady()\n\t\t\tif ready4 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\n\t\tif !ready4 {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\t\"address\":   d.lease.Address.String(),\n\t\t\t}).Error(\"dhcpc: Interface4 ready timeout\")\n\t\t}\n\n\t\tfor {\n\t\t\ttime.Sleep(d.Interval)\n\n\t\t\tready4, _ := d.lease.IfaceReady()\n\t\t\tif !ready4 {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\t\t\"address\":   d.lease.Address.String(),\n\t\t\t\t}).Error(\"dhcpc: Interface4 not ready\")\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tok, e := d.lease.Renew4()\n\t\t\tif e != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\t\t\"address\":   d.lease.Address.String(),\n\t\t\t\t\t\"error\":     e,\n\t\t\t\t}).Error(\"dhcpc: Failed to renew lease4\")\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif !ok {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\t\t\"address\":   d.lease.Address.String(),\n\t\t\t\t\t\"error\":     err,\n\t\t\t\t}).Error(\"dhcpc: Failed to receive lease4 renewal\")\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\t\"address\":   d.lease.Address.String(),\n\t\t\t\t\"gateway\":   d.lease.Gateway.String(),\n\t\t\t\t\"server\":    d.lease.ServerAddress.String(),\n\t\t\t\t\"time\":      d.lease.LeaseTime.String(),\n\t\t\t}).Info(\"dhcpc: Renewed ipv4\")\n\n\t\t\td.sync()\n\t\t}\n\t}\n}\n\nfunc (d *Dhcpc) run6() (err error) {\n\td.lease.ServerAddress6 = nil\n\td.lease.LeaseTime6 = 0\n\td.lease.TransactionId6 = \"\"\n\td.lease.IaId6 = [4]byte{}\n\td.lease.ServerId6 = nil\n\n\tfor {\n\t\tfor {\n\t\t\tok, e := d.lease.Exchange6()\n\t\t\tif e != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface6\": d.DhcpIface6,\n\t\t\t\t\t\"address6\":   d.lease.Address6.String(),\n\t\t\t\t\t\"error\":      e,\n\t\t\t\t}).Error(\"dhcpc: Failed to exchange lease6\")\n\t\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !ok {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface6\": d.DhcpIface6,\n\t\t\t\t}).Error(\"dhcpc: Failed to receive lease6\")\n\t\t\t\ttime.Sleep(1000 * time.Millisecond)\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"interface6\": d.DhcpIface6,\n\t\t\t\"address6\":   d.lease.Address6.String(),\n\t\t\t\"server6\":    d.lease.ServerAddress6.String(),\n\t\t\t\"time6\":      d.lease.LeaseTime6.String(),\n\t\t}).Info(\"dhcpc: Exchanged ipv6\")\n\n\t\td.sync()\n\n\t\tready6 := false\n\t\tfor i := 0; i < 20; i++ {\n\t\t\t_, ready6 = d.lease.IfaceReady()\n\t\t\tif ready6 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\n\t\tif !ready6 {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"interface6\": d.DhcpIface6,\n\t\t\t\t\"address6\":   d.lease.Address6.String(),\n\t\t\t}).Error(\"dhcpc: Interface6 ready timeout\")\n\t\t}\n\n\t\tfor {\n\t\t\ttime.Sleep(d.Interval)\n\n\t\t\t_, ready6 := d.lease.IfaceReady()\n\t\t\tif !ready6 {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface6\": d.DhcpIface6,\n\t\t\t\t\t\"address6\":   d.lease.Address6.String(),\n\t\t\t\t}).Error(\"dhcpc: Interface6 not ready\")\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tok, e := d.lease.Renew6()\n\t\t\tif e != nil {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface6\": d.DhcpIface6,\n\t\t\t\t\t\"address6\":   d.lease.Address6.String(),\n\t\t\t\t\t\"error\":      e,\n\t\t\t\t}).Error(\"dhcpc: Failed to renew lease6\")\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif !ok {\n\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\"interface6\": d.DhcpIface6,\n\t\t\t\t\t\"address6\":   d.lease.Address6.String(),\n\t\t\t\t\t\"error\":      err,\n\t\t\t\t}).Error(\"dhcpc: Failed to receive lease6 renewal\")\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"interface6\": d.DhcpIface6,\n\t\t\t\t\"address6\":   d.lease.Address6.String(),\n\t\t\t\t\"server6\":    d.lease.ServerAddress6.String(),\n\t\t\t\t\"time6\":      d.lease.LeaseTime6.String(),\n\t\t\t}).Info(\"dhcpc: Renewed ipv6\")\n\n\t\t\td.sync()\n\t\t}\n\t}\n}\n\nfunc (d *Dhcpc) Run(ip4, ip6 bool) {\n\td.lease = &Lease{\n\t\tIface:    d.DhcpIface,\n\t\tIface6:   d.DhcpIface6,\n\t\tAddress:  d.DhcpIp,\n\t\tAddress6: d.DhcpIp6,\n\t}\n\n\tgo d.startSync()\n\n\twaiters := &sync.WaitGroup{}\n\tif ip4 {\n\t\twaiters.Add(1)\n\t\tgo func() {\n\t\t\tdefer waiters.Done()\n\t\t\tfor {\n\t\t\t\terr := d.run4()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\t\t\t\"address\":   d.DhcpIp,\n\t\t\t\t\t\t\"address6\":  d.DhcpIp6,\n\t\t\t\t\t\t\"error\":     err,\n\t\t\t\t\t}).Error(\"dhcpc: Run error\")\n\t\t\t\t}\n\n\t\t\t\ttime.Sleep(3 * time.Second)\n\t\t\t}\n\t\t}()\n\t}\n\n\tif ip6 {\n\t\twaiters.Add(1)\n\t\tgo func() {\n\t\t\tdefer waiters.Done()\n\t\t\tfor {\n\t\t\t\terr := d.run6()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\t\t\"interface\": d.DhcpIface,\n\t\t\t\t\t\t\"address\":   d.DhcpIp,\n\t\t\t\t\t\t\"address6\":  d.DhcpIp6,\n\t\t\t\t\t\t\"error\":     err,\n\t\t\t\t\t}).Error(\"dhcpc: Run error\")\n\t\t\t\t}\n\n\t\t\t\ttime.Sleep(3 * time.Second)\n\t\t\t}\n\t\t}()\n\t}\n\n\twaiters.Wait()\n}\n\nfunc Main() (err error) {\n\timdsAddress := os.Getenv(\"IMDS_ADDRESS\")\n\timdsPort := os.Getenv(\"IMDS_PORT\")\n\timdsSecret := os.Getenv(\"IMDS_SECRET\")\n\tdhcpIface := os.Getenv(\"DHCP_IFACE\")\n\tdhcpIface6 := os.Getenv(\"DHCP_IFACE6\")\n\tdhcpIp := os.Getenv(\"DHCP_IP\")\n\tdhcpIp6 := os.Getenv(\"DHCP_IP6\")\n\tinternval := os.Getenv(\"DHCP_INTERVAL\")\n\tos.Unsetenv(\"IMDS_ADDRESS\")\n\tos.Unsetenv(\"IMDS_PORT\")\n\tos.Unsetenv(\"IMDS_SECRET\")\n\tos.Unsetenv(\"DHCP_IFACE\")\n\tos.Unsetenv(\"DHCP_IFACE6\")\n\tos.Unsetenv(\"DHCP_IP\")\n\tos.Unsetenv(\"DHCP_IP6\")\n\tos.Unsetenv(\"DHCP_INTERVAL\")\n\n\tlogger.Init(\n\t\tlogger.SetTimeFormat(\"\"),\n\t)\n\n\tlogLock := sync.Mutex{}\n\tlogger.AddHandler(func(record *logger.Record) {\n\t\tlogLock.Lock()\n\t\tfmt.Print(record.String())\n\t\tlogLock.Unlock()\n\t})\n\n\tip4 := false\n\tflag.BoolVar(&ip4, \"ip4\", false, \"Enable IPv4\")\n\n\tip6 := false\n\tflag.BoolVar(&ip6, \"ip6\", false, \"Enable IPv6\")\n\n\tflag.Parse()\n\n\timdsPortInt, _ := strconv.Atoi(imdsPort)\n\tinternvalInt, _ := strconv.Atoi(internval)\n\n\tclient := &Dhcpc{\n\t\tImdsAddress: strings.Split(imdsAddress, \"/\")[0],\n\t\tImdsPort:    imdsPortInt,\n\t\tImdsSecret:  imdsSecret,\n\t\tDhcpIface:   dhcpIface,\n\t\tDhcpIface6:  dhcpIface6,\n\t\tsyncTrigger: make(chan struct{}, 1),\n\t}\n\n\tif internvalInt != 0 {\n\t\tclient.Interval = time.Duration(internvalInt) * time.Second\n\t} else {\n\t\tclient.Interval = DefaultInterval\n\t}\n\n\tif dhcpIp != \"\" {\n\t\tip, ipnet, _ := net.ParseCIDR(dhcpIp)\n\t\tif ip != nil && ipnet != nil {\n\t\t\tipnet.IP = ip\n\t\t\tclient.DhcpIp = ipnet\n\t\t}\n\t}\n\n\tif dhcpIp6 != \"\" {\n\t\tip, ipnet, _ := net.ParseCIDR(dhcpIp6)\n\t\tif ip != nil && ipnet != nil {\n\t\t\tipnet.IP = ip\n\t\t\tclient.DhcpIp6 = ipnet\n\t\t}\n\t}\n\n\tclient.Run(ip4, ip6)\n\n\treturn\n}\n"
  },
  {
    "path": "dhcpc/imds.go",
    "content": "package dhcpc\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nvar (\n\thttpClient = &http.Client{\n\t\tTimeout: 10 * time.Second,\n\t}\n)\n\ntype SyncData struct {\n\tAddress  string `json:\"address\"`\n\tGateway  string `json:\"gateway\"`\n\tAddress6 string `json:\"address6\"`\n\tGateway6 string `json:\"gateway6\"`\n}\n\ntype Imds struct {\n\tAddress string `json:\"address\"`\n\tPort    int    `json:\"port\"`\n\tSecret  string `json:\"secret\"`\n}\n\nfunc (m *Imds) NewRequest(method, pth string, data interface{}) (\n\treq *http.Request, err error) {\n\n\tu := &url.URL{}\n\tu.Scheme = \"http\"\n\tu.Host = fmt.Sprintf(\"%s:%d\", m.Address, m.Port)\n\tu.Path = pth\n\n\tvar body io.Reader\n\tif data != nil {\n\t\treqDataBuf := &bytes.Buffer{}\n\t\terr = json.NewEncoder(reqDataBuf).Encode(data)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"dhcpc: Failed to parse request data\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tbody = reqDataBuf\n\t}\n\n\treq, err = http.NewRequest(method, u.String(), body)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to create imds request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"pritunl-dhcp\")\n\treq.Header.Set(\"Auth-Token\", m.Secret)\n\tif data != nil {\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t}\n\n\treturn\n}\n\nfunc (m *Imds) Sync(lease *Lease) (err error) {\n\treq, err := m.NewRequest(\"PUT\", \"/dhcp\", lease)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: Imds request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tbody := \"\"\n\t\tdata, _ := ioutil.ReadAll(resp.Body)\n\t\tif data != nil {\n\t\t\tbody = string(data)\n\t\t}\n\n\t\terrData := &errortypes.ErrorData{}\n\t\terr = json.Unmarshal(data, errData)\n\t\tif err != nil || errData.Error == \"\" {\n\t\t\terrData = nil\n\t\t}\n\n\t\tif errData != nil && errData.Message != \"\" {\n\t\t\tbody = errData.Message\n\t\t}\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\"dhcpc: Imds server sync error %d - %s\",\n\t\t\t\tresp.StatusCode, body),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dhcpc/lease.go",
    "content": "package dhcpc\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/insomniacslk/dhcp/dhcpv6\"\n)\n\ntype Lease struct {\n\tIface              string        `json:\"iface\"`\n\tIface6             string        `json:\"iface6\"`\n\tAddress            *net.IPNet    `json:\"address\"`\n\tGateway            net.IP        `json:\"gateway\"`\n\tAddress6           *net.IPNet    `json:\"address6\"`\n\tServerAddress      net.IP        `json:\"server\"`\n\tServerAddress6     net.IP        `json:\"server6\"`\n\tLeaseTime          time.Duration `json:\"ttl\"`\n\tLeaseTime6         time.Duration `json:\"ttl6\"`\n\tPreferredLifetime6 time.Duration `json:\"-\"`\n\tValidLifetime6     time.Duration `json:\"-\"`\n\tTransactionId      string        `json:\"-\"`\n\tTransactionId6     string        `json:\"-\"`\n\tIaId6              [4]byte       `json:\"-\"`\n\tServerId6          dhcpv6.DUID   `json:\"-\"`\n}\n\nfunc (l *Lease) IfaceReady() (ready4, ready6 bool) {\n\tiface, err := net.InterfaceByName(l.Iface)\n\tif err != nil {\n\t\treturn\n\t}\n\n\taddrs, _ := iface.Addrs()\n\tfor _, addr := range addrs {\n\t\tipnet, ok := addr.(*net.IPNet)\n\t\tif ok {\n\t\t\tif l.Address != nil && ipnet.IP.Equal(l.Address.IP) {\n\t\t\t\tready4 = true\n\t\t\t}\n\n\t\t\tif l.Address6 != nil && ipnet.IP.Equal(l.Address6.IP) {\n\t\t\t\tready6 = true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dhcpc/lease4.go",
    "content": "package dhcpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/insomniacslk/dhcp/dhcpv4\"\n\t\"github.com/insomniacslk/dhcp/dhcpv4/nclient4\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n)\n\nfunc (l *Lease) Renew4() (ok bool, err error) {\n\tif l.Address == nil || l.Address.IP == nil || l.ServerAddress == nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"dhcpc: Cannot call renew with unset address\"),\n\t\t}\n\t\treturn\n\t}\n\n\tiface, err := net.InterfaceByName(l.Iface)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to find interface\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdhLease, err := buildDhLease(l, iface.HardwareAddr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tserverAddr := &net.UDPAddr{\n\t\tIP:   l.ServerAddress,\n\t\tPort: nclient4.ServerPort,\n\t}\n\n\tclient, err := nclient4.New(l.Iface,\n\t\tnclient4.WithServerAddr(serverAddr),\n\t\tnclient4.WithTimeout(DhcpTimeout),\n\t\tnclient4.WithRetry(DhcpRetries),\n\t\tnclient4.WithUnicast(&net.UDPAddr{\n\t\t\tIP:   l.Address.IP,\n\t\t\tPort: nclient4.ClientPort,\n\t\t}),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to create client\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer client.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), DhcpTimeout)\n\tdefer cancel()\n\n\trenewedLease, err := client.Renew(ctx, dhLease,\n\t\tdhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to exchange renewal\"),\n\t\t}\n\t\treturn\n\t}\n\n\trenewed := extractDhLease(renewedLease)\n\tif renewed != nil {\n\t\tok = true\n\t\tl.Address = renewed.Address\n\t\tl.Gateway = renewed.Gateway\n\t\tl.ServerAddress = renewed.ServerAddress\n\t\tl.LeaseTime = renewed.LeaseTime\n\t\tl.TransactionId = renewed.TransactionId\n\t}\n\n\treturn\n}\n\nfunc (l *Lease) Exchange4() (ok bool, err error) {\n\tclient, err := nclient4.New(\n\t\tl.Iface,\n\t\tnclient4.WithTimeout(DhcpTimeout),\n\t\tnclient4.WithRetry(DhcpRetries),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to create client\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer client.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), DhcpTimeout)\n\tdefer cancel()\n\n\topts := []dhcpv4.Modifier{\n\t\tdhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)),\n\t}\n\n\tif l.Address != nil && l.Address.IP != nil {\n\t\topts = append(opts, dhcpv4.WithOption(\n\t\t\tdhcpv4.OptRequestedIPAddress(l.Address.IP)))\n\t}\n\n\tnclientLease, err := client.Request(\n\t\tctx,\n\t\topts...,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: IPv4 exchange failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tlease := extractDhLease(nclientLease)\n\tif lease != nil {\n\t\tok = true\n\t\tl.Address = lease.Address\n\t\tl.Gateway = lease.Gateway\n\t\tl.ServerAddress = lease.ServerAddress\n\t\tl.LeaseTime = lease.LeaseTime\n\t\tl.TransactionId = lease.TransactionId\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dhcpc/lease6.go",
    "content": "package dhcpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/insomniacslk/dhcp/dhcpv6\"\n\t\"github.com/insomniacslk/dhcp/dhcpv6/nclient6\"\n\t\"github.com/insomniacslk/dhcp/iana\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n)\n\nfunc (l *Lease) Renew6() (ok bool, err error) {\n\tif l.Address6 == nil || l.Address6.IP == nil || l.ServerAddress == nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"dhcpc: Cannot call renew with unset address\"),\n\t\t}\n\t\treturn\n\t}\n\n\tiface, err := net.InterfaceByName(l.Iface)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to find interface\"),\n\t\t}\n\t\treturn\n\t}\n\n\tclient, err := nclient6.New(\n\t\tl.Iface,\n\t\tnclient6.WithTimeout(DhcpTimeout),\n\t\tnclient6.WithRetry(DhcpRetries),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to create dhcp6 client\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer client.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), DhcpTimeout)\n\tdefer cancel()\n\n\tserverAddr := &net.UDPAddr{\n\t\tIP:   l.ServerAddress6,\n\t\tPort: 547,\n\t}\n\n\tiaAddr := &dhcpv6.OptIAAddress{\n\t\tIPv6Addr: l.Address6.IP,\n\t}\n\n\tif l.PreferredLifetime6 > 0 {\n\t\tiaAddr.PreferredLifetime = l.PreferredLifetime6\n\t}\n\tif l.ValidLifetime6 > 0 {\n\t\tiaAddr.ValidLifetime = l.ValidLifetime6\n\t}\n\n\tmsg, err := dhcpv6.NewMessage()\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to create dhcp6 message\"),\n\t\t}\n\t\treturn\n\t}\n\tmsg.MessageType = dhcpv6.MessageTypeRenew\n\tmsg.AddOption(dhcpv6.OptClientID(&dhcpv6.DUIDLLT{\n\t\tHWType:        iana.HWTypeEthernet,\n\t\tLinkLayerAddr: iface.HardwareAddr,\n\t}))\n\tmsg.AddOption(dhcpv6.OptServerID(l.ServerId6))\n\t// msg.AddOption(&dhcpv6.OptFQDN{\n\t// \tFlags: 0x01,\n\t// \tDomainName: &rfc1035label.Labels{\n\t// \t\tLabels: []string{\"instance-name\"},\n\t// \t},\n\t// })\n\t// msg.AddOption(dhcpv6.OptRequestedOption(\n\t// \tdhcpv6.OptionDNSRecursiveNameServer,\n\t// \tdhcpv6.OptionDomainSearchList,\n\t// ))\n\t// msg.AddOption(dhcpv6.OptElapsedTime(0))\n\tmsg.UpdateOption(&dhcpv6.OptIANA{\n\t\tIaId: l.IaId6,\n\t\tOptions: dhcpv6.IdentityOptions{\n\t\t\tOptions: []dhcpv6.Option{iaAddr},\n\t\t},\n\t})\n\n\treply, err := client.SendAndRead(\n\t\tctx,\n\t\tserverAddr,\n\t\tmsg,\n\t\tnclient6.IsMessageType(dhcpv6.MessageTypeReply),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to renew DHCPv6 lease\"),\n\t\t}\n\t\treturn\n\t}\n\n\trenewed := extractDhcpv6Lease(reply, l.Iface)\n\tif renewed != nil && renewed.Address6 != nil {\n\t\tok = true\n\t\tl.Address6 = renewed.Address6\n\t\tl.ServerAddress6 = renewed.ServerAddress6\n\t\tl.PreferredLifetime6 = renewed.PreferredLifetime6\n\t\tl.ValidLifetime6 = renewed.ValidLifetime6\n\t\tl.LeaseTime6 = renewed.LeaseTime6\n\t\tl.TransactionId6 = renewed.TransactionId6\n\t\tl.ServerId6 = renewed.ServerId6\n\t}\n\n\treturn\n}\n\nfunc (l *Lease) Exchange6() (ok bool, err error) {\n\tiface, err := net.InterfaceByName(l.Iface)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to find interface\"),\n\t\t}\n\t\treturn\n\t}\n\n\tclient, err := nclient6.New(\n\t\tl.Iface,\n\t\tnclient6.WithTimeout(DhcpTimeout),\n\t\tnclient6.WithRetry(DhcpRetries),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to create DHCPv6 client\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer client.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), DhcpTimeout)\n\tdefer cancel()\n\n\tl.IaId6 = [4]byte{0, 0, 0, 1}\n\n\tmodifiers := []dhcpv6.Modifier{\n\t\tdhcpv6.WithClientID(&dhcpv6.DUIDLLT{\n\t\t\tHWType:        iana.HWTypeEthernet,\n\t\t\tLinkLayerAddr: iface.HardwareAddr,\n\t\t}),\n\t\tdhcpv6.WithRequestedOptions(\n\t\t\tdhcpv6.OptionDNSRecursiveNameServer,\n\t\t\tdhcpv6.OptionDomainSearchList,\n\t\t),\n\t\t//dhcpv6.WithFQDN(0x01, \"instance-name\"),\n\t\tdhcpv6.WithIAID(l.IaId6),\n\t}\n\n\tif l.Address6 != nil && l.Address6.IP != nil {\n\t\tiaAddr := &dhcpv6.OptIAAddress{\n\t\t\tIPv6Addr: l.Address6.IP,\n\t\t}\n\t\tiaNa := &dhcpv6.OptIANA{\n\t\t\tIaId: l.IaId6,\n\t\t\tOptions: dhcpv6.IdentityOptions{\n\t\t\t\tOptions: []dhcpv6.Option{iaAddr},\n\t\t\t},\n\t\t}\n\t\tmodifiers = append(modifiers, dhcpv6.WithOption(iaNa))\n\t}\n\n\treply, err := client.RapidSolicit(ctx, modifiers...)\n\tif err != nil {\n\t\treply, err = client.Solicit(ctx, modifiers...)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"dhcpc: DHCPv6 exchange failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tlease := extractDhcpv6Lease(reply, l.Iface)\n\tif lease != nil && lease.Address6 != nil {\n\t\tok = true\n\t\tl.Address6 = lease.Address6\n\t\tl.ServerAddress6 = lease.ServerAddress6\n\t\tl.PreferredLifetime6 = lease.PreferredLifetime6\n\t\tl.ValidLifetime6 = lease.ValidLifetime6\n\t\tl.LeaseTime6 = lease.LeaseTime6\n\t\tl.TransactionId6 = lease.TransactionId6\n\t\tl.ServerId6 = lease.ServerId6\n\t}\n\n\treturn\n}\n\nfunc extractDhcpv6Lease(reply *dhcpv6.Message, ifaceName string) *Lease {\n\tif reply == nil {\n\t\treturn nil\n\t}\n\n\tlease := &Lease{\n\t\tIface:          ifaceName,\n\t\tTransactionId6: reply.TransactionID.String(),\n\t}\n\n\tserverID := reply.Options.ServerID()\n\tif serverID != nil {\n\t\tlease.ServerId6 = reply.Options.ServerID()\n\t}\n\n\t// // Extract server address from relay message or use link-local\n\t// relayMsg := reply.GetOneOption(dhcpv6.OptionRelayMsg)\n\t// if relayMsg != nil {\n\t// \t// Server address might be in relay message\n\t// \tif rm, ok := relayMsg.(*dhcpv6.OptRelayMessage); ok && rm.RelayMessage != nil {\n\t// \t\tlease.ServerAddress = rm.RelayMessage.PeerAddr\n\t// \t}\n\t// }\n\n\t// // Extract unicast server address from Option 12 if available\n\t// unicastOpt := reply.GetOneOption(dhcpv6.OptionUnicast)\n\t// if unicastOpt != nil {\n\t// \t// Option 12 contains the server's unicast IPv6 address\n\t// \tif unicastData := unicastOpt.ToBytes(); len(unicastData) >= 16 {\n\t// \t\tlease.ServerAddress6 = net.IP(unicastData[:16])\n\t// \t}\n\t// }\n\n\t// Fallback to multicast if unicast not available\n\tif lease.ServerAddress6 == nil {\n\t\tlease.ServerAddress6 = dhcpv6.AllDHCPRelayAgentsAndServers\n\t}\n\n\tiana := reply.Options.OneIANA()\n\tif iana != nil {\n\t\tlease.IaId6 = iana.IaId\n\n\t\tfor _, opt := range iana.Options.Options {\n\t\t\tif addr, ok := opt.(*dhcpv6.OptIAAddress); ok {\n\t\t\t\tlease.Address6 = &net.IPNet{\n\t\t\t\t\tIP:   addr.IPv6Addr,\n\t\t\t\t\tMask: net.CIDRMask(64, 128),\n\t\t\t\t}\n\t\t\t\tlease.PreferredLifetime6 = addr.PreferredLifetime\n\t\t\t\tlease.ValidLifetime6 = addr.ValidLifetime\n\t\t\t\tlease.LeaseTime6 = addr.ValidLifetime\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn lease\n}\n"
  },
  {
    "path": "dhcpc/systemd.go",
    "content": "package dhcpc\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/features\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/permission\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst systemdNamespaceTemplate = `[Unit]\nDescription=Pritunl Cloud DHCP Client\nAfter=network.target\n\n[Service]\nType=simple\nUser=%s\nEnvironment=\"IMDS_ADDRESS=%s\"\nEnvironment=\"IMDS_PORT=%d\"\nEnvironment=\"IMDS_SECRET=%s\"\nEnvironment=\"DHCP_IFACE=%s\"\nEnvironment=\"DHCP_IFACE6=%s\"\nEnvironment=\"DHCP_IP=%s\"\nEnvironment=\"DHCP_IP6=%s\"\nEnvironment=\"DHCP_INTERVAL=%d\"\nExecStart=/usr/bin/pritunl-cloud %s dhcp-client\nTimeoutStopSec=5\nRestart=always\nRestartSec=3\nPrivateTmp=true\nProtectHome=true\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\nNetworkNamespacePath=/var/run/netns/%s\nAmbientCapabilities=CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_NET_ADMIN\n`\n\nconst systemdTemplate = `[Unit]\nDescription=Pritunl Cloud DHCP Client\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nEnvironment=\"IMDS_ADDRESS=%s\"\nEnvironment=\"IMDS_PORT=%d\"\nEnvironment=\"IMDS_SECRET=%s\"\nEnvironment=\"DHCP_IFACE=%s\"\nEnvironment=\"DHCP_IFACE6=%s\"\nEnvironment=\"DHCP_IP=%s\"\nEnvironment=\"DHCP_IP6=%s\"\nEnvironment=\"DHCP_INTERVAL=%d\"\nExecStart=/usr/sbin/ip netns exec %s /usr/bin/pritunl-cloud %s dhcp-client\nTimeoutStopSec=5\nRestart=always\nRestartSec=3\nPrivateTmp=true\nProtectHome=true\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\nAmbientCapabilities=CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_NET_ADMIN\n`\n\nfunc WriteService(vmId bson.ObjectID,\n\tnamespace, imdsSecret, dhcpIface, dhcpIface6, dhcpIp, dhcpIp6 string,\n\tip4, ip6, systemdNamespace bool) (err error) {\n\n\tunitPath := paths.GetUnitPathDhcpc(vmId)\n\n\tif imdsSecret == \"\" {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"dhcpc: Cannot start dhcp client with empty secret\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif dhcpIface == \"\" {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"dhcpc: Cannot start dhcp client with empty iface\"),\n\t\t}\n\t\treturn\n\t}\n\n\targs := []string{}\n\tif ip4 {\n\t\targs = append(args, \"-ip4\")\n\t}\n\tif ip6 {\n\t\targs = append(args, \"-ip6\")\n\t}\n\n\toutput := \"\"\n\tif systemdNamespace {\n\t\toutput = fmt.Sprintf(\n\t\t\tsystemdNamespaceTemplate,\n\t\t\tpermission.GetUserName(vmId),\n\t\t\tsettings.Hypervisor.ImdsAddress,\n\t\t\tsettings.Hypervisor.ImdsPort,\n\t\t\timdsSecret,\n\t\t\tdhcpIface,\n\t\t\tdhcpIface6,\n\t\t\tdhcpIp,\n\t\t\tdhcpIp6,\n\t\t\tsettings.Hypervisor.DhcpRenewTtl,\n\t\t\tstrings.Join(args, \" \"),\n\t\t\tnamespace,\n\t\t)\n\t} else {\n\t\toutput = fmt.Sprintf(\n\t\t\tsystemdTemplate,\n\t\t\tsettings.Hypervisor.ImdsAddress,\n\t\t\tsettings.Hypervisor.ImdsPort,\n\t\t\timdsSecret,\n\t\t\tdhcpIface,\n\t\t\tdhcpIface6,\n\t\t\tdhcpIp,\n\t\t\tdhcpIp6,\n\t\t\tsettings.Hypervisor.DhcpRenewTtl,\n\t\t\tstrings.Join(args, \" \"),\n\t\t\tnamespace,\n\t\t)\n\t}\n\n\terr = utils.CreateWrite(unitPath, output, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Start(db *database.Database, virt *vm.VirtualMachine,\n\tiface, iface6 string, ip4, ip6 bool) (err error) {\n\n\tnamespace := vm.GetNamespace(virt.Id, 0)\n\n\thasSystemdNamespace := features.HasSystemdNamespace()\n\tunit := paths.GetUnitNameDhcpc(virt.Id)\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\":           virt.Id.Hex(),\n\t\t\"systemd_unit\": unit,\n\t}).Info(\"dhcpc: Starting virtual machine dhcp client\")\n\n\t_ = systemd.Stop(unit)\n\n\terr = WriteService(virt.Id, namespace, virt.ImdsDhcpSecret, iface, iface6,\n\t\tvirt.DhcpIp, virt.DhcpIp6, ip4, ip6, hasSystemdNamespace)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Reload()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Start(unit)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Stop(virt *vm.VirtualMachine) (err error) {\n\tunit := paths.GetUnitNameDhcpc(virt.Id)\n\n\t_ = systemd.Stop(unit)\n\n\treturn\n}\n"
  },
  {
    "path": "dhcpc/utils.go",
    "content": "package dhcpc\n\nimport (\n\t\"encoding/hex\"\n\t\"net\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/insomniacslk/dhcp/dhcpv4\"\n\t\"github.com/insomniacslk/dhcp/dhcpv4/nclient4\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n)\n\nfunc buildDhLease(lease *Lease, addr net.HardwareAddr) (\n\tdhLease *nclient4.Lease, err error) {\n\n\txid := TransactionIdUnmarshal(lease.TransactionId)\n\n\toffer, err := dhcpv4.New(\n\t\tdhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),\n\t\tdhcpv4.WithTransactionID(xid),\n\t\tdhcpv4.WithHwAddr(addr),\n\t\tdhcpv4.WithYourIP(lease.Address.IP),\n\t\tdhcpv4.WithServerIP(lease.ServerAddress),\n\t\tdhcpv4.WithGatewayIP(lease.Gateway),\n\t\tdhcpv4.WithOption(dhcpv4.OptSubnetMask(\n\t\t\tnet.IPMask(lease.Address.Mask))),\n\t\tdhcpv4.WithOption(dhcpv4.OptRouter(lease.Gateway)),\n\t\tdhcpv4.WithOption(dhcpv4.OptServerIdentifier(lease.ServerAddress)),\n\t\tdhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(lease.LeaseTime)),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to create offer message\"),\n\t\t}\n\t\treturn\n\t}\n\n\tack, err := dhcpv4.New(\n\t\tdhcpv4.WithMessageType(dhcpv4.MessageTypeAck),\n\t\tdhcpv4.WithTransactionID(xid),\n\t\tdhcpv4.WithHwAddr(addr),\n\t\tdhcpv4.WithYourIP(lease.Address.IP),\n\t\tdhcpv4.WithServerIP(lease.ServerAddress),\n\t\tdhcpv4.WithGatewayIP(lease.Gateway),\n\t\tdhcpv4.WithOption(dhcpv4.OptSubnetMask(\n\t\t\tnet.IPMask(lease.Address.Mask))),\n\t\tdhcpv4.WithOption(dhcpv4.OptRouter(lease.Gateway)),\n\t\tdhcpv4.WithOption(dhcpv4.OptServerIdentifier(lease.ServerAddress)),\n\t\tdhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(lease.LeaseTime)),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcpc: Failed to create ack message\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdhLease = &nclient4.Lease{\n\t\tOffer: offer,\n\t\tACK:   ack,\n\t}\n\n\treturn\n}\n\nfunc extractDhLease(dhLease *nclient4.Lease) (lease *Lease) {\n\tif dhLease == nil || dhLease.ACK == nil {\n\t\treturn\n\t}\n\n\tack := dhLease.ACK\n\n\tlease = &Lease{\n\t\tAddress: &net.IPNet{\n\t\t\tIP:   ack.YourIPAddr,\n\t\t\tMask: net.IPMask(net.IP{255, 255, 255, 0}),\n\t\t},\n\t\tGateway:       ack.GatewayIPAddr,\n\t\tServerAddress: ack.ServerIPAddr,\n\t\tTransactionId: ack.TransactionID.String(),\n\t}\n\n\tif subnet := ack.SubnetMask(); subnet != nil {\n\t\tlease.Address.Mask = subnet\n\t}\n\n\tif lease.Gateway.Equal(net.IPv4zero) || lease.Gateway == nil {\n\t\tif routers := ack.Router(); len(routers) > 0 {\n\t\t\tlease.Gateway = routers[0]\n\t\t}\n\t}\n\n\tserverID := ack.ServerIdentifier()\n\tif serverID != nil {\n\t\tlease.ServerAddress = serverID\n\t}\n\n\tleaseTime := ack.IPAddressLeaseTime(0)\n\tif leaseTime > 0 {\n\t\tlease.LeaseTime = leaseTime\n\t}\n\n\treturn\n}\n\nfunc TransactionIdUnmarshal(str string) dhcpv4.TransactionID {\n\tvar tranId dhcpv4.TransactionID\n\n\tif len(str) >= 2 && str[:2] == \"0x\" {\n\t\tstr = str[2:]\n\t}\n\n\tbytes, err := hex.DecodeString(str)\n\tif err != nil {\n\t\treturn tranId\n\t}\n\n\tif len(bytes) != 4 {\n\t\treturn tranId\n\t}\n\n\tcopy(tranId[:], bytes)\n\treturn tranId\n}\n"
  },
  {
    "path": "dhcps/dhcp4.go",
    "content": "package dhcps\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/insomniacslk/dhcp/dhcpv4\"\n\t\"github.com/insomniacslk/dhcp/dhcpv4/server4\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Server4 struct {\n\tIface        string   `json:\"iface\"`\n\tClientIp     string   `json:\"client_ip\"`\n\tGatewayIp    string   `json:\"gateway_ip\"`\n\tPrefixLen    int      `json:\"prefix_len\"`\n\tDnsServers   []string `json:\"dns_servers\"`\n\tMtu          int      `json:\"mtu\"`\n\tLifetime     int      `json:\"lifetime\"`\n\tDebug        bool     `json:\"debug\"`\n\tdnsServersIp []net.IP\n\tserver       *server4.Server\n\tlifetime     time.Duration\n}\n\nfunc (s *Server4) handler(conn net.PacketConn, peer net.Addr,\n\treq *dhcpv4.DHCPv4) {\n\n\terr := s.handleMsg(conn, peer, req)\n\tif err != nil {\n\t\tif s.Debug {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"peer\":  peer.String(),\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"dhcps: DHCPv4 handler error\")\n\t\t}\n\t}\n}\n\nfunc (s *Server4) handleMsg(conn net.PacketConn, peer net.Addr,\n\treq *dhcpv4.DHCPv4) (err error) {\n\n\tif req.MessageType() != dhcpv4.MessageTypeDiscover &&\n\t\treq.MessageType() != dhcpv4.MessageTypeRequest {\n\n\t\treturn\n\t}\n\n\tif s.Debug {\n\t\tfmt.Printf(\"Peer: %s\\n\", peer.String())\n\t\tfmt.Println(req.Summary())\n\t}\n\n\tresp, err := dhcpv4.NewReplyFromRequest(req)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to create reply\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif req.MessageType() == dhcpv4.MessageTypeRequest {\n\t\tresp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))\n\t} else {\n\t\tresp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))\n\t}\n\n\tgatewayIp := net.ParseIP(s.GatewayIp)\n\tclientIp := net.ParseIP(s.ClientIp)\n\n\tresp.YourIPAddr = clientIp\n\tresp.UpdateOption(dhcpv4.OptRouter(gatewayIp))\n\tresp.UpdateOption(dhcpv4.OptSubnetMask(\n\t\tnet.CIDRMask(s.PrefixLen, net.IPv4len*8)))\n\tresp.UpdateOption(dhcpv4.OptServerIdentifier(gatewayIp))\n\tresp.UpdateOption(dhcpv4.OptIPAddressLeaseTime(s.lifetime))\n\n\trequested := req.ParameterRequestList()\n\tif requested.Has(dhcpv4.OptionDomainNameServer) {\n\t\tresp.UpdateOption(dhcpv4.OptDNS(s.dnsServersIp...))\n\t}\n\n\tif s.Mtu != 0 {\n\t\tresp.UpdateOption(dhcpv4.Option{\n\t\t\tCode:  dhcpv4.OptionInterfaceMTU,\n\t\t\tValue: dhcpv4.Uint16(s.Mtu),\n\t\t})\n\t}\n\n\tif s.Debug {\n\t\tfmt.Printf(\"Peer: %s\\n\", peer.String())\n\t\tfmt.Println(resp.Summary())\n\t}\n\n\t_, err = conn.WriteTo(resp.ToBytes(), peer)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"dhcps: DHCPv4 resp write error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Server4) Start() (err error) {\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"iface\":       s.Iface,\n\t\t\"client_ip\":   s.ClientIp,\n\t\t\"gateway_ip\":  s.GatewayIp,\n\t\t\"prefix_len\":  s.PrefixLen,\n\t\t\"dns_servers\": s.DnsServers,\n\t\t\"mtu\":         s.Mtu,\n\t\t\"lifetime\":    s.Lifetime,\n\t\t\"debug\":       s.Debug,\n\t}).Info(\"dhcps: Starting server4\")\n\n\ts.lifetime = time.Duration(s.Lifetime) * time.Second\n\n\tif s.DnsServers != nil && len(s.DnsServers) > 0 {\n\t\tdnsServers := []net.IP{}\n\t\tfor _, dnsServer := range s.DnsServers {\n\t\t\tdnsServers = append(dnsServers, net.ParseIP(dnsServer))\n\t\t}\n\t\ts.dnsServersIp = dnsServers\n\t}\n\n\thost4 := &net.UDPAddr{\n\t\tPort: dhcpv4.ServerPort,\n\t}\n\n\ts.server, err = server4.NewServer(s.Iface, host4, s.handler)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to create server4\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = s.server.Serve()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to start server4\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dhcps/dhcp6.go",
    "content": "package dhcps\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/insomniacslk/dhcp/dhcpv6\"\n\t\"github.com/insomniacslk/dhcp/dhcpv6/server6\"\n\t\"github.com/insomniacslk/dhcp/iana\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Server6 struct {\n\tIface        string   `json:\"iface\"`\n\tClientIp     string   `json:\"client_ip\"`\n\tGatewayIp    string   `json:\"gateway_ip\"`\n\tPrefixLen    int      `json:\"prefix_len\"`\n\tDnsServers   []string `json:\"dns_servers\"`\n\tMtu          int      `json:\"mtu\"`\n\tLifetime     int      `json:\"lifetime\"`\n\tDebug        bool     `json:\"debug\"`\n\tserverId     dhcpv6.DUID\n\tdnsServersIp []net.IP\n\tserver       *server6.Server\n\tlifetime     time.Duration\n\tprefix       *net.IPNet\n\tclientAddr   net.IP\n}\n\nfunc (s *Server6) handler(conn net.PacketConn, peer net.Addr,\n\treq dhcpv6.DHCPv6) {\n\n\terr := s.handleMsg(conn, peer, req)\n\tif err != nil {\n\t\tif s.Debug {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"peer\":  peer.String(),\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"dhcps: DHCPv6 handler error\")\n\t\t}\n\t}\n}\n\nfunc (s *Server6) handleMsg(conn net.PacketConn, peer net.Addr,\n\treq dhcpv6.DHCPv6) (err error) {\n\n\tmsg, err := req.GetInnerMessage()\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcps: DHCPv6 get inner message error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tclientId := msg.Options.ClientID()\n\tif clientId == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"dhcps: DHCPv6 missing client id\"),\n\t\t}\n\t\treturn\n\t}\n\n\tserverId := msg.Options.ServerID()\n\n\tif s.Debug {\n\t\tfmt.Printf(\"Peer: %s\\n\", peer.String())\n\t\tfmt.Println(msg.Summary())\n\t}\n\n\tswitch msg.Type() {\n\tcase dhcpv6.MessageTypeSolicit, dhcpv6.MessageTypeConfirm,\n\t\tdhcpv6.MessageTypeRebind:\n\n\t\tif serverId != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"dhcps: DHCPv6 invalid server id\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\tcase dhcpv6.MessageTypeRequest, dhcpv6.MessageTypeRenew,\n\t\tdhcpv6.MessageTypeRelease, dhcpv6.MessageTypeDecline:\n\n\t\tif serverId == nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"dhcps: DHCPv6 missing server id\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif !serverId.Equal(s.serverId) {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"dhcps: DHCPv6 server id mismatch\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tvar resp dhcpv6.DHCPv6\n\tswitch msg.Type() {\n\tcase dhcpv6.MessageTypeSolicit:\n\t\trapidCommit := msg.GetOneOption(dhcpv6.OptionRapidCommit)\n\t\tif rapidCommit != nil {\n\t\t\tresp, err = dhcpv6.NewReplyFromMessage(msg)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err, \"dhcps: DHCPv6 new reply \"+\n\t\t\t\t\t\t\"from message error\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tresp, err = dhcpv6.NewAdvertiseFromSolicit(msg)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err, \"dhcps: DHCPv6 new advertise \"+\n\t\t\t\t\t\t\"from solicit error\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase dhcpv6.MessageTypeRequest, dhcpv6.MessageTypeConfirm,\n\t\tdhcpv6.MessageTypeRenew, dhcpv6.MessageTypeRebind,\n\t\tdhcpv6.MessageTypeRelease, dhcpv6.MessageTypeInformationRequest:\n\n\t\tresp, err = dhcpv6.NewReplyFromMessage(msg)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"dhcps: DHCPv6 new reply \"+\n\t\t\t\t\t\"from message error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"dhcps: Unknown DHCPv6 message type\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp.AddOption(dhcpv6.OptServerID(s.serverId))\n\n\terr = s.process(msg, req, resp)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif s.Debug {\n\t\tfmt.Printf(\"Peer: %s\\n\", peer.String())\n\t\tfmt.Println(resp.Summary())\n\t}\n\n\t_, err = conn.WriteTo(resp.ToBytes(), peer)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"dhcps: DHCPv6 reply write error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Server6) process(msg *dhcpv6.Message,\n\treq, resp dhcpv6.DHCPv6) (err error) {\n\n\tswitch msg.Type() {\n\tcase dhcpv6.MessageTypeSolicit, dhcpv6.MessageTypeRequest,\n\t\tdhcpv6.MessageTypeConfirm, dhcpv6.MessageTypeRenew,\n\t\tdhcpv6.MessageTypeRebind:\n\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"dhcps: DHCPv6 ignore message type %s\", msg.Type()),\n\t\t}\n\t\treturn\n\t}\n\n\toia := &dhcpv6.OptIANA{\n\t\tT1: s.lifetime / 2,\n\t\tT2: time.Duration(float32(s.lifetime) / 1.5),\n\t}\n\n\troia := msg.Options.OneIANA()\n\tif roia != nil {\n\t\tcopy(oia.IaId[:], roia.IaId[:])\n\t} else {\n\t\tcopy(oia.IaId[:], []byte(\"CLOUD\"))\n\t}\n\n\toiaAddr := &dhcpv6.OptIAAddress{\n\t\tIPv6Addr:          s.clientAddr,\n\t\tPreferredLifetime: s.lifetime,\n\t\tValidLifetime:     s.lifetime,\n\t}\n\n\toia.Options = dhcpv6.IdentityOptions{\n\t\tOptions: []dhcpv6.Option{\n\t\t\toiaAddr,\n\t\t},\n\t}\n\n\tresp.AddOption(oia)\n\n\tiapd := msg.Options.OneIAPD()\n\tif iapd != nil {\n\t\trespIapd := &dhcpv6.OptIAPD{\n\t\t\tT1: s.lifetime / 2,\n\t\t\tT2: time.Duration(float32(s.lifetime) / 1.5),\n\t\t}\n\t\tcopy(respIapd.IaId[:], iapd.IaId[:])\n\n\t\tprefixOpt := &dhcpv6.OptIAPrefix{\n\t\t\tPreferredLifetime: s.lifetime,\n\t\t\tValidLifetime:     s.lifetime,\n\t\t\tPrefix:            s.prefix,\n\t\t}\n\n\t\trespIapd.Options = dhcpv6.PDOptions{\n\t\t\tOptions: []dhcpv6.Option{\n\t\t\t\tprefixOpt,\n\t\t\t},\n\t\t}\n\n\t\tresp.AddOption(respIapd)\n\t}\n\n\tif msg.IsOptionRequested(dhcpv6.OptionDNSRecursiveNameServer) &&\n\t\ts.dnsServersIp != nil {\n\n\t\tresp.UpdateOption(dhcpv6.OptDNS(s.dnsServersIp...))\n\t}\n\n\tfqdn := msg.GetOneOption(dhcpv6.OptionFQDN)\n\tif fqdn != nil {\n\t\tresp.AddOption(fqdn)\n\t}\n\n\tresp.AddOption(&dhcpv6.OptStatusCode{\n\t\tStatusCode:    iana.StatusSuccess,\n\t\tStatusMessage: \"success\",\n\t})\n\n\treturn\n}\n\nfunc (s *Server6) Start() (err error) {\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"iface\":       s.Iface,\n\t\t\"client_ip\":   s.ClientIp,\n\t\t\"gateway_ip\":  s.GatewayIp,\n\t\t\"prefix_len\":  s.PrefixLen,\n\t\t\"dns_servers\": s.DnsServers,\n\t\t\"mtu\":         s.Mtu,\n\t\t\"lifetime\":    s.Lifetime,\n\t\t\"debug\":       s.Debug,\n\t}).Info(\"dhcps: Starting server6\")\n\n\ts.lifetime = time.Duration(s.Lifetime) * time.Second\n\n\tiface, err := net.InterfaceByName(s.Iface)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to find network interface\"),\n\t\t}\n\t\treturn\n\t}\n\n\tclientAddr, prefix, err := net.ParseCIDR(fmt.Sprintf(\n\t\t\"%s/%d\", s.ClientIp, s.PrefixLen))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to parse client IP and prefix\"),\n\t\t}\n\t\treturn\n\t}\n\ts.clientAddr = clientAddr\n\ts.prefix = prefix\n\n\tif !s.prefix.Contains(s.clientAddr) {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"dhcps: Client IP not within prefix\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(s.DnsServers) > 0 {\n\t\tdnsServers := []net.IP{}\n\t\tfor _, dnsServer := range s.DnsServers {\n\t\t\tdnsServers = append(dnsServers, net.ParseIP(dnsServer))\n\t\t}\n\t\ts.dnsServersIp = dnsServers\n\t}\n\n\ts.serverId = &dhcpv6.DUIDLLT{\n\t\tHWType:        iana.HWTypeEthernet,\n\t\tLinkLayerAddr: iface.HardwareAddr,\n\t\tTime:          dhcpv6.GetTime(),\n\t}\n\n\thost6 := &net.UDPAddr{\n\t\tIP:   net.ParseIP(\"::\"),\n\t\tPort: dhcpv6.DefaultServerPort,\n\t}\n\n\ts.server, err = server6.NewServer(iface.Name, host6, s.handler)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to create server6\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = s.server.Serve()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to start server6\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dhcps/ndp.go",
    "content": "package dhcps\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/mdlayher/ndp\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype ServerNdp struct {\n\tIface       string   `json:\"iface\"`\n\tClientIp    string   `json:\"client_ip\"`\n\tGatewayIp   string   `json:\"gateway_ip\"`\n\tPrefixLen   int      `json:\"prefix_len\"`\n\tDnsServers  []string `json:\"dns_servers\"`\n\tMtu         int      `json:\"mtu\"`\n\tLifetime    int      `json:\"lifetime\"`\n\tDelay       int      `json:\"delay\"`\n\tDebug       bool     `json:\"debug\"`\n\tiface       *net.Interface\n\tgatewayAddr netip.Addr\n\tprefixAddr  netip.Addr\n\tlifetime    time.Duration\n\tdelay       time.Duration\n}\n\nfunc (s *ServerNdp) Start() (err error) {\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"iface\":       s.Iface,\n\t\t\"client_ip\":   s.ClientIp,\n\t\t\"gateway_ip\":  s.GatewayIp,\n\t\t\"prefix_len\":  s.PrefixLen,\n\t\t\"dns_servers\": s.DnsServers,\n\t\t\"mtu\":         s.Mtu,\n\t\t\"lifetime\":    s.Lifetime,\n\t\t\"delay\":       s.Delay,\n\t\t\"debug\":       s.Debug,\n\t}).Info(\"dhcps: Starting ndp server\")\n\n\ts.lifetime = time.Duration(s.Lifetime) * time.Second\n\ts.delay = time.Duration(s.Delay) * time.Second\n\n\ts.iface, err = net.InterfaceByName(s.Iface)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to find network interface\"),\n\t\t}\n\t\treturn\n\t}\n\n\ts.gatewayAddr, err = netip.ParseAddr(s.GatewayIp)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to parse gateway addr\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, prefixNet, err := net.ParseCIDR(fmt.Sprintf(\n\t\t\"%s/%d\", s.ClientIp, s.PrefixLen))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to parse client IP and prefix\"),\n\t\t}\n\t\treturn\n\t}\n\n\tprefix, err := netip.ParsePrefix(prefixNet.String())\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to parse client addr and prefix\"),\n\t\t}\n\t\treturn\n\t}\n\n\ts.prefixAddr = prefix.Masked().Addr()\n\n\tfor {\n\t\terr = s.run()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"dhcps: NDP server error\")\n\t\t}\n\n\t\ttime.Sleep(s.delay)\n\t}\n}\n\nfunc (s *ServerNdp) run() (err error) {\n\tconn, _, err := ndp.Listen(s.iface, ndp.LinkLocal)\n\tif err != nil {\n\t\terr = &errortypes.NetworkError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to listen for NDP messages\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.JoinGroup(netip.MustParseAddr(\"ff02::2\"))\n\tif err != nil {\n\t\terr = &errortypes.NetworkError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to join NDP group\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = s.sendAdvertise(conn, netip.IPv6LinkLocalAllNodes())\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *ServerNdp) sendAdvertise(conn *ndp.Conn, dst netip.Addr) (err error) {\n\topts := []ndp.Option{\n\t\t&ndp.PrefixInformation{\n\t\t\tPrefix:                         s.prefixAddr,\n\t\t\tPrefixLength:                   uint8(s.PrefixLen),\n\t\t\tOnLink:                         true,\n\t\t\tAutonomousAddressConfiguration: false,\n\t\t\tValidLifetime:                  s.lifetime,\n\t\t\tPreferredLifetime:              s.lifetime,\n\t\t},\n\t\t&ndp.LinkLayerAddress{\n\t\t\tDirection: ndp.Source,\n\t\t\tAddr:      s.iface.HardwareAddr,\n\t\t},\n\t}\n\n\tif s.Mtu != 0 {\n\t\topts = append(opts, &ndp.MTU{\n\t\t\tMTU: uint32(s.Mtu),\n\t\t})\n\t}\n\n\tif len(s.DnsServers) > 0 {\n\t\tdnsAddrs := make([]netip.Addr, 0, len(s.DnsServers))\n\t\tfor _, dns := range s.DnsServers {\n\t\t\taddr, err := netip.ParseAddr(dns)\n\t\t\tif err == nil && addr.Is6() {\n\t\t\t\tdnsAddrs = append(dnsAddrs, addr)\n\t\t\t}\n\t\t}\n\t\tif len(dnsAddrs) > 0 {\n\t\t\topts = append(opts, &ndp.RecursiveDNSServer{\n\t\t\t\tLifetime: s.lifetime,\n\t\t\t\tServers:  dnsAddrs,\n\t\t\t})\n\t\t}\n\t}\n\n\tmsgRa := &ndp.RouterAdvertisement{\n\t\tCurrentHopLimit:           64,\n\t\tRouterSelectionPreference: ndp.Medium,\n\t\tRouterLifetime:            s.lifetime,\n\t\tManagedConfiguration:      true,\n\t\tOtherConfiguration:        true,\n\t\tOptions:                   opts,\n\t}\n\n\tif s.Debug {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"gateway\":         s.gatewayAddr.String(),\n\t\t\t\"prefix\":          s.prefixAddr.String(),\n\t\t\t\"prefix_len\":      s.PrefixLen,\n\t\t\t\"router_lifetime\": s.lifetime,\n\t\t}).Info(\"dhcps: Sending router advertisement\")\n\t}\n\n\terr = conn.WriteTo(msgRa, nil, dst)\n\tif err != nil {\n\t\terr = &errortypes.NetworkError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to write NDP message\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *ServerNdp) readSolicitations(conn *ndp.Conn) (err error) {\n\tif s.Debug {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"gateway\":         s.gatewayAddr.String(),\n\t\t\t\"prefix\":          s.prefixAddr.String(),\n\t\t\t\"prefix_len\":      s.PrefixLen,\n\t\t\t\"router_lifetime\": s.lifetime,\n\t\t}).Info(\"dhcps: Reading router solicitations\")\n\t}\n\n\terr = conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\tmsg, _, from, err := conn.ReadFrom()\n\tif err != nil {\n\t\tif netErr, ok := err.(net.Error); ok && netErr.Timeout() {\n\t\t\terr = nil\n\t\t\treturn\n\t\t}\n\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to read NDP message\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif _, ok := msg.(*ndp.RouterSolicitation); ok {\n\t\tif s.Debug {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"from\": from.String(),\n\t\t\t}).Info(\"dhcps: Received Router Solicitation\")\n\t\t}\n\n\t\terr = s.sendAdvertise(conn, from)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dhcps/systemd.go",
    "content": "package dhcps\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/features\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/permission\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tdhcpCaps = \"CAP_NET_BIND_SERVICE CAP_NET_BROADCAST\"\n\tndpCaps  = \"CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW\"\n)\n\nconst systemdTemplate = `[Unit]\nDescription=Pritunl Cloud %s\nAfter=network.target\n\n[Service]\nEnvironment=CONFIG='%s'\nType=simple\nUser=root\nExecStart=/usr/sbin/ip netns exec %s %s %s\nTimeoutStopSec=5\nPrivateTmp=true\nProtectHome=true\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\nAmbientCapabilities=%s\n`\n\nconst systemdNamespaceTemplate = `[Unit]\nDescription=Pritunl Cloud %s\nAfter=network.target\n\n[Service]\nEnvironment=CONFIG='%s'\nType=simple\nUser=%s\nExecStart=%s %s\nTimeoutStopSec=5\nPrivateTmp=true\nProtectHome=true\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\nNetworkNamespacePath=/var/run/netns/%s\nAmbientCapabilities=%s\n`\n\nfunc UpdateEbtables(vmId bson.ObjectID, namespace string) (err error) {\n\tiface := vm.GetIface(vmId, 0)\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"iptables\",\n\t\t\"-t\", \"mangle\",\n\t\t\"-A\", \"POSTROUTING\",\n\t\t\"-o\", settings.Hypervisor.BridgeIfaceName,\n\t\t\"-p\", \"udp\",\n\t\t\"-m\", \"udp\",\n\t\t\"--sport\", \"67\",\n\t\t\"-j\", \"CHECKSUM\",\n\t\t\"--checksum-fill\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ip6tables\",\n\t\t\"-t\", \"mangle\",\n\t\t\"-A\", \"POSTROUTING\",\n\t\t\"-o\", settings.Hypervisor.BridgeIfaceName,\n\t\t\"-p\", \"udp\",\n\t\t\"-m\", \"udp\",\n\t\t\"--sport\", \"547\",\n\t\t\"-j\", \"CHECKSUM\",\n\t\t\"--checksum-fill\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-I\", \"OUTPUT\",\n\t\t\"-o\", iface,\n\t\t\"-p\", \"IPv4\",\n\t\t\"--ip-protocol\", \"udp\",\n\t\t\"--ip-sport\", \"67\",\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-A\", \"OUTPUT\",\n\t\t\"-p\", \"IPv4\",\n\t\t\"--ip-protocol\", \"udp\",\n\t\t\"--ip-sport\", \"67\",\n\t\t\"-j\", \"DROP\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-I\", \"OUTPUT\",\n\t\t\"-o\", iface,\n\t\t\"-p\", \"IPv6\",\n\t\t\"--ip6-protocol\", \"udp\",\n\t\t\"--ip6-sport\", \"547\",\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-A\", \"OUTPUT\",\n\t\t\"-p\", \"IPv6\",\n\t\t\"--ip6-protocol\", \"udp\",\n\t\t\"--ip6-sport\", \"547\",\n\t\t\"-j\", \"DROP\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-I\", \"OUTPUT\",\n\t\t\"-o\", iface,\n\t\t\"-p\", \"IPv6\",\n\t\t\"--ip6-protocol\", \"ipv6-icmp\",\n\t\t\"--ip6-icmp-type\", \"134\",\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-A\", \"OUTPUT\",\n\t\t\"-p\", \"IPv6\",\n\t\t\"--ip6-protocol\", \"ipv6-icmp\",\n\t\t\"--ip6-icmp-type\", \"134\",\n\t\t\"-j\", \"DROP\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ClearEbtables(vmId bson.ObjectID, namespace string) (err error) {\n\tiface := vm.GetIface(vmId, 0)\n\n\t_, _ = utils.ExecCombinedOutput(\n\t\t\"\",\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"iptables\",\n\t\t\"-t\", \"mangle\",\n\t\t\"-D\", \"POSTROUTING\",\n\t\t\"-o\", settings.Hypervisor.BridgeIfaceName,\n\t\t\"-p\", \"udp\",\n\t\t\"-m\", \"udp\",\n\t\t\"--sport\", \"67\",\n\t\t\"-j\", \"CHECKSUM\",\n\t\t\"--checksum-fill\",\n\t)\n\t_, _ = utils.ExecCombinedOutput(\n\t\t\"\",\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ip6tables\",\n\t\t\"-t\", \"mangle\",\n\t\t\"-D\", \"POSTROUTING\",\n\t\t\"-o\", settings.Hypervisor.BridgeIfaceName,\n\t\t\"-p\", \"udp\",\n\t\t\"-m\", \"udp\",\n\t\t\"--sport\", \"547\",\n\t\t\"-j\", \"CHECKSUM\",\n\t\t\"--checksum-fill\",\n\t)\n\n\t_, _ = utils.ExecCombinedOutput(\n\t\t\"\",\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-D\", \"OUTPUT\",\n\t\t\"-o\", iface,\n\t\t\"-p\", \"IPv4\",\n\t\t\"--ip-protocol\", \"udp\",\n\t\t\"--ip-sport\", \"67\",\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\t_, _ = utils.ExecCombinedOutput(\n\t\t\"\",\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-D\", \"OUTPUT\",\n\t\t\"-p\", \"IPv4\",\n\t\t\"--ip-protocol\", \"udp\",\n\t\t\"--ip-sport\", \"67\",\n\t\t\"-j\", \"DROP\",\n\t)\n\n\t_, _ = utils.ExecCombinedOutput(\n\t\t\"\",\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-D\", \"OUTPUT\",\n\t\t\"-o\", iface,\n\t\t\"-p\", \"IPv6\",\n\t\t\"--ip6-protocol\", \"udp\",\n\t\t\"--ip6-sport\", \"547\",\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\t_, _ = utils.ExecCombinedOutput(\n\t\t\"\",\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-D\", \"OUTPUT\",\n\t\t\"-p\", \"IPv6\",\n\t\t\"--ip6-protocol\", \"udp\",\n\t\t\"--ip6-sport\", \"547\",\n\t\t\"-j\", \"DROP\",\n\t)\n\n\t_, _ = utils.ExecCombinedOutput(\n\t\t\"\",\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-D\", \"OUTPUT\",\n\t\t\"-o\", iface,\n\t\t\"-p\", \"IPv6\",\n\t\t\"--ip6-protocol\", \"ipv6-icmp\",\n\t\t\"--ip6-icmp-type\", \"134\",\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\t_, _ = utils.ExecCombinedOutput(\n\t\t\"\",\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"ebtables\",\n\t\t\"-D\", \"OUTPUT\",\n\t\t\"-p\", \"IPv6\",\n\t\t\"--ip6-protocol\", \"ipv6-icmp\",\n\t\t\"--ip6-icmp-type\", \"134\",\n\t\t\"-j\", \"DROP\",\n\t)\n\n\treturn\n}\n\nfunc WriteService(vmId bson.ObjectID, namespace string,\n\tconfig interface{}, systemdNamespace bool) (err error) {\n\n\tparam := \"\"\n\tunitPath := \"\"\n\tcaps := \"\"\n\n\tcurPath, err := os.Executable()\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to get executable path\"),\n\t\t}\n\t\treturn\n\t}\n\n\tconfData, err := json.Marshal(config)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"dhcps: Failed to marshal config\"),\n\t\t}\n\t\treturn\n\t}\n\n\tswitch config.(type) {\n\tcase *Server4:\n\t\tparam = \"dhcp4-server\"\n\t\tunitPath = paths.GetUnitPathDhcp4(vmId, 0)\n\t\tcaps = dhcpCaps\n\t\tbreak\n\tcase *Server6:\n\t\tparam = \"dhcp6-server\"\n\t\tunitPath = paths.GetUnitPathDhcp6(vmId, 0)\n\t\tcaps = dhcpCaps\n\t\tbreak\n\tcase *ServerNdp:\n\t\tparam = \"ndp-server\"\n\t\tunitPath = paths.GetUnitPathNdp(vmId, 0)\n\t\tcaps = ndpCaps\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.TypeError{\n\t\t\terrors.New(\"dhcps: Unknown config type\"),\n\t\t}\n\t\treturn\n\t}\n\n\toutput := \"\"\n\tif systemdNamespace {\n\t\toutput = fmt.Sprintf(\n\t\t\tsystemdNamespaceTemplate,\n\t\t\tparam,\n\t\t\tstring(confData),\n\t\t\tpermission.GetUserName(vmId),\n\t\t\tcurPath,\n\t\t\tparam,\n\t\t\tnamespace,\n\t\t\tcaps,\n\t\t)\n\t} else {\n\t\toutput = fmt.Sprintf(\n\t\t\tsystemdTemplate,\n\t\t\tparam,\n\t\t\tstring(confData),\n\t\t\tnamespace,\n\t\t\tcurPath,\n\t\t\tparam,\n\t\t\tcaps,\n\t\t)\n\t}\n\n\terr = utils.CreateWrite(unitPath, output, 0644)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Start(db *database.Database, virt *vm.VirtualMachine,\n\tdc *datacenter.Datacenter, zne *zone.Zone, vc *vpc.Vpc) (err error) {\n\n\tnamespace := vm.GetNamespace(virt.Id, 0)\n\n\thasSystemdNamespace := features.HasSystemdNamespace()\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\": virt.Id.Hex(),\n\t}).Info(\"dhcps: Starting virtual machine dhcp server\")\n\n\tif virt.NetworkAdapters == nil || len(virt.NetworkAdapters) < 1 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"dhcps: Missing virt network adapter\"),\n\t\t}\n\t\treturn\n\t}\n\tsubnetId := virt.NetworkAdapters[0].Subnet\n\n\tvcNet, err := vc.GetNetwork()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcidr, _ := vcNet.Mask.Size()\n\taddr, gatewayAddr, err := vc.GetIp(db, subnetId, virt.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\taddr6 := vc.GetIp6(virt.Id)\n\tgatewayAddr6 := vc.GetGatewayIp6(virt.Id)\n\n\tmtu := dc.GetInstanceMtu()\n\n\tserver4 := &Server4{\n\t\tIface:     settings.Hypervisor.BridgeIfaceName,\n\t\tClientIp:  addr.String(),\n\t\tGatewayIp: gatewayAddr.String(),\n\t\tPrefixLen: cidr,\n\t\tDnsServers: []string{\n\t\t\tstrings.Split(settings.Hypervisor.ImdsAddress, \"/\")[0],\n\t\t\tzne.GetDnsServerPrimary(),\n\t\t\tzne.GetDnsServerSecondary(),\n\t\t},\n\t\tMtu:      mtu,\n\t\tLifetime: settings.Hypervisor.DhcpLifetime,\n\t}\n\tserver6 := &Server6{\n\t\tIface:      settings.Hypervisor.BridgeIfaceName,\n\t\tClientIp:   addr6.String(),\n\t\tGatewayIp:  gatewayAddr6.String(),\n\t\tPrefixLen:  64,\n\t\tDnsServers: []string{},\n\t\tMtu:        mtu,\n\t\tLifetime:   settings.Hypervisor.DhcpLifetime,\n\t}\n\tserverNdp := &ServerNdp{\n\t\tIface:      settings.Hypervisor.BridgeIfaceName,\n\t\tClientIp:   addr6.String(),\n\t\tGatewayIp:  gatewayAddr6.String(),\n\t\tPrefixLen:  64,\n\t\tDnsServers: []string{},\n\t\tMtu:        mtu,\n\t\tLifetime:   settings.Hypervisor.DhcpLifetime,\n\t\tDelay:      settings.Hypervisor.NdpRaInterval,\n\t}\n\n\terr = UpdateEbtables(virt.Id, namespace)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tunitServer4 := paths.GetUnitNameDhcp4(virt.Id, 0)\n\tunitServer6 := paths.GetUnitNameDhcp6(virt.Id, 0)\n\tunitServerNdp := paths.GetUnitNameNdp(virt.Id, 0)\n\n\t_ = systemd.Stop(unitServer4)\n\t_ = systemd.Stop(unitServer6)\n\t_ = systemd.Stop(unitServerNdp)\n\n\terr = WriteService(virt.Id, namespace, server4, hasSystemdNamespace)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = WriteService(virt.Id, namespace, server6, hasSystemdNamespace)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = WriteService(virt.Id, namespace, serverNdp, hasSystemdNamespace)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Reload()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Start(unitServer4)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = systemd.Start(unitServer6)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = systemd.Start(unitServerNdp)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Stop(virt *vm.VirtualMachine) (err error) {\n\tnamespace := vm.GetNamespace(virt.Id, 0)\n\tunitServer4 := paths.GetUnitNameDhcp4(virt.Id, 0)\n\tunitServer6 := paths.GetUnitNameDhcp6(virt.Id, 0)\n\tunitServerNdp := paths.GetUnitNameNdp(virt.Id, 0)\n\n\t_ = systemd.Stop(unitServer4)\n\t_ = systemd.Stop(unitServer6)\n\t_ = systemd.Stop(unitServerNdp)\n\n\terr = ClearEbtables(virt.Id, namespace)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "disk/constants.go",
    "content": "package disk\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n)\n\nconst (\n\tProvision = \"provision\"\n\tAvailable = \"available\"\n\tAttached  = \"attached\"\n\n\tSnapshot = \"snapshot\"\n\tBackup   = \"backup\"\n\tExpand   = \"expand\"\n\tRestore  = \"restore\"\n\tDestroy  = \"destroy\"\n\n\tQcow2 = \"qcow2\"\n\tLvm   = \"lvm\"\n\n\tXfs     = \"xfs\"\n\tExt4    = \"ext4\"\n\tLvmXfs  = \"lvm_xfs\"\n\tLvmExt4 = \"lvm_ext4\"\n\n\tLinux         = \"linux\"\n\tLinuxLegacy   = \"linux_legacy\"\n\tLinuxUnsigned = \"linux_unsigned\"\n\tBsd           = \"bsd\"\n\n\tAlpineLinux = \"alpinelinux\"\n\tArchLinux   = \"archlinux\"\n\tRedHat      = \"redhat\"\n\tFedora      = \"fedora\"\n\tUbuntu      = \"ubuntu\"\n\tFreeBSD     = \"freebsd\"\n)\n\nvar (\n\tVacant           = bson.NilObjectID\n\tValidSystemTypes = set.NewSet(\n\t\tLinux,\n\t\tLinuxLegacy,\n\t\tLinuxUnsigned,\n\t\tBsd,\n\t)\n\tValidSystemKinds = set.NewSet(\n\t\tAlpineLinux,\n\t\tArchLinux,\n\t\tRedHat,\n\t\tFedora,\n\t\tUbuntu,\n\t\tFreeBSD,\n\t)\n)\n"
  },
  {
    "path": "disk/disk.go",
    "content": "package disk\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/lock\"\n\t\"github.com/pritunl/pritunl-cloud/lvm\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Disk struct {\n\tId               bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName             string        `bson:\"name\" json:\"name\"`\n\tComment          string        `bson:\"comment\" json:\"comment\"`\n\tCreated          time.Time     `bson:\"created\" json:\"created\"`\n\tState            string        `bson:\"state\" json:\"state\"`\n\tAction           string        `bson:\"action\" json:\"action\"`\n\tType             string        `bson:\"type\" json:\"type\"`\n\tSystemType       string        `bson:\"system_type\" json:\"system_type\"`\n\tSystemKind       string        `bson:\"system_kind\" json:\"system_kind\"`\n\tUuid             string        `bson:\"uuid\" json:\"uuid\"`\n\tDatacenter       bson.ObjectID `bson:\"datacenter\" json:\"datacenter\"`\n\tZone             bson.ObjectID `bson:\"zone\" json:\"zone\"`\n\tNode             bson.ObjectID `bson:\"node\" json:\"node\"`\n\tPool             bson.ObjectID `bson:\"pool\" json:\"pool\"`\n\tOrganization     bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tInstance         bson.ObjectID `bson:\"instance\" json:\"instance\"`\n\tSourceInstance   bson.ObjectID `bson:\"source_instance\" json:\"source_instance\"`\n\tDeployment       bson.ObjectID `bson:\"deployment\" json:\"deployment\"`\n\tDeleteProtection bool          `bson:\"delete_protection\" json:\"delete_protection\"`\n\tFileSystem       string        `bson:\"file_system\" json:\"file_system\"`\n\tImage            bson.ObjectID `bson:\"image\" json:\"image\"`\n\tRestoreImage     bson.ObjectID `bson:\"restore_image\" json:\"restore_image\"`\n\tBacking          bool          `bson:\"backing\" json:\"backing\"`\n\tBackingImage     string        `bson:\"backing_image\" json:\"backing_image\"`\n\tIndex            string        `bson:\"index\" json:\"index\"`\n\tSize             int           `bson:\"size\" json:\"size\"`\n\tLvSize           int           `bson:\"lv_size\" json:\"lv_size\"`\n\tNewSize          int           `bson:\"new_size\" json:\"new_size\"`\n\tBackup           bool          `bson:\"backup\" json:\"backup\"`\n\tLastBackup       time.Time     `bson:\"last_backup\" json:\"last_backup\"`\n\tcurIndex         string        `bson:\"-\" json:\"-\"`\n\tcurInstance      bson.ObjectID `bson:\"-\" json:\"-\"`\n}\n\nfunc (d *Disk) IsActive() bool {\n\treturn d.State == Available || d.State == Attached\n}\n\nfunc (d *Disk) IsAvailable() bool {\n\tif d.State == Available && d.Action == \"\" && d.Instance.IsZero() {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (d *Disk) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\td.Name = utils.FilterName(d.Name)\n\n\tif d.Organization.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"organization_required\",\n\t\t\tMessage: \"Missing required organization\",\n\t\t}\n\t\treturn\n\t}\n\n\tif !d.Instance.IsZero() && d.Index != \"\" {\n\t\tindex, e := strconv.Atoi(d.Index)\n\t\tif e != nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"index_invalid\",\n\t\t\t\tMessage: \"Disk index invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif index < 0 || index > 10 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"index_out_of_range\",\n\t\t\t\tMessage: \"Disk index out of range\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\td.Index = strconv.Itoa(index)\n\t}\n\n\tif d.Datacenter.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_datacenter\",\n\t\t\tMessage: \"Missing required datacenter\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.Zone.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_zone\",\n\t\t\tMessage: \"Missing required zone\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.Node.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_node\",\n\t\t\tMessage: \"Missing required node\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.Type == \"\" {\n\t\td.Type = Qcow2\n\t}\n\n\tswitch d.Type {\n\tcase Qcow2:\n\t\td.Pool = bson.NilObjectID\n\t\tif d.Node.IsZero() {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"node_required\",\n\t\t\t\tMessage: \"Missing required node\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase Lvm:\n\t\td.Node = bson.NilObjectID\n\t\tif d.Pool.IsZero() {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"pool_required\",\n\t\t\t\tMessage: \"Missing required pool\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif d.Backing || d.BackingImage != \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"backing_image_invalid\",\n\t\t\t\tMessage: \"LVM disk cannot have backing image\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unknown_type\",\n\t\t\tMessage: \"Unknown disk type\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.SystemType == \"\" {\n\t\td.SystemType = Linux\n\t}\n\tif !ValidSystemTypes.Contains(d.SystemType) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_system_type\",\n\t\t\tMessage: \"Disk system type invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.SystemKind != \"\" && !ValidSystemKinds.Contains(d.SystemKind) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_system_kind\",\n\t\t\tMessage: \"Disk system kind invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch d.FileSystem {\n\tcase Xfs, Ext4, \"\":\n\t\td.LvSize = 0\n\t\tbreak\n\tcase LvmXfs, LvmExt4:\n\t\tif d.LvSize > d.Size {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"lv_size_invalid\",\n\t\t\t\tMessage: \"LV size cannot be greater than disk size\",\n\t\t\t}\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unknown_file_system\",\n\t\t\tMessage: \"Unknown disk file system\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.Backup && d.BackingImage != \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"backing_image_backup\",\n\t\t\tMessage: \"Cannot enable backups with backing image\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.Action == Restore && d.RestoreImage.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"restore_missing_image\",\n\t\t\tMessage: \"Cannot restore without image set\",\n\t\t}\n\t\treturn\n\t}\n\n\tif !d.Instance.IsZero() {\n\t\tdisks, e := GetInstance(db, d.Instance)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, dsk := range disks {\n\t\t\tif dsk.Id != d.Id && dsk.Index == d.Index {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"disk_index_in_use\",\n\t\t\t\t\tMessage: \"Disk index is already in use on instance\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif !strings.HasPrefix(d.Index, \"hold\") {\n\t\t\td.Index = fmt.Sprintf(\"hold_%s\", bson.NewObjectID().Hex())\n\t\t}\n\t\td.Deployment = Vacant\n\t}\n\n\tif d.State == \"\" {\n\t\td.State = Provision\n\t}\n\n\tif d.Size < 10 {\n\t\td.Size = 10\n\t}\n\n\tif d.Action == Expand {\n\t\tif d.NewSize == 0 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"new_size_missing\",\n\t\t\t\tMessage: \"Cannot expand without new size\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif d.NewSize < d.Size {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"new_size_invalid\",\n\t\t\t\tMessage: \"New size cannot be less then current size\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\td.NewSize = 0\n\t}\n\n\tif d.DeleteProtection && d.curInstance != d.Instance {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"delete_protection_index\",\n\t\t\tMessage: \"Cannot change instance with delete protection enabled\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.DeleteProtection && d.curIndex != d.Index {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"delete_protection_index\",\n\t\t\tMessage: \"Cannot change index with delete protection enabled\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Disk) PreCommit() {\n\td.curIndex = d.Index\n\td.curInstance = d.Instance\n}\n\nfunc (d *Disk) Reserve(db *database.Database,\n\tinstId bson.ObjectID, index int,\n\tdeplyId bson.ObjectID) (reserved bool, err error) {\n\n\tcoll := db.Disks()\n\n\tresp, err := coll.UpdateOne(db, &bson.M{\n\t\t\"_id\":      d.Id,\n\t\t\"instance\": Vacant,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"instance\":   instId,\n\t\t\t\"index\":      strconv.Itoa(index),\n\t\t\t\"deployment\": deplyId,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif resp.MatchedCount == 1 && resp.ModifiedCount == 1 {\n\t\treserved = true\n\t}\n\n\treturn\n}\n\nfunc (d *Disk) Unreserve(db *database.Database,\n\tinstId bson.ObjectID, deplyId bson.ObjectID) (err error) {\n\n\tcoll := db.Disks()\n\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\":        d.Id,\n\t\t\"instance\":   instId,\n\t\t\"deployment\": deplyId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"index\":      fmt.Sprintf(\"hold_%s\", bson.NewObjectID().Hex()),\n\t\t\t\"instance\":   Vacant,\n\t\t\t\"deployment\": Vacant,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Disk) Commit(db *database.Database) (err error) {\n\tcoll := db.Disks()\n\n\terr = coll.Commit(d.Id, d)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Disk) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Disks()\n\n\terr = coll.CommitFields(d.Id, d, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Disk) Insert(db *database.Database) (err error) {\n\tcoll := db.Disks()\n\n\td.Created = time.Now()\n\n\t_, err = coll.InsertOne(db, d)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Disk) Destroy(db *database.Database) (err error) {\n\tdskPath := paths.GetDiskPath(d.Id)\n\n\tif d.DeleteProtection {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"disk_id\": d.Id.Hex(),\n\t\t}).Info(\"disk: Delete protection ignore disk destroy\")\n\n\t\td.Action = \"\"\n\t\terr = d.CommitFields(db, set.NewSet(\"action\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\n\t\treturn\n\t}\n\n\tif d.Type == Lvm {\n\t\tpl, e := pool.Get(db, d.Pool)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tvgName := pl.VgName\n\t\tlvName := d.Id.Hex()\n\n\t\tacquired, e := lock.LvmLock(db, vgName, lvName)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif !acquired {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.New(\"data: Failed to acquire LVM lock\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdefer func() {\n\t\t\terr2 := lock.LvmUnlock(db, vgName, lvName)\n\t\t\tif err2 != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err2,\n\t\t\t\t}).Error(\"data: Failed to unlock lvm\")\n\t\t\t}\n\t\t}()\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"disk_id\": d.Id.Hex(),\n\t\t\t\"vg_name\": vgName,\n\t\t\t\"lv_name\": lvName,\n\t\t}).Info(\"qemu: Destroying LVM disk\")\n\n\t\terr = lvm.RemoveLv(vgName, lvName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"disk_id\":   d.Id.Hex(),\n\t\t\t\"disk_path\": dskPath,\n\t\t}).Info(\"qemu: Destroying QCOW disk\")\n\n\t\terr = utils.RemoveAll(dskPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = Remove(db, d.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "disk/sort.go",
    "content": "package disk\n\ntype Disks []*Disk\n\nfunc (d Disks) Len() int {\n\treturn len(d)\n}\n\nfunc (d Disks) Swap(i, j int) {\n\td[i], d[j] = d[j], d[i]\n}\n\nfunc (d Disks) Less(i, j int) bool {\n\treturn d[i].Index < d[j].Index\n}\n"
  },
  {
    "path": "disk/utils.go",
    "content": "package disk\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, diskId bson.ObjectID) (\n\tdsk *Disk, err error) {\n\n\tcoll := db.Disks()\n\tdsk = &Disk{}\n\n\terr = coll.FindOneId(diskId, dsk)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (dsk *Disk, err error) {\n\tcoll := db.Disks()\n\tdsk = &Disk{}\n\n\terr = coll.FindOne(db, query).Decode(dsk)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, diskId bson.ObjectID) (\n\tdsk *Disk, err error) {\n\n\tcoll := db.Disks()\n\tdsk = &Disk{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          diskId,\n\t\t\"organization\": orgId,\n\t}).Decode(dsk)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tdisks []*Disk, err error) {\n\n\tcoll := db.Disks()\n\tdisks = []*Disk{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Disk{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdisks = append(disks, nde)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllMap(db *database.Database, query *bson.M) (\n\tdisks map[bson.ObjectID]*Disk, err error) {\n\n\tcoll := db.Disks()\n\tdisks = map[bson.ObjectID]*Disk{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdsk := &Disk{}\n\t\terr = cursor.Decode(dsk)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdisks[dsk.Id] = dsk\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (disks []*Disk, count int64, err error) {\n\n\tcoll := db.Disks()\n\tdisks = []*Disk{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdsk := &Disk{}\n\t\terr = cursor.Decode(dsk)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdisks = append(disks, dsk)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetInstance(db *database.Database, instId bson.ObjectID) (\n\tdisks []*Disk, err error) {\n\n\tcoll := db.Disks()\n\tdisks = []*Disk{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"instance\": instId,\n\t\t},\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"index\", 1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdsk := &Disk{}\n\t\terr = cursor.Decode(dsk)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdisks = append(disks, dsk)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetInstanceIndex(db *database.Database, instId bson.ObjectID,\n\tindex string) (dsk *Disk, err error) {\n\n\tcoll := db.Disks()\n\tdsk = &Disk{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"instance\": instId,\n\t\t\"index\":    index,\n\t}).Decode(dsk)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetNode(db *database.Database, nodeId bson.ObjectID,\n\tnodePools []bson.ObjectID) (disks []*Disk, err error) {\n\n\tcoll := db.Disks()\n\tdisks = []*Disk{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"$or\": []bson.M{\n\t\t\t{\"node\": nodeId},\n\t\t\t{\"pool\": &bson.M{\n\t\t\t\t\"$in\": nodePools,\n\t\t\t}},\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdsk := &Disk{}\n\t\terr = cursor.Decode(dsk)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdisks = append(disks, dsk)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, diskId bson.ObjectID) (err error) {\n\tcoll := db.Disks()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": diskId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Detach(db *database.Database, dskIds bson.ObjectID) (err error) {\n\tcoll := db.Disks()\n\n\terr = coll.UpdateId(dskIds, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"index\":      fmt.Sprintf(\"hold_%s\", bson.NewObjectID().Hex()),\n\t\t\t\"instance\":   Vacant,\n\t\t\t\"deployment\": Vacant,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Delete(db *database.Database, dskId bson.ObjectID) (err error) {\n\tcoll := db.Disks()\n\n\terr = coll.UpdateId(dskId, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Destroy,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeleteOrg(db *database.Database, orgId, dskId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Disks()\n\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\":          dskId,\n\t\t\"organization\": orgId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Destroy,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeleteMulti(db *database.Database, dskIds []bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Disks()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": dskIds,\n\t\t},\n\t\t\"delete_protection\": &bson.M{\n\t\t\t\"$ne\": true,\n\t\t},\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Destroy,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeleteMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tdskIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Disks()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": dskIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t\t\"delete_protection\": &bson.M{\n\t\t\t\"$ne\": true,\n\t\t},\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Destroy,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc UpdateMulti(db *database.Database, dskIds []bson.ObjectID,\n\tdoc *bson.M) (err error) {\n\n\tcoll := db.Disks()\n\n\tquery := &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": dskIds,\n\t\t},\n\t}\n\n\tif (*doc)[\"action\"] == Destroy {\n\t\t(*query)[\"delete_protection\"] = &bson.M{\n\t\t\t\"$ne\": true,\n\t\t}\n\t}\n\n\t_, err = coll.UpdateMany(db, query, &bson.M{\n\t\t\"$set\": doc,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc UpdateMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tdskIds []bson.ObjectID, doc *bson.M) (err error) {\n\n\tcoll := db.Disks()\n\n\tquery := &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": dskIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t}\n\n\tif (*doc)[\"action\"] == Destroy {\n\t\t(*query)[\"delete_protection\"] = &bson.M{\n\t\t\t\"$ne\": true,\n\t\t}\n\t}\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": dskIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t}, &bson.M{\n\t\t\"$set\": doc,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllKeys(db *database.Database, ndeId bson.ObjectID) (\n\tkeys set.Set, err error) {\n\n\tcoll := db.Disks()\n\tkeys = set.NewSet()\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"node\": ndeId,\n\t}, options.Find().\n\t\tSetProjection(bson.D{\n\t\t\t{\"node\", 1},\n\t\t\t{\"backing_image\", 1},\n\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdsk := &Disk{}\n\t\terr = cursor.Decode(dsk)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif dsk.BackingImage != \"\" {\n\t\t\tkeys.Add(dsk.BackingImage)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc SetDeleteProtection(db *database.Database, instId bson.ObjectID,\n\tprotection bool) (err error) {\n\n\tcoll := db.Disks()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"instance\": instId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"delete_protection\": protection,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dns/aws.go",
    "content": "package dns\n\nimport (\n\t\"strings\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/credentials\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/route53\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Aws struct {\n\tsess        *session.Session\n\tsessRoute53 *route53.Route53\n\tcacheZoneId map[string]string\n}\n\nfunc (a *Aws) Connect(db *database.Database,\n\tsecr *secret.Secret) (err error) {\n\n\tif secr.Type != secret.AWS {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Secret type not AWS\"),\n\t\t}\n\t\treturn\n\t}\n\n\ta.cacheZoneId = map[string]string{}\n\n\ta.sess, err = session.NewSession(&aws.Config{\n\t\tRegion: aws.String(secr.Region),\n\t\tCredentials: credentials.NewStaticCredentials(\n\t\t\tsecr.Key, secr.Value, \"\"),\n\t})\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: AWS session error\"),\n\t\t}\n\t\treturn\n\t}\n\n\ta.sessRoute53 = route53.New(a.sess)\n\n\treturn\n}\n\nfunc (a *Aws) DnsZoneFind(domain string) (zoneId string, err error) {\n\tdomain = extractDomain(domain)\n\n\tzoneId = a.cacheZoneId[domain]\n\tif zoneId != \"\" {\n\t\treturn\n\t}\n\n\tinput := &route53.ListHostedZonesInput{}\n\n\tresult, err := a.sessRoute53.ListHostedZones(input)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: AWS route53 zone lookup error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, zone := range result.HostedZones {\n\t\tif matchDomains(*zone.Name, domain) {\n\t\t\tzoneId = *zone.Id\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif zoneId == \"\" {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: AWS route53 zone not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\ta.cacheZoneId[domain] = zoneId\n\n\treturn\n}\n\nfunc (a *Aws) DnsCommit(db *database.Database,\n\tdomain, recordType string, ops []*Operation) (err error) {\n\n\tdomain = cleanDomain(domain)\n\n\tzoneId, err := a.DnsZoneFind(domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdeleteResourceRecs := []*route53.ResourceRecord{}\n\tupdateResourceRecs := []*route53.ResourceRecord{}\n\toperations := []string{}\n\tfor _, op := range ops {\n\t\tif recordType == \"AAAA\" {\n\t\t\tval := normalizeIp(op.Value)\n\t\t\tif val == \"\" {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Newf(\"dns: Invalid ipv6 address %s\", op.Value),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\top.Value = val\n\t\t}\n\n\t\tresourceRec := &route53.ResourceRecord{\n\t\t\tValue: aws.String(op.Value),\n\t\t}\n\n\t\tswitch op.Operation {\n\t\tcase UPSERT, RETAIN:\n\t\t\toperations = append(operations, \"add:\"+op.Value)\n\t\t\tupdateResourceRecs = append(updateResourceRecs, resourceRec)\n\t\tcase DELETE:\n\t\t\tcurVals, e := a.DnsFind(db, domain, recordType)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\texists := false\n\t\t\tfor _, val := range curVals {\n\t\t\t\tif val == op.Value {\n\t\t\t\t\texists = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !exists {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"domain\":    domain,\n\t\t\t\t\t\"operation\": \"remove:\" + op.Value,\n\t\t\t\t}).Info(\"domain: Skipping delete on changed record\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\toperations = append(operations, \"remove:\"+op.Value)\n\t\t\tdeleteResourceRecs = append(deleteResourceRecs, resourceRec)\n\t\t}\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"domain\":     domain,\n\t\t\"operations\": operations,\n\t}).Info(\"domain: AWS dns batch operation\")\n\n\tif len(updateResourceRecs) == 0 && len(deleteResourceRecs) > 0 {\n\t\tinput := &route53.ChangeResourceRecordSetsInput{\n\t\t\tChangeBatch: &route53.ChangeBatch{\n\t\t\t\tChanges: []*route53.Change{\n\t\t\t\t\t{\n\t\t\t\t\t\tAction: aws.String(\"DELETE\"),\n\t\t\t\t\t\tResourceRecordSet: &route53.ResourceRecordSet{\n\t\t\t\t\t\t\tName: aws.String(domain),\n\t\t\t\t\t\t\tType: aws.String(recordType),\n\t\t\t\t\t\t\tTTL: aws.Int64(int64(\n\t\t\t\t\t\t\t\tsettings.Acme.DnsAwsTtl)),\n\t\t\t\t\t\t\tResourceRecords: deleteResourceRecs,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tComment: aws.String(\"Pritunl delete record\"),\n\t\t\t},\n\t\t\tHostedZoneId: aws.String(zoneId),\n\t\t}\n\n\t\t_, err = a.sessRoute53.ChangeResourceRecordSets(input)\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"delete\") &&\n\t\t\t\tstrings.Contains(err.Error(), \"not found\") {\n\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\terr = &errortypes.ApiError{\n\t\t\t\t\terrors.Wrap(err, \"acme: AWS record delete error\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(updateResourceRecs) > 0 {\n\t\tinput := &route53.ChangeResourceRecordSetsInput{\n\t\t\tChangeBatch: &route53.ChangeBatch{\n\t\t\t\tChanges: []*route53.Change{\n\t\t\t\t\t{\n\t\t\t\t\t\tAction: aws.String(\"UPSERT\"),\n\t\t\t\t\t\tResourceRecordSet: &route53.ResourceRecordSet{\n\t\t\t\t\t\t\tName: aws.String(domain),\n\t\t\t\t\t\t\tType: aws.String(recordType),\n\t\t\t\t\t\t\tTTL: aws.Int64(int64(\n\t\t\t\t\t\t\t\tsettings.Acme.DnsAwsTtl)),\n\t\t\t\t\t\t\tResourceRecords: updateResourceRecs,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tComment: aws.String(\"Pritunl update record\"),\n\t\t\t},\n\t\t\tHostedZoneId: aws.String(zoneId),\n\t\t}\n\n\t\t_, err = a.sessRoute53.ChangeResourceRecordSets(input)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Wrap(err, \"acme: AWS record update error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (a *Aws) DnsFind(db *database.Database, domain, recordType string) (\n\tvals []string, err error) {\n\n\tvals = []string{}\n\tdomain = cleanDomain(domain)\n\n\tzoneId, err := a.DnsZoneFind(domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tinput := &route53.ListResourceRecordSetsInput{\n\t\tHostedZoneId:    aws.String(zoneId),\n\t\tStartRecordName: aws.String(domain),\n\t\tStartRecordType: aws.String(recordType),\n\t}\n\n\tresult, err := a.sessRoute53.ListResourceRecordSets(input)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: AWS record list error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, recordSet := range result.ResourceRecordSets {\n\t\tif recordSet.Type != nil && *recordSet.Type == recordType &&\n\t\t\trecordSet.Name != nil && matchDomains(*recordSet.Name, domain) {\n\n\t\t\tfor _, record := range recordSet.ResourceRecords {\n\t\t\t\tif record.Value != nil {\n\t\t\t\t\tval := *record.Value\n\n\t\t\t\t\tif recordType == \"AAAA\" {\n\t\t\t\t\t\tval = normalizeIp(val)\n\t\t\t\t\t}\n\n\t\t\t\t\tif val == \"\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tvals = append(vals, val)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (a *Aws) DnsTxtGet(db *database.Database, domain string) (\n\tvals []string, err error) {\n\n\tvals = []string{}\n\n\tzoneId, err := a.DnsZoneFind(domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tinput := &route53.ListResourceRecordSetsInput{\n\t\tHostedZoneId:    aws.String(zoneId),\n\t\tStartRecordName: aws.String(domain),\n\t\tStartRecordType: aws.String(\"TXT\"),\n\t}\n\n\tresult, err := a.sessRoute53.ListResourceRecordSets(input)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: AWS route53 record set error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, recordSet := range result.ResourceRecordSets {\n\t\tif recordSet.Type != nil && *recordSet.Type == \"TXT\" &&\n\t\t\trecordSet.Name != nil && matchDomains(*recordSet.Name, domain) {\n\n\t\t\tfor _, record := range recordSet.ResourceRecords {\n\t\t\t\tif record.Value != nil {\n\t\t\t\t\tvals = append(vals, *record.Value)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (a *Aws) DnsTxtUpsert(db *database.Database,\n\tdomain, val string) (err error) {\n\n\tzoneId, err := a.DnsZoneFind(domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tinput := &route53.ChangeResourceRecordSetsInput{\n\t\tChangeBatch: &route53.ChangeBatch{\n\t\t\tChanges: []*route53.Change{\n\t\t\t\t{\n\t\t\t\t\tAction: aws.String(\"UPSERT\"),\n\t\t\t\t\tResourceRecordSet: &route53.ResourceRecordSet{\n\t\t\t\t\t\tName: aws.String(domain),\n\t\t\t\t\t\tType: aws.String(\"TXT\"),\n\t\t\t\t\t\tTTL:  aws.Int64(int64(settings.Acme.DnsAwsTtl)),\n\t\t\t\t\t\tResourceRecords: []*route53.ResourceRecord{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValue: aws.String(val),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tComment: aws.String(\"Pritunl update TXT record\"),\n\t\t},\n\t\tHostedZoneId: aws.String(zoneId),\n\t}\n\n\t_, err = a.sessRoute53.ChangeResourceRecordSets(input)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: AWS route53 record set error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (a *Aws) DnsTxtDelete(db *database.Database,\n\tdomain, val string) (err error) {\n\n\tzoneId, err := a.DnsZoneFind(domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tinput := &route53.ChangeResourceRecordSetsInput{\n\t\tChangeBatch: &route53.ChangeBatch{\n\t\t\tChanges: []*route53.Change{\n\t\t\t\t{\n\t\t\t\t\tAction: aws.String(\"DELETE\"),\n\t\t\t\t\tResourceRecordSet: &route53.ResourceRecordSet{\n\t\t\t\t\t\tName: aws.String(domain),\n\t\t\t\t\t\tType: aws.String(\"TXT\"),\n\t\t\t\t\t\tTTL:  aws.Int64(int64(settings.Acme.DnsAwsTtl)),\n\t\t\t\t\t\tResourceRecords: []*route53.ResourceRecord{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tValue: aws.String(val),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tComment: aws.String(\"Pritunl delete TXT record\"),\n\t\t},\n\t\tHostedZoneId: aws.String(zoneId),\n\t}\n\n\t_, err = a.sessRoute53.ChangeResourceRecordSets(input)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"delete\") &&\n\t\t\tstrings.Contains(err.Error(), \"not found\") {\n\n\t\t\terr = nil\n\t\t} else {\n\t\t\terr = &errortypes.ApiError{\n\t\t\t\terrors.Wrap(err, \"acme: AWS record change error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dns/cloudflare.go",
    "content": "package dns\n\nimport (\n\t\"github.com/cloudflare/cloudflare-go\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Cloudflare struct {\n\tsess        *cloudflare.API\n\ttoken       string\n\tcacheZoneId map[string]string\n}\n\nfunc (c *Cloudflare) Connect(db *database.Database,\n\tsecr *secret.Secret) (err error) {\n\n\tif secr.Type != secret.Cloudflare {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Secret type not cloudflare\"),\n\t\t}\n\t\treturn\n\t}\n\n\tc.sess, err = cloudflare.NewWithAPIToken(utils.FilterStr(secr.Key, 256))\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"dns: Failed to connect to cloudflare api\"),\n\t\t}\n\t\treturn\n\t}\n\n\tc.cacheZoneId = map[string]string{}\n\n\treturn\n}\n\nfunc (c *Cloudflare) DnsZoneFind(db *database.Database, domain string) (\n\tzoneId string, err error) {\n\n\tdomain = extractDomain(domain)\n\n\tzoneId = c.cacheZoneId[domain]\n\tif zoneId != \"\" {\n\t\treturn\n\t}\n\n\tzones, err := c.sess.ListZones(db)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Failed to list cloudflare zones\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, zone := range zones {\n\t\tif matchDomains(zone.Name, domain) {\n\t\t\tzoneId = zone.ID\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif zoneId == \"\" {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Cloudflare zone not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tc.cacheZoneId[domain] = zoneId\n\n\treturn\n}\n\nfunc (c *Cloudflare) DnsCommit(db *database.Database,\n\tdomain, recordType string, ops []*Operation) (err error) {\n\n\tdomain = cleanDomain(domain)\n\n\tzoneId, err := c.DnsZoneFind(db, domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlistParams := cloudflare.ListDNSRecordsParams{\n\t\tType: recordType,\n\t\tName: domain,\n\t}\n\n\trecords, _, err := c.sess.ListDNSRecords(\n\t\tdb,\n\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\tlistParams,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"acme: Failed to get DNS records\"),\n\t\t}\n\t\treturn\n\t}\n\n\trecordIds := map[string]string{}\n\tfor _, record := range records {\n\t\tif record.Type == recordType && matchDomains(record.Name, domain) {\n\t\t\tval := record.Content\n\t\t\tif recordType == \"AAAA\" {\n\t\t\t\tval = normalizeIp(val)\n\t\t\t}\n\n\t\t\tif val == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trecordIds[val] = record.ID\n\t\t}\n\t}\n\n\tfor _, op := range ops {\n\t\tif recordType == \"AAAA\" {\n\t\t\tval := normalizeIp(op.Value)\n\t\t\tif val == \"\" {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Newf(\"dns: Invalid ipv6 address %s\", op.Value),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\top.Value = val\n\t\t}\n\t}\n\n\tfor _, op := range ops {\n\t\tif op.Operation != DELETE {\n\t\t\tcontinue\n\t\t}\n\n\t\trecordId := recordIds[op.Value]\n\t\tif recordId == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tdelete(recordIds, op.Value)\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"operation\": \"delete\",\n\t\t\t\"record_id\": recordId,\n\t\t\t\"domain\":    domain,\n\t\t\t\"value\":     op.Value,\n\t\t}).Info(\"domain: Cloudflare dns operation\")\n\n\t\terr = c.sess.DeleteDNSRecord(\n\t\t\tdb,\n\t\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\t\trecordId,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"dns: Failed to delete record\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, op := range ops {\n\t\tif op.Operation != RETAIN && op.Operation != UPSERT {\n\t\t\tcontinue\n\t\t}\n\n\t\trecordId := recordIds[op.Value]\n\t\tif recordId == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tdelete(recordIds, op.Value)\n\n\t\top.Operation = \"\"\n\t}\n\n\tfor _, op := range ops {\n\t\tif op.Operation != RETAIN && op.Operation != UPSERT {\n\t\t\tcontinue\n\t\t}\n\n\t\tupdateVal := \"\"\n\t\trecordId := \"\"\n\t\tfor val, recId := range recordIds {\n\t\t\tupdateVal = val\n\t\t\trecordId = recId\n\t\t\tbreak\n\t\t}\n\t\tif recordId != \"\" {\n\t\t\tdelete(recordIds, updateVal)\n\t\t}\n\n\t\tif recordId == \"\" {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"operation\": \"create\",\n\t\t\t\t\"domain\":    domain,\n\t\t\t\t\"value\":     op.Value,\n\t\t\t}).Info(\"domain: Cloudflare dns operation\")\n\n\t\t\tcreateParams := cloudflare.CreateDNSRecordParams{\n\t\t\t\tType:    recordType,\n\t\t\t\tName:    domain,\n\t\t\t\tContent: op.Value,\n\t\t\t\tTTL:     settings.Acme.DnsCloudflareTtl,\n\t\t\t}\n\n\t\t\t_, err = c.sess.CreateDNSRecord(\n\t\t\t\tdb,\n\t\t\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\t\t\tcreateParams,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err, \"acme: Failed to create record\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"operation\": \"update\",\n\t\t\t\t\"record_id\": recordId,\n\t\t\t\t\"domain\":    domain,\n\t\t\t\t\"value\":     op.Value,\n\t\t\t}).Info(\"domain: Cloudflare dns operation\")\n\n\t\t\tupdateParams := cloudflare.UpdateDNSRecordParams{\n\t\t\t\tID:      recordId,\n\t\t\t\tType:    recordType,\n\t\t\t\tName:    domain,\n\t\t\t\tContent: op.Value,\n\t\t\t\tTTL:     settings.Acme.DnsCloudflareTtl,\n\t\t\t}\n\n\t\t\t_, err = c.sess.UpdateDNSRecord(\n\t\t\t\tdb,\n\t\t\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\t\t\tupdateParams,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err, \"acme: Failed to update record\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tfor val, recordId := range recordIds {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"operation\": \"delete_unknown\",\n\t\t\t\"record_id\": recordId,\n\t\t\t\"domain\":    domain,\n\t\t\t\"value\":     val,\n\t\t}).Info(\"domain: Cloudflare dns operation\")\n\n\t\terr = c.sess.DeleteDNSRecord(\n\t\t\tdb,\n\t\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\t\trecordId,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"dns: Failed to delete record\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (c *Cloudflare) DnsFind(db *database.Database,\n\tdomain, recordType string) (vals []string, err error) {\n\n\tvals = []string{}\n\tdomain = cleanDomain(domain)\n\n\tzoneId, err := c.DnsZoneFind(db, domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlistParams := cloudflare.ListDNSRecordsParams{\n\t\tType: recordType,\n\t\tName: domain,\n\t}\n\n\trecords, _, err := c.sess.ListDNSRecords(\n\t\tdb,\n\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\tlistParams,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"acme: Failed to get DNS records\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, record := range records {\n\t\tif record.Type == recordType && matchDomains(record.Name, domain) {\n\t\t\tval := record.Content\n\t\t\tif recordType == \"AAAA\" {\n\t\t\t\tval = normalizeIp(val)\n\t\t\t}\n\n\t\t\tif val == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvals = append(vals, val)\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (c *Cloudflare) DnsTxtGet(db *database.Database,\n\tdomain string) (vals []string, err error) {\n\n\tvals = []string{}\n\n\tdomain = cleanDomain(domain)\n\n\tzoneId, err := c.DnsZoneFind(db, domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlistParams := cloudflare.ListDNSRecordsParams{\n\t\tType: \"TXT\",\n\t\tName: domain,\n\t}\n\n\trecords, _, err := c.sess.ListDNSRecords(\n\t\tdb,\n\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\tlistParams,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"acme: Failed to get DNS records\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, record := range records {\n\t\tif record.Type == \"TXT\" && matchDomains(record.Name, domain) {\n\t\t\tvals = append(vals, record.Content)\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (c *Cloudflare) DnsTxtUpsert(db *database.Database,\n\tdomain, val string) (err error) {\n\n\tdomain = cleanDomain(domain)\n\n\tzoneId, err := c.DnsZoneFind(db, domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlistParams := cloudflare.ListDNSRecordsParams{\n\t\tType: \"TXT\",\n\t\tName: domain,\n\t}\n\n\trecords, _, err := c.sess.ListDNSRecords(\n\t\tdb,\n\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\tlistParams,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"acme: Failed to get DNS records\"),\n\t\t}\n\t\treturn\n\t}\n\n\trecordId := \"\"\n\tfor _, record := range records {\n\t\tif record.Type == \"TXT\" && matchDomains(record.Name, domain) {\n\t\t\trecordId = record.ID\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif recordId == \"\" {\n\t\tcreateParams := cloudflare.CreateDNSRecordParams{\n\t\t\tType:    \"TXT\",\n\t\t\tName:    domain,\n\t\t\tContent: val,\n\t\t\tTTL:     settings.Acme.DnsCloudflareTtl,\n\t\t}\n\n\t\t_, err = c.sess.CreateDNSRecord(\n\t\t\tdb,\n\t\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\t\tcreateParams,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"acme: Failed to create DNS record\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tupdateParams := cloudflare.UpdateDNSRecordParams{\n\t\t\tType:    \"TXT\",\n\t\t\tName:    domain,\n\t\t\tContent: val,\n\t\t\tTTL:     settings.Acme.DnsCloudflareTtl,\n\t\t}\n\n\t\t_, err = c.sess.UpdateDNSRecord(\n\t\t\tdb,\n\t\t\tcloudflare.ResourceIdentifier(recordId),\n\t\t\tupdateParams,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"acme: Failed to update DNS record\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (c *Cloudflare) DnsTxtDelete(db *database.Database,\n\tdomain, val string) (err error) {\n\n\tdomain = cleanDomain(domain)\n\n\tzoneId, err := c.DnsZoneFind(db, domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlistParams := cloudflare.ListDNSRecordsParams{\n\t\tType: \"TXT\",\n\t\tName: domain,\n\t}\n\n\trecords, _, err := c.sess.ListDNSRecords(\n\t\tdb,\n\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\tlistParams,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"acme: Failed to get DNS records\"),\n\t\t}\n\t\treturn\n\t}\n\n\trecordId := \"\"\n\tfor _, record := range records {\n\t\tif record.Type == \"TXT\" &&\n\t\t\tmatchDomains(record.Name, domain) &&\n\t\t\tmatchTxt(record.Content, val) {\n\n\t\t\trecordId = record.ID\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif recordId != \"\" {\n\t\terr = c.sess.DeleteDNSRecord(\n\t\t\tdb,\n\t\t\tcloudflare.ZoneIdentifier(zoneId),\n\t\t\trecordId,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"acme: Failed to delete DNS record\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dns/constants.go",
    "content": "package dns\n\nconst (\n\tUPSERT = \"upsert\"\n\tDELETE = \"delete\"\n\tRETAIN = \"retain\"\n)\n"
  },
  {
    "path": "dns/dns.go",
    "content": "package dns\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n)\n\ntype Operation struct {\n\tOperation string\n\tValue     string\n}\n\ntype Service interface {\n\tConnect(db *database.Database, secr *secret.Secret) (err error)\n\tDnsCommit(db *database.Database, domain, recordType string,\n\t\tops []*Operation) (err error)\n\tDnsFind(db *database.Database, domain, recordType string) (\n\t\tvals []string, err error)\n}\n"
  },
  {
    "path": "dns/errors.go",
    "content": "package dns\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype NotFoundError struct {\n\terrors.DropboxError\n}\n\ntype ServiceError struct {\n\terrors.DropboxError\n}\n\ntype UnknownError struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "dns/google.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/sirupsen/logrus\"\n\t\"google.golang.org/api/dns/v1\"\n\t\"google.golang.org/api/option\"\n)\n\ntype Google struct {\n\tservice     *dns.Service\n\tproject     string\n\tcacheZoneId map[string]string\n}\n\ntype googleKey struct {\n\tType                    string `json:\"type\"`\n\tProjectId               string `json:\"project_id\"`\n\tPrivateKeyId            string `json:\"private_key_id\"`\n\tPrivateKey              string `json:\"private_key\"`\n\tClientEmail             string `json:\"client_email\"`\n\tClientId                string `json:\"client_id\"`\n\tAuthUri                 string `json:\"auth_uri\"`\n\tTokenUri                string `json:\"token_uri\"`\n\tAuthProviderX509CertURL string `json:\"auth_provider_x509_cert_url\"`\n\tClientX509CertURL       string `json:\"client_x509_cert_url\"`\n}\n\ntype googleZoneInfo struct {\n\tname         string\n\tdnsName      string\n\tdnsNameClean string\n}\n\nfunc (g *Google) Connect(db *database.Database,\n\tsecr *secret.Secret) (err error) {\n\n\tif secr.Type != secret.GoogleCloud {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Secret type not GCP\"),\n\t\t}\n\t\treturn\n\t}\n\n\tg.cacheZoneId = map[string]string{}\n\n\tgoogleKey := &googleKey{}\n\tif secr.Key != \"\" {\n\t\terr = json.Unmarshal([]byte(secr.Key), googleKey)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\terr,\n\t\t\t\t\t\"acme: Failed to parse Google Cloud credentials\",\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tg.project = googleKey.ProjectId\n\t} else {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"acme: GCP project ID not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tctx := context.Background()\n\topts := []option.ClientOption{\n\t\toption.WithCredentialsJSON([]byte(secr.Key)),\n\t}\n\n\tg.service, err = dns.NewService(ctx, opts...)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(\n\t\t\t\terr,\n\t\t\t\t\"acme: Failed to create Google Cloud DNS service\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (g *Google) DnsZoneFind(db *database.Database, domain string) (\n\tzoneId string, err error) {\n\n\tdomainClean := strings.Trim(domain, \".\")\n\n\tzoneId = g.cacheZoneId[domainClean]\n\tif zoneId != \"\" {\n\t\treturn\n\t}\n\n\tctx := context.Background()\n\tzones, err := g.service.ManagedZones.List(g.project).Context(ctx).Do()\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Google Cloud zone list error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tzoneList := []*googleZoneInfo{}\n\tfor _, zone := range zones.ManagedZones {\n\t\tif zone.DnsName != \"\" {\n\t\t\tzoneList = append(zoneList, &googleZoneInfo{\n\t\t\t\tname:         zone.Name,\n\t\t\t\tdnsName:      zone.DnsName,\n\t\t\t\tdnsNameClean: strings.Trim(zone.DnsName, \".\"),\n\t\t\t})\n\t\t}\n\t}\n\n\tfor i := 0; i < len(zoneList); i++ {\n\t\tfor j := i + 1; j < len(zoneList); j++ {\n\t\t\tif len(zoneList[i].dnsNameClean) < len(zoneList[j].dnsNameClean) {\n\t\t\t\tzoneList[i], zoneList[j] = zoneList[j], zoneList[i]\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, zone := range zoneList {\n\t\tif matchDomains(zone.dnsNameClean, domainClean) {\n\t\t\tzoneId = zone.name\n\t\t\tbreak\n\t\t}\n\n\t\tif strings.HasSuffix(domainClean, \".\"+zone.dnsNameClean) {\n\t\t\tzoneId = zone.name\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif zoneId == \"\" {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Google Cloud DNS zone not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tg.cacheZoneId[domainClean] = zoneId\n\n\treturn\n}\n\nfunc (g *Google) DnsCommit(db *database.Database,\n\tdomain, recordType string, ops []*Operation) (err error) {\n\n\tdomain = cleanDomain(domain)\n\n\tzoneId, err := g.DnsZoneFind(db, domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tctx := context.Background()\n\n\tlistCall := g.service.ResourceRecordSets.List(g.project, zoneId)\n\tlistCall.Name(domain + \".\")\n\tlistCall.Type(recordType)\n\n\texistingRecords, err := listCall.Context(ctx).Do()\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: GCP record list error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tchange := &dns.Change{\n\t\tAdditions: []*dns.ResourceRecordSet{},\n\t\tDeletions: []*dns.ResourceRecordSet{},\n\t}\n\n\toperations := []string{}\n\n\tvar existingRecSet *dns.ResourceRecordSet\n\tfor _, recSet := range existingRecords.Rrsets {\n\t\tif matchDomains(recSet.Name, domain+\".\") && recSet.Type == recordType {\n\t\t\texistingRecSet = recSet\n\t\t\tbreak\n\t\t}\n\t}\n\n\taddValues := []string{}\n\tremoveValues := []string{}\n\n\tfor _, op := range ops {\n\t\tif recordType == \"AAAA\" {\n\t\t\tval := normalizeIp(op.Value)\n\t\t\tif val == \"\" {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Newf(\"dns: Invalid ipv6 address %s\", op.Value),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\top.Value = val\n\t\t}\n\n\t\tswitch op.Operation {\n\t\tcase UPSERT, RETAIN:\n\t\t\toperations = append(operations, \"add:\"+op.Value)\n\t\t\taddValues = append(addValues, op.Value)\n\t\tcase DELETE:\n\t\t\tremoveValues = append(removeValues, op.Value)\n\t\t\toperations = append(operations, \"remove:\"+op.Value)\n\t\t}\n\t}\n\n\texistingValues := set.NewSet()\n\tif existingRecSet != nil {\n\t\tfor _, value := range existingRecSet.Rrdatas {\n\t\t\texistingValues.Add(value)\n\t\t}\n\t}\n\n\tnewValues := existingValues.Copy()\n\tfor _, value := range removeValues {\n\t\tnewValues.Remove(value)\n\t}\n\n\tfor _, value := range addValues {\n\t\tnewValues.Add(value)\n\t}\n\n\tif existingValues.IsEqual(newValues) {\n\t\treturn\n\t}\n\n\tif existingRecSet != nil {\n\t\tchange.Deletions = append(change.Deletions, existingRecSet)\n\t}\n\n\tif newValues.Len() > 0 {\n\t\tttl := int64(settings.Acme.DnsGoogleCloudTtl)\n\t\tif existingRecSet != nil && existingRecSet.Ttl > 0 {\n\t\t\tttl = existingRecSet.Ttl\n\t\t}\n\n\t\tvalues := []string{}\n\t\tfor valueInf := range newValues.Iter() {\n\t\t\tvalues = append(values, valueInf.(string))\n\t\t}\n\t\tsort.Strings(values)\n\n\t\tnewRrSet := &dns.ResourceRecordSet{\n\t\t\tName:    domain + \".\",\n\t\t\tType:    recordType,\n\t\t\tTtl:     ttl,\n\t\t\tRrdatas: values,\n\t\t}\n\t\tchange.Additions = append(change.Additions, newRrSet)\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"domain\":     domain,\n\t\t\"operations\": operations,\n\t}).Info(\"domain: Google Cloud dns batch operation\")\n\n\t_, err = g.service.Changes.Create(g.project, zoneId, change).Context(ctx).Do()\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Google Cloud record change error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (g *Google) DnsFind(db *database.Database, domain, recordType string) (\n\tvals []string, err error) {\n\n\tvals = []string{}\n\tdomain = cleanDomain(domain)\n\n\tzoneId, err := g.DnsZoneFind(db, domain)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tctx := context.Background()\n\n\tlistCall := g.service.ResourceRecordSets.List(g.project, zoneId)\n\tlistCall.Name(domain + \".\")\n\tlistCall.Type(recordType)\n\n\trecords, err := listCall.Context(ctx).Do()\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: GCP record list error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, recSet := range records.Rrsets {\n\t\tif matchDomains(recSet.Name, domain+\".\") && recSet.Type == recordType {\n\t\t\tfor _, rdata := range recSet.Rrdatas {\n\t\t\t\tval := rdata\n\t\t\t\tif recordType == \"AAAA\" {\n\t\t\t\t\tval = normalizeIp(val)\n\t\t\t\t}\n\n\t\t\t\tif val == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvals = append(vals, val)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dns/oracle.go",
    "content": "package dns\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/oracle/oci-go-sdk/v65/dns\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Oracle struct {\n\ttoken       string\n\tcacheZoneId map[string]string\n\tprovider    *secret.OracleProvider\n}\n\nfunc (o *Oracle) OracleUser() string {\n\treturn \"\"\n}\n\nfunc (o *Oracle) OraclePrivateKey() string {\n\treturn \"\"\n}\n\nfunc (o *Oracle) Connect(db *database.Database,\n\tsecr *secret.Secret) (err error) {\n\n\tif secr.Type != secret.OracleCloud {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Secret type not Oracle Cloud\"),\n\t\t}\n\t\treturn\n\t}\n\n\to.cacheZoneId = map[string]string{}\n\n\to.provider, err = secr.GetOracleProvider()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (o *Oracle) DnsZoneFind(db *database.Database, domain string) (\n\tzoneId string, err error) {\n\n\tdomain = extractDomain(domain)\n\n\tzoneId = o.cacheZoneId[domain]\n\tif zoneId != \"\" {\n\t\treturn\n\t}\n\n\tcompartmentId, err := o.provider.CompartmentOCID()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq := dns.ListZonesRequest{\n\t\tCompartmentId: &compartmentId,\n\t}\n\n\tclient, err := o.provider.GetDnsClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzones, err := client.ListZones(db, req)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Oracle zone list error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, zone := range zones.Items {\n\t\tif matchDomains(*zone.Name, domain) {\n\t\t\tzoneId = *zone.Id\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif zoneId == \"\" {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Oracle zone not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\to.cacheZoneId[domain] = zoneId\n\n\treturn\n}\n\nfunc (o *Oracle) DnsCommit(db *database.Database,\n\tdomain, recordType string, ops []*Operation) (err error) {\n\n\tzoneName := extractDomain(domain)\n\tdomain = cleanDomain(domain)\n\n\titems := []dns.RecordOperation{}\n\n\tvalues := set.NewSet()\n\toperations := []string{}\n\n\tfor _, op := range ops {\n\t\tif recordType == \"AAAA\" {\n\t\t\tval := normalizeIp(op.Value)\n\t\t\tif val == \"\" {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Newf(\"dns: Invalid ipv6 address %s\", op.Value),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\top.Value = val\n\t\t}\n\n\t\tvalues.Add(op.Value)\n\n\t\tswitch op.Operation {\n\t\tcase RETAIN:\n\t\t\tbreak\n\t\tcase UPSERT:\n\t\t\toperations = append(operations, \"add:\"+op.Value)\n\t\t\titems = append(items, dns.RecordOperation{\n\t\t\t\tDomain: &domain,\n\t\t\t\tRtype:  utils.PointerString(recordType),\n\t\t\t\tTtl: utils.PointerInt(\n\t\t\t\t\tsettings.Acme.DnsOracleCloudTtl),\n\t\t\t\tRdata:     utils.PointerString(op.Value),\n\t\t\t\tOperation: dns.RecordOperationOperationAdd,\n\t\t\t})\n\t\t\tbreak\n\t\tcase DELETE:\n\t\t\toperations = append(operations, \"remove:\"+op.Value)\n\t\t\titems = append(items, dns.RecordOperation{\n\t\t\t\tDomain: &domain,\n\t\t\t\tRtype:  utils.PointerString(recordType),\n\t\t\t\tTtl: utils.PointerInt(\n\t\t\t\t\tsettings.Acme.DnsOracleCloudTtl),\n\t\t\t\tRdata:     utils.PointerString(op.Value),\n\t\t\t\tOperation: dns.RecordOperationOperationRemove,\n\t\t\t})\n\t\t\tbreak\n\t\t}\n\t}\n\n\tclient, err := o.provider.GetDnsClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tgetReq := dns.GetZoneRecordsRequest{\n\t\tZoneNameOrId: utils.PointerString(zoneName),\n\t\tDomain:       utils.PointerString(domain),\n\t}\n\n\tresp, err := client.GetZoneRecords(db, getReq)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Oracle zone record get error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, record := range resp.Items {\n\t\tif record.Rtype != nil && *record.Rtype == recordType &&\n\t\t\trecord.Rdata != nil {\n\n\t\t\tval := *record.Rdata\n\t\t\tif recordType == \"AAAA\" {\n\t\t\t\tval = normalizeIp(val)\n\t\t\t}\n\n\t\t\tif val == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif values.Contains(val) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\toperations = append(operations, \"remove_unknown:\"+*record.Rdata)\n\t\t\titems = append(items, dns.RecordOperation{\n\t\t\t\tDomain: &domain,\n\t\t\t\tRtype:  utils.PointerString(recordType),\n\t\t\t\tTtl: utils.PointerInt(\n\t\t\t\t\tsettings.Acme.DnsOracleCloudTtl),\n\t\t\t\tRdata:     utils.PointerString(*record.Rdata),\n\t\t\t\tOperation: dns.RecordOperationOperationRemove,\n\t\t\t})\n\t\t}\n\t}\n\n\tif len(items) == 0 {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"domain\":     domain,\n\t\t\"operations\": operations,\n\t}).Info(\"domain: Oracle dns batch operation\")\n\n\treq := dns.PatchZoneRecordsRequest{\n\t\tZoneNameOrId: utils.PointerString(zoneName),\n\t\tPatchZoneRecordsDetails: dns.PatchZoneRecordsDetails{\n\t\t\tItems: items,\n\t\t},\n\t}\n\n\t_, err = client.PatchZoneRecords(db, req)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Oracle zone patch error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (o *Oracle) DnsFind(db *database.Database,\n\tdomain, recordType string) (vals []string, err error) {\n\n\tzoneName := extractDomain(domain)\n\tdomain = cleanDomain(domain)\n\n\treq := dns.GetZoneRecordsRequest{\n\t\tZoneNameOrId: utils.PointerString(zoneName),\n\t\tDomain:       utils.PointerString(domain),\n\t}\n\n\tclient, err := o.provider.GetDnsClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresp, err := client.GetZoneRecords(db, req)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Oracle zone record get error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, record := range resp.Items {\n\t\tif record.Rtype != nil && *record.Rtype == recordType &&\n\t\t\trecord.Rdata != nil {\n\n\t\t\tval := *record.Rdata\n\t\t\tif recordType == \"AAAA\" {\n\t\t\t\tval = normalizeIp(val)\n\t\t\t}\n\n\t\t\tif val == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvals = append(vals, val)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (o *Oracle) DnsTxtGet(db *database.Database,\n\tdomain string) (vals []string, err error) {\n\n\tzoneName := extractDomain(domain)\n\tdomain = cleanDomain(domain)\n\n\treq := dns.GetZoneRecordsRequest{\n\t\tZoneNameOrId: utils.PointerString(zoneName),\n\t\tDomain:       utils.PointerString(domain),\n\t}\n\n\tclient, err := o.provider.GetDnsClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresp, err := client.GetZoneRecords(db, req)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Oracle zone record get error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, record := range resp.Items {\n\t\tif record.Rtype != nil && *record.Rtype == \"TXT\" &&\n\t\t\trecord.Rdata != nil {\n\n\t\t\tvals = append(vals, *record.Rdata)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (o *Oracle) DnsTxtUpsert(db *database.Database,\n\tdomain, val string) (err error) {\n\n\tzoneName := extractDomain(domain)\n\tdomain = cleanDomain(domain)\n\n\treq := dns.PatchZoneRecordsRequest{\n\t\tZoneNameOrId: utils.PointerString(zoneName),\n\t\tPatchZoneRecordsDetails: dns.PatchZoneRecordsDetails{\n\t\t\tItems: []dns.RecordOperation{\n\t\t\t\t{\n\t\t\t\t\tDomain: &domain,\n\t\t\t\t\tRtype:  utils.PointerString(\"TXT\"),\n\t\t\t\t\tTtl: utils.PointerInt(\n\t\t\t\t\t\tsettings.Acme.DnsOracleCloudTtl),\n\t\t\t\t\tRdata:     utils.PointerString(val),\n\t\t\t\t\tOperation: dns.RecordOperationOperationAdd,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tclient, err := o.provider.GetDnsClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = client.PatchZoneRecords(db, req)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Oracle zone patch error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (o *Oracle) DnsTxtDelete(db *database.Database,\n\tdomain, val string) (err error) {\n\n\tzoneName := extractDomain(domain)\n\tdomain = cleanDomain(domain)\n\n\treq := dns.PatchZoneRecordsRequest{\n\t\tZoneNameOrId: utils.PointerString(zoneName),\n\t\tPatchZoneRecordsDetails: dns.PatchZoneRecordsDetails{\n\t\t\tItems: []dns.RecordOperation{\n\t\t\t\t{\n\t\t\t\t\tDomain:    &domain,\n\t\t\t\t\tRtype:     utils.PointerString(\"TXT\"),\n\t\t\t\t\tRdata:     utils.PointerString(val),\n\t\t\t\t\tOperation: dns.RecordOperationOperationRemove,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tclient, err := o.provider.GetDnsClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = client.PatchZoneRecords(db, req)\n\tif err != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Wrap(err, \"acme: Oracle zone patch error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "dns/utils.go",
    "content": "package dns\n\nimport (\n\t\"net\"\n\t\"strings\"\n\n\t\"golang.org/x/net/publicsuffix\"\n)\n\nfunc matchDomains(x, y string) bool {\n\tif strings.Trim(x, \".\") == strings.Trim(y, \".\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc matchTxt(x, y string) bool {\n\tif strings.Trim(x, \"\\\"\") == strings.Trim(y, \"\\\"\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc normalizeIp(addr string) string {\n\tip := net.ParseIP(addr)\n\tif ip == nil {\n\t\treturn \"\"\n\t}\n\n\treturn strings.ToLower(ip.String())\n}\n\nfunc extractDomain(domain string) string {\n\tdomain = strings.Trim(domain, \".\")\n\ttopDomain, err := publicsuffix.EffectiveTLDPlusOne(domain)\n\tif err != nil {\n\t\treturn domain\n\t}\n\treturn topDomain\n}\n\nfunc cleanDomain(domain string) string {\n\treturn strings.Trim(domain, \".\")\n}\n"
  },
  {
    "path": "dnss/constants.go",
    "content": "package dnss\n\nconst (\n\tTtl = 10\n)\n"
  },
  {
    "path": "dnss/database.go",
    "content": "package dnss\n\nimport (\n\t\"net\"\n\t\"sync/atomic\"\n\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n)\n\nvar (\n\tdatabase atomic.Pointer[Database]\n)\n\ntype Database struct {\n\tA     map[string][]net.IP `json:\"a\"`\n\tAAAA  map[string][]net.IP `json:\"aaaa\"`\n\tCNAME map[string]string   `json:\"cname\"`\n}\n\nfunc init() {\n\tdatabase.Store(&Database{\n\t\tA:     map[string][]net.IP{},\n\t\tAAAA:  map[string][]net.IP{},\n\t\tCNAME: map[string]string{},\n\t})\n}\n\nfunc UpdateDatabase(db *Database) {\n\tdatabase.Store(db)\n}\n\nfunc LoadConfig(domains []*types.Domain) {\n\tdb := &Database{\n\t\tA:     map[string][]net.IP{},\n\t\tAAAA:  map[string][]net.IP{},\n\t\tCNAME: map[string]string{},\n\t}\n\n\tfor _, domn := range domains {\n\t\tswitch domn.Type {\n\t\tcase domain.A:\n\t\t\tdb.A[domn.Domain] = append(db.A[domn.Domain], domn.Ip)\n\t\tcase domain.AAAA:\n\t\t\tdb.AAAA[domn.Domain] = append(db.AAAA[domn.Domain], domn.Ip)\n\t\tcase domain.CNAME:\n\t\t\tdb.CNAME[domn.Domain] = domn.Target\n\t\t}\n\t}\n\n\tUpdateDatabase(db)\n}\n"
  },
  {
    "path": "dnss/dnss.go",
    "content": "package dnss\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/coredns/coredns/plugin/forward\"\n\t\"github.com/coredns/coredns/plugin/pkg/proxy\"\n\t\"github.com/coredns/coredns/plugin/pkg/transport\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/miekg/dns\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype Server struct {\n\tmux *dns.ServeMux\n\tudp *dns.Server\n\ttcp *dns.Server\n}\n\nfunc (s *Server) ListenUdp() (err error) {\n\terr = s.udp.ListenAndServe()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"dnss: Server udp listen error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Server) ListenTcp() (err error) {\n\terr = s.tcp.ListenAndServe()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"dnss: Server tcp listen error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Server) Shutdown() (err error) {\n\te := s.tcp.Shutdown()\n\tif e != nil {\n\t\terr = e\n\t}\n\n\te = s.udp.Shutdown()\n\tif e != nil {\n\t\terr = e\n\t}\n\n\treturn\n}\n\nfunc NewServer(host string) (server *Server) {\n\tmux := dns.NewServeMux()\n\n\tprxy := proxy.NewProxy(\"google\", \"8.8.8.8:53\", transport.DNS)\n\tprxy.SetReadTimeout(2 * time.Second)\n\tprxy.Start(60 * time.Second)\n\n\tfwd := forward.New()\n\tfwd.SetProxy(prxy)\n\n\tcustom := &Plugin{\n\t\tNext: fwd,\n\t}\n\n\tmux.HandleFunc(\".\", func(w dns.ResponseWriter, r *dns.Msg) {\n\t\tcustom.ServeDNS(context.Background(), w, r)\n\t})\n\n\treturn &Server{\n\t\tmux: mux,\n\t\tudp: &dns.Server{\n\t\t\tAddr:    host,\n\t\t\tNet:     \"udp\",\n\t\t\tHandler: mux,\n\t\t},\n\t\ttcp: &dns.Server{\n\t\t\tAddr:    host,\n\t\t\tNet:     \"tcp\",\n\t\t\tHandler: mux,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "dnss/plugin.go",
    "content": "package dnss\n\nimport (\n\t\"context\"\n\n\t\"github.com/coredns/coredns/plugin\"\n\t\"github.com/miekg/dns\"\n)\n\ntype Plugin struct {\n\tNext plugin.Handler\n}\n\nfunc (p *Plugin) ServeDNS(ctx context.Context,\n\tw dns.ResponseWriter, r *dns.Msg) (int, error) {\n\n\tif len(r.Question) == 0 {\n\t\treturn plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)\n\t}\n\n\tq := r.Question[0]\n\tname := q.Name\n\tqtype := q.Qtype\n\tdb := database.Load()\n\tfound := false\n\tvar answers []dns.RR\n\n\ttargetCname, okCname := db.CNAME[name]\n\tipsA, okA := db.A[name]\n\tipsAAAA, okAAAA := db.AAAA[name]\n\tinternalDomain := false\n\tif okCname || okA || okAAAA {\n\t\tinternalDomain = true\n\t}\n\n\tif okCname {\n\t\tanswers = append(answers, &dns.CNAME{\n\t\t\tHdr: dns.RR_Header{\n\t\t\t\tName:   name,\n\t\t\t\tRrtype: dns.TypeCNAME,\n\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\tTtl:    Ttl,\n\t\t\t},\n\t\t\tTarget: targetCname,\n\t\t})\n\t\tfound = true\n\n\t\tif qtype == dns.TypeA || qtype == dns.TypeAAAA {\n\t\t\tswitch qtype {\n\t\t\tcase dns.TypeA:\n\t\t\t\tif ips, ok := db.A[targetCname]; ok {\n\t\t\t\t\tfor _, ip := range ips {\n\t\t\t\t\t\tanswers = append(answers, &dns.A{\n\t\t\t\t\t\t\tHdr: dns.RR_Header{\n\t\t\t\t\t\t\t\tName:   targetCname,\n\t\t\t\t\t\t\t\tRrtype: dns.TypeA,\n\t\t\t\t\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\t\t\t\t\tTtl:    Ttl,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tA: ip,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttargetQuery := new(dns.Msg)\n\t\t\t\t\ttargetQuery.SetQuestion(targetCname, dns.TypeA)\n\n\t\t\t\t\trw := &Response{\n\t\t\t\t\t\tResponseWriter: w,\n\t\t\t\t\t}\n\t\t\t\t\tcode, err := p.Next.ServeDNS(ctx, rw, targetQuery)\n\n\t\t\t\t\tif err == nil && rw.msg != nil {\n\t\t\t\t\t\tanswers = append(answers, rw.msg.Answer...)\n\t\t\t\t\t}\n\n\t\t\t\t\tm := new(dns.Msg)\n\t\t\t\t\tm.SetReply(r)\n\t\t\t\t\tm.Authoritative = false\n\t\t\t\t\tm.RecursionAvailable = true\n\t\t\t\t\tm.Answer = answers\n\t\t\t\t\tw.WriteMsg(m)\n\t\t\t\t\treturn code, err\n\t\t\t\t}\n\t\t\tcase dns.TypeAAAA:\n\t\t\t\tif ips, ok := db.AAAA[targetCname]; ok {\n\t\t\t\t\tfor _, ip := range ips {\n\t\t\t\t\t\tanswers = append(answers, &dns.AAAA{\n\t\t\t\t\t\t\tHdr: dns.RR_Header{\n\t\t\t\t\t\t\t\tName:   targetCname,\n\t\t\t\t\t\t\t\tRrtype: dns.TypeAAAA,\n\t\t\t\t\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\t\t\t\t\tTtl:    Ttl,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tAAAA: ip,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttargetQuery := new(dns.Msg)\n\t\t\t\t\ttargetQuery.SetQuestion(targetCname, dns.TypeAAAA)\n\n\t\t\t\t\trw := &Response{\n\t\t\t\t\t\tResponseWriter: w,\n\t\t\t\t\t}\n\t\t\t\t\tcode, err := p.Next.ServeDNS(ctx, rw, targetQuery)\n\n\t\t\t\t\tif err == nil && rw.msg != nil {\n\t\t\t\t\t\tanswers = append(answers, rw.msg.Answer...)\n\t\t\t\t\t}\n\n\t\t\t\t\tmsg := new(dns.Msg)\n\t\t\t\t\tmsg.SetReply(r)\n\t\t\t\t\tmsg.Authoritative = false\n\t\t\t\t\tmsg.RecursionAvailable = true\n\t\t\t\t\tmsg.Answer = answers\n\t\t\t\t\tw.WriteMsg(msg)\n\t\t\t\t\treturn code, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmsg := new(dns.Msg)\n\t\tmsg.SetReply(r)\n\t\tmsg.Authoritative = true\n\t\tmsg.RecursionAvailable = true\n\t\tmsg.Answer = answers\n\t\tw.WriteMsg(msg)\n\t\treturn dns.RcodeSuccess, nil\n\t}\n\n\tswitch qtype {\n\tcase dns.TypeA:\n\t\tif okA {\n\t\t\tfor _, ip := range ipsA {\n\t\t\t\tanswers = append(answers, &dns.A{\n\t\t\t\t\tHdr: dns.RR_Header{\n\t\t\t\t\t\tName:   name,\n\t\t\t\t\t\tRrtype: dns.TypeA,\n\t\t\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\t\t\tTtl:    Ttl,\n\t\t\t\t\t},\n\t\t\t\t\tA: ip,\n\t\t\t\t})\n\t\t\t}\n\t\t\tfound = true\n\t\t}\n\tcase dns.TypeAAAA:\n\t\tif okAAAA {\n\t\t\tfor _, ip := range ipsAAAA {\n\t\t\t\tanswers = append(answers, &dns.AAAA{\n\t\t\t\t\tHdr: dns.RR_Header{\n\t\t\t\t\t\tName:   name,\n\t\t\t\t\t\tRrtype: dns.TypeAAAA,\n\t\t\t\t\t\tClass:  dns.ClassINET,\n\t\t\t\t\t\tTtl:    Ttl,\n\t\t\t\t\t},\n\t\t\t\t\tAAAA: ip,\n\t\t\t\t})\n\t\t\t}\n\t\t\tfound = true\n\t\t}\n\t}\n\n\tif found {\n\t\tmsg := new(dns.Msg)\n\t\tmsg.SetReply(r)\n\t\tmsg.Authoritative = true\n\t\tmsg.RecursionAvailable = true\n\t\tmsg.Answer = answers\n\t\tw.WriteMsg(msg)\n\t\treturn dns.RcodeSuccess, nil\n\t} else if internalDomain {\n\t\tmsg := new(dns.Msg)\n\t\tmsg.SetReply(r)\n\t\tmsg.Authoritative = true\n\t\tmsg.RecursionAvailable = true\n\t\tw.WriteMsg(msg)\n\t\treturn dns.RcodeSuccess, nil\n\t}\n\n\treturn plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)\n}\n\nfunc (p *Plugin) Name() string {\n\treturn \"pritunl-cloud\"\n}\n"
  },
  {
    "path": "dnss/response.go",
    "content": "package dnss\n\nimport (\n\t\"github.com/miekg/dns\"\n)\n\ntype Response struct {\n\tdns.ResponseWriter\n\tmsg *dns.Msg\n}\n\nfunc (r *Response) WriteMsg(m *dns.Msg) error {\n\tr.msg = m\n\treturn nil\n}\n"
  },
  {
    "path": "domain/constants.go",
    "content": "package domain\n\nimport \"github.com/pritunl/mongo-go-driver/v2/bson\"\n\nconst (\n\tLocal       = \"local\"\n\tAWS         = \"aws\"\n\tCloudflare  = \"cloudflare\"\n\tOracleCloud = \"oracle_cloud\"\n\n\tA     = \"A\"\n\tAAAA  = \"AAAA\"\n\tCNAME = \"CNAME\"\n\tTXT   = \"TXT\"\n\n\tINSERT = \"insert\"\n\tUPDATE = \"update\"\n\tDELETE = \"delete\"\n)\n\nvar (\n\tVacant = bson.NilObjectID\n)\n"
  },
  {
    "path": "domain/domain.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/dns\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Domain struct {\n\tId            bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName          string        `bson:\"name\" json:\"name\"`\n\tComment       string        `bson:\"comment\" json:\"comment\"`\n\tOrganization  bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tType          string        `bson:\"type\" json:\"type\"`\n\tSecret        bson.ObjectID `bson:\"secret\" json:\"secret\"`\n\tRootDomain    string        `bson:\"root_domain\" json:\"root_domain\"`\n\tLockId        bson.ObjectID `bson:\"lock_id\" json:\"lock_id\"`\n\tLockTimestamp time.Time     `bson:\"lock_timestamp\" json:\"lock_timestamp\"`\n\tLastUpdate    time.Time     `bson:\"last_update\" json:\"last_update\"`\n\tRecords       []*Record     `bson:\"-\" json:\"records\"`\n\tOrigRecords   []*Record     `bson:\"-\" json:\"-\"`\n}\n\ntype Completion struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n}\n\nfunc (d *Domain) Locked() bool {\n\treturn !d.LockId.IsZero() && time.Since(d.LockTimestamp) < time.Duration(\n\t\tsettings.System.DomainLockTtl)*time.Second\n}\n\nfunc (d *Domain) Copy() *Domain {\n\tdomn := *d\n\n\trecs := make([]*Record, len(domn.Records))\n\tfor i, rec := range domn.Records {\n\t\trecs[i] = rec.Copy()\n\t}\n\tdomn.Records = recs\n\n\torigRecs := make([]*Record, len(domn.OrigRecords))\n\tfor i, rec := range domn.OrigRecords {\n\t\torigRecs[i] = rec.Copy()\n\t}\n\tdomn.OrigRecords = origRecs\n\n\treturn &domn\n}\n\nfunc (d *Domain) Json() {\n\tnewRecords := make([]*Record, 0, len(d.Records))\n\n\tfor _, rec := range d.Records {\n\t\tif !rec.IsDeleted() {\n\t\t\tnewRecords = append(newRecords, rec)\n\t\t}\n\t}\n\n\tsort.Sort(Records(newRecords))\n\n\td.Records = newRecords\n}\n\nfunc (d *Domain) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\td.Name = utils.FilterName(d.Name)\n\n\td.RootDomain = utils.FilterDomain(d.RootDomain)\n\n\tif d.Organization.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"organization_required\",\n\t\t\tMessage: \"Missing required organization\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch d.Type {\n\tcase Local, \"\":\n\t\td.Type = Local\n\t\td.Secret = bson.NilObjectID\n\t\tbreak\n\tcase AWS:\n\t\tbreak\n\tcase Cloudflare:\n\t\tbreak\n\tcase OracleCloud:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"type_invalid\",\n\t\t\tMessage: \"Type invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif d.Type != Local && d.Secret.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secret_invalid\",\n\t\t\tMessage: \"Secret invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tnewRecords := []*Record{}\n\tfor _, record := range d.Records {\n\t\trecord.Domain = d.Id\n\n\t\tif record.Operation == DELETE && record.Id.IsZero() {\n\t\t\tcontinue\n\t\t}\n\n\t\terrData, err = record.Validate(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\treturn\n\t\t}\n\n\t\tnewRecords = append(newRecords, record)\n\t}\n\td.Records = newRecords\n\n\treturn\n}\n\nfunc (d *Domain) PreCommit() {\n\td.OrigRecords = d.Records\n}\n\nfunc (d *Domain) CommitRecords(db *database.Database) (err error) {\n\terr = d.commitRecords(db, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) CommitRecordsSilent(db *database.Database) (err error) {\n\terr = d.commitRecords(db, false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) commitRecords(db *database.Database,\n\tsetTtl bool) (err error) {\n\n\tacquired := false\n\tvar lockId bson.ObjectID\n\tfor i := 0; i < 100; i++ {\n\t\tlockId, acquired, err = Lock(db, d.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif acquired {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(200 * time.Millisecond)\n\t}\n\tif !acquired {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.New(\"domain: Failed to acquire domain lock\"),\n\t\t}\n\t\treturn\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tdefer func() {\n\t\tcancel()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\te := Unlock(db, d.Id, lockId)\n\t\tif e != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"domain\": d.Id.Hex(),\n\t\t\t\t\"error\":  e,\n\t\t\t}).Error(\"domain: Failed to unlock domain\")\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tticker := time.NewTicker(1 * time.Second)\n\t\tdefer ticker.Stop()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-ticker.C:\n\t\t\t\te := Relock(db, d.Id, lockId)\n\t\t\t\tif e != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"domain\": d.Id.Hex(),\n\t\t\t\t\t\t\"error\":  e,\n\t\t\t\t\t}).Error(\"domain: Failed to relock domain\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tvar secr *secret.Secret\n\tif d.Type != Local {\n\t\tsecr, err = secret.GetOrg(db, d.Organization, d.Secret)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tnewRecords := []*Record{}\n\tfor _, record := range d.Records {\n\t\tif record.Operation == DELETE || record.IsDeleted() {\n\t\t\trecord.Operation = DELETE\n\t\t\tfor _, origRecord := range d.OrigRecords {\n\t\t\t\tif record.Id == origRecord.Id {\n\t\t\t\t\trecord = origRecord\n\t\t\t\t\trecord.Operation = DELETE\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tnewRecords = append(newRecords, record)\n\t}\n\td.Records = newRecords\n\n\tbatches := map[string]map[string]*Record{}\n\n\tfor _, record := range d.Records {\n\t\tbatchKey := record.SubDomain + \":\" + record.Type\n\t\tif batches[batchKey] == nil {\n\t\t\tbatches[batchKey] = map[string]*Record{}\n\t\t}\n\t\tcurRecord := batches[batchKey][record.Value]\n\t\tif curRecord == nil || record.Priority() > curRecord.Priority() {\n\t\t\tbatches[batchKey][record.Value] = record\n\t\t}\n\t}\n\n\tif setTtl {\n\t\td.LastUpdate = time.Now()\n\t\terr = d.CommitFields(db, set.NewSet(\"last_update\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif d.Type == OracleCloud {\n\t\terr = d.asyncBatches(db, secr, batches)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = d.syncBatches(db, secr, batches)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) syncBatches(db *database.Database, secr *secret.Secret,\n\tbatches map[string]map[string]*Record) (err error) {\n\n\tfor _, recordMap := range batches {\n\t\trecords := make([]*Record, 0, len(recordMap))\n\t\tfor _, record := range recordMap {\n\t\t\trecords = append(records, record)\n\t\t}\n\n\t\terr = d.UpdateRecords(db, secr, records)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) asyncBatches(db *database.Database, secr *secret.Secret,\n\tbatches map[string]map[string]*Record) (err error) {\n\n\twaiters := &sync.WaitGroup{}\n\twaiters.Add(len(batches))\n\n\tsemaphore := make(\n\t\tchan struct{},\n\t\tsettings.Acme.DnsMaxConcurrent,\n\t)\n\terrs := make(chan error, len(batches))\n\n\tfor _, recordMap := range batches {\n\t\trecords := make([]*Record, 0, len(recordMap))\n\t\tfor _, record := range recordMap {\n\t\t\trecords = append(records, record)\n\t\t}\n\n\t\tgo func() {\n\t\t\tsemaphore <- struct{}{}\n\t\t\tdefer func() {\n\t\t\t\t<-semaphore\n\t\t\t\twaiters.Done()\n\t\t\t}()\n\n\t\t\te := d.UpdateRecords(db, secr, records)\n\t\t\tif e != nil {\n\t\t\t\terrs <- e\n\t\t\t}\n\t\t}()\n\t}\n\n\twaiters.Wait()\n\tclose(errs)\n\n\tselect {\n\tcase err = <-errs:\n\t\treturn err\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (d *Domain) UpdateRecords(db *database.Database, secr *secret.Secret,\n\trecords []*Record) (err error) {\n\n\tops := []*dns.Operation{}\n\tsubDomain := \"\"\n\tdnsType := \"\"\n\n\tfor _, rec := range records {\n\t\tif subDomain == \"\" {\n\t\t\tsubDomain = rec.SubDomain\n\t\t} else if rec.SubDomain != subDomain {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"domain: Update subdomain inconsistent\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif dnsType == \"\" {\n\t\t\tdnsType = rec.Type\n\t\t} else if rec.Type != dnsType {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"domain: Update type inconsistent\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tswitch rec.Operation {\n\t\tcase INSERT, UPDATE:\n\t\t\tops = append(ops, &dns.Operation{\n\t\t\t\tOperation: dns.UPSERT,\n\t\t\t\tValue:     rec.Value,\n\t\t\t})\n\t\t\tbreak\n\t\tcase DELETE:\n\t\t\tops = append(ops, &dns.Operation{\n\t\t\t\tOperation: dns.DELETE,\n\t\t\t\tValue:     rec.Value,\n\t\t\t})\n\t\t\tbreak\n\t\tdefault:\n\t\t\tops = append(ops, &dns.Operation{\n\t\t\t\tOperation: dns.RETAIN,\n\t\t\t\tValue:     rec.Value,\n\t\t\t})\n\t\t}\n\t}\n\n\tdomain := subDomain + \".\" + d.RootDomain\n\n\tif d.Type != Local {\n\t\tsvc, e := d.GetDnsService(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\terr = svc.Connect(db, secr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = svc.DnsCommit(db, domain, dnsType, ops)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, rec := range records {\n\t\trec.Timestamp = time.Now()\n\n\t\tswitch rec.Operation {\n\t\tcase INSERT:\n\t\t\terr = rec.Insert(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase DELETE:\n\t\t\terr = rec.Remove(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tdefault:\n\t\t\terr = rec.Commit(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) MergeRecords(deployId bson.ObjectID,\n\tnewRecs []*Record) (newDomn *Domain) {\n\n\trecMap := map[string]map[string]map[string]*Record{}\n\n\tfor _, rec := range d.Records {\n\t\tif rec.Deployment != deployId || rec.IsDeleted() {\n\t\t\tcontinue\n\t\t}\n\n\t\tif recMap[rec.SubDomain] == nil {\n\t\t\trecMap[rec.SubDomain] = map[string]map[string]*Record{}\n\t\t}\n\t\tif recMap[rec.SubDomain][rec.Type] == nil {\n\t\t\trecMap[rec.SubDomain][rec.Type] = map[string]*Record{}\n\t\t}\n\t\trecMap[rec.SubDomain][rec.Type][rec.Value] = rec\n\t}\n\n\tfor _, newRec := range newRecs {\n\t\tif recMap[newRec.SubDomain] == nil {\n\t\t\trecMap[newRec.SubDomain] = map[string]map[string]*Record{}\n\t\t}\n\t\tif recMap[newRec.SubDomain][newRec.Type] == nil {\n\t\t\trecMap[newRec.SubDomain][newRec.Type] = map[string]*Record{}\n\t\t}\n\n\t\trec := recMap[newRec.SubDomain][newRec.Type][newRec.Value]\n\t\tif rec == nil {\n\t\t\tif newDomn == nil {\n\t\t\t\tnewDomn = d.Copy()\n\t\t\t\tnewDomn.PreCommit()\n\t\t\t}\n\t\t\tnewRec.Operation = INSERT\n\t\t\tnewDomn.Records = append(newDomn.Records, newRec)\n\t\t} else {\n\t\t\tdelete(recMap[newRec.SubDomain][newRec.Type], newRec.Value)\n\t\t}\n\t}\n\n\tfor subDomain, typeMap := range recMap {\n\t\tfor typeName, valueMap := range typeMap {\n\t\t\tfor value := range valueMap {\n\t\t\t\tif newDomn == nil {\n\t\t\t\t\tnewDomn = d.Copy()\n\t\t\t\t\tnewDomn.PreCommit()\n\t\t\t\t}\n\n\t\t\t\tfor i, domainRec := range newDomn.Records {\n\t\t\t\t\tif domainRec.SubDomain == subDomain &&\n\t\t\t\t\t\tdomainRec.Type == typeName &&\n\t\t\t\t\t\tdomainRec.Value == value {\n\n\t\t\t\t\t\tnewDomn.Records[i].Operation = DELETE\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) Commit(db *database.Database) (err error) {\n\tcoll := db.Domains()\n\n\terr = coll.Commit(d.Id, d)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Domains()\n\n\terr = coll.CommitFields(d.Id, d, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) Insert(db *database.Database) (err error) {\n\tcoll := db.Domains()\n\n\tif !d.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"domain: Domain already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = coll.InsertOne(db, d)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) GetDnsService(db *database.Database) (\n\tsvc dns.Service, err error) {\n\n\tswitch d.Type {\n\tcase AWS:\n\t\tsvc = &dns.Aws{}\n\t\tbreak\n\tcase Cloudflare:\n\t\tsvc = &dns.Cloudflare{}\n\t\tbreak\n\tcase OracleCloud:\n\t\tsvc = &dns.Oracle{}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Newf(\"domain: Unknown domain type\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) preloadRecords(recs []*Record) {\n\tif recs == nil {\n\t\td.Records = []*Record{}\n\t} else {\n\t\td.Records = recs\n\t}\n}\n\nfunc (d *Domain) LoadRecords(db *database.Database,\n\tskipDeleted bool) (err error) {\n\n\tcoll := db.DomainsRecords()\n\trecs := []*Record{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"domain\": d.Id,\n\t}, options.Find().\n\t\tSetSort(bson.D{{\"sub_domain\", 1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\trec := &Record{}\n\t\terr = cursor.Decode(rec)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif skipDeleted && (rec.Operation == DELETE ||\n\t\t\t!rec.DeleteTimestamp.IsZero()) {\n\n\t\t\tcontinue\n\t\t}\n\t\trecs = append(recs, rec)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\td.Records = recs\n\n\treturn\n}\n"
  },
  {
    "path": "domain/record.go",
    "content": "package domain\n\nimport (\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/miekg/dns\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Record struct {\n\tId              bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tDomain          bson.ObjectID `bson:\"domain\" json:\"domain\"`\n\tResource        bson.ObjectID `bson:\"resource\" json:\"resource\"`\n\tDeployment      bson.ObjectID `bson:\"deployment\" json:\"deployment\"`\n\tTimestamp       time.Time     `bson:\"timestamp\" json:\"timestamp\"`\n\tDeleteTimestamp time.Time     `bson:\"delete_timestamp\" json:\"delete_timestamp\"`\n\tSubDomain       string        `bson:\"sub_domain\" json:\"sub_domain\"`\n\tType            string        `bson:\"type\" json:\"type\"`\n\tValue           string        `bson:\"value\" json:\"value\"`\n\tOperation       string        `bson:\"-\" json:\"operation\"`\n}\n\nfunc (r *Record) Priority() int {\n\tswitch r.Operation {\n\tcase INSERT:\n\t\treturn 3\n\tcase UPDATE:\n\t\treturn 2\n\tcase DELETE:\n\t\treturn 1\n\tdefault:\n\t\treturn 0\n\t}\n}\n\nfunc (r *Record) IsDeleted() bool {\n\treturn !r.DeleteTimestamp.IsZero()\n}\n\nfunc (r *Record) Copy() *Record {\n\trec := *r\n\treturn &rec\n}\n\nfunc (r *Record) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tr.SubDomain = utils.FilterDomain(r.SubDomain)\n\tif r.SubDomain == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"subdomain_required\",\n\t\t\tMessage: \"Missing required sub-domain\",\n\t\t}\n\t\treturn\n\t}\n\n\tif r.Domain.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"domain_required\",\n\t\t\tMessage: \"Missing required domain\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch r.Type {\n\tcase A:\n\t\tip := net.ParseIP(r.Value)\n\t\tif ip == nil || ip.To4() == nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_value\",\n\t\t\t\tMessage: \"Domain value is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tr.Value = ip.String()\n\tcase AAAA:\n\t\tip := net.ParseIP(r.Value)\n\t\tif ip == nil || ip.To4() != nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_value\",\n\t\t\t\tMessage: \"Domain value is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tr.Value = ip.String()\n\tcase CNAME:\n\t\tif !strings.HasSuffix(r.Value, \".\") {\n\t\t\tr.Value += \".\"\n\t\t}\n\n\t\tif !dns.IsFqdn(r.Value) {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_value\",\n\t\t\t\tMessage: \"Domain value is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tr.Value = strings.TrimSuffix(r.Value, \".\")\n\tdefault:\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.New(\"domain: Unknown record type\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif r.Value == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"value_required\",\n\t\t\tMessage: \"Missing required value\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (r *Record) Commit(db *database.Database) (err error) {\n\tcoll := db.DomainsRecords()\n\n\terr = coll.Commit(r.Id, r)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (r *Record) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.DomainsRecords()\n\n\terr = coll.CommitFields(r.Id, r, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (r *Record) Remove(db *database.Database) (err error) {\n\tcoll := db.DomainsRecords()\n\n\tif r.DeleteTimestamp.IsZero() {\n\t\tr.DeleteTimestamp = time.Now()\n\n\t\terr = coll.CommitFields(r.Id, r, set.NewSet(\"delete_timestamp\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t}\n\n\tdeleteTtl := time.Duration(settings.System.DomainDeleteTtl) * time.Second\n\tif time.Since(r.DeleteTimestamp) < deleteTtl {\n\t\treturn\n\t}\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": r.Id,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (r *Record) Insert(db *database.Database) (err error) {\n\tcoll := db.DomainsRecords()\n\n\topts := options.FindOneAndUpdate().\n\t\tSetUpsert(true).\n\t\tSetReturnDocument(options.After)\n\n\tnewRec := &Record{}\n\terr = coll.FindOneAndUpdate(db, &bson.M{\n\t\t\"domain\":     r.Domain,\n\t\t\"sub_domain\": r.SubDomain,\n\t\t\"type\":       r.Type,\n\t\t\"value\":      r.Value,\n\t}, &bson.M{\n\t\t\"$set\": r,\n\t}, opts).Decode(&newRec)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tr.Id = newRec.Id\n\n\treturn\n}\n"
  },
  {
    "path": "domain/sort.go",
    "content": "package domain\n\nimport (\n\t\"strings\"\n)\n\ntype Records []*Record\n\nfunc (r Records) Len() int {\n\treturn len(r)\n}\n\nfunc (r Records) Swap(i, j int) {\n\tr[i], r[j] = r[j], r[i]\n}\n\nfunc (r Records) Less(i, j int) bool {\n\tpartsI := strings.Split(r[i].SubDomain, \".\")\n\tpartsJ := strings.Split(r[j].SubDomain, \".\")\n\n\tfor idx := 0; idx < len(partsI)/2; idx++ {\n\t\tpartsI[idx], partsI[len(partsI)-1-idx] = partsI[len(\n\t\t\tpartsI)-1-idx], partsI[idx]\n\t}\n\tfor idx := 0; idx < len(partsJ)/2; idx++ {\n\t\tpartsJ[idx], partsJ[len(partsJ)-1-idx] = partsJ[len(\n\t\t\tpartsJ)-1-idx], partsJ[idx]\n\t}\n\n\tminLen := len(partsI)\n\tif len(partsJ) < minLen {\n\t\tminLen = len(partsJ)\n\t}\n\n\tfor idx := 0; idx < minLen; idx++ {\n\t\tif partsI[idx] != partsJ[idx] {\n\t\t\treturn partsI[idx] < partsJ[idx]\n\t\t}\n\t}\n\n\treturn len(partsI) < len(partsJ)\n}\n"
  },
  {
    "path": "domain/utils.go",
    "content": "package domain\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc Refresh(db *database.Database, domnId bson.ObjectID) {\n\tcoll := db.Domains()\n\tdomn := &Domain{}\n\n\terr := coll.FindOne(db, &bson.M{\n\t\t\"_id\": domnId,\n\t}).Decode(domn)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"domain\": domn.Id.Hex(),\n\t\t\t\"error\":  err,\n\t\t}).Error(\"domain: Domain refresh failed to find domain\")\n\t\treturn\n\t}\n\n\tif domn.Locked() {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"domain\": domn.Id.Hex(),\n\t\t}).Info(\"domain: Skipping refresh on locked domain\")\n\t\treturn\n\t}\n\n\terr = domn.LoadRecords(db, false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = domn.CommitRecordsSilent(db)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"domain\": domn.Id.Hex(),\n\t\t\t\"error\":  err,\n\t\t}).Error(\"domain: Domain refresh failed\")\n\t\treturn\n\t}\n\n\tdeleteTtl := time.Duration(settings.System.DomainDeleteTtl) * time.Second\n\tnow := time.Now()\n\tfor _, rec := range domn.Records {\n\t\tif rec.IsDeleted() && now.Sub(rec.DeleteTimestamp) > deleteTtl {\n\t\t\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\t\t\"_id\":              rec.Id,\n\t\t\t\t\"delete_timestamp\": rec.DeleteTimestamp,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Get(db *database.Database, domnId bson.ObjectID) (\n\tdomn *Domain, err error) {\n\n\tcoll := db.Domains()\n\tdomn = &Domain{}\n\n\terr = coll.FindOneId(domnId, domn)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, domnId bson.ObjectID) (\n\tdomn *Domain, err error) {\n\n\tcoll := db.Domains()\n\tdomn = &Domain{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          domnId,\n\t\t\"organization\": orgId,\n\t}).Decode(domn)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (domn *Domain, err error) {\n\tcoll := db.Domains()\n\tdomn = &Domain{}\n\n\terr = coll.FindOne(db, query).Decode(domn)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExistsOrg(db *database.Database, orgId, domnId bson.ObjectID) (\n\texists bool, err error) {\n\n\tcoll := db.Domains()\n\n\tn, err := coll.CountDocuments(db, &bson.M{\n\t\t\"_id\":          domnId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif n > 0 {\n\t\texists = true\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tdomns []*Domain, err error) {\n\n\tcoll := db.Domains()\n\tdomns = []*Domain{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdmn := &Domain{}\n\t\terr = cursor.Decode(dmn)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdomns = append(domns, dmn)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetLoadedAllIds(db *database.Database, domnIds []bson.ObjectID) (\n\tdomns []*Domain, err error) {\n\n\tcoll := db.DomainsRecords()\n\tdomainRecs := map[bson.ObjectID][]*Record{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"domain\": &bson.M{\n\t\t\t\"$in\": domnIds,\n\t\t},\n\t}, options.Find().\n\t\tSetSort(bson.D{{\"sub_domain\", 1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\trec := &Record{}\n\t\terr = cursor.Decode(rec)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdomainRecs[rec.Domain] = append(domainRecs[rec.Domain], rec)\n\t}\n\n\tcoll = db.Domains()\n\tdomns = []*Domain{}\n\n\tcursor, err = coll.Find(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": domnIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdmn := &Domain{}\n\t\terr = cursor.Decode(dmn)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdmn.preloadRecords(domainRecs[dmn.Id])\n\n\t\tdomns = append(domns, dmn)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc PreloadedRecords(domns []*Domain, recs []*Record) []*Domain {\n\tdomainRecs := map[bson.ObjectID][]*Record{}\n\tfor _, rec := range recs {\n\t\tdomainRecs[rec.Domain] = append(domainRecs[rec.Domain], rec)\n\t}\n\n\tfor _, domn := range domns {\n\t\tdomn.preloadRecords(domainRecs[domn.Id])\n\t}\n\n\treturn domns\n}\n\nfunc GetAllName(db *database.Database, query *bson.M) (\n\tdomns []*Domain, err error) {\n\n\tcoll := db.Domains()\n\tdomns = []*Domain{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetProjection(bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"organization\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdmn := &Domain{}\n\t\terr = cursor.Decode(dmn)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tdomns = append(domns, dmn)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetRecordAll(db *database.Database, query *bson.M) (\n\trecs []*Record, err error) {\n\n\tcoll := db.DomainsRecords()\n\trecs = []*Record{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\trec := &Record{}\n\t\terr = cursor.Decode(rec)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\trecs = append(recs, rec)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Lock(db *database.Database, domnId bson.ObjectID) (\n\tlockId bson.ObjectID, acquired bool, err error) {\n\n\tcoll := db.Domains()\n\n\tnewLockId := bson.NewObjectID()\n\tnow := time.Now()\n\tttl := now.Add(-time.Duration(\n\t\tsettings.System.DomainLockTtl) * time.Second)\n\n\tresp, err := coll.UpdateOne(db, &bson.M{\n\t\t\"_id\": domnId,\n\t\t\"$or\": []bson.M{\n\t\t\t{\"lock_id\": Vacant},\n\t\t\t{\"lock_timestamp\": bson.M{\"$lt\": ttl}},\n\t\t},\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"lock_id\":        newLockId,\n\t\t\t\"lock_timestamp\": now,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\tif resp.ModifiedCount > 0 {\n\t\tlockId = newLockId\n\t\tacquired = true\n\t}\n\n\treturn\n}\n\nfunc Relock(db *database.Database, domnId,\n\tlockId bson.ObjectID) (err error) {\n\n\tcoll := db.Domains()\n\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\":     domnId,\n\t\t\"lock_id\": lockId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"lock_timestamp\": time.Now(),\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Unlock(db *database.Database, domnId,\n\tlockId bson.ObjectID) (err error) {\n\n\tcoll := db.Domains()\n\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\":     domnId,\n\t\t\"lock_id\": lockId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"lock_id\": Vacant,\n\t\t},\n\t\t\"$unset\": &bson.M{\n\t\t\t\"lock_timestamp\": 1,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, domnId bson.ObjectID) (err error) {\n\tcoll := db.Domains()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": domnId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, domnId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Domains()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          domnId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, domnIds []bson.ObjectID) (err error) {\n\tcoll := db.Domains()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": domnIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tdomnIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Domains()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": domnIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "drive/drive.go",
    "content": "package drive\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\nvar (\n\tsyncLast  time.Time\n\tsyncLock  sync.Mutex\n\tsyncCache []*Device\n)\n\ntype Device struct {\n\tId string `bson:\"id\" json:\"id\"`\n}\n"
  },
  {
    "path": "drive/utils.go",
    "content": "package drive\n\nimport (\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc GetDevices() (devices []*Device, err error) {\n\tif time.Since(syncLast) < 30*time.Second {\n\t\tdevices = syncCache\n\t\treturn\n\t}\n\n\tsyncLock.Lock()\n\tdefer syncLock.Unlock()\n\n\tdiskIds, err := ioutil.ReadDir(\"/dev/disk/by-id/\")\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"drive: Failed to list disk IDs\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, item := range diskIds {\n\t\tfilename := item.Name()\n\n\t\tdevice := &Device{\n\t\t\tId: filename,\n\t\t}\n\t\tdevices = append(devices, device)\n\t}\n\n\tsyncCache = devices\n\tsyncLast = time.Now()\n\n\treturn\n}\n\nfunc GetDriveHashId(id string) string {\n\thash := md5.New()\n\thash.Write([]byte(id))\n\treturn fmt.Sprintf(\"%x\", hash.Sum(nil))\n}\n"
  },
  {
    "path": "engine/bash.go",
    "content": "package engine\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nconst shellEnvExport = `\necho \"<STARTER_ENV_EXPORT>\"\nenv\necho \"</STARTER_ENV_EXPORT>\"\n`\n\nvar colorRe = regexp.MustCompile(`\\x1b\\[[0-9;]*[a-zA-Z]`)\n\ntype BashEngine struct {\n\tcmd        *exec.Cmd\n\tcwd        string\n\tshell      string\n\tstdout     io.ReadCloser\n\tstderr     io.ReadCloser\n\tcurEnvKeys set.Set\n\tstarter    *Engine\n}\n\nfunc (b *BashEngine) Init(strt *Engine) (err error) {\n\tb.starter = strt\n\tb.curEnvKeys = set.NewSet()\n\n\t_, err = exec.LookPath(\"bash\")\n\tif err == nil {\n\t\tb.shell = \"bash\"\n\t} else {\n\t\tb.shell = \"sh\"\n\t\terr = nil\n\t}\n\n\tfor _, pairStr := range os.Environ() {\n\t\tpair := strings.SplitN(pairStr, \"=\", 2)\n\t\tb.curEnvKeys.Add(pair[0])\n\t}\n\n\treturn\n}\n\nfunc (b *BashEngine) streamOut(reader io.Reader) (env []string) {\n\tscanner := bufio.NewScanner(reader)\n\tenvCapture := false\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tcleanLine := colorRe.ReplaceAllString(line, \"\")\n\n\t\tif envCapture {\n\t\t\tif strings.HasPrefix(line, \"</STARTER_ENV_EXPORT>\") {\n\t\t\t\tenvCapture = false\n\t\t\t} else {\n\t\t\t\tenv = append(env, line)\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(line, \"<STARTER_ENV_EXPORT>\") {\n\t\t\t\tenvCapture = true\n\t\t\t} else if cleanLine != \"\" {\n\t\t\t\tb.starter.ProcessOutput(cleanLine)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (b *BashEngine) streamErr(reader io.Reader) {\n\tscanner := bufio.NewScanner(reader)\n\tenvCapture := false\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tcleanLine := colorRe.ReplaceAllString(line, \"\")\n\n\t\tif envCapture {\n\t\t\tif strings.HasPrefix(line, \"echo \\\"</STARTER_ENV_EXPORT>\\\"\") {\n\t\t\t\tenvCapture = false\n\t\t\t} else if strings.TrimSpace(line) == \"env\" {\n\n\t\t\t} else if cleanLine != \"\" {\n\t\t\t\tb.starter.ProcessOutput(cleanLine)\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(line, \"echo \\\"<STARTER_ENV_EXPORT>\\\"\") {\n\t\t\t\tenvCapture = true\n\t\t\t} else if cleanLine != \"\" {\n\t\t\t\tb.starter.ProcessOutput(cleanLine)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (b *BashEngine) Run(block string) (err error) {\n\tcmd := exec.Command(b.shell, \"-v\", \"-c\", block+shellEnvExport)\n\tcmd.Env = b.starter.GetEnviron()\n\tcmd.Dir = b.starter.GetCwd()\n\n\tstdout, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to create stdout pipe\"),\n\t\t}\n\t\treturn\n\t}\n\n\tstderr, err := cmd.StderrPipe()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to create stderr pipe\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = cmd.Start()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to start command\"),\n\t\t}\n\t\treturn\n\t}\n\n\tenv := []string{}\n\twg := sync.WaitGroup{}\n\twg.Add(2)\n\n\tgo func() {\n\t\tenv = b.streamOut(stdout)\n\t\twg.Done()\n\t}()\n\n\tgo func() {\n\t\tb.streamErr(stderr)\n\t\twg.Done()\n\t}()\n\n\terr = cmd.Wait()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Exit error in \"+b.shell),\n\t\t}\n\t\treturn\n\t}\n\n\twg.Wait()\n\n\tfor _, pairStr := range env {\n\t\tpair := strings.SplitN(pairStr, \"=\", 2)\n\t\tif len(pair) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tkey, val := pair[0], pair[1]\n\n\t\tif key == \"PWD\" {\n\t\t\tb.starter.UpdateCwd(val)\n\t\t} else if !b.curEnvKeys.Contains(key) {\n\t\t\tb.starter.UpdateEnv(key, val)\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "engine/constants.go",
    "content": "package engine\n\nconst (\n\tQueueSize = 256\n)\n"
  },
  {
    "path": "engine/engine.go",
    "content": "package engine\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\ntype Engine struct {\n\tcwd        string\n\tenv        map[string]string\n\tblocks     []*Block\n\tbash       *BashEngine\n\tpython     *PythonEngine\n\tlock       sync.Mutex\n\toutputLock sync.Mutex\n\tfault      atomic.Value\n\tqueue      chan []*Block\n\tOnStatus   func(status string)\n}\n\nfunc (e *Engine) UpdateEnv(key, val string) {\n\te.env[key] = val\n}\n\nfunc (e *Engine) UpdateCwd(cwd string) {\n\te.cwd = cwd\n}\n\nfunc (e *Engine) ProcessOutput(output string) {\n\te.outputLock.Lock()\n\tfmt.Println(output)\n\te.outputLock.Unlock()\n}\n\nfunc (e *Engine) GetEnv() map[string]string {\n\treturn e.env\n}\n\nfunc (e *Engine) GetCwd() string {\n\treturn e.cwd\n}\n\nfunc (e *Engine) GetEnviron() (env []string) {\n\tenv = []string{}\n\n\tfor _, pair := range os.Environ() {\n\t\tkey := strings.SplitN(pair, \"=\", 2)[0]\n\t\tif e.env[key] == \"\" {\n\t\t\tenv = append(env, pair)\n\t\t}\n\t}\n\n\tfor key, val := range e.env {\n\t\tenv = append(env, key+\"=\"+val)\n\t}\n\n\treturn\n}\n\nfunc (e *Engine) Init() (err error) {\n\te.queue = make(chan []*Block, QueueSize)\n\te.fault.Store(false)\n\n\te.cwd, err = os.Getwd()\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"starter: Failed to get working dir\"),\n\t\t}\n\t\treturn\n\t}\n\n\te.env = map[string]string{}\n\n\te.bash = &BashEngine{}\n\terr = e.bash.Init(e)\n\tif err != nil {\n\t\treturn\n\t}\n\n\te.python = &PythonEngine{}\n\terr = e.python.Init(e)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr2 := e.python.Exit()\n\t\tif err2 != nil {\n\t\t\tpanic(err2)\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (e *Engine) StartRunner() {\n\tgo e.runner()\n}\n\nfunc (e *Engine) getBlocks() (blocks []*Block) {\n\tblocks = <-e.queue\n\n\tfor {\n\t\tselect {\n\t\tcase req := <-e.queue:\n\t\t\tblocks = req\n\t\tdefault:\n\t\t\treturn blocks\n\t\t}\n\t}\n}\n\nfunc (e *Engine) UpdateSpec(data string) (err error) {\n\tblocks, err := Parse(data)\n\tif err != nil {\n\t\treturn\n\t}\n\n\te.blocks = blocks\n\n\treturn\n}\n\nfunc (e *Engine) runner() {\n\tfor {\n\t\tblocks := e.getBlocks()\n\n\t\tif !e.fault.Load().(bool) {\n\t\t\te.OnStatus(types.ReloadingClean)\n\t\t} else {\n\t\t\te.OnStatus(types.ReloadingFault)\n\t\t}\n\n\t\t_, err := e.Run(Reload, blocks)\n\t\tif err != nil {\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"agent: Failed to run spec\")\n\t\t\te.OnStatus(types.Fault)\n\t\t} else {\n\t\t\te.OnStatus(types.Running)\n\t\t}\n\t}\n}\n\nfunc (e *Engine) Run(phase string, blocks []*Block) (fatal bool, err error) {\n\tfor i, block := range blocks {\n\t\tswitch phase {\n\t\tcase Initial:\n\t\t\tbreak\n\t\tcase Reboot:\n\t\t\tif block.Phase != Reboot && block.Phase != Reload {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\tcase Reload:\n\t\t\tif block.Phase != Reload {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\terr = e.runBlock(block.Type, block.Code)\n\t\tif err != nil {\n\t\t\tfor _, block := range blocks[i:] {\n\t\t\t\tswitch phase {\n\t\t\t\tcase Initial:\n\t\t\t\t\tif block.Phase != Reload {\n\t\t\t\t\t\tfatal = true\n\t\t\t\t\t}\n\t\t\t\tcase Reboot:\n\t\t\t\t\tif block.Phase != Reboot && block.Phase != Reload {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif block.Phase != Reload {\n\t\t\t\t\t\tfatal = true\n\t\t\t\t\t}\n\t\t\t\tcase Reload:\n\t\t\t\t\tif block.Phase != Reload {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\te.fault.Store(true)\n\t\t\treturn\n\t\t}\n\t}\n\n\te.fault.Store(false)\n\n\treturn\n}\n\nfunc (e *Engine) runBlock(blockType, block string) (err error) {\n\tswitch blockType {\n\tcase \"shell\":\n\t\terr = e.bash.Run(block)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\tcase \"python\":\n\t\terr = e.python.Run(block)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (e *Engine) Queue(data string) {\n\tblocks, err := Parse(data)\n\tif err != nil {\n\t\treturn\n\t}\n\n\te.lock.Lock()\n\tif len(e.queue) >= QueueSize-16 {\n\t\treturn\n\t}\n\te.queue <- blocks\n\te.lock.Unlock()\n}\n"
  },
  {
    "path": "engine/parser.go",
    "content": "package engine\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n)\n\ntype Block struct {\n\tType    string\n\tPhase   string\n\tCode    string\n\tLineNum int\n}\n\nconst (\n\tInitial = \"initial\"\n\tReboot  = \"reboot\"\n\tReload  = \"reload\"\n\tImage   = \"image\"\n)\n\nvar (\n\tcodeBlockRe = regexp.MustCompile(`^([a-zA-Z]+)\\s*(\\{([^}]+)\\})?$`)\n)\n\nfunc Parse(data string) (blocks []*Block, err error) {\n\tblocks = []*Block{}\n\n\tvar curBlock *Block\n\n\tfor n, line := range strings.Split(data, \"\\n\") {\n\t\tif curBlock == nil {\n\t\t\tif strings.HasPrefix(line, \"```\") {\n\t\t\t\tlang, attrs := parseCodeBlockHeader(line[3:])\n\n\t\t\t\tphase := Initial\n\t\t\t\tif attrs != nil {\n\t\t\t\t\tswitch attrs[\"phase\"] {\n\t\t\t\t\tcase Initial:\n\t\t\t\t\t\tphase = Initial\n\t\t\t\t\tcase Reboot:\n\t\t\t\t\t\tphase = Reboot\n\t\t\t\t\tcase Reload:\n\t\t\t\t\t\tphase = Reload\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch lang {\n\t\t\t\tcase \"shell\":\n\t\t\t\t\tcurBlock = &Block{\n\t\t\t\t\t\tType:  \"shell\",\n\t\t\t\t\t\tPhase: phase,\n\t\t\t\t\t}\n\t\t\t\tcase \"python\":\n\t\t\t\t\tcurBlock = &Block{\n\t\t\t\t\t\tType:  \"python\",\n\t\t\t\t\t\tPhase: phase,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif line == \"```\" {\n\t\t\t\tcurBlock.LineNum = n + 1\n\t\t\t\tblocks = append(blocks, curBlock)\n\t\t\t\tcurBlock = nil\n\t\t\t} else {\n\t\t\t\tcurBlock.Code += line + \"\\n\"\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc parseCodeBlockHeader(input string) (language string,\n\tattrs map[string]string) {\n\n\tattrs = map[string]string{}\n\n\tmatches := codeBlockRe.FindStringSubmatch(input)\n\tif len(matches) == 0 {\n\t\treturn\n\t}\n\n\tlanguage = matches[1]\n\tif len(matches) < 3 {\n\t\treturn\n\t}\n\n\tattrPairs := strings.Split(matches[2], \",\")\n\tfor _, pair := range attrPairs {\n\t\tpair = strings.TrimPrefix(pair, \"{\")\n\t\tpair = strings.TrimSuffix(pair, \"}\")\n\n\t\tkeyValue := strings.SplitN(pair, \"=\", 2)\n\t\tif len(keyValue) == 2 {\n\t\t\tkey := strings.TrimSpace(keyValue[0])\n\t\t\tvalue := strings.Trim(strings.TrimSpace(keyValue[1]), `\"`)\n\t\t\tattrs[key] = value\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "engine/python.go",
    "content": "package engine\n\nimport (\n\t\"bufio\"\n\t\"container/list\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nvar (\n\tPythonExec = \"python3\"\n)\n\nconst pyEngine = `#!/usr/bin/env python3\nimport platform\nimport os\nimport json\nimport traceback\n\ndef _pystarter_get_startup():\n    python_version = platform.python_version()\n    compiler = platform.python_compiler()\n    os_name = platform.system().lower() + \" \" + platform.release()\n    build_date = \" \".join(platform.python_build()[1].split()[1:4])\n    return f\"Python {python_version} ({build_date}) [{compiler}] on {os_name}\"\n\ndef export(name, arg):\n    name = ''.join(c for c in name if c.isalnum() or c == '_')\n    arg = json.dumps(arg)\n    print(f\"<PYSTARTER_EXPORT_VAR>{name}={arg}</PYSTARTER_EXPORT_VAR>\")\n\nprint(_pystarter_get_startup())\nprint(\"<PYSTARTER_INIT_COMPLETE/>\")\n\nwhile True:\n    data = input()\n    if not data:\n        continue\n\n    data = json.loads(data)\n\n    if data[\"type\"] == \"exit\":\n        exit(0)\n    elif data[\"type\"] == \"env\":\n        for key, val in data[\"env\"].items():\n            os.environ[key] = val\n        print(\"<PYSTARTER_UPDATE_COMPLETE/>\")\n    elif data[\"type\"] == \"chdir\":\n        os.chdir(data[\"input\"])\n        print(\"<PYSTARTER_UPDATE_COMPLETE/>\")\n    elif data[\"type\"] == \"exec\":\n        try:\n            exec(data[\"input\"])\n        except Exception as err:\n            print(f\"An error occurred: {err}\")\n            print(f\"Exception type: {type(err).__name__}\")\n            traceback.print_exc()\n        print(f\"<PYSTARTER_EXEC_COMPLETE>\"\n            f\"{os.getcwd()}</PYSTARTER_EXEC_COMPLETE>\")\n`\n\ntype pythonData struct {\n\tType  string            `json:\"type\"`\n\tInput string            `json:\"input\"`\n\tEnv   map[string]string `json:\"env\"`\n}\n\ntype PythonEngine struct {\n\tcmd         *exec.Cmd\n\tcwd         string\n\tstdin       io.WriteCloser\n\tstdout      io.ReadCloser\n\tstderr      io.ReadCloser\n\tcmdErr      error\n\tinputLines  *list.List\n\twaiter      chan bool\n\tinitialized bool\n\tstarter     *Engine\n\n\toutput string\n}\n\nfunc (p *PythonEngine) Init(strt *Engine) (err error) {\n\tp.starter = strt\n\n\treturn\n}\n\nfunc (p *PythonEngine) start() (err error) {\n\tp.waiter = make(chan bool, 16)\n\tp.inputLines = list.New()\n\n\tp.cmd = exec.Command(PythonExec, \"-u\", \"-c\", pyEngine)\n\tp.cmd.Env = p.starter.GetEnviron()\n\tp.cmd.Dir = p.starter.GetCwd()\n\n\tp.stdout, err = p.cmd.StdoutPipe()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to get py stdout\"),\n\t\t}\n\t\treturn\n\t}\n\n\tp.stderr, err = p.cmd.StderrPipe()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to get py stderr\"),\n\t\t}\n\t\treturn\n\t}\n\n\tp.stdin, err = p.cmd.StdinPipe()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to get py stdin\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = p.cmd.Start()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to start py\"),\n\t\t}\n\t\treturn\n\t}\n\tp.wait()\n\n\tp.copyOutput(p.stdout)\n\tp.copyOutput(p.stderr)\n\n\t<-p.waiter\n\n\treturn\n}\n\nfunc (p *PythonEngine) flushOutput() {\n\tif p.output == \"\" {\n\t\treturn\n\t}\n\toutput := p.output\n\tp.output = \"\"\n\toutput = strings.TrimRight(output, \"\\n\")\n\tp.starter.ProcessOutput(output)\n}\n\nfunc (p *PythonEngine) copyOutput(src io.ReadCloser) {\n\tgo func() {\n\t\tscanner := bufio.NewScanner(src)\n\t\tfor scanner.Scan() {\n\t\t\toutput := scanner.Text()\n\n\t\t\tif !p.initialized {\n\t\t\t\tif strings.Contains(output, \"<PYSTARTER_INIT_COMPLETE/>\") {\n\t\t\t\t\tp.initialized = true\n\t\t\t\t\tp.waiter <- true\n\t\t\t\t} else {\n\t\t\t\t\tp.starter.ProcessOutput(output)\n\t\t\t\t}\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\texecDoneStart := strings.Index(output, \"<PYSTARTER_EXEC_COMPLETE>\")\n\t\t\texportStart := strings.Index(output, \"<PYSTARTER_EXPORT_VAR>\")\n\n\t\t\tif execDoneStart != -1 {\n\t\t\t\texecDoneStart += 25\n\t\t\t\texecDoneEnd := strings.Index(output,\n\t\t\t\t\t\"</PYSTARTER_EXEC_COMPLETE>\")\n\t\t\t\tif execDoneEnd == -1 {\n\t\t\t\t\terr := &errortypes.ExecError{\n\t\t\t\t\t\terrors.Newf(\n\t\t\t\t\t\t\t\"starter: Incomplete exec response '%s'\", output),\n\t\t\t\t\t}\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\n\t\t\t\tp.starter.UpdateCwd(output[execDoneStart:execDoneEnd])\n\t\t\t\tp.waiter <- true\n\t\t\t} else if exportStart != -1 {\n\t\t\t\texportStart += 22\n\t\t\t\texportEnd := strings.Index(output, \"</PYSTARTER_EXPORT_VAR>\")\n\t\t\t\tif exportEnd == -1 {\n\t\t\t\t\terr := &errortypes.ExecError{\n\t\t\t\t\t\terrors.Newf(\n\t\t\t\t\t\t\t\"starter: Incomplete export '%s'\", output),\n\t\t\t\t\t}\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\n\t\t\t\tpair := strings.SplitN(output[exportStart:exportEnd], \"=\", 2)\n\t\t\t\tif len(pair) == 2 {\n\t\t\t\t\tkey, val := pair[0], pair[1]\n\n\t\t\t\t\tif val[0] == '\"' && val[len(val)-1] == '\"' {\n\t\t\t\t\t\tval = val[1 : len(val)-1]\n\t\t\t\t\t}\n\n\t\t\t\t\tp.starter.UpdateEnv(key, val)\n\t\t\t\t}\n\t\t\t} else if strings.Contains(output,\n\t\t\t\t\"<PYSTARTER_UPDATE_COMPLETE/>\") {\n\n\t\t\t\tp.waiter <- true\n\t\t\t} else {\n\t\t\t\tp.output += output + \"\\n\"\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (p *PythonEngine) wait() {\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif p.stdin != nil {\n\t\t\t\t_ = p.stdin.Close()\n\t\t\t}\n\t\t}()\n\n\t\terr := p.cmd.Wait()\n\t\tif err != nil {\n\t\t\tp.flushOutput()\n\t\t\terr = &errortypes.ExecError{\n\t\t\t\terrors.Wrap(err, \"starter: Exit error in py\"),\n\t\t\t}\n\t\t\tp.cmdErr = err\n\t\t\tp.waiter <- true\n\t\t\treturn\n\t\t}\n\n\t\tp.waiter <- true\n\t}()\n}\n\nfunc (p *PythonEngine) updateEnv() (err error) {\n\tdefer func() {\n\t\tp.flushOutput()\n\t}()\n\tp.waiter = make(chan bool, 16)\n\n\tdata := &pythonData{\n\t\tType: \"env\",\n\t\tEnv:  p.starter.GetEnv(),\n\t}\n\n\tdataIn, err := json.Marshal(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"starter: Failed to marshal env\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = fmt.Fprintln(p.stdin, string(dataIn))\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to update env in py\"),\n\t\t}\n\t\treturn\n\t}\n\n\t<-p.waiter\n\n\treturn\n}\n\nfunc (p *PythonEngine) updateCwd() (err error) {\n\tdefer func() {\n\t\tp.flushOutput()\n\t}()\n\tp.waiter = make(chan bool, 16)\n\n\tdata := &pythonData{\n\t\tType:  \"chdir\",\n\t\tInput: p.starter.GetCwd(),\n\t}\n\n\tdataIn, err := json.Marshal(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"starter: Failed to marshal env\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = fmt.Fprintln(p.stdin, string(dataIn))\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to update env in py\"),\n\t\t}\n\t\treturn\n\t}\n\n\t<-p.waiter\n\n\treturn\n}\n\nfunc (p *PythonEngine) run(code string) (err error) {\n\tdefer func() {\n\t\tp.flushOutput()\n\t}()\n\tp.waiter = make(chan bool, 16)\n\n\tdata := &pythonData{\n\t\tType:  \"exec\",\n\t\tInput: code,\n\t}\n\n\tdataIn, err := json.Marshal(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"starter: Failed to marshal code\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = fmt.Fprintln(p.stdin, string(dataIn))\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to run code in py\"),\n\t\t}\n\t\treturn\n\t}\n\n\t<-p.waiter\n\n\treturn\n}\n\nfunc (p *PythonEngine) Exit() (err error) {\n\tp.waiter = make(chan bool, 16)\n\n\tif p.cmd == nil {\n\t\treturn\n\t}\n\n\tdata := &pythonData{\n\t\tType: \"exit\",\n\t}\n\n\tdataIn, err := json.Marshal(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"starter: Failed to marshal exit signal\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = fmt.Fprintln(p.stdin, string(dataIn))\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrap(err, \"starter: Failed to run exit signal in py\"),\n\t\t}\n\t\treturn\n\t}\n\n\t<-p.waiter\n\n\tif p.cmdErr != nil {\n\t\terr = p.cmdErr\n\t}\n\n\treturn\n}\n\nfunc (p *PythonEngine) Run(code string) (err error) {\n\tif p.cmd == nil {\n\t\terr = p.start()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = p.updateEnv()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = p.updateCwd()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, line := range strings.Split(code, \"\\n\") {\n\t\tp.starter.ProcessOutput(\">>> \" + line)\n\t}\n\n\terr = p.run(code)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif p.cmdErr != nil {\n\t\terr = p.cmdErr\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "errortypes/errortypes.go",
    "content": "package errortypes\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype UnknownError struct {\n\terrors.DropboxError\n}\n\ntype NotFoundError struct {\n\terrors.DropboxError\n}\n\ntype ReadError struct {\n\terrors.DropboxError\n}\n\ntype WriteError struct {\n\terrors.DropboxError\n}\n\ntype ParseError struct {\n\terrors.DropboxError\n}\n\ntype AuthenticationError struct {\n\terrors.DropboxError\n}\n\ntype VerificationError struct {\n\terrors.DropboxError\n}\n\ntype ApiError struct {\n\terrors.DropboxError\n}\n\ntype DatabaseError struct {\n\terrors.DropboxError\n}\n\ntype RequestError struct {\n\terrors.DropboxError\n}\n\ntype ConnectionError struct {\n\terrors.DropboxError\n}\n\ntype TimeoutError struct {\n\terrors.DropboxError\n}\n\ntype ExecError struct {\n\terrors.DropboxError\n}\n\ntype NetworkError struct {\n\terrors.DropboxError\n}\n\ntype TypeError struct {\n\terrors.DropboxError\n}\n\ntype ErrorData struct {\n\tError   string `json:\"error\"`\n\tMessage string `json:\"error_msg\"`\n}\n\nfunc (e *ErrorData) GetError() (err error) {\n\terr = &ParseError{\n\t\terrors.Newf(\"error: Parse error %s - %s\", e.Error, e.Message),\n\t}\n\treturn\n}\n\nfunc GetErrorMessage(err error) string {\n\tif err == nil {\n\t\treturn \"\"\n\t}\n\tif intErr, ok := err.(errors.DropboxError); ok {\n\t\treturn intErr.GetMessage()\n\t}\n\treturn err.Error()\n}\n"
  },
  {
    "path": "eval/constants.go",
    "content": "package eval\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\ntype Equal struct{}\ntype NotEqual struct{}\ntype Less struct{}\ntype LessEqual struct{}\ntype Greater struct{}\ntype GreaterEqual struct{}\ntype If struct{}\ntype And struct{}\ntype Or struct{}\ntype For struct{}\ntype Then struct{}\n\nconst (\n\tStatementMaxLength = 1024\n\tStatementMaxParts  = 30\n)\n\nvar StatementSafeCharacters = set.NewSet(\n\t'a',\n\t'b',\n\t'c',\n\t'd',\n\t'e',\n\t'f',\n\t'g',\n\t'h',\n\t'i',\n\t'j',\n\t'k',\n\t'l',\n\t'm',\n\t'n',\n\t'o',\n\t'p',\n\t'q',\n\t'r',\n\t's',\n\t't',\n\t'u',\n\t'v',\n\t'w',\n\t'x',\n\t'y',\n\t'z',\n\t'A',\n\t'B',\n\t'C',\n\t'D',\n\t'E',\n\t'F',\n\t'G',\n\t'H',\n\t'I',\n\t'J',\n\t'K',\n\t'L',\n\t'M',\n\t'N',\n\t'O',\n\t'P',\n\t'Q',\n\t'R',\n\t'S',\n\t'T',\n\t'U',\n\t'V',\n\t'W',\n\t'X',\n\t'Y',\n\t'Z',\n\t'0',\n\t'1',\n\t'2',\n\t'3',\n\t'4',\n\t'5',\n\t'6',\n\t'7',\n\t'8',\n\t'9',\n\t' ',\n\t'.',\n\t'_',\n\t'-',\n\t'=',\n\t'>',\n\t'<',\n\t'!',\n\t'\\'',\n\t'(',\n\t')',\n)\n"
  },
  {
    "path": "eval/errortypes.go",
    "content": "package eval\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"html/template\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n)\n\ntype EvalError struct {\n\tStatement string\n\tIndex     int\n\tErrIndex  int\n\tLength    int\n\terrors.DropboxError\n}\n\nfunc NewEvalError(statement string, index, errorIndex int,\n\tlength int, templMsg string, args ...interface{}) (err error) {\n\n\tevalErr := &EvalError{\n\t\tStatement: statement,\n\t\tIndex:     index + 1,\n\t\tErrIndex:  errorIndex + 1,\n\t\tLength:    length,\n\t}\n\n\ttmpl, err := template.New(\"eval\").Parse(templMsg)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"eval: Failed to parse eval error template\"),\n\t\t}\n\t\treturn\n\t}\n\n\terrorMsg := &bytes.Buffer{}\n\terr = tmpl.Execute(errorMsg, evalErr)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"eval: Failed to execute eval error template\"),\n\t\t}\n\t\treturn\n\t}\n\n\terrorMsgStr := fmt.Sprintf(errorMsg.String(), args...)\n\terrorMsgStr += fmt.Sprintf(\" index=%d\", evalErr.Index)\n\terrorMsgStr += fmt.Sprintf(\" error_index=%d\", evalErr.ErrIndex)\n\terrorMsgStr += fmt.Sprintf(\" statement=\\\"%s\\\"\", evalErr.Statement)\n\n\tevalErr.DropboxError = errors.New(errorMsgStr)\n\terr = evalErr\n\n\treturn\n}\n"
  },
  {
    "path": "eval/eval.go",
    "content": "package eval\n\nimport (\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype Data map[string]map[string]interface{}\n\ntype Parser struct {\n\tstatement string\n\tparts     []string\n\tpartsLen  int\n\tdata      Data\n}\n\nfunc (p *Parser) parseRef(ref string, pos int) (val interface{}, err error) {\n\tn := len(ref)\n\tif n == 0 {\n\t\treturn\n\t}\n\n\tif ref[0] == '\\'' {\n\t\tif ref[n-1] != '\\'' {\n\t\t\terr = NewEvalError(\n\t\t\t\tp.statement,\n\t\t\t\tpos,\n\t\t\t\tpos,\n\t\t\t\tp.partsLen,\n\t\t\t\t\"eval: Invalid string {{.ErrIndex}}\",\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t\tval = ref[1 : n-1]\n\t\treturn\n\t}\n\n\tswitch ref {\n\tcase \"true\":\n\t\tval = true\n\t\treturn\n\tcase \"false\":\n\t\tval = true\n\t\treturn\n\tcase \"==\":\n\t\tval = Equal{}\n\t\treturn\n\tcase \"!=\":\n\t\tval = NotEqual{}\n\t\treturn\n\tcase \"<\":\n\t\tval = Less{}\n\t\treturn\n\tcase \"<=\":\n\t\tval = LessEqual{}\n\t\treturn\n\tcase \">\":\n\t\tval = Greater{}\n\t\treturn\n\tcase \">=\":\n\t\tval = GreaterEqual{}\n\t\treturn\n\tcase \"IF\":\n\t\tval = If{}\n\t\treturn\n\tcase \"AND\":\n\t\tval = And{}\n\t\treturn\n\tcase \"OR\":\n\t\tval = Or{}\n\t\treturn\n\tcase \"FOR\":\n\t\tval = For{}\n\t\treturn\n\tcase \"THEN\":\n\t\tval = Then{}\n\t\treturn\n\t}\n\n\tif ref == \"true\" {\n\t\tval = true\n\t\treturn\n\t} else if ref == \"false\" {\n\t\tval = false\n\t\treturn\n\t}\n\n\tintVal, e := strconv.Atoi(ref)\n\tif e == nil {\n\t\tval = intVal\n\t\treturn\n\t}\n\n\tfloatVal, e := strconv.ParseFloat(ref, 64)\n\tif e == nil {\n\t\tval = floatVal\n\t\treturn\n\t}\n\n\tsplit := strings.Split(ref, \".\")\n\tif len(split) != 2 {\n\t\terr = NewEvalError(\n\t\t\tp.statement,\n\t\t\tpos,\n\t\t\tpos,\n\t\t\tp.partsLen,\n\t\t\t\"eval: Invalid reference {{.ErrIndex}}\",\n\t\t)\n\t\treturn\n\t}\n\n\tgroup := p.data[split[0]]\n\tif group == nil {\n\t\terr = NewEvalError(\n\t\t\tp.statement,\n\t\t\tpos,\n\t\t\tpos,\n\t\t\tp.partsLen,\n\t\t\t\"eval: Invalid reference group {{.ErrIndex}}\",\n\t\t)\n\t\treturn\n\t}\n\n\tgroupVal := group[split[1]]\n\tif groupVal == nil {\n\t\terr = NewEvalError(\n\t\t\tp.statement,\n\t\t\tpos,\n\t\t\tpos,\n\t\t\tp.partsLen,\n\t\t\t\"eval: Invalid reference group key {{.ErrIndex}}\",\n\t\t)\n\t\treturn\n\t} else {\n\t\tif fVal, ok := groupVal.(float64); ok {\n\t\t\tif fVal == float64(int(fVal)) {\n\t\t\t\tval = int(fVal)\n\t\t\t} else {\n\t\t\t\tval = fVal\n\t\t\t}\n\t\t} else {\n\t\t\tval = groupVal\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (p *Parser) parseComp(left, right, comp interface{}) bool {\n\tif reflect.TypeOf(left) != reflect.TypeOf(right) {\n\t\treturn false\n\t}\n\n\tswitch leftVal := left.(type) {\n\tcase bool:\n\t\tswitch comp.(type) {\n\t\tcase Equal:\n\t\t\treturn leftVal == right.(bool)\n\t\tcase NotEqual:\n\t\t\treturn leftVal != right.(bool)\n\t\tcase Less:\n\t\t\treturn false\n\t\tcase LessEqual:\n\t\t\treturn false\n\t\tcase Greater:\n\t\t\treturn false\n\t\tcase GreaterEqual:\n\t\t\treturn false\n\t\tdefault:\n\t\t\tpanic(\"Invalid comp\")\n\t\t}\n\tcase string:\n\t\tswitch comp.(type) {\n\t\tcase Equal:\n\t\t\treturn leftVal == right.(string)\n\t\tcase NotEqual:\n\t\t\treturn leftVal != right.(string)\n\t\tcase Less:\n\t\t\treturn leftVal < right.(string)\n\t\tcase LessEqual:\n\t\t\treturn leftVal <= right.(string)\n\t\tcase Greater:\n\t\t\treturn leftVal > right.(string)\n\t\tcase GreaterEqual:\n\t\t\treturn leftVal >= right.(string)\n\t\tdefault:\n\t\t\tpanic(\"Invalid comp\")\n\t\t}\n\tcase int:\n\t\tswitch comp.(type) {\n\t\tcase Equal:\n\t\t\treturn leftVal == right.(int)\n\t\tcase NotEqual:\n\t\t\treturn leftVal != right.(int)\n\t\tcase Less:\n\t\t\treturn leftVal < right.(int)\n\t\tcase LessEqual:\n\t\t\treturn leftVal <= right.(int)\n\t\tcase Greater:\n\t\t\treturn leftVal > right.(int)\n\t\tcase GreaterEqual:\n\t\t\treturn leftVal >= right.(int)\n\t\tdefault:\n\t\t\tpanic(\"Invalid comp\")\n\t\t}\n\tcase float64:\n\t\tswitch comp.(type) {\n\t\tcase Equal:\n\t\t\treturn leftVal == right.(float64)\n\t\tcase NotEqual:\n\t\t\treturn leftVal != right.(float64)\n\t\tcase Less:\n\t\t\treturn leftVal < right.(float64)\n\t\tcase LessEqual:\n\t\t\treturn leftVal <= right.(float64)\n\t\tcase Greater:\n\t\t\treturn leftVal > right.(float64)\n\t\tcase GreaterEqual:\n\t\t\treturn leftVal >= right.(float64)\n\t\tdefault:\n\t\t\tpanic(\"Invalid comp\")\n\t\t}\n\tdefault:\n\t\tpanic(\"Invalid type\")\n\t}\n\n\treturn false\n}\n\nfunc (p *Parser) Eval() (resp string, threshold int, err error) {\n\tp.parts = strings.Fields(p.statement)\n\tp.partsLen = len(p.parts)\n\tif p.partsLen < 6 {\n\t\terr = NewEvalError(\n\t\t\tp.statement,\n\t\t\t0,\n\t\t\t0,\n\t\t\tp.partsLen,\n\t\t\t\"eval: Statement under min parts\",\n\t\t)\n\t\treturn\n\t} else if p.partsLen > 30 {\n\t\terr = NewEvalError(\n\t\t\tp.statement,\n\t\t\t0,\n\t\t\t0,\n\t\t\tp.partsLen,\n\t\t\t\"eval: Statement exceeds max parts\",\n\t\t)\n\t\treturn\n\t} else if len(p.statement) > 1024 {\n\t\terr = NewEvalError(\n\t\t\tp.statement,\n\t\t\t0,\n\t\t\t0,\n\t\t\tp.partsLen,\n\t\t\t\"eval: Statement exceeds max length\",\n\t\t)\n\t\treturn\n\t}\n\n\tif p.parts[0] != \"IF\" {\n\t\terr = NewEvalError(\n\t\t\tp.statement,\n\t\t\t0,\n\t\t\t0,\n\t\t\tp.partsLen,\n\t\t\t\"eval: Statement part {{.ErrorIndex}} invalid\",\n\t\t)\n\t\treturn\n\t}\n\n\ti := 1\n\tvar expr interface{}\n\tresults := []bool{}\n\tfinal := false\n\tfor x := 0; x < 100; x++ {\n\t\tif p.partsLen < i+4 {\n\t\t\terr = NewEvalError(\n\t\t\t\tp.statement,\n\t\t\t\ti,\n\t\t\t\ti,\n\t\t\t\tp.partsLen,\n\t\t\t\t\"eval: Incomplete expression\",\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t\tindex := i\n\t\ti += 4\n\n\t\tleftOp, e := p.parseRef(p.parts[index], index)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tcomp, e := p.parseRef(p.parts[index+1], index+1)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\trightOp, e := p.parseRef(p.parts[index+2], index+2)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tnext, e := p.parseRef(p.parts[index+3], index+3)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tswitch leftOp.(type) {\n\t\tcase string:\n\t\t\tbreak\n\t\tcase int:\n\t\t\tbreak\n\t\tcase float64:\n\t\t\tbreak\n\t\tcase bool:\n\t\t\tbreak\n\t\tdefault:\n\t\t\terr = NewEvalError(\n\t\t\t\tp.statement,\n\t\t\t\tindex,\n\t\t\t\tindex,\n\t\t\t\tp.partsLen,\n\t\t\t\t\"eval: Invalid left operator {{.ErrIndex}}\",\n\t\t\t)\n\t\t\treturn\n\t\t}\n\n\t\tswitch rightOp.(type) {\n\t\tcase string:\n\t\t\tbreak\n\t\tcase int:\n\t\t\tbreak\n\t\tcase float64:\n\t\t\tbreak\n\t\tcase bool:\n\t\t\tbreak\n\t\tdefault:\n\t\t\terr = NewEvalError(\n\t\t\t\tp.statement,\n\t\t\t\tindex,\n\t\t\t\tindex+2,\n\t\t\t\tp.partsLen,\n\t\t\t\t\"eval: Invalid right operator {{.ErrIndex}}\",\n\t\t\t)\n\t\t\treturn\n\t\t}\n\n\t\tswitch comp.(type) {\n\t\tcase Equal:\n\t\t\tbreak\n\t\tcase NotEqual:\n\t\t\tbreak\n\t\tcase Less:\n\t\t\tbreak\n\t\tcase LessEqual:\n\t\t\tbreak\n\t\tcase Greater:\n\t\t\tbreak\n\t\tcase GreaterEqual:\n\t\t\tbreak\n\t\tdefault:\n\t\t\terr = NewEvalError(\n\t\t\t\tp.statement,\n\t\t\t\tindex,\n\t\t\t\tindex+1,\n\t\t\t\tp.partsLen,\n\t\t\t\t\"eval: Invalid comparison operator {{.ErrIndex}}\",\n\t\t\t)\n\t\t\treturn\n\t\t}\n\n\t\tresult := p.parseComp(leftOp, rightOp, comp)\n\t\tresults = append(results, result)\n\n\t\tif _, ok := next.(For); ok {\n\t\t\tif index+6 != p.partsLen-1 {\n\t\t\t\terr = NewEvalError(\n\t\t\t\t\tp.statement,\n\t\t\t\t\tindex,\n\t\t\t\t\tindex+6,\n\t\t\t\t\tp.partsLen,\n\t\t\t\t\t\"eval: Expected %d length\", index+6,\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnext, e = p.parseRef(p.parts[index+5], index+5)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif _, ok := next.(Then); !ok {\n\t\t\t\terr = NewEvalError(\n\t\t\t\t\tp.statement,\n\t\t\t\t\tindex,\n\t\t\t\t\tindex+5,\n\t\t\t\t\tp.partsLen,\n\t\t\t\t\t\"eval: Expected THAN at {{.ErrIndex}}\",\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tforVal, e := p.parseRef(p.parts[index+4], index+4)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif forInt, ok := forVal.(int); ok {\n\t\t\t\tthreshold = forInt\n\t\t\t} else {\n\t\t\t\terr = NewEvalError(\n\t\t\t\t\tp.statement,\n\t\t\t\t\tindex,\n\t\t\t\t\tindex+4,\n\t\t\t\t\tp.partsLen,\n\t\t\t\t\t\"eval: Expected FOR value to be int\",\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tindex += 2\n\t\t}\n\n\t\tswitch next.(type) {\n\t\tcase And:\n\t\t\tif expr == nil {\n\t\t\t\texpr = And{}\n\t\t\t} else if _, ok := expr.(And); !ok {\n\t\t\t\terr = NewEvalError(\n\t\t\t\t\tp.statement,\n\t\t\t\t\tindex,\n\t\t\t\t\tindex+2,\n\t\t\t\t\tp.partsLen,\n\t\t\t\t\t\"eval: Cannot mix OR with AND\",\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase Or:\n\t\t\tif expr == nil {\n\t\t\t\texpr = Or{}\n\t\t\t} else if _, ok := expr.(Or); !ok {\n\t\t\t\terr = NewEvalError(\n\t\t\t\t\tp.statement,\n\t\t\t\t\tindex,\n\t\t\t\t\tindex+2,\n\t\t\t\t\tp.partsLen,\n\t\t\t\t\t\"eval: Cannot mix OR with AND\",\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase Then:\n\t\t\tif index+4 != p.partsLen-1 {\n\t\t\t\terr = NewEvalError(\n\t\t\t\t\tp.statement,\n\t\t\t\t\tindex,\n\t\t\t\t\tindex,\n\t\t\t\t\tp.partsLen,\n\t\t\t\t\t\"eval: Expected %d length\", index+4,\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif _, ok := expr.(Or); ok {\n\t\t\t\tfor _, result := range results {\n\t\t\t\t\tif result {\n\t\t\t\t\t\tfinal = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tfinal = false\n\t\t\t\t} else {\n\t\t\t\t\tfinal = true\n\t\t\t\t\tfor _, result := range results {\n\t\t\t\t\t\tif !result {\n\t\t\t\t\t\t\tfinal = false\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif final {\n\t\t\t\trespInf, e := p.parseRef(p.parts[index+4], index+4)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif respStr, ok := respInf.(string); ok {\n\t\t\t\t\tresp = respStr\n\t\t\t\t} else {\n\t\t\t\t\terr = NewEvalError(\n\t\t\t\t\t\tp.statement,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\tindex+4,\n\t\t\t\t\t\tp.partsLen,\n\t\t\t\t\t\t\"eval: Result must be string\",\n\t\t\t\t\t)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\tdefault:\n\t\t\terr = NewEvalError(\n\t\t\t\tp.statement,\n\t\t\t\tindex,\n\t\t\t\tindex+3,\n\t\t\t\tp.partsLen,\n\t\t\t\t\"eval: Invalid continuation\",\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = NewEvalError(\n\t\tp.statement,\n\t\t0,\n\t\t0,\n\t\tp.partsLen,\n\t\t\"eval: Infinite loop\",\n\t)\n\treturn\n}\n\nfunc Eval(data Data, statement string) (resp string,\n\tthreshold int, err error) {\n\n\tparsr := &Parser{\n\t\tstatement: statement,\n\t\tdata:      data,\n\t}\n\n\tresp, threshold, err = parsr.Eval()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "eval/utils.go",
    "content": "package eval\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n)\n\nfunc Validate(statement string) (err error) {\n\tif len(statement) == 0 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"eval: Empty statement\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(statement) > StatementMaxLength {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"eval: Statement exceeds max length\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor i, c := range statement {\n\t\tif !StatementSafeCharacters.Contains(c) {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"eval: Illegal char (%s) at %d\", string(c), i+1),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "event/event.go",
    "content": "package event\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tlisteners = map[string][]func(*EventPublish){}\n)\n\ntype Event struct {\n\tId        bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tChannel   string        `bson:\"channel\" json:\"channel\"`\n\tTimestamp time.Time     `bson:\"timestamp\" json:\"timestamp\"`\n\tData      bson.M        `bson:\"data\" json:\"data\"`\n}\n\ntype EventPublish struct {\n\tId        bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tChannel   string        `bson:\"channel\" json:\"channel\"`\n\tTimestamp time.Time     `bson:\"timestamp\" json:\"timestamp\"`\n\tData      interface{}   `bson:\"data\" json:\"data\"`\n}\n\ntype CustomEvent interface {\n\tGetId() bson.ObjectID\n\tGetData() interface{}\n}\n\ntype Dispatch struct {\n\tType string `bson:\"type\" json:\"type\"`\n}\n\nfunc getCursorId(db *database.Database, coll *database.Collection,\n\tchannels []string) (id bson.ObjectID, err error) {\n\n\tmsg := &EventPublish{}\n\n\tvar query *bson.M\n\tif len(channels) == 1 {\n\t\tquery = &bson.M{\n\t\t\t\"channel\": channels[0],\n\t\t}\n\t} else {\n\t\tquery = &bson.M{\n\t\t\t\"channel\": &bson.M{\n\t\t\t\t\"$in\": channels,\n\t\t\t},\n\t\t}\n\t}\n\n\tfor i := 0; i < 2; i++ {\n\t\terr = coll.FindOne(\n\t\t\tdb,\n\t\t\tquery,\n\t\t\toptions.FindOne().\n\t\t\t\tSetSort(bson.D{{\"$natural\", -1}}),\n\t\t).Decode(msg)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\tif i > 0 {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch err.(type) {\n\t\t\tcase *database.NotFoundError:\n\t\t\t\t// Cannot use client-side ObjectId for tailable collection\n\t\t\t\terr = Publish(db, channels[0], nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\terr = database.ParseError(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\tdefault:\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tid = msg.Id\n\n\treturn\n}\n\nfunc getCursorIdRetry(channels []string) bson.ObjectID {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tfor {\n\t\tcoll := db.Events()\n\n\t\tcursorId, err := getCursorId(db, coll, channels)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"event: Subscribe cursor error\")\n\n\t\t\tdb.Close()\n\t\t\tdb = database.GetDatabase()\n\n\t\t\ttime.Sleep(constants.RetryDelay)\n\n\t\t\tcontinue\n\t\t}\n\n\t\treturn cursorId\n\t}\n}\n\nfunc Publish(db *database.Database, channel string, data interface{}) (\n\terr error) {\n\n\tcoll := db.Events()\n\n\tmsg := &EventPublish{\n\t\tId:        bson.NewObjectID(),\n\t\tChannel:   channel,\n\t\tTimestamp: time.Now(),\n\t\tData:      data,\n\t}\n\n\t_, err = coll.InsertOne(db, msg)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc PublishDispatch(db *database.Database, typ string) (\n\terr error) {\n\n\tevt := &Dispatch{\n\t\tType: typ,\n\t}\n\n\terr = Publish(db, \"dispatch\", evt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Subscribe(channels []string, duration time.Duration,\n\tonMsg func(*EventPublish, error) bool) {\n\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\tcoll := db.Events()\n\n\tcursorId := getCursorIdRetry(channels)\n\n\tvar channelBson interface{}\n\tif len(channels) == 1 {\n\t\tchannelBson = channels[0]\n\t} else {\n\t\tchannelBson = &bson.M{\n\t\t\t\"$in\": channels,\n\t\t}\n\t}\n\n\tqueryOpts := options.Find().\n\t\tSetSort(bson.D{{\"$natural\", 1}}).\n\t\tSetMaxAwaitTime(duration).\n\t\tSetCursorType(options.TailableAwait)\n\tquery := bson.M{\n\t\t\"_id\": bson.M{\n\t\t\t\"$gt\": cursorId,\n\t\t},\n\t\t\"channel\": channelBson,\n\t}\n\tvar cursor *mongo.Cursor\n\tvar err error\n\tfor {\n\t\tcursor, err = coll.Find(\n\t\t\tdb,\n\t\t\tquery,\n\t\t\tqueryOpts,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"event: Listener find error\")\n\n\t\t\tif !onMsg(nil, err) {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(constants.RetryDelay)\n\t}\n\tdefer func() {\n\t\tdefer func() {\n\t\t\trecover()\n\t\t}()\n\t\tif r := recover(); r != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": errors.New(fmt.Sprintf(\"%s\", r)),\n\t\t\t}).Error(\"event: Event panic\")\n\t\t}\n\t\tcursor.Close(db)\n\t}()\n\n\tfor {\n\t\tfor cursor.Next(db) {\n\t\t\tmsg := &EventPublish{}\n\t\t\terr = cursor.Decode(msg)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"event: Listener decode error\")\n\n\t\t\t\tif !onMsg(nil, err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\ttime.Sleep(constants.RetryDelay)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcursorId = msg.Id\n\n\t\t\tif msg.Data == nil {\n\t\t\t\t// Blank msg for cursor\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !onMsg(msg, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr = cursor.Err()\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"event: Listener cursor error\")\n\n\t\t\tif !onMsg(nil, err) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttime.Sleep(constants.RetryDelay)\n\t\t}\n\n\t\tcursor.Close(db)\n\t\tdb.Close()\n\t\tdb = database.GetDatabase()\n\t\tcoll = db.Events()\n\n\t\tquery := &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$gt\": cursorId,\n\t\t\t},\n\t\t\t\"channel\": channelBson,\n\t\t}\n\t\tfor {\n\t\t\tcursor, err = coll.Find(\n\t\t\t\tdb,\n\t\t\t\tquery,\n\t\t\t\tqueryOpts,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"event: Listener find error\")\n\n\t\t\t\tif !onMsg(nil, err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttime.Sleep(constants.RetryDelay)\n\t\t}\n\t}\n}\n\nfunc SubscribeType(channels []string, duration time.Duration,\n\tnewEvent func() CustomEvent, onMsg func(CustomEvent, error) bool) {\n\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\tcoll := db.Events()\n\n\tcursorId := getCursorIdRetry(channels)\n\n\tvar channelBson interface{}\n\tif len(channels) == 1 {\n\t\tchannelBson = channels[0]\n\t} else {\n\t\tchannelBson = &bson.M{\n\t\t\t\"$in\": channels,\n\t\t}\n\t}\n\n\tqueryOpts := options.Find().\n\t\tSetSort(bson.D{{\"$natural\", 1}}).\n\t\tSetMaxAwaitTime(duration).\n\t\tSetCursorType(options.TailableAwait)\n\n\tquery := &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$gt\": cursorId,\n\t\t},\n\t\t\"channel\": channelBson,\n\t}\n\n\tvar cursor *mongo.Cursor\n\tvar err error\n\tfor {\n\t\tcursor, err = coll.Find(\n\t\t\tdb,\n\t\t\tquery,\n\t\t\tqueryOpts,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"event: Listener find error\")\n\n\t\t\tif !onMsg(nil, err) {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(constants.RetryDelay)\n\t}\n\tdefer func() {\n\t\tdefer func() {\n\t\t\trecover()\n\t\t}()\n\t\tcursor.Close(db)\n\t}()\n\n\tfor {\n\t\tfor cursor.Next(db) {\n\t\t\tmsg := newEvent()\n\t\t\terr = cursor.Decode(msg)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"event: Listener decode error\")\n\n\t\t\t\tif !onMsg(nil, err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\ttime.Sleep(constants.RetryDelay)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcursorId = msg.GetId()\n\n\t\t\tif msg.GetData() == nil {\n\t\t\t\t// Blank msg for cursor\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !onMsg(msg, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr = cursor.Err()\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"event: Listener cursor error\")\n\n\t\t\tif !onMsg(nil, err) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttime.Sleep(constants.RetryDelay)\n\t\t}\n\n\t\tcursor.Close(db)\n\t\tdb.Close()\n\t\tdb = database.GetDatabase()\n\t\tcoll = db.Events()\n\n\t\tquery := &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$gt\": cursorId,\n\t\t\t},\n\t\t\t\"channel\": channelBson,\n\t\t}\n\t\tfor {\n\t\t\tcursor, err = coll.Find(\n\t\t\t\tdb,\n\t\t\t\tquery,\n\t\t\t\tqueryOpts,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"event: Listener find error\")\n\n\t\t\t\tif !onMsg(nil, err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttime.Sleep(constants.RetryDelay)\n\t\t}\n\t}\n}\n\nfunc Register(channel string, callback func(*EventPublish)) {\n\tcallbacks := listeners[channel]\n\n\tif callbacks == nil {\n\t\tcallbacks = []func(*EventPublish){}\n\t}\n\n\tlisteners[channel] = append(callbacks, callback)\n}\n\nfunc subscribe(channels []string) {\n\tSubscribe(channels, 10*time.Second,\n\t\tfunc(msg *EventPublish, err error) bool {\n\t\t\tif msg == nil || err != nil {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tfor _, listener := range listeners[msg.Channel] {\n\t\t\t\tlistener(msg)\n\t\t\t}\n\n\t\t\treturn true\n\t\t})\n}\n\nfunc init() {\n\tmodule := requires.New(\"event\")\n\tmodule.After(\"settings\")\n\n\tmodule.Handler = func() (err error) {\n\t\tgo func() {\n\t\t\tchannels := []string{}\n\n\t\t\tfor channel := range listeners {\n\t\t\t\tchannels = append(channels, channel)\n\t\t\t}\n\n\t\t\tif len(channels) > 0 {\n\t\t\t\tsubscribe(channels)\n\t\t\t}\n\t\t}()\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "event/listener.go",
    "content": "package event\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Listener struct {\n\tdb       *database.Database\n\tstate    bool\n\tchannels []string\n\tstream   chan *Event\n\tonce     sync.Once\n}\n\nfunc (l *Listener) Listen() chan *Event {\n\treturn l.stream\n}\n\nfunc (l *Listener) Close() {\n\tl.state = false\n\tl.once.Do(func() {\n\t\tclose(l.stream)\n\t})\n}\n\nfunc (l *Listener) sub(cursorId bson.ObjectID) {\n\tcoll := l.db.Events()\n\n\tvar channelBson interface{}\n\tif len(l.channels) == 1 {\n\t\tchannelBson = l.channels[0]\n\t} else {\n\t\tchannelBson = &bson.M{\n\t\t\t\"$in\": l.channels,\n\t\t}\n\t}\n\n\tqueryOpts := options.Find().\n\t\tSetSort(bson.D{{\"$natural\", 1}}).\n\t\tSetMaxAwaitTime(10 * time.Second).\n\t\tSetCursorType(options.TailableAwait)\n\n\tquery := &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$gt\": cursorId,\n\t\t},\n\t\t\"channel\": channelBson,\n\t}\n\n\tvar cursor *mongo.Cursor\n\tvar err error\n\tfor {\n\t\tcursor, err = coll.Find(\n\t\t\tl.db,\n\t\t\tquery,\n\t\t\tqueryOpts,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"event: Listener find error\")\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\n\t\tif !l.state {\n\t\t\treturn\n\t\t}\n\n\t\ttime.Sleep(constants.RetryDelay)\n\n\t\tif !l.state {\n\t\t\treturn\n\t\t}\n\t}\n\n\tdefer func() {\n\t\tdefer func() {\n\t\t\trecover()\n\t\t}()\n\t\tif r := recover(); r != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": errors.New(fmt.Sprintf(\"%s\", r)),\n\t\t\t}).Error(\"event: Event panic\")\n\t\t}\n\t\tcursor.Close(l.db)\n\t}()\n\n\tfor {\n\t\tfor cursor.Next(l.db) {\n\t\t\tmsg := &Event{}\n\t\t\terr = cursor.Decode(msg)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"event: Listener decode error\")\n\n\t\t\t\ttime.Sleep(constants.RetryDelay)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcursorId = msg.Id\n\n\t\t\tif msg.Data == nil {\n\t\t\t\t// Blank msg for cursor\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !l.state {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tl.stream <- msg\n\t\t}\n\n\t\tif !l.state {\n\t\t\treturn\n\t\t}\n\n\t\terr = cursor.Err()\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"event: Listener cursor error\")\n\n\t\t\ttime.Sleep(constants.RetryDelay)\n\t\t}\n\n\t\tif !l.state {\n\t\t\treturn\n\t\t}\n\n\t\tcursor.Close(l.db)\n\t\tcoll = l.db.Events()\n\n\t\tquery := &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$gt\": cursorId,\n\t\t\t},\n\t\t\t\"channel\": channelBson,\n\t\t}\n\n\t\tfor {\n\t\t\tcursor, err = coll.Find(\n\t\t\t\tl.db,\n\t\t\t\tquery,\n\t\t\t\tqueryOpts,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"event: Listener find error\")\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif !l.state {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttime.Sleep(constants.RetryDelay)\n\n\t\t\tif !l.state {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (l *Listener) init() (err error) {\n\tcoll := l.db.Events()\n\tcursorId, err := getCursorId(l.db, coll, l.channels)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tl.state = true\n\n\tgo func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": errors.New(fmt.Sprintf(\"%s\", r)),\n\t\t\t}).Error(\"event: Listener panic\")\n\t\t}\n\t\tl.sub(cursorId)\n\t}()\n\n\treturn\n}\n\nfunc SubscribeListener(db *database.Database, channels []string) (\n\tlst *Listener, err error) {\n\n\tlst = &Listener{\n\t\tdb:       db,\n\t\tchannels: channels,\n\t\tstream:   make(chan *Event, 10),\n\t}\n\n\terr = lst.init()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "event/socket.go",
    "content": "package event\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gorilla/websocket\"\n)\n\nvar (\n\tUpgrader = websocket.Upgrader{\n\t\tHandshakeTimeout: 30 * time.Second,\n\t\tReadBufferSize:   1024,\n\t\tWriteBufferSize:  1024,\n\t\tCheckOrigin: func(r *http.Request) bool {\n\t\t\treturn true\n\t\t},\n\t}\n\tWebSockets     = set.NewSet()\n\tWebSocketsLock = sync.Mutex{}\n)\n\ntype WebSocket struct {\n\tConn     *websocket.Conn\n\tTicker   *time.Ticker\n\tListener *Listener\n\tCancel   context.CancelFunc\n\tClosed   bool\n}\n\nfunc (w *WebSocket) Close() {\n\tw.Closed = true\n\tfunc() {\n\t\tdefer func() {\n\t\t\trecover()\n\t\t}()\n\t\tw.Cancel()\n\t}()\n\tfunc() {\n\t\tdefer func() {\n\t\t\trecover()\n\t\t}()\n\t\tw.Ticker.Stop()\n\t}()\n\tfunc() {\n\t\tdefer func() {\n\t\t\trecover()\n\t\t}()\n\t\tw.Listener.Close()\n\t}()\n\tfunc() {\n\t\tdefer func() {\n\t\t\trecover()\n\t\t}()\n\t\tw.Conn.Close()\n\t}()\n}\n\nfunc WebSocketsStop() {\n\tWebSocketsLock.Lock()\n\tfor socketInf := range WebSockets.Iter() {\n\t\tfunc() {\n\t\t\tsocket := socketInf.(*WebSocket)\n\t\t\tsocket.Close()\n\t\t}()\n\t}\n\tWebSockets = set.NewSet()\n\tWebSocketsLock.Unlock()\n}\n"
  },
  {
    "path": "features/qemu.go",
    "content": "package features\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tverCache     = version{}\n\tverCacheLock = sync.Mutex{}\n\tverCacheTime = time.Time{}\n)\n\nconst (\n\tLibexec = \"/usr/libexec/qemu-kvm\"\n\tSystem  = \"/usr/bin/qemu-system-x86_64\"\n)\n\ntype version struct {\n\tMajor int\n\tMinor int\n\tPatch int\n}\n\nfunc GetQemuPath() (path string, err error) {\n\texists, err := utils.Exists(System)\n\tif err != nil {\n\t\treturn\n\t}\n\tif exists {\n\t\tpath = System\n\t} else {\n\t\tpath = Libexec\n\t}\n\n\treturn\n}\n\nfunc GetQemuVersion() (major, minor, patch int, err error) {\n\tverCacheLock.Lock()\n\tif time.Since(verCacheTime) < 1*time.Minute {\n\t\tmajor = verCache.Major\n\t\tminor = verCache.Minor\n\t\tpatch = verCache.Patch\n\t\tverCacheLock.Unlock()\n\t\treturn\n\t}\n\tverCacheLock.Unlock()\n\n\tqemuPath, err := GetQemuPath()\n\tif err != nil {\n\t\treturn\n\t}\n\n\toutput, _ := utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\tqemuPath, \"--version\",\n\t)\n\n\tlines := strings.Split(output, \"\\n\")\n\n\tfor _, line := range lines {\n\t\tfields := strings.Fields(line)\n\t\tif len(fields) < 4 || strings.ToLower(fields[2]) != \"version\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tversions := strings.Split(fields[3], \".\")\n\t\tif len(versions) != 3 {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar e error\n\t\tmajor, e = strconv.Atoi(versions[0])\n\t\tif e != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tminor, e = strconv.Atoi(versions[1])\n\t\tif e != nil {\n\t\t\tmajor = 0\n\t\t\tcontinue\n\t\t}\n\n\t\tpatch, e = strconv.Atoi(versions[2])\n\t\tif e != nil {\n\t\t\tmajor = 0\n\t\t\tminor = 0\n\t\t\tcontinue\n\t\t}\n\n\t\tbreak\n\t}\n\n\tif major == 0 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"qemu: Invalid Qemu version '%s'\", output),\n\t\t}\n\t\treturn\n\t}\n\n\tverCacheLock.Lock()\n\tverCache.Major = major\n\tverCache.Minor = minor\n\tverCache.Patch = patch\n\tverCacheTime = time.Now()\n\tverCacheLock.Unlock()\n\n\treturn\n}\n\nfunc GetKernelVersion() (major, minor, patch int, err error) {\n\tuname := &syscall.Utsname{}\n\n\terr = syscall.Uname(uname)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to get syscall uname\"),\n\t\t}\n\t\treturn\n\t}\n\n\tversion := utils.Int8Str(uname.Release[:])\n\n\tversions := strings.Split(version, \"-\")\n\tif len(versions) < 2 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\n\t\t\t\t\"qemu: Failed to parse uname version 1 '%s'\",\n\t\t\t\tversion,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tversions = strings.Split(versions[0], \".\")\n\tif len(versions) < 3 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\n\t\t\t\t\"qemu: Failed to parse uname version 2 '%s'\",\n\t\t\t\tversion,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tmajor, err = strconv.Atoi(versions[0])\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrapf(\n\t\t\t\terr,\n\t\t\t\t\"qemu: Failed to parse uname version 3 '%s'\",\n\t\t\t\tversion,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tminor, err = strconv.Atoi(versions[1])\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrapf(\n\t\t\t\terr,\n\t\t\t\t\"qemu: Failed to parse uname version 4 '%s'\",\n\t\t\t\tversion,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tpatch, err = strconv.Atoi(versions[2])\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrapf(\n\t\t\t\terr,\n\t\t\t\t\"qemu: Failed to parse uname version 5 '%s'\",\n\t\t\t\tversion,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetUringSupport() (supported bool, err error) {\n\tkallsyms, err := ioutil.ReadFile(\"/proc/kallsyms\")\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"features: Failed to read /proc/kallsyms\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif !strings.Contains(string(kallsyms), \"io_uring_init\") {\n\t\treturn\n\t}\n\n\tmajor, minor, _, err := GetKernelVersion()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif major < 5 {\n\t\treturn\n\t} else if major == 5 && minor < 2 {\n\t\treturn\n\t}\n\n\tmajor, minor, _, err = GetQemuVersion()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif major < 6 {\n\t\treturn\n\t} else if major == 6 && minor < 2 {\n\t\treturn\n\t}\n\n\tsysctlData, err := ioutil.ReadFile(\"/proc/sys/kernel/io_uring_disabled\")\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\terr = nil\n\t\t\tsupported = true\n\t\t\treturn\n\t\t}\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"features: Failed to read io_uring_disabled\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdisabledStr := strings.TrimSpace(string(sysctlData))\n\tdisabled, parseErr := strconv.Atoi(disabledStr)\n\tif parseErr != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrapf(\n\t\t\t\tparseErr,\n\t\t\t\t\"features: Failed to parse io_uring_disabled value '%s'\",\n\t\t\t\tdisabledStr,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tif disabled == 2 {\n\t\treturn\n\t}\n\n\tsupported = true\n\treturn\n}\n\nfunc GetExtUringSupport() (supported bool, err error) {\n\tmajor, minor, _, err := GetQemuVersion()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif major > 10 {\n\t\tsupported = true\n\t}\n\n\tif major == 10 && minor >= 2 {\n\t\tsupported = true\n\t}\n\n\treturn\n}\n\nfunc GetMemoryBackendSupport() (supported bool, err error) {\n\tmajor, _, _, err := GetQemuVersion()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif major >= 6 {\n\t\tsupported = true\n\t}\n\n\treturn\n}\n\nfunc GetRunWithSupport() (supported bool, err error) {\n\tmajor, _, _, err := GetQemuVersion()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif major >= 9 {\n\t\tsupported = true\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "features/systemd.go",
    "content": "package features\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc GetSystemdVersion() (ver int) {\n\toutput, _ := utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"/usr/bin/systemctl\", \"--version\",\n\t)\n\n\tlines := strings.Split(output, \"\\n\")\n\tfor _, line := range lines {\n\t\tif !strings.Contains(line, \"systemd\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tfields := strings.Fields(line)\n\t\tif len(fields) < 2 {\n\t\t\tcontinue\n\t\t}\n\n\t\tn, err := strconv.Atoi(fields[1])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tver = n\n\t\tbreak\n\t}\n\n\treturn\n}\n\nfunc HasSystemdNamespace() bool {\n\tver := GetSystemdVersion()\n\tif ver >= 243 {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "finder/constants.go",
    "content": "package finder\n\nconst (\n\tDomainKind      = \"domain\"\n\tVpcKind         = \"vpc\"\n\tSubnetKind      = \"subnet\"\n\tDatacenterKind  = \"datacenter\"\n\tNodeKind        = \"node\"\n\tPoolKind        = \"pool\"\n\tZoneKind        = \"zone\"\n\tShapeKind       = \"shape\"\n\tDiskKind        = \"disk\"\n\tImageKind       = \"image\"\n\tBuildKind       = \"build\"\n\tInstanceKind    = \"instance\"\n\tFirewallKind    = \"firewall\"\n\tPlanKind        = \"plan\"\n\tCertificateKind = \"certificate\"\n\tSecretKind      = \"secret\"\n\tPodKind         = \"pod\"\n\tUnitKind        = \"unit\"\n\tJournalKind     = \"journal\"\n)\n"
  },
  {
    "path": "finder/resources.go",
    "content": "package finder\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/plan\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\ntype Resources struct {\n\tOrganization bson.ObjectID\n\tDatacenter   *datacenter.Datacenter\n\tZone         *zone.Zone\n\tVpc          *vpc.Vpc\n\tSubnet       *vpc.Subnet\n\tShape        *shape.Shape\n\tNode         *node.Node\n\tPool         *pool.Pool\n\tImage        *image.Image\n\tDisks        []*disk.Disk\n\tInstance     *instance.Instance\n\tPlan         *plan.Plan\n\tDomain       *domain.Domain\n\tCertificate  *certificate.Certificate\n\tSecret       *secret.Secret\n\tDeployment   *deployment.Deployment\n\tPod          *PodBase\n\tUnit         *UnitBase\n\tSelector     string\n}\n\nvar tokenRe = regexp.MustCompile(\n\t`\\+\\/([a-zA-Z0-9-]*)\\/([a-zA-Z0-9-_.]*)(?:(?:\\/|\\:)([a-zA-Z0-9-_.]*)(?:\\/([a-zA-Z0-9-_.]*))?)?`)\n\nfunc (r *Resources) Find(db *database.Database, token string) (\n\tkind string, err error) {\n\n\tmatches := tokenRe.FindStringSubmatch(token)\n\tif len(matches) < 3 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"spec: Invalid token '%s'\", token),\n\t\t}\n\t\treturn\n\t}\n\n\tkind = matches[1]\n\tresource := matches[2]\n\ttag := \"\"\n\tr.Selector = \"\"\n\n\tif len(matches) > 3 {\n\t\tif strings.Contains(token, \":\") {\n\t\t\ttag = matches[3]\n\t\t\tif len(matches) > 4 {\n\t\t\t\tr.Selector = matches[4]\n\t\t\t}\n\t\t} else {\n\t\t\tr.Selector = matches[3]\n\t\t}\n\t}\n\n\tswitch kind {\n\tcase DomainKind:\n\t\tr.Domain, err = domain.GetOne(db, &bson.M{\n\t\t\t\"name\":         resource,\n\t\t\t\"organization\": r.Organization,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase VpcKind:\n\t\tquery := bson.M{\n\t\t\t\"name\":         resource,\n\t\t\t\"organization\": r.Organization,\n\t\t}\n\t\tif r.Datacenter != nil {\n\t\t\tquery[\"datacenter\"] = r.Datacenter.Id\n\t\t} else if r.Zone != nil {\n\t\t\tquery[\"datacenter\"] = r.Zone.Datacenter\n\t\t}\n\t\tr.Vpc, err = vpc.GetOne(db, &query)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase SubnetKind:\n\t\tif r.Vpc != nil {\n\t\t\tsubnet := r.Vpc.GetSubnetName(resource)\n\t\t\tr.Subnet = subnet\n\t\t}\n\t\tbreak\n\tcase DatacenterKind:\n\t\tr.Datacenter, err = datacenter.GetOne(db, &bson.M{\n\t\t\t\"name\": resource,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase NodeKind:\n\t\tr.Node, err = node.GetOne(db, &bson.M{\n\t\t\t\"name\": resource,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase PoolKind:\n\t\tr.Pool, err = pool.GetOne(db, &bson.M{\n\t\t\t\"name\": resource,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase ZoneKind:\n\t\tr.Zone, err = zone.GetOne(db, &bson.M{\n\t\t\t\"name\": resource,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tr.Datacenter, err = datacenter.Get(db, r.Zone.Datacenter)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase ShapeKind:\n\t\tquery := bson.M{\n\t\t\t\"name\": resource,\n\t\t}\n\t\tif r.Datacenter != nil {\n\t\t\tquery[\"datacenter\"] = r.Datacenter.Id\n\t\t} else if r.Zone != nil {\n\t\t\tquery[\"datacenter\"] = r.Zone.Datacenter\n\t\t}\n\t\tr.Shape, err = shape.GetOne(db, &query)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase ImageKind:\n\t\tif image.Releases.Contains(resource) {\n\t\t\timgs, e := image.GetAll(db, &bson.M{\n\t\t\t\t\"release\": resource,\n\t\t\t\t\"organization\": &bson.M{\n\t\t\t\t\t\"$in\": []bson.ObjectID{r.Organization, image.Global},\n\t\t\t\t},\n\t\t\t})\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar latestImg *image.Image\n\t\t\tfor _, img := range imgs {\n\t\t\t\tif latestImg == nil {\n\t\t\t\t\tlatestImg = img\n\t\t\t\t} else if img.Build > latestImg.Build {\n\t\t\t\t\tlatestImg = img\n\t\t\t\t}\n\n\t\t\t\tif img.Build == tag {\n\t\t\t\t\tr.Image = img\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif latestImg != nil && (tag == \"\" || tag == \"latest\") {\n\t\t\t\tr.Image = latestImg\n\t\t\t}\n\t\t}\n\n\t\tif r.Image == nil {\n\t\t\tr.Image, err = image.GetOne(db, &bson.M{\n\t\t\t\t\"name\": resource,\n\t\t\t\t\"organization\": &bson.M{\n\t\t\t\t\t\"$in\": []bson.ObjectID{r.Organization, image.Global},\n\t\t\t\t},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tbreak\n\tcase BuildKind:\n\t\tr.Unit, err = GetUnitBase(db, r.Organization, resource)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif r.Unit != nil {\n\t\t\tif tag == \"\" || tag == \"latest\" {\n\t\t\t\tdeplys, e := deployment.GetAllSorted(db, &bson.M{\n\t\t\t\t\t\"unit\": r.Unit.Id,\n\t\t\t\t})\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\t\terr = nil\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor _, deply := range deplys {\n\t\t\t\t\tr.Deployment = deply\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdeplys, e := deployment.GetAllSorted(db, &bson.M{\n\t\t\t\t\t\"unit\": r.Unit.Id,\n\t\t\t\t\t\"tags\": tag,\n\t\t\t\t})\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\t\terr = nil\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor _, deply := range deplys {\n\t\t\t\t\tr.Deployment = deply\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase DiskKind:\n\t\tr.Disks, err = disk.GetAll(db, &bson.M{\n\t\t\t\"name\":         resource,\n\t\t\t\"organization\": r.Organization,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase InstanceKind:\n\t\tr.Instance, err = instance.GetOne(db, &bson.M{\n\t\t\t\"name\":         resource,\n\t\t\t\"organization\": r.Organization,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase PlanKind:\n\t\tr.Plan, err = plan.GetOne(db, &bson.M{\n\t\t\t\"name\":         resource,\n\t\t\t\"organization\": r.Organization,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase CertificateKind:\n\t\tr.Certificate, err = certificate.GetOne(db, &bson.M{\n\t\t\t\"name\":         resource,\n\t\t\t\"organization\": r.Organization,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase SecretKind:\n\t\tr.Secret, err = secret.GetOne(db, &bson.M{\n\t\t\t\"name\":         resource,\n\t\t\t\"organization\": r.Organization,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase PodKind:\n\t\tr.Pod, err = GetPodBase(db, &bson.M{\n\t\t\t\"name\":         resource,\n\t\t\t\"organization\": r.Organization,\n\t\t})\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase UnitKind:\n\t\tr.Unit, err = GetUnitBase(db, r.Organization, resource)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"spec: Unknown kind '%s'\", kind),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\ntype PodBase struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\"`\n\tOrganization bson.ObjectID `bson:\"organization\"`\n\tName         string        `bson:\"name\"`\n}\n\ntype UnitBase struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\"`\n\tPod          bson.ObjectID `bson:\"pod\"`\n\tOrganization bson.ObjectID `bson:\"organization\"`\n\tName         string        `bson:\"name\"`\n}\n\nfunc GetPodBase(db *database.Database, query *bson.M) (\n\tpd *PodBase, err error) {\n\n\tcoll := db.Pods()\n\tpd = &PodBase{}\n\n\terr = coll.FindOne(db, query).Decode(pd)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetUnitBase(db *database.Database, orgId bson.ObjectID,\n\tname string) (unt *UnitBase, err error) {\n\n\tcoll := db.Units()\n\tunt = &UnitBase{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"name\":         name,\n\t\t\"organization\": orgId,\n\t}).Decode(unt)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "firewall/constants.go",
    "content": "package firewall\n\nimport \"github.com/pritunl/mongo-go-driver/v2/bson\"\n\nconst (\n\tAll       = \"all\"\n\tIcmp      = \"icmp\"\n\tTcp       = \"tcp\"\n\tUdp       = \"udp\"\n\tMulticast = \"multicast\"\n\tBroadcast = \"broadcast\"\n)\n\nvar (\n\tGlobal = bson.NilObjectID\n)\n"
  },
  {
    "path": "firewall/firewall.go",
    "content": "package firewall\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Rule struct {\n\tSourceIps []string `bson:\"source_ips\" json:\"source_ips\"`\n\tProtocol  string   `bson:\"protocol\" json:\"protocol\"`\n\tPort      string   `bson:\"port\" json:\"port\"`\n}\n\ntype Mapping struct {\n\tIpvs         bool   `bson:\"ipvs\" json:\"ipvs\"`\n\tAddress      string `bson:\"adress\" json:\"adress\"`\n\tProtocol     string `bson:\"protocol\" json:\"protocol\"`\n\tExternalPort int    `bson:\"external_port\" json:\"external_port\"`\n\tInternalPort int    `bson:\"internal_port\" json:\"internal_port\"`\n}\n\nfunc (r *Rule) SetName(ipv6 bool) (name string) {\n\tswitch r.Protocol {\n\tcase All:\n\t\tif ipv6 {\n\t\t\tname = \"pr6_all\"\n\t\t} else {\n\t\t\tname = \"pr4_all\"\n\t\t}\n\t\tbreak\n\tcase Icmp:\n\t\tif ipv6 {\n\t\t\tname = \"pr6_icmp\"\n\t\t} else {\n\t\t\tname = \"pr4_icmp\"\n\t\t}\n\t\tbreak\n\tcase Multicast:\n\t\tif ipv6 {\n\t\t\tname = \"pr6_multi\"\n\t\t} else {\n\t\t\tname = \"pr4_multi\"\n\t\t}\n\t\tbreak\n\tcase Broadcast:\n\t\tif ipv6 {\n\t\t\tname = \"pr6_broad\"\n\t\t} else {\n\t\t\tname = \"pr4_broad\"\n\t\t}\n\t\tbreak\n\tcase Tcp, Udp:\n\t\tif ipv6 {\n\t\t\tname = fmt.Sprintf(\n\t\t\t\t\"pr6_%s_%s\",\n\t\t\t\tr.Protocol,\n\t\t\t\tstrings.Replace(r.Port, \"-\", \"_\", 1),\n\t\t\t)\n\t\t} else {\n\t\t\tname = fmt.Sprintf(\n\t\t\t\t\"pr4_%s_%s\",\n\t\t\t\tr.Protocol,\n\t\t\t\tstrings.Replace(r.Port, \"-\", \"_\", 1),\n\t\t\t)\n\t\t}\n\t\tbreak\n\tdefault:\n\t\tbreak\n\t}\n\n\treturn\n}\n\ntype Firewall struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tComment      string        `bson:\"comment\" json:\"comment\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tRoles        []string      `bson:\"roles\" json:\"roles\"`\n\tIngress      []*Rule       `bson:\"ingress\" json:\"ingress\"`\n}\n\nfunc (f *Firewall) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tf.Name = utils.FilterName(f.Name)\n\n\tif f.Roles == nil {\n\t\tf.Roles = []string{}\n\t}\n\n\tif f.Ingress == nil {\n\t\tf.Ingress = []*Rule{}\n\t}\n\n\tfor _, rule := range f.Ingress {\n\t\tswitch rule.Protocol {\n\t\tcase All:\n\t\t\trule.Port = \"\"\n\t\t\tbreak\n\t\tcase Icmp:\n\t\t\trule.Port = \"\"\n\t\t\tbreak\n\t\tcase Tcp, Udp, Multicast, Broadcast:\n\t\t\tports := strings.Split(rule.Port, \"-\")\n\n\t\t\tportInt, e := strconv.Atoi(ports[0])\n\t\t\tif e != nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_ingress_rule_port\",\n\t\t\t\t\tMessage: \"Invalid ingress rule port\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif portInt < 1 || portInt > 65535 {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_ingress_rule_port\",\n\t\t\t\t\tMessage: \"Invalid ingress rule port\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tparsedPort := strconv.Itoa(portInt)\n\t\t\tif len(ports) > 1 {\n\t\t\t\tportInt2, e := strconv.Atoi(ports[1])\n\t\t\t\tif e != nil {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"invalid_ingress_rule_port\",\n\t\t\t\t\t\tMessage: \"Invalid ingress rule port\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif portInt < 1 || portInt > 65535 || portInt2 <= portInt {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"invalid_ingress_rule_port\",\n\t\t\t\t\t\tMessage: \"Invalid ingress rule port\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tparsedPort += \"-\" + strconv.Itoa(portInt2)\n\t\t\t}\n\n\t\t\trule.Port = parsedPort\n\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_ingress_rule_protocol\",\n\t\t\t\tMessage: \"Invalid ingress rule protocol\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif rule.Protocol == Multicast || rule.Protocol == Broadcast {\n\t\t\trule.SourceIps = []string{}\n\t\t} else {\n\t\t\tfor i, sourceIp := range rule.SourceIps {\n\t\t\t\tif sourceIp == \"\" {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"invalid_ingress_rule_source_ip\",\n\t\t\t\t\t\tMessage: \"Empty ingress rule source IP\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif !strings.Contains(sourceIp, \"/\") {\n\t\t\t\t\tif strings.Contains(sourceIp, \":\") {\n\t\t\t\t\t\tsourceIp += \"/128\"\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsourceIp += \"/32\"\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t_, sourceCidr, e := net.ParseCIDR(sourceIp)\n\t\t\t\tif e != nil {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"invalid_ingress_rule_source_ip\",\n\t\t\t\t\t\tMessage: \"Invalid ingress rule source IP\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\trule.SourceIps[i] = sourceCidr.String()\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (f *Firewall) Commit(db *database.Database) (err error) {\n\tcoll := db.Firewalls()\n\n\terr = coll.Commit(f.Id, f)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (f *Firewall) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Firewalls()\n\n\terr = coll.CommitFields(f.Id, f, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (f *Firewall) Insert(db *database.Database) (err error) {\n\tcoll := db.Firewalls()\n\n\tif !f.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"firewall: Firewall already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := coll.InsertOne(db, f)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tf.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  },
  {
    "path": "firewall/spec.go",
    "content": "package firewall\n\nimport (\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc GetSpecRules(instances []*instance.Instance,\n\tdeploymentsNode map[bson.ObjectID]*deployment.Deployment,\n\tspecsMap map[bson.ObjectID]*spec.Spec,\n\tspecsUnitsMap map[bson.ObjectID]*unit.Unit,\n\tdeploymentsDeployedMap map[bson.ObjectID]*deployment.Deployment) (\n\tfirewalls map[string][]*Rule, err error) {\n\n\tfirewalls = map[string][]*Rule{}\n\tfor _, inst := range instances {\n\t\tif inst.Deployment.IsZero() {\n\t\t\tcontinue\n\t\t}\n\n\t\tdeply := deploymentsNode[inst.Deployment]\n\t\tif deply == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tspc := specsMap[deply.Spec]\n\t\tif spc == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif spc.Firewall == nil || spc.Firewall.Ingress == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tnamespaces := []string{}\n\t\tfor i := range inst.Virt.NetworkAdapters {\n\t\t\tnamespaces = append(namespaces, vm.GetNamespace(inst.Id, i))\n\t\t}\n\n\t\tfor _, specRule := range spc.Firewall.Ingress {\n\t\t\trule := &Rule{\n\t\t\t\tProtocol:  specRule.Protocol,\n\t\t\t\tPort:      specRule.Port,\n\t\t\t\tSourceIps: specRule.SourceIps,\n\t\t\t}\n\n\t\t\tfor _, ref := range specRule.Sources {\n\t\t\t\tif ref.Kind != spec.Unit {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\truleUnit := specsUnitsMap[ref.Id]\n\t\t\t\tif ruleUnit == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfor _, ruleDeplyId := range ruleUnit.Deployments {\n\t\t\t\t\truleDeply := deploymentsDeployedMap[ruleDeplyId]\n\t\t\t\t\tif ruleDeply == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tinstData := ruleDeply.InstanceData\n\t\t\t\t\tif instData == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch ref.Selector {\n\t\t\t\t\tcase \"\", \"private_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.PrivateIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"private_ips6\":\n\t\t\t\t\t\tfor _, ip := range instData.PrivateIps6 {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"public_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.PublicIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"public_ips6\":\n\t\t\t\t\t\tfor _, ip := range instData.PublicIps6 {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"cloud_private_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.CloudPrivateIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"cloud_public_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.CloudPublicIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"cloud_public_ips6\":\n\t\t\t\t\t\tfor _, ip := range instData.CloudPublicIps6 {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"host_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.HostIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(rule.SourceIps) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, namespace := range namespaces {\n\t\t\t\tfirewalls[namespace] = append(firewalls[namespace], rule)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GetSpecRulesSlow(db *database.Database,\n\tnodeId bson.ObjectID, instances []*instance.Instance) (\n\tfirewalls map[string][]*Rule, nodePortsMap map[string][]*nodeport.Mapping,\n\terr error) {\n\n\tdeployments, err := deployment.GetAll(db, &bson.M{\n\t\t\"node\": nodeId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdeploymentsNode := map[bson.ObjectID]*deployment.Deployment{}\n\tdeploymentsDeployedMap := map[bson.ObjectID]*deployment.Deployment{}\n\tdeploymentsIdSet := set.NewSet()\n\tpodIdsSet := set.NewSet()\n\tunitIdsSet := set.NewSet()\n\tspecIdsSet := set.NewSet()\n\tfor _, deply := range deployments {\n\t\tdeploymentsNode[deply.Id] = deply\n\t\tdeploymentsIdSet.Add(deply.Id)\n\n\t\tif deply.State == deployment.Deployed {\n\t\t\tdeploymentsDeployedMap[deply.Id] = deply\n\t\t}\n\n\t\tpodIdsSet.Add(deply.Pod)\n\t\tunitIdsSet.Add(deply.Unit)\n\t\tspecIdsSet.Add(deply.Spec)\n\t}\n\n\tspecIds := []bson.ObjectID{}\n\tfor specId := range specIdsSet.Iter() {\n\t\tspecIds = append(specIds, specId.(bson.ObjectID))\n\t}\n\n\tspecs, err := spec.GetAll(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": specIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tspecUnitsSet := set.NewSet()\n\tspecsMap := map[bson.ObjectID]*spec.Spec{}\n\tfor _, spc := range specs {\n\t\tspecsMap[spc.Id] = spc\n\n\t\tif spc.Firewall != nil {\n\t\t\tfor _, rule := range spc.Firewall.Ingress {\n\t\t\t\tfor _, ref := range rule.Sources {\n\t\t\t\t\tspecUnitsSet.Add(ref.Id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tspecUnitIds := []bson.ObjectID{}\n\tfor unitId := range specUnitsSet.Iter() {\n\t\tspecUnitIds = append(specUnitIds, unitId.(bson.ObjectID))\n\t}\n\n\tspecUnits, err := unit.GetAll(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": specUnitIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tspecDeploymentsSet := set.NewSet()\n\tspecsUnitsMap := map[bson.ObjectID]*unit.Unit{}\n\tfor _, specUnit := range specUnits {\n\t\tspecsUnitsMap[specUnit.Id] = specUnit\n\n\t\tfor _, deplyId := range specUnit.Deployments {\n\t\t\tspecDeploymentsSet.Add(deplyId)\n\t\t}\n\t}\n\n\tspecDeploymentIds := []bson.ObjectID{}\n\tfor deplyIdInf := range specDeploymentsSet.Iter() {\n\t\tdeplyId := deplyIdInf.(bson.ObjectID)\n\t\tif !deploymentsIdSet.Contains(deplyId) {\n\t\t\tspecDeploymentIds = append(specDeploymentIds, deplyId)\n\t\t}\n\t}\n\n\tspecDeployments, err := deployment.GetAll(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": specDeploymentIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, specDeployment := range specDeployments {\n\t\tdeploymentsIdSet.Add(specDeployment.Id)\n\n\t\tif specDeployment.State == deployment.Deployed {\n\t\t\tdeploymentsDeployedMap[specDeployment.Id] = specDeployment\n\t\t}\n\t}\n\n\tpodIds := []bson.ObjectID{}\n\tfor podId := range podIdsSet.Iter() {\n\t\tpodIds = append(podIds, podId.(bson.ObjectID))\n\t}\n\n\tpods, err := pod.GetAll(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": podIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpodsMap := map[bson.ObjectID]*pod.Pod{}\n\tfor _, pd := range pods {\n\t\tpodsMap[pd.Id] = pd\n\t}\n\n\tunitIds := []bson.ObjectID{}\n\tfor unitId := range unitIdsSet.Iter() {\n\t\tunitIds = append(unitIds, unitId.(bson.ObjectID))\n\t}\n\n\tunits := []*unit.Unit{}\n\tif len(unitIds) > 0 {\n\t\tunits, err = unit.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": unitIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tunitsMap := map[bson.ObjectID]*unit.Unit{}\n\tpodDeploymentsSet := set.NewSet()\n\tfor _, unt := range units {\n\t\tunitsMap[unt.Id] = unt\n\n\t\tfor _, deplyId := range unt.Deployments {\n\t\t\tpodDeploymentsSet.Add(deplyId)\n\t\t}\n\t}\n\n\tpodDeploymentIds := []bson.ObjectID{}\n\tfor deplyIdInf := range podDeploymentsSet.Iter() {\n\t\tdeplyId := deplyIdInf.(bson.ObjectID)\n\t\tif !deploymentsIdSet.Contains(deplyId) {\n\t\t\tpodDeploymentIds = append(podDeploymentIds, deplyId)\n\t\t}\n\t}\n\n\tpodDeployments, err := deployment.GetAll(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": podDeploymentIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, podDeployment := range podDeployments {\n\t\tdeploymentsIdSet.Add(podDeployment.Id)\n\n\t\tif podDeployment.State == deployment.Deployed {\n\t\t\tdeploymentsDeployedMap[podDeployment.Id] = podDeployment\n\t\t}\n\t}\n\n\tnodePortsMap = map[string][]*nodeport.Mapping{}\n\tfirewalls = map[string][]*Rule{}\n\tfor _, inst := range instances {\n\t\tnodePortsMap[inst.NetworkNamespace] = append(\n\t\t\tnodePortsMap[inst.NetworkNamespace], inst.NodePorts...)\n\n\t\tif inst.Deployment.IsZero() {\n\t\t\tcontinue\n\t\t}\n\n\t\tdeply := deploymentsNode[inst.Deployment]\n\t\tif deply == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tspc := specsMap[deply.Spec]\n\t\tif spc == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif spc.Firewall == nil || spc.Firewall.Ingress == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tnamespaces := []string{}\n\t\tfor i := range inst.Virt.NetworkAdapters {\n\t\t\tnamespaces = append(namespaces, vm.GetNamespace(inst.Id, i))\n\t\t}\n\n\t\tfor _, specRule := range spc.Firewall.Ingress {\n\t\t\trule := &Rule{\n\t\t\t\tProtocol:  specRule.Protocol,\n\t\t\t\tPort:      specRule.Port,\n\t\t\t\tSourceIps: specRule.SourceIps,\n\t\t\t}\n\n\t\t\tfor _, ref := range specRule.Sources {\n\t\t\t\tif ref.Kind != spec.Unit {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\truleUnit := specsUnitsMap[ref.Id]\n\t\t\t\tif ruleUnit == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfor _, ruleDeplyId := range ruleUnit.Deployments {\n\t\t\t\t\truleDeply := deploymentsDeployedMap[ruleDeplyId]\n\t\t\t\t\tif ruleDeply == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tinstData := ruleDeply.InstanceData\n\t\t\t\t\tif instData == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch ref.Selector {\n\t\t\t\t\tcase \"\", \"private_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.PrivateIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"private_ips6\":\n\t\t\t\t\t\tfor _, ip := range instData.PrivateIps6 {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"public_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.PublicIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"public_ips6\":\n\t\t\t\t\t\tfor _, ip := range instData.PublicIps6 {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"cloud_private_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.CloudPrivateIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"cloud_public_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.CloudPublicIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"cloud_public_ips6\":\n\t\t\t\t\t\tfor _, ip := range instData.CloudPublicIps6 {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"host_ips\":\n\t\t\t\t\t\tfor _, ip := range instData.HostIps {\n\t\t\t\t\t\t\trule.SourceIps = append(\n\t\t\t\t\t\t\t\trule.SourceIps,\n\t\t\t\t\t\t\t\tstrings.Split(ip, \"/\")[0]+\"/32\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(rule.SourceIps) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, namespace := range namespaces {\n\t\t\t\tfirewalls[namespace] = append(firewalls[namespace], rule)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "firewall/utils.go",
    "content": "package firewall\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc Get(db *database.Database, fireId bson.ObjectID) (\n\tfire *Firewall, err error) {\n\n\tcoll := db.Firewalls()\n\tfire = &Firewall{}\n\n\terr = coll.FindOneId(fireId, fire)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, fireId bson.ObjectID) (\n\tfire *Firewall, err error) {\n\n\tcoll := db.Firewalls()\n\tfire = &Firewall{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          fireId,\n\t\t\"organization\": orgId,\n\t}).Decode(fire)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tfires []*Firewall, err error) {\n\n\tcoll := db.Firewalls()\n\tfires = []*Firewall{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tfire := &Firewall{}\n\t\terr = cursor.Decode(fire)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tfires = append(fires, fire)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetRoles(db *database.Database, roles []string) (\n\tfires []*Firewall, err error) {\n\n\tcoll := db.Firewalls()\n\tfires = []*Firewall{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"organization\": Global,\n\t\t\t\"roles\": &bson.M{\n\t\t\t\t\"$in\": roles,\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tfire := &Firewall{}\n\t\terr = cursor.Decode(fire)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tfires = append(fires, fire)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetMapRoles(db *database.Database, roles []string) (\n\tfires map[string][]*Firewall, err error) {\n\n\tcoll := db.Firewalls()\n\tfires = map[string][]*Firewall{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"roles\": &bson.M{\n\t\t\t\"$in\": roles,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tfire := &Firewall{}\n\t\terr = cursor.Decode(fire)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, role := range fire.Roles {\n\t\t\troleFires := fires[role]\n\t\t\tif roleFires == nil {\n\t\t\t\troleFires = []*Firewall{}\n\t\t\t}\n\t\t\tfires[role] = append(roleFires, fire)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrgMapRoles(db *database.Database, orgId bson.ObjectID) (\n\tfires map[string][]*Firewall, err error) {\n\n\tcoll := db.Firewalls()\n\tfires = map[string][]*Firewall{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tfire := &Firewall{}\n\t\terr = cursor.Decode(fire)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, role := range fire.Roles {\n\t\t\troleFires := fires[role]\n\t\t\tif roleFires == nil {\n\t\t\t\troleFires = []*Firewall{}\n\t\t\t}\n\t\t\tfires[role] = append(roleFires, fire)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrgRoles(db *database.Database, orgId bson.ObjectID,\n\troles []string) (fires []*Firewall, err error) {\n\n\tcoll := db.Firewalls()\n\tfires = []*Firewall{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"organization\": orgId,\n\t\t\t\"roles\": &bson.M{\n\t\t\t\t\"$in\": roles,\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tfire := &Firewall{}\n\t\terr = cursor.Decode(fire)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tfires = append(fires, fire)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (fires []*Firewall, count int64, err error) {\n\n\tcoll := db.Firewalls()\n\tfires = []*Firewall{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tfire := &Firewall{}\n\t\terr = cursor.Decode(fire)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tfires = append(fires, fire)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, fireId bson.ObjectID) (err error) {\n\tcoll := db.Firewalls()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": fireId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, fireId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Firewalls()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          fireId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, fireIds []bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Firewalls()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": fireIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tfireIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Firewalls()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": fireIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc MergeIngress(fires []*Firewall) (rules []*Rule) {\n\trules = []*Rule{}\n\trulesMap := map[string]*Rule{}\n\trulesKey := []string{}\n\n\tfor _, fire := range fires {\n\t\tfor _, ingress := range fire.Ingress {\n\t\t\tkey := fmt.Sprintf(\"%s-%s\", ingress.Protocol, ingress.Port)\n\t\t\trule := rulesMap[key]\n\t\t\tif rule == nil {\n\t\t\t\trule = &Rule{\n\t\t\t\t\tProtocol:  ingress.Protocol,\n\t\t\t\t\tPort:      ingress.Port,\n\t\t\t\t\tSourceIps: ingress.SourceIps,\n\t\t\t\t}\n\t\t\t\trulesMap[key] = rule\n\t\t\t\trulesKey = append(rulesKey, key)\n\t\t\t} else {\n\t\t\t\tsourceIps := set.NewSet()\n\t\t\t\tfor _, sourceIp := range rule.SourceIps {\n\t\t\t\t\tsourceIps.Add(sourceIp)\n\t\t\t\t}\n\n\t\t\t\tfor _, sourceIp := range ingress.SourceIps {\n\t\t\t\t\tif sourceIps.Contains(sourceIp) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tsourceIps.Add(sourceIp)\n\t\t\t\t\trule.SourceIps = append(rule.SourceIps, sourceIp)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Strings(rulesKey)\n\tfor _, key := range rulesKey {\n\t\trules = append(rules, rulesMap[key])\n\t}\n\n\treturn\n}\n\nfunc GetAllIngress(db *database.Database, nodeSelf *node.Node,\n\tinstances []*instance.Instance, specRules map[string][]*Rule,\n\tnodePortsMap map[string][]*nodeport.Mapping) (\n\tnodeFirewall []*Rule, firewalls map[string][]*Rule,\n\tmappings map[string][]*Mapping,\n\tinstNamespaces map[bson.ObjectID][]string, err error) {\n\n\tif nodeSelf.Firewall {\n\t\tfires, e := GetRoles(db, nodeSelf.Roles)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tingress := MergeIngress(fires)\n\t\tnodeFirewall = ingress\n\t}\n\n\tinstNamespaces = map[bson.ObjectID][]string{}\n\tnodePortIps := map[string]string{}\n\tfirewalls = map[string][]*Rule{}\n\tfor _, inst := range instances {\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tnamespaces := []string{}\n\t\tfor i := range inst.Virt.NetworkAdapters {\n\t\t\tnamespaces = append(namespaces, vm.GetNamespace(inst.Id, i))\n\t\t}\n\t\tinstNamespaces[inst.Id] = namespaces\n\n\t\tif len(inst.NodePortIps) > 0 && len(namespaces) > 0 {\n\t\t\tnodePortIps[namespaces[0]] = inst.NodePortIps[0]\n\t\t}\n\n\t\tfires, e := GetOrgRoles(db,\n\t\t\tinst.Organization, inst.Roles)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tingress := MergeIngress(fires)\n\n\t\tfor _, namespace := range namespaces {\n\t\t\t_, ok := firewalls[namespace]\n\t\t\tif ok {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"namespace\":   namespace,\n\t\t\t\t}).Error(\"firewall: Namespace conflict\")\n\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.New(\"firewall: Namespace conflict\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfirewalls[namespace] = ingress\n\t\t}\n\t}\n\n\tfor namespace, rules := range specRules {\n\t\tfirewalls[namespace] = append(firewalls[namespace], rules...)\n\t}\n\n\tmappings = map[string][]*Mapping{}\n\texternalPorts := map[int]string{}\n\tfor namespace, ndePorts := range nodePortsMap {\n\t\tfor _, ndePort := range ndePorts {\n\t\t\tipvs := false\n\n\t\t\textNamespace := externalPorts[ndePort.ExternalPort]\n\t\t\tif extNamespace != \"\" {\n\t\t\t\tipvs = true\n\n\t\t\t\tif extNamespace != \"-\" {\n\t\t\t\t\tfor _, mapping := range mappings[extNamespace] {\n\t\t\t\t\t\tif mapping.ExternalPort == ndePort.ExternalPort {\n\t\t\t\t\t\t\tmapping.Ipvs = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\texternalPorts[ndePort.ExternalPort] = \"-\"\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texternalPorts[ndePort.ExternalPort] = namespace\n\t\t\t}\n\n\t\t\tmappings[namespace] = append(mappings[namespace], &Mapping{\n\t\t\t\tIpvs:         ipvs,\n\t\t\t\tAddress:      nodePortIps[namespace],\n\t\t\t\tProtocol:     ndePort.Protocol,\n\t\t\t\tExternalPort: ndePort.ExternalPort,\n\t\t\t\tInternalPort: ndePort.InternalPort,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GetAllIngressPreloaded(nodeSelf *node.Node,\n\tinstances []*instance.Instance, specRules map[string][]*Rule,\n\tnodePortsMap map[string][]*nodeport.Mapping,\n\tfiresMap map[string][]*Firewall) (\n\tnodeFirewall []*Rule, firewalls map[string][]*Rule,\n\tmappings map[string][]*Mapping,\n\tinstNamespaces map[bson.ObjectID][]string, err error) {\n\n\tif nodeSelf.Firewall {\n\t\tfires := []*Firewall{}\n\t\tfor _, role := range nodeSelf.Roles {\n\t\t\tfor _, fire := range firesMap[role] {\n\t\t\t\tif fire.Organization == Global {\n\t\t\t\t\tfires = append(fires, fire)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tingress := MergeIngress(fires)\n\t\tnodeFirewall = ingress\n\t}\n\n\tinstNamespaces = map[bson.ObjectID][]string{}\n\tnodePortIps := map[string]string{}\n\tfirewalls = map[string][]*Rule{}\n\tfor _, inst := range instances {\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tnamespaces := []string{}\n\t\tfor i := range inst.Virt.NetworkAdapters {\n\t\t\tnamespaces = append(namespaces, vm.GetNamespace(inst.Id, i))\n\t\t}\n\t\tinstNamespaces[inst.Id] = namespaces\n\n\t\tif len(inst.NodePortIps) > 0 && len(namespaces) > 0 {\n\t\t\tnodePortIps[namespaces[0]] = inst.NodePortIps[0]\n\t\t}\n\n\t\tfires := []*Firewall{}\n\t\tfor _, role := range inst.Roles {\n\t\t\tfor _, fire := range firesMap[role] {\n\t\t\t\tif fire.Organization == inst.Organization {\n\t\t\t\t\tfires = append(fires, fire)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tingress := MergeIngress(fires)\n\n\t\tfor _, namespace := range namespaces {\n\t\t\t_, ok := firewalls[namespace]\n\t\t\tif ok {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"namespace\":   namespace,\n\t\t\t\t}).Error(\"firewall: Namespace conflict\")\n\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.New(\"firewall: Namespace conflict\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfirewalls[namespace] = ingress\n\t\t}\n\t}\n\n\tfor namespace, rules := range specRules {\n\t\tfirewalls[namespace] = append(firewalls[namespace], rules...)\n\t}\n\n\tmappings = map[string][]*Mapping{}\n\texternalPorts := map[int]string{}\n\tfor namespace, ndePorts := range nodePortsMap {\n\t\tfor _, ndePort := range ndePorts {\n\t\t\tipvs := false\n\n\t\t\textNamespace := externalPorts[ndePort.ExternalPort]\n\t\t\tif extNamespace != \"\" {\n\t\t\t\tipvs = true\n\n\t\t\t\tif extNamespace != \"-\" {\n\t\t\t\t\tfor _, mapping := range mappings[extNamespace] {\n\t\t\t\t\t\tif mapping.ExternalPort == ndePort.ExternalPort {\n\t\t\t\t\t\t\tmapping.Ipvs = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\texternalPorts[ndePort.ExternalPort] = \"-\"\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texternalPorts[ndePort.ExternalPort] = namespace\n\t\t\t}\n\n\t\t\tmappings[namespace] = append(mappings[namespace], &Mapping{\n\t\t\t\tIpvs:         ipvs,\n\t\t\t\tAddress:      nodePortIps[namespace],\n\t\t\t\tProtocol:     ndePort.Protocol,\n\t\t\t\tExternalPort: ndePort.ExternalPort,\n\t\t\t\tInternalPort: ndePort.InternalPort,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "geo/geo.go",
    "content": "package geo\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tclient = &http.Client{\n\t\tTimeout: 10 * time.Second,\n\t}\n)\n\ntype Geo struct {\n\tAddress       string    `bson:\"_id\" json:\"address\"`\n\tIsp           string    `bson:\"i\" json:\"isp\"`\n\tContinent     string    `bson:\"z\" json:\"continent\"`\n\tContinentCode string    `bson:\"q\" json:\"continent_code\"`\n\tCountry       string    `bson:\"c\" json:\"country\"`\n\tCountryCode   string    `bson:\"w\" json:\"country_code\"`\n\tRegion        string    `bson:\"r\" json:\"region\"`\n\tRegionCode    string    `bson:\"e\" json:\"region_code\"`\n\tCity          string    `bson:\"a\" json:\"city\"`\n\tLongitude     float64   `bson:\"x\" json:\"longitude\"`\n\tLatitude      float64   `bson:\"y\" json:\"latitude\"`\n\tTimestamp     time.Time `bson:\"t\" json:\"-\"`\n}\n\ntype geoData struct {\n\tLicense string `json:\"license\"`\n\tAddress string `json:\"address\"`\n}\n\nfunc get(addr string) (ge *Geo, err error) {\n\tif settings.System.License == \"\" {\n\t\treturn\n\t}\n\n\treqGeoData := &geoData{\n\t\tLicense: settings.System.License,\n\t\tAddress: addr,\n\t}\n\n\treqData := &bytes.Buffer{}\n\terr = json.NewEncoder(reqData).Encode(reqGeoData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"geo: Failed to parse request data\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\tsettings.Auth.Server+\"/geo\",\n\t\treqData,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"geo: Failed to create request\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"geo: Failed to send request\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"geo: Geo request error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tge = &Geo{}\n\terr = json.NewDecoder(resp.Body).Decode(ge)\n\tif err != nil {\n\t\tge = nil\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"geo: Failed to parse response\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Get(db *database.Database, addr string) (ge *Geo, err error) {\n\tge = &Geo{}\n\tcoll := db.Geo()\n\n\terr = coll.FindOneId(addr, ge)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\tge = nil\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\tif ge == nil {\n\t\tge, err = get(addr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif ge != nil {\n\t\t\tge.Timestamp = time.Now()\n\t\t\t_, _ = coll.InsertOne(db, ge)\n\t\t} else {\n\t\t\tge = &Geo{}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/pritunl/pritunl-cloud\n\ngo 1.25.9\n\nrequire (\n\tgithub.com/aws/aws-sdk-go v1.55.5\n\tgithub.com/cloudflare/cloudflare-go v0.104.0\n\tgithub.com/coredns/coredns v1.14.3\n\tgithub.com/dropbox/godropbox v0.0.0-20230623171840-436d2007a9fd\n\tgithub.com/duosecurity/duo_api_golang v0.0.0-20250430191550-ac36954387e7\n\tgithub.com/gin-gonic/gin v1.10.0\n\tgithub.com/go-webauthn/webauthn v0.12.3\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/gorilla/securecookie v1.1.2\n\tgithub.com/gorilla/sessions v1.4.0\n\tgithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674\n\tgithub.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f\n\tgithub.com/mdlayher/ndp v1.1.0\n\tgithub.com/miekg/dns v1.1.72\n\tgithub.com/minio/minio-go/v7 v7.0.76\n\tgithub.com/oracle/oci-go-sdk/v65 v65.74.0\n\tgithub.com/pritunl/mongo-go-driver/v2 v2.3.0\n\tgithub.com/pritunl/tools v1.2.5\n\tgithub.com/sirupsen/logrus v1.9.3\n\tgithub.com/twilio/twilio-go v1.23.0\n\tgithub.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90\n\tgolang.org/x/crypto v0.50.0\n\tgolang.org/x/net v0.53.0\n\tgolang.org/x/oauth2 v0.36.0\n\tgolang.org/x/sys v0.43.0\n\tgoogle.golang.org/api v0.276.0\n\tgopkg.in/yaml.v2 v2.4.0\n)\n\nrequire (\n\tcloud.google.com/go/auth v0.20.0 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.9.0 // indirect\n\tgithub.com/apparentlymart/go-cidr v1.1.1 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/bytedance/sonic v1.12.2 // indirect\n\tgithub.com/bytedance/sonic/loader v0.2.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/cloudwego/base64x v0.1.4 // indirect\n\tgithub.com/cloudwego/iasm v0.2.0 // indirect\n\tgithub.com/coredns/caddy v1.1.4 // indirect\n\tgithub.com/dnstap/golang-dnstap v0.4.0 // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/farsightsec/golang-framestream v0.3.0 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.1 // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.5 // indirect\n\tgithub.com/gin-contrib/sse v0.1.0 // indirect\n\tgithub.com/go-ini/ini v1.67.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-playground/locales v0.14.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.18.1 // indirect\n\tgithub.com/go-playground/validator/v10 v10.22.1 // indirect\n\tgithub.com/go-webauthn/x v0.1.20 // indirect\n\tgithub.com/goccy/go-json v0.10.3 // indirect\n\tgithub.com/gofrs/flock v0.13.0 // indirect\n\tgithub.com/golang-jwt/jwt/v5 v5.3.1 // indirect\n\tgithub.com/golang/mock v1.7.0-rc.1 // indirect\n\tgithub.com/golang/snappy v1.0.0 // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n\tgithub.com/google/go-tpm v0.9.3 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.22.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect\n\tgithub.com/hashicorp/golang-lru v1.0.2 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/jmespath/go-jmespath v0.4.0 // indirect\n\tgithub.com/josharian/native v1.1.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.18.5 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.3.0 // indirect\n\tgithub.com/leodido/go-urn v1.4.0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mdlayher/packet v1.1.2 // indirect\n\tgithub.com/mdlayher/socket v0.6.0 // indirect\n\tgithub.com/minio/md5-simd v1.1.2 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/opentracing/opentracing-go v1.2.0 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.3 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.22 // indirect\n\tgithub.com/pires/go-proxyproto v0.12.0 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/prometheus/client_golang v1.23.2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.67.5 // indirect\n\tgithub.com/prometheus/procfs v0.20.1 // indirect\n\tgithub.com/quic-go/qpack v0.6.0 // indirect\n\tgithub.com/quic-go/quic-go v0.59.0 // indirect\n\tgithub.com/rs/xid v1.6.0 // indirect\n\tgithub.com/sony/gobreaker v1.0.0 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n\tgithub.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect\n\tgithub.com/ugorji/go/codec v1.2.12 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/xdg-go/scram v1.1.2 // indirect\n\tgithub.com/xdg-go/stringprep v1.0.4 // indirect\n\tgithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect\n\tgo.opentelemetry.io/otel v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.43.0 // indirect\n\tgo.uber.org/mock v0.6.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.4 // indirect\n\tgolang.org/x/arch v0.10.0 // indirect\n\tgolang.org/x/mod v0.35.0 // indirect\n\tgolang.org/x/sync v0.20.0 // indirect\n\tgolang.org/x/text v0.36.0 // indirect\n\tgolang.org/x/time v0.15.0 // indirect\n\tgolang.org/x/tools v0.44.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect\n\tgoogle.golang.org/grpc v1.80.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=\ncloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ngithub.com/apparentlymart/go-cidr v1.1.1 h1:oEEk8CE0HP0YpHxsegk/TaOtR2FLHdWv4p3eM4ceUwg=\ngithub.com/apparentlymart/go-cidr v1.1.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=\ngithub.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=\ngithub.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=\ngithub.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg=\ngithub.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=\ngithub.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=\ngithub.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=\ngithub.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cloudflare/cloudflare-go v0.104.0 h1:R/lB0dZupaZbOgibAH/BRrkFbZ6Acn/WsKg2iX2xXuY=\ngithub.com/cloudflare/cloudflare-go v0.104.0/go.mod h1:pfUQ4PIG4ISI0/Mmc21Bp86UnFU0ktmPf3iTgbSL+cM=\ngithub.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=\ngithub.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=\ngithub.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=\ngithub.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=\ngithub.com/coredns/caddy v1.1.4 h1:+Lls5xASB0QsA2jpCroCOwpPlb5GjIGlxdjXxdX0XVo=\ngithub.com/coredns/caddy v1.1.4/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=\ngithub.com/coredns/coredns v1.14.3 h1:hWWoTdONblKIWhC8QPkxLEGIbewhR5xyTedqLVPsvvE=\ngithub.com/coredns/coredns v1.14.3/go.mod h1:15BWsGGxupagKQ3p09pIIZ5kcgmyawquey6gqNWRaEI=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dnstap/golang-dnstap v0.4.0 h1:KRHBoURygdGtBjDI2w4HifJfMAhhOqDuktAokaSa234=\ngithub.com/dnstap/golang-dnstap v0.4.0/go.mod h1:FqsSdH58NAmkAvKcpyxht7i4FoBjKu8E4JUPt8ipSUs=\ngithub.com/dropbox/godropbox v0.0.0-20230623171840-436d2007a9fd h1:s2vYw+2c+7GR1ccOaDuDcKsmNB/4RIxyu5liBm1VRbs=\ngithub.com/dropbox/godropbox v0.0.0-20230623171840-436d2007a9fd/go.mod h1:Vr/Q4p40Kce7JAHDITjDhiy/zk07W4tqD5YVi5FD0PA=\ngithub.com/duosecurity/duo_api_golang v0.0.0-20250430191550-ac36954387e7 h1:2QX96efe1AvKmqAdqeAn3efxI3lr+EULVbzRxZ/rKGQ=\ngithub.com/duosecurity/duo_api_golang v0.0.0-20250430191550-ac36954387e7/go.mod h1:hJ6IPTuCAvWv+i9ubnPZB3VpVRuj/+SAblWFcI0mjEU=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/farsightsec/golang-framestream v0.3.0 h1:/spFQHucTle/ZIPkYqrfshQqPe2VQEzesH243TjIwqA=\ngithub.com/farsightsec/golang-framestream v0.3.0/go.mod h1:eNde4IQyEiA5br02AouhEHCu3p3UzrCdFR4LuQHklMI=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=\ngithub.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=\ngithub.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=\ngithub.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=\ngithub.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=\ngithub.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=\ngithub.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=\ngithub.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=\ngithub.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=\ngithub.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=\ngithub.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=\ngithub.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=\ngithub.com/go-webauthn/webauthn v0.12.3 h1:hHQl1xkUuabUU9uS+ISNCMLs9z50p9mDUZI/FmkayNE=\ngithub.com/go-webauthn/webauthn v0.12.3/go.mod h1:4JRe8Z3W7HIw8NGEWn2fnUwecoDzkkeach/NnvhkqGY=\ngithub.com/go-webauthn/x v0.1.20 h1:brEBDqfiPtNNCdS/peu8gARtq8fIPsHz0VzpPjGvgiw=\ngithub.com/go-webauthn/x v0.1.20/go.mod h1:n/gAc8ssZJGATM0qThE+W+vfgXiMedsWi3wf/C4lld0=\ngithub.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=\ngithub.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=\ngithub.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=\ngithub.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=\ngithub.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=\ngithub.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=\ngithub.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=\ngithub.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=\ngithub.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=\ngithub.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=\ngithub.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=\ngithub.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=\ngithub.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=\ngithub.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=\ngithub.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=\ngithub.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=\ngithub.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=\ngithub.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=\ngithub.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=\ngithub.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU=\ngithub.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM=\ngithub.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=\ngithub.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=\ngithub.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=\ngithub.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q=\ngithub.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mdlayher/ndp v1.1.0 h1:QylGKGVtH60sKZUE88+IW5ila1Z/M9/OXhWdsVKuscs=\ngithub.com/mdlayher/ndp v1.1.0/go.mod h1:FmgESgemgjl38vuOIyAHWUUL6vQKA/pQNkvXdWsdQFM=\ngithub.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=\ngithub.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=\ngithub.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU=\ngithub.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18=\ngithub.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=\ngithub.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=\ngithub.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=\ngithub.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=\ngithub.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=\ngithub.com/minio/minio-go/v7 v7.0.76 h1:9nxHH2XDai61cT/EFhyIw/wW4vJfpPNvl7lSFpRt+Ng=\ngithub.com/minio/minio-go/v7 v7.0.76/go.mod h1:AVM3IUN6WwKzmwBxVdjzhH8xq+f57JSbbvzqvUzR6eg=\ngithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=\ngithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=\ngithub.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=\ngithub.com/oracle/oci-go-sdk/v65 v65.74.0 h1:oA2VXpecSTwc45QJGsKNoxCBwbUMuXLQ2W4pLZZarro=\ngithub.com/oracle/oci-go-sdk/v65 v65.74.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=\ngithub.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=\ngithub.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=\ngithub.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=\ngithub.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=\ngithub.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pritunl/mongo-go-driver/v2 v2.3.0 h1:ZQ8ZujYwwY+1TEXwf8pGytdprmgGpe/HRP7fa4xbRow=\ngithub.com/pritunl/mongo-go-driver/v2 v2.3.0/go.mod h1:U8W2Evrwe7KTE6DFgMV1Ub/WLXzJht/lyXnLHEK5E+8=\ngithub.com/pritunl/tools v1.2.5 h1:qbVj1QQdhhgQLBa2JnjBlVbPJt/t33veI/QN+kJq28s=\ngithub.com/pritunl/tools v1.2.5/go.mod h1:BiNzTb2ZCesQ5k/Mx0mhOwGXNJNdZk+4jqg39GjRXKU=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=\ngithub.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=\ngithub.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=\ngithub.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=\ngithub.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=\ngithub.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=\ngithub.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=\ngithub.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=\ngithub.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=\ngithub.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=\ngithub.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/twilio/twilio-go v1.23.0 h1:cIJD6XnVuRqnMVp8LswoOTEi4/JK9WctOTUvUR2gLf0=\ngithub.com/twilio/twilio-go v1.23.0/go.mod h1:zRkMjudW7v7MqQ3cWNZmSoZJ7EBjPZ4OpNh2zm7Q6ko=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=\ngithub.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=\ngithub.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90 h1:rB0J+hLNltG1Qv+UF+MkdFz89XMps5BOAFJN4xWjc+s=\ngithub.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90/go.mod h1:BUbeWZiieNxAuuADTBNb3/aeje6on3DhU3rpWsQSB1E=\ngithub.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=\ngithub.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=\ngithub.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=\ngithub.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=\ngithub.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=\ngo.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=\ngo.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=\ngo.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=\ngo.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=\ngo.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=\ngo.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=\ngo.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=\ngo.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=\ngo.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=\ngo.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=\ngo.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=\ngo.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=\ngo.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=\ngolang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8=\ngolang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=\ngolang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=\ngolang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=\ngolang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=\ngolang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=\ngolang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=\ngolang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=\ngolang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=\ngolang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=\ngolang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=\ngolang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=\ngonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=\ngoogle.golang.org/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY=\ngoogle.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw=\ngoogle.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=\ngoogle.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 h1:yOzSCGPx+cp5VO7IxvZ9SBFF7j1tZVcNtlHR2iYKtVo=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:Q9HWtNeE7tM9npdIsEvqXj1QJIvVoeAV3rtXtS715Cw=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=\ngoogle.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=\ngoogle.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nnullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=\n"
  },
  {
    "path": "guest/guest.go",
    "content": "package guest\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tsocketsLock = utils.NewMultiTimeoutLock(1 * time.Minute)\n)\n\ntype Command struct {\n\tExecute   string                 `json:\"execute\"`\n\tArguments map[string]interface{} `json:\"arguments,omitempty\"`\n}\n\ntype Response struct {\n\tReturn  map[string]interface{} `json:\"return\"`\n\tError   map[string]interface{} `json:\"error,omitempty\"`\n\tRawData []byte                 `json:\"-\"`\n}\n"
  },
  {
    "path": "guest/power.go",
    "content": "package guest\n\nimport (\n\t\"encoding/json\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Shutdown(vmId bson.ObjectID) (err error) {\n\tlockId := socketsLock.Lock(vmId.Hex())\n\tdefer socketsLock.Unlock(vmId.Hex(), lockId)\n\n\tsockPath := paths.GetGuestPath(vmId)\n\n\texists, err := utils.Exists(sockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !exists {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.New(\"guest: Guest agent socket missing\"),\n\t\t}\n\t\treturn\n\t}\n\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"guest: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(5 * time.Second))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"guest: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcmd := Command{\n\t\tExecute: \"guest-shutdown\",\n\t\tArguments: map[string]interface{}{\n\t\t\t\"mode\": \"powerdown\",\n\t\t},\n\t}\n\n\tcmdData, err := json.Marshal(cmd)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"guest: Failed to marshal socket data\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcmdData = append(cmdData, '\\n')\n\n\t_, err = conn.Write(cmdData)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"guest: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\tbuffer := make([]byte, 8192)\n\tn, err := conn.Read(buffer)\n\tif err != nil {\n\t\terr = nil\n\t\treturn\n\t}\n\n\tvar response Response\n\tresponse.RawData = buffer[:n]\n\terr = json.Unmarshal(response.RawData, &response)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"guest: Failed to parse socket data\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif response.Error != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Newf(\"guest: Guest returned error %v\", response.Error),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "hnetwork/hnetwork.go",
    "content": "package hnetwork\n\nimport (\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tinitialized = false\n\tcurGateway  = \"\"\n\tcurRule     *IptablesRule\n)\n\ntype IptablesRule struct {\n\tSource string\n\tOutput string\n}\n\nfunc (h *IptablesRule) Add() (err error) {\n\targs := []string{\n\t\t\"-t\", \"nat\",\n\t\t\"-A\", \"POSTROUTING\",\n\t}\n\n\tif h.Source != \"\" {\n\t\targs = append(args, \"-s\", h.Source)\n\t}\n\tif h.Output != \"\" {\n\t\targs = append(args, \"-o\", h.Output)\n\t}\n\n\targs = append(args,\n\t\t\"-m\", \"comment\",\n\t\t\"--comment\", \"pritunl_cloud_host_nat\",\n\t\t\"-j\", \"MASQUERADE\",\n\t)\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"matching rule exist\",\n\t\t\t\"match by that name\",\n\t\t},\n\t\t\"iptables\",\n\t\targs...,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (h *IptablesRule) Remove() (err error) {\n\targs := []string{\n\t\t\"-t\", \"nat\",\n\t\t\"-D\", \"POSTROUTING\",\n\t}\n\n\tif h.Source != \"\" {\n\t\targs = append(args, \"-s\", h.Source)\n\t}\n\tif h.Output != \"\" {\n\t\targs = append(args, \"-o\", h.Output)\n\t}\n\n\targs = append(args,\n\t\t\"-m\", \"comment\",\n\t\t\"--comment\", \"pritunl_cloud_host_nat\",\n\t\t\"-j\", \"MASQUERADE\",\n\t)\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"matching rule exist\",\n\t\t\t\"match by that name\",\n\t\t},\n\t\t\"iptables\",\n\t\targs...,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc loadIptablesNat() (rules []*IptablesRule, err error) {\n\trules = []*IptablesRule{}\n\n\toutput, err := utils.ExecOutput(\"\", \"iptables\", \"-t\", \"nat\", \"-S\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, line := range strings.Split(output, \"\\n\") {\n\t\tif !strings.Contains(line, \"POSTROUTING\") ||\n\t\t\t!strings.Contains(line, \"MASQUERADE\") ||\n\t\t\t!strings.Contains(line, \"pritunl_cloud_host_nat\") {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tcmd := strings.Fields(line)\n\t\tcmdLen := len(cmd)\n\t\tif cmdLen < 3 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"iptables_rule\": line,\n\t\t\t}).Error(\"hnetwork: Invalid iptables state\")\n\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"hnetwork: Invalid iptables state\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\trule := &IptablesRule{}\n\n\t\tfor i, item := range cmd {\n\t\t\tif item == \"-s\" {\n\t\t\t\tif len(cmd) < i+2 {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t\t}).Error(\"hnetwork: Invalid iptables host nat source\")\n\n\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\terrors.New(\n\t\t\t\t\t\t\t\"hnetwork: Invalid iptables host nat source\"),\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trule.Source = cmd[i+1]\n\t\t\t}\n\n\t\t\tif item == \"-o\" {\n\t\t\t\tif len(cmd) < i+2 {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t\t}).Error(\"hnetwork: Invalid iptables host nat output\")\n\n\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\terrors.New(\n\t\t\t\t\t\t\t\"hnetwork: Invalid iptables host nat output\"),\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trule.Output = cmd[i+1]\n\t\t\t}\n\t\t}\n\n\t\trules = append(rules, rule)\n\t}\n\n\treturn\n}\n\nfunc removeNetwork(stat *state.State) (err error) {\n\tif curGateway != \"\" || stat.HasInterfaces(\n\t\tsettings.Hypervisor.HostNetworkName) {\n\n\t\terr = clearAddr()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tcurGateway = \"\"\n\t}\n\n\treturn\n}\n\nfunc ApplyState(stat *state.State) (err error) {\n\tinitializeInst := false\n\thostNetName := settings.Hypervisor.HostNetworkName\n\n\tif !initialized {\n\t\taddr, e := getAddr()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\trules, e := loadIptablesNat()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif len(rules) > 1 {\n\t\t\tfor _, rule := range rules {\n\t\t\t\terr = rule.Remove()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t} else if len(rules) == 1 {\n\t\t\tcurRule = rules[0]\n\t\t}\n\n\t\tinitializeInst = true\n\t\tinitialized = true\n\t\tcurGateway = addr\n\t}\n\n\tif !stat.HasInterfaces(hostNetName) {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"iface\": hostNetName,\n\t\t}).Info(\"hnetwork: Creating host interface\")\n\n\t\terr = create()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tcurGateway = \"\"\n\t\tinitializeInst = true\n\t}\n\n\thostBlock, err := block.GetNodeBlock(stat.Node().Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tgatewayCidr := hostBlock.GetGatewayCidr()\n\tif gatewayCidr == \"\" {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"host_block\": hostBlock.Id.Hex(),\n\t\t}).Error(\"hnetwork: Host network block gateway is invalid\")\n\n\t\terr = removeNetwork(stat)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t}\n\n\tif curGateway != gatewayCidr {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"host_block\":         hostBlock.Id.Hex(),\n\t\t\t\"host_block_gateway\": gatewayCidr,\n\t\t}).Info(\"hnetwork: Updating host network bridge\")\n\n\t\terr = setAddr(gatewayCidr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tcurGateway = gatewayCidr\n\t\tinitializeInst = true\n\t}\n\n\tif stat.Node().HostNat {\n\t\thostNet, e := hostBlock.GetNetwork()\n\t\tif e != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"host_block\": hostBlock.Id.Hex(),\n\t\t\t\t\"error\":      e,\n\t\t\t}).Error(\"hnetwork: Host nat block network invalid\")\n\t\t} else {\n\t\t\tnewRule := &IptablesRule{\n\t\t\t\tSource: hostNet.String(),\n\t\t\t\tOutput: stat.Node().DefaultInterface,\n\t\t\t}\n\n\t\t\tif curRule == nil || curRule.Source != newRule.Source ||\n\t\t\t\tcurRule.Output != newRule.Output {\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"host_block\":  hostBlock.Id.Hex(),\n\t\t\t\t\t\"host_source\": newRule.Source,\n\t\t\t\t\t\"host_output\": newRule.Output,\n\t\t\t\t}).Info(\"hnetwork: Updating host network nat\")\n\n\t\t\t\tif curRule != nil {\n\t\t\t\t\terr = curRule.Remove()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tcurRule = nil\n\t\t\t\t}\n\n\t\t\t\terr = newRule.Add()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"host_block\":  hostBlock.Id.Hex(),\n\t\t\t\t\t\t\"host_source\": newRule.Source,\n\t\t\t\t\t\t\"host_output\": newRule.Output,\n\t\t\t\t\t\t\"error\":       err,\n\t\t\t\t\t}).Error(\"hnetwork: Host nat add rule failed\")\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\tcurRule = newRule\n\t\t\t\t}\n\n\t\t\t\tinitializeInst = true\n\t\t\t}\n\t\t}\n\t} else if curRule != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"host_block\":  hostBlock.Id.Hex(),\n\t\t\t\"host_source\": curRule.Source,\n\t\t\t\"host_output\": curRule.Output,\n\t\t}).Info(\"hnetwork: Updating host network nat\")\n\n\t\terr = curRule.Remove()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tcurRule = nil\n\t}\n\n\tif initializeInst {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"host_block\": hostBlock.Id.Hex(),\n\t\t}).Info(\"hnetwork: Updating instance host network\")\n\n\t\tinstances := stat.Instances()\n\t\tfor _, inst := range instances {\n\t\t\tutils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"Cannot find device\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"link\", \"set\",\n\t\t\t\tvm.GetIfaceHost(inst.Id, 0), \"master\", hostNetName,\n\t\t\t)\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "hnetwork/utils.go",
    "content": "package hnetwork\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/pritunl/pritunl-cloud/bridges\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc create() (err error) {\n\terr = iproute.BridgeAdd(\"\", settings.Hypervisor.HostNetworkName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"link\", \"set\",\n\t\t\"dev\", settings.Hypervisor.HostNetworkName, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tbridges.ClearCache()\n\n\treturn\n}\n\nfunc getAddr() (addr string, err error) {\n\taddress, _, err := iproute.AddressGetIface(\n\t\t\"\", settings.Hypervisor.HostNetworkName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif address != nil {\n\t\taddr = address.Local + fmt.Sprintf(\"/%d\", address.Prefix)\n\t}\n\n\treturn\n}\n\nfunc setAddr(addr string) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"link\", \"set\",\n\t\t\"dev\", settings.Hypervisor.HostNetworkName, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"addr\", \"flush\",\n\t\t\"dev\", settings.Hypervisor.HostNetworkName,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"addr\", \"add\", addr,\n\t\t\"dev\", settings.Hypervisor.HostNetworkName,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc clearAddr() (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"link\", \"set\",\n\t\t\"dev\", settings.Hypervisor.HostNetworkName, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"addr\", \"flush\",\n\t\t\"dev\", settings.Hypervisor.HostNetworkName,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "hugepages/hugepages.go",
    "content": "package hugepages\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc HugepageSize() (count int, size uint64, err error) {\n\tvirt, err := utils.GetMemInfo()\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"hugepages: Failed to read virtual memory\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcount = int(virt.HugePagesTotal)\n\tsize = virt.HugePageSize\n\n\tif size < 1024 {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Newf(\"hugepages: Invalid hugepage size %d\", size),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc UpdateHugepagesSize() (err error) {\n\terr = utils.ExistsMkdir(settings.Hypervisor.HugepagesPath, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnodeHugepagesSize := node.Self.HugepagesSize\n\tif nodeHugepagesSize == 0 {\n\t\treturn\n\t}\n\n\tcurHugepagesCount, hugepageSize, err := HugepageSize()\n\tif err != nil {\n\t\treturn\n\t}\n\n\thugepagesSize := uint64(nodeHugepagesSize) * 1024\n\thugepagesCount := int(hugepagesSize / hugepageSize)\n\n\tif curHugepagesCount != hugepagesCount {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"cur_nr_hugepages\": curHugepagesCount,\n\t\t\t\"new_nr_hugepages\": hugepagesCount,\n\t\t}).Info(\"hugepages: Updating hugepages size\")\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"sysctl\",\n\t\t\t\"-w\",\n\t\t\tfmt.Sprintf(\"vm.nr_hugepages=%d\", hugepagesCount),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "image/constants.go",
    "content": "package image\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/tools/set\"\n)\n\nconst (\n\tUefi    = \"uefi\"\n\tBios    = \"bios\"\n\tUnknown = \"unknown\"\n\n\tLinux         = \"linux\"\n\tLinuxLegacy   = \"linux_legacy\"\n\tLinuxUnsigned = \"linux_unsigned\"\n\tBsd           = \"bsd\"\n\n\tRedHat = \"redhat\"\n\tFedora = \"fedora\"\n\tUbuntu = \"ubuntu\"\n\n\tAlmaLinux8    = \"almalinux8\"\n\tAlmaLinux9    = \"almalinux9\"\n\tAlmaLinux10   = \"almalinux10\"\n\tAlmaLinux11   = \"almalinux11\"\n\tAlmaLinux12   = \"almalinux12\"\n\tAlmaLinux13   = \"almalinux13\"\n\tAlmaLinux14   = \"almalinux14\"\n\tAlmaLinux15   = \"almalinux15\"\n\tAlmaLinux16   = \"almalinux16\"\n\tAlpineLinux   = \"alpinelinux\"\n\tArchLinux     = \"archlinux\"\n\tFedora42      = \"fedora42\"\n\tFedora43      = \"fedora43\"\n\tFedora44      = \"fedora44\"\n\tFedora45      = \"fedora45\"\n\tFedora46      = \"fedora46\"\n\tFedora47      = \"fedora47\"\n\tFedora48      = \"fedora48\"\n\tFedora49      = \"fedora49\"\n\tFedora50      = \"fedora50\"\n\tFedora51      = \"fedora51\"\n\tFedora52      = \"fedora52\"\n\tFedora53      = \"fedora53\"\n\tFedora54      = \"fedora54\"\n\tFedora55      = \"fedora55\"\n\tFedora56      = \"fedora56\"\n\tFedora57      = \"fedora57\"\n\tFedora58      = \"fedora58\"\n\tFedora59      = \"fedora59\"\n\tFedora60      = \"fedora60\"\n\tFedora61      = \"fedora61\"\n\tFedora62      = \"fedora62\"\n\tFreeBSD       = \"freebsd\"\n\tOracleLinux7  = \"oraclelinux7\"\n\tOracleLinux8  = \"oraclelinux8\"\n\tOracleLinux9  = \"oraclelinux9\"\n\tOracleLinux10 = \"oraclelinux10\"\n\tOracleLinux11 = \"oraclelinux11\"\n\tOracleLinux12 = \"oraclelinux12\"\n\tOracleLinux13 = \"oraclelinux13\"\n\tOracleLinux14 = \"oraclelinux14\"\n\tOracleLinux15 = \"oraclelinux15\"\n\tOracleLinux16 = \"oraclelinux16\"\n\tRockyLinux8   = \"rockylinux8\"\n\tRockyLinux9   = \"rockylinux9\"\n\tRockyLinux10  = \"rockylinux10\"\n\tRockyLinux11  = \"rockylinux11\"\n\tRockyLinux12  = \"rockylinux12\"\n\tRockyLinux13  = \"rockylinux13\"\n\tRockyLinux14  = \"rockylinux14\"\n\tRockyLinux15  = \"rockylinux15\"\n\tRockyLinux16  = \"rockylinux16\"\n\tUbuntu2404    = \"ubuntu2404\"\n\tUbuntu2604    = \"ubuntu2604\"\n\tUbuntu2804    = \"ubuntu2804\"\n\tUbuntu3004    = \"ubuntu3004\"\n\tUbuntu3204    = \"ubuntu3204\"\n\tUbuntu3404    = \"ubuntu3404\"\n\tUbuntu3604    = \"ubuntu3604\"\n\tUbuntu3804    = \"ubuntu3804\"\n\tUbuntu4004    = \"ubuntu4004\"\n\tUbuntu4204    = \"ubuntu4204\"\n\tUbuntu4404    = \"ubuntu4404\"\n)\n\nvar (\n\tGlobal   = bson.NilObjectID\n\tReleases = set.NewSet(\n\t\tAlmaLinux8,\n\t\tAlmaLinux9,\n\t\tAlmaLinux10,\n\t\tAlmaLinux11,\n\t\tAlmaLinux12,\n\t\tAlmaLinux13,\n\t\tAlmaLinux14,\n\t\tAlmaLinux15,\n\t\tAlmaLinux16,\n\t\tAlpineLinux,\n\t\tArchLinux,\n\t\tFedora42,\n\t\tFedora43,\n\t\tFedora44,\n\t\tFedora45,\n\t\tFedora46,\n\t\tFedora47,\n\t\tFedora48,\n\t\tFedora49,\n\t\tFedora50,\n\t\tFedora51,\n\t\tFedora52,\n\t\tFedora53,\n\t\tFedora54,\n\t\tFedora55,\n\t\tFedora56,\n\t\tFedora57,\n\t\tFedora58,\n\t\tFedora59,\n\t\tFedora60,\n\t\tFedora61,\n\t\tFedora62,\n\t\tFreeBSD,\n\t\tOracleLinux7,\n\t\tOracleLinux8,\n\t\tOracleLinux9,\n\t\tOracleLinux10,\n\t\tOracleLinux11,\n\t\tOracleLinux12,\n\t\tOracleLinux13,\n\t\tOracleLinux14,\n\t\tOracleLinux15,\n\t\tOracleLinux16,\n\t\tRockyLinux8,\n\t\tRockyLinux9,\n\t\tRockyLinux10,\n\t\tRockyLinux11,\n\t\tRockyLinux12,\n\t\tRockyLinux13,\n\t\tRockyLinux14,\n\t\tRockyLinux15,\n\t\tRockyLinux16,\n\t\tUbuntu2404,\n\t\tUbuntu2604,\n\t\tUbuntu2804,\n\t\tUbuntu3004,\n\t\tUbuntu3204,\n\t\tUbuntu3404,\n\t\tUbuntu3604,\n\t\tUbuntu3804,\n\t\tUbuntu4004,\n\t\tUbuntu4204,\n\t\tUbuntu4404,\n\t)\n\tValidSystemTypes = set.NewSet(\n\t\tLinux,\n\t\tLinuxLegacy,\n\t\tLinuxUnsigned,\n\t\tBsd,\n\t)\n\tValidSystemKinds = set.NewSet(\n\t\tAlpineLinux,\n\t\tArchLinux,\n\t\tRedHat,\n\t\tFedora,\n\t\tUbuntu,\n\t\tFreeBSD,\n\t)\n)\n"
  },
  {
    "path": "image/errortypes.go",
    "content": "package image\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype LostImageError struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "image/image.go",
    "content": "package image\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Image struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tDisk         bson.ObjectID `bson:\"disk\" json:\"disk\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tRelease      string        `bson:\"release\" json:\"release\"`\n\tBuild        string        `bson:\"build\" json:\"build\"`\n\tComment      string        `bson:\"comment\" json:\"comment\"`\n\tDeployment   bson.ObjectID `bson:\"deployment\" json:\"deployment\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tSigned       bool          `bson:\"signed\" json:\"signed\"`\n\tType         string        `bson:\"type\" json:\"type\"`\n\tSystemType   string        `bson:\"system_type\" json:\"system_type\"`\n\tSystemKind   string        `bson:\"system_kind\" json:\"system_kind\"`\n\tFirmware     string        `bson:\"firmware\" json:\"firmware\"`\n\tStorage      bson.ObjectID `bson:\"storage\" json:\"storage\"`\n\tKey          string        `bson:\"key\" json:\"key\"`\n\tLastModified time.Time     `bson:\"last_modified\" json:\"last_modified\"`\n\tStorageClass string        `bson:\"storage_class\" json:\"storage_class\"`\n\tHash         string        `bson:\"hash\" json:\"hash\"`\n\tEtag         string        `bson:\"etag\" json:\"etag\"`\n\tTags         []string      `bson:\"-\" json:\"tags\"`\n}\n\ntype Completion struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tRelease      string        `bson:\"release\" json:\"release\"`\n\tBuild        string        `bson:\"build\" json:\"build\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tDeployment   bson.ObjectID `bson:\"deployment\" json:\"deployment\"`\n\tType         string        `bson:\"type\" json:\"type\"`\n\tFirmware     string        `bson:\"firmware\" json:\"firmware\"`\n\tKey          string        `bson:\"key\" json:\"key\"`\n\tStorage      bson.ObjectID `bson:\"storage\" json:\"storage\"`\n\tTags         []string      `bson:\"-\" json:\"tags\"`\n}\n\nfunc (i *Image) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\ti.Name = utils.FilterName(i.Name)\n\n\tif i.Firmware == \"\" {\n\t\ti.Firmware = Uefi\n\t}\n\n\tif i.SystemType == \"\" {\n\t\ti.SystemType = Linux\n\t}\n\tif !ValidSystemTypes.Contains(i.SystemType) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_system_type\",\n\t\t\tMessage: \"Image system type invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.SystemKind != \"\" && !ValidSystemKinds.Contains(i.SystemKind) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_system_kind\",\n\t\t\tMessage: \"Image system kind invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (i *Image) Parse() {\n\tif i.Name == \"\" {\n\t\ti.Name = i.Key\n\t}\n\n\tif i.Signed {\n\t\ti.Name, i.Release, i.Build = ParseImageName(i.Key)\n\t}\n}\n\nfunc (i *Image) GetSystemType() string {\n\tif i.SystemType != \"\" {\n\t\treturn i.SystemType\n\t}\n\n\tname := strings.ToLower(i.Name)\n\tif strings.Contains(name, \"bsd\") {\n\t\treturn Bsd\n\t}\n\n\tif strings.Contains(name, \"alpinelinux\") {\n\t\treturn LinuxUnsigned\n\t}\n\n\tif strings.Contains(name, \"archlinux\") {\n\t\treturn LinuxUnsigned\n\t}\n\n\tif strings.Contains(name, \"oraclelinux7\") {\n\t\treturn LinuxLegacy\n\t}\n\n\tif strings.Contains(name, \"redhat7\") {\n\t\treturn LinuxLegacy\n\t}\n\n\treturn Linux\n}\n\nfunc (i *Image) GetSystemKind() string {\n\tif i.SystemKind != \"\" {\n\t\treturn i.SystemKind\n\t}\n\n\tname := strings.ToLower(i.Name)\n\tif strings.Contains(name, \"freebsd\") {\n\t\treturn FreeBSD\n\t}\n\n\tif strings.Contains(name, \"alpinelinux\") {\n\t\treturn AlpineLinux\n\t}\n\n\tif strings.Contains(name, \"archlinux\") {\n\t\treturn ArchLinux\n\t}\n\n\tif strings.Contains(name, \"ubuntu\") {\n\t\treturn Ubuntu\n\t}\n\n\tif strings.Contains(name, \"fedora\") {\n\t\treturn Fedora\n\t}\n\n\tif strings.Contains(name, \"redhat\") ||\n\t\tstrings.Contains(name, \"almalinux\") ||\n\t\tstrings.Contains(name, \"oraclelinux\") ||\n\t\tstrings.Contains(name, \"rockylinux\") {\n\n\t\treturn RedHat\n\t}\n\n\treturn \"\"\n}\n\nfunc (i *Image) Json() {\n\ti.Parse()\n}\n\nfunc (i *Image) Commit(db *database.Database) (err error) {\n\tcoll := db.Images()\n\n\terr = coll.Commit(i.Id, i)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (i *Image) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Images()\n\n\terr = coll.CommitFields(i.Id, i, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (i *Image) Insert(db *database.Database) (err error) {\n\tcoll := db.Images()\n\n\t_, err = coll.InsertOne(db, i)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (i *Image) Upsert(db *database.Database) (err error) {\n\tcoll := db.Images()\n\n\tfields := bson.M{\n\t\t\"name\":          i.Name,\n\t\t\"deployment\":    i.Deployment,\n\t\t\"organization\":  i.Organization,\n\t\t\"disk\":          i.Disk,\n\t\t\"signed\":        i.Signed,\n\t\t\"type\":          i.Type,\n\t\t\"system_type\":   i.SystemType,\n\t\t\"system_kind\":   i.SystemKind,\n\t\t\"firmware\":      i.Firmware,\n\t\t\"storage\":       i.Storage,\n\t\t\"key\":           i.Key,\n\t\t\"last_modified\": i.LastModified,\n\t\t\"storage_class\": i.StorageClass,\n\t\t\"hash\":          i.Hash,\n\t\t\"etag\":          i.Etag,\n\t}\n\n\tresp, err := coll.UpdateOne(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"storage\": i.Storage,\n\t\t\t\"key\":     i.Key,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$set\": fields,\n\t\t},\n\t\toptions.UpdateOne().SetUpsert(true),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif resp.UpsertedID != nil {\n\t\ti.Id = resp.UpsertedID.(bson.ObjectID)\n\t}\n\n\treturn\n}\n\nfunc (i *Image) Sync(db *database.Database) (err error) {\n\tcoll := db.Images()\n\n\ti.Parse()\n\ti.SystemType = i.GetSystemType()\n\ti.SystemKind = i.GetSystemKind()\n\n\tif strings.HasPrefix(i.Key, \"backup/\") ||\n\t\tstrings.HasPrefix(i.Key, \"snapshot/\") {\n\n\t\tresp, e := coll.UpdateOne(\n\t\t\tdb,\n\t\t\t&bson.M{\n\t\t\t\t\"storage\": i.Storage,\n\t\t\t\t\"key\":     i.Key,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$set\": &bson.M{\n\t\t\t\t\t\"organization\":  bson.NilObjectID,\n\t\t\t\t\t\"release\":       i.Release,\n\t\t\t\t\t\"build\":         i.Build,\n\t\t\t\t\t\"storage\":       i.Storage,\n\t\t\t\t\t\"key\":           i.Key,\n\t\t\t\t\t\"signed\":        i.Signed,\n\t\t\t\t\t\"type\":          i.Type,\n\t\t\t\t\t\"system_type\":   i.SystemType,\n\t\t\t\t\t\"system_kind\":   i.SystemKind,\n\t\t\t\t\t\"firmware\":      i.Firmware,\n\t\t\t\t\t\"etag\":          i.Etag,\n\t\t\t\t\t\"last_modified\": i.LastModified,\n\t\t\t\t\t\"storage_class\": i.StorageClass,\n\t\t\t\t},\n\t\t\t\t\"$setOnInsert\": &bson.M{\n\t\t\t\t\t\"name\":       i.Name,\n\t\t\t\t\t\"disk\":       bson.NilObjectID,\n\t\t\t\t\t\"deployment\": bson.NilObjectID,\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t\tif e != nil {\n\t\t\terr = database.ParseError(e)\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = &LostImageError{\n\t\t\t\t\terrors.Wrap(err, \"image: Lost image\"),\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif resp.UpsertedID != nil {\n\t\t\ti.Id = resp.UpsertedID.(bson.ObjectID)\n\t\t}\n\t} else {\n\t\tresp, e := coll.UpdateOne(\n\t\t\tdb,\n\t\t\t&bson.M{\n\t\t\t\t\"storage\": i.Storage,\n\t\t\t\t\"key\":     i.Key,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$set\": &bson.M{\n\t\t\t\t\t\"organization\":  bson.NilObjectID,\n\t\t\t\t\t\"name\":          i.Name,\n\t\t\t\t\t\"release\":       i.Release,\n\t\t\t\t\t\"build\":         i.Build,\n\t\t\t\t\t\"storage\":       i.Storage,\n\t\t\t\t\t\"key\":           i.Key,\n\t\t\t\t\t\"signed\":        i.Signed,\n\t\t\t\t\t\"type\":          i.Type,\n\t\t\t\t\t\"system_type\":   i.SystemType,\n\t\t\t\t\t\"system_kind\":   i.SystemKind,\n\t\t\t\t\t\"firmware\":      i.Firmware,\n\t\t\t\t\t\"etag\":          i.Etag,\n\t\t\t\t\t\"last_modified\": i.LastModified,\n\t\t\t\t},\n\t\t\t\t\"$setOnInsert\": &bson.M{\n\t\t\t\t\t\"disk\":       bson.NilObjectID,\n\t\t\t\t\t\"deployment\": bson.NilObjectID,\n\t\t\t\t},\n\t\t\t},\n\t\t\toptions.UpdateOne().SetUpsert(true),\n\t\t)\n\t\tif e != nil {\n\t\t\terr = database.ParseError(e)\n\t\t\treturn\n\t\t}\n\n\t\tif resp.UpsertedID != nil {\n\t\t\ti.Id = resp.UpsertedID.(bson.ObjectID)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (i *Image) Remove(db *database.Database) (err error) {\n\tif !i.Deployment.IsZero() {\n\t\terr = deployment.Remove(db, i.Deployment)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = Remove(db, i.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "image/sort.go",
    "content": "package image\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype ImagesSort []*Image\n\nfunc (x ImagesSort) Len() int {\n\treturn len(x)\n}\n\nfunc (x ImagesSort) Swap(i, j int) {\n\tx[i], x[j] = x[j], x[i]\n}\n\nfunc (x ImagesSort) Less(i, j int) bool {\n\treturn utils.NaturalCompare(x[i].Name, x[j].Name) < 0\n}\n\ntype CompletionsSort []*Completion\n\nfunc (x CompletionsSort) Len() int {\n\treturn len(x)\n}\n\nfunc (x CompletionsSort) Swap(i, j int) {\n\tx[i], x[j] = x[j], x[i]\n}\n\nfunc (x CompletionsSort) Less(i, j int) bool {\n\treturn utils.NaturalCompare(x[i].Name, x[j].Name) < 0\n}\n"
  },
  {
    "path": "image/utils.go",
    "content": "package image\n\nimport (\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\tminio \"github.com/minio/minio-go/v7\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tetagReg  = regexp.MustCompile(\"[^a-zA-Z0-9]+\")\n\tdistroRe = regexp.MustCompile(`^([a-z]+)([0-9]*)`)\n\tdateRe   = regexp.MustCompile(`_(\\d{2})(\\d{2})(\\d{2})?$`)\n)\n\nfunc GetEtag(info minio.ObjectInfo) string {\n\tetag := info.ETag\n\tif etag == \"\" {\n\t\tmodifiedHash := md5.New()\n\t\tmodifiedHash.Write(\n\t\t\t[]byte(info.LastModified.Format(time.RFC3339)))\n\t\tetag = fmt.Sprintf(\"%x\", modifiedHash.Sum(nil))\n\t}\n\treturn etagReg.ReplaceAllString(etag, \"\")\n}\n\nfunc ParseImageName(key string) (name, release, build string) {\n\tbaseName := strings.TrimSuffix(key, filepath.Ext(key))\n\n\tdateMatch := dateRe.FindStringSubmatch(baseName)\n\tif len(dateMatch) != 3 && len(dateMatch) != 4 {\n\t\tname = key\n\t\treturn\n\t}\n\tyearStr, monthStr := dateMatch[1], dateMatch[2]\n\tbuild = yearStr + monthStr\n\tif len(dateMatch) == 4 {\n\t\tbuild += dateMatch[3]\n\t}\n\n\tbase := strings.TrimSuffix(baseName, dateMatch[0])\n\ttokens := strings.Split(base, \"_\")\n\tif len(tokens) == 0 {\n\t\tname = key\n\t\treturn\n\t}\n\n\tdistroMatch := distroRe.FindStringSubmatch(tokens[0])\n\tif len(distroMatch) < 2 {\n\t\tname = key\n\t\treturn\n\t}\n\tdistro := distroMatch[1]\n\tversion := \"\"\n\tif len(distroMatch) >= 3 {\n\t\tversion = distroMatch[2]\n\t}\n\n\tif version == \"\" {\n\t\tname = fmt.Sprintf(\"%s-%s%s\", distro, yearStr, monthStr)\n\t} else {\n\t\tname = fmt.Sprintf(\"%s%s-%s%s\", distro, version, yearStr, monthStr)\n\t}\n\n\tif Releases.Contains(distro + version) {\n\t\trelease = distro + version\n\t}\n\n\treturn\n}\n\nfunc Get(db *database.Database, imgId bson.ObjectID) (\n\timg *Image, err error) {\n\n\tcoll := db.Images()\n\timg = &Image{}\n\n\terr = coll.FindOneId(imgId, img)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetKey(db *database.Database, storeId bson.ObjectID, key string) (\n\timg *Image, err error) {\n\n\tcoll := db.Images()\n\timg = &Image{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"storage\": storeId,\n\t\t\"key\":     key,\n\t}).Decode(img)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, imgId bson.ObjectID) (\n\timg *Image, err error) {\n\n\tcoll := db.Images()\n\timg = &Image{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          imgId,\n\t\t\"organization\": orgId,\n\t}).Decode(img)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrgPublic(db *database.Database, orgId, imgId bson.ObjectID) (\n\timg *Image, err error) {\n\n\tcoll := db.Images()\n\timg = &Image{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          imgId,\n\t\t\"organization\": Global,\n\t}).Decode(img)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (img *Image, err error) {\n\tcoll := db.Images()\n\timg = &Image{}\n\n\terr = coll.FindOne(db, query).Decode(img)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Distinct(db *database.Database, storeId bson.ObjectID) (\n\tkeys []string, err error) {\n\n\tcoll := db.Images()\n\tkeys = []string{}\n\n\terr = coll.Distinct(db, \"key\", &bson.M{\n\t\t\"storage\": storeId,\n\t}).Decode(&keys)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExistsOrg(db *database.Database, orgId, imgId bson.ObjectID) (\n\texists bool, err error) {\n\n\tcoll := db.Images()\n\n\tn, err := coll.CountDocuments(db, &bson.M{\n\t\t\"_id\":          imgId,\n\t\t\"organization\": Global,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif n > 0 {\n\t\texists = true\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\timgs []*Image, err error) {\n\n\tcoll := db.Images()\n\timgs = []*Image{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\timg := &Image{}\n\t\terr = cursor.Decode(img)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\timgs = append(imgs, img)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllCompletion(db *database.Database, query *bson.M) (\n\timgs []*Completion, err error) {\n\n\tcoll := db.Images()\n\timgs = []*Completion{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetProjection(bson.D{\n\t\t\t\t{\"_id\", 1},\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"release\", 1},\n\t\t\t\t{\"build\", 1},\n\t\t\t\t{\"organization\", 1},\n\t\t\t\t{\"deployment\", 1},\n\t\t\t\t{\"type\", 1},\n\t\t\t\t{\"firmware\", 1},\n\t\t\t\t{\"key\", 1},\n\t\t\t\t{\"storage\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\timg := &Completion{}\n\t\terr = cursor.Decode(img)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\timgs = append(imgs, img)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M, page, pageCount int64) (\n\timgs []*Image, count int64, err error) {\n\n\tcoll := db.Images()\n\timgs = []*Image{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"key\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\timg := &Image{}\n\t\terr = cursor.Decode(img)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\timgs = append(imgs, img)\n\t\timg = &Image{}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNames(db *database.Database, query *bson.M) (\n\timages []*Image, err error) {\n\n\tcoll := db.Images()\n\timages = []*Image{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"key\", 1}}).\n\t\t\tSetProjection(bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"key\", 1},\n\t\t\t\t{\"signed\", 1},\n\t\t\t\t{\"firmware\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\timg := &Image{}\n\t\terr = cursor.Decode(img)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\timg.Json()\n\n\t\timages = append(images, img)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllKeys(db *database.Database) (keys set.Set, err error) {\n\tcoll := db.Images()\n\tkeys = set.NewSet()\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{},\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"key\", 1}}).\n\t\t\tSetProjection(bson.D{\n\t\t\t\t{\"_id\", 1},\n\t\t\t\t{\"etag\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\timg := &Image{}\n\t\terr = cursor.Decode(img)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tkeys.Add(fmt.Sprintf(\"%s-%s\", img.Id.Hex(), img.Etag))\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, imgId bson.ObjectID) (err error) {\n\tcoll := db.Images()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": imgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "imds/config.go",
    "content": "package imds\n\nimport (\n\t\"sync\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\nvar (\n\tcurConfs     = map[bson.ObjectID]*types.Config{}\n\tcurConfsLock = sync.Mutex{}\n)\n\nfunc BuildConfig(inst *instance.Instance, virt *vm.VirtualMachine,\n\tunt *unit.Unit, spc *spec.Spec, vc *vpc.Vpc, subnet *vpc.Subnet,\n\tpods []*pod.Pod, podUnitsMap map[bson.ObjectID][]*unit.Unit,\n\tdeployments map[bson.ObjectID]*deployment.Deployment,\n\tsecrs []*secret.Secret, certs []*certificate.Certificate,\n\tdomains []*types.Domain) (conf *types.Config, err error) {\n\n\tconf = &types.Config{\n\t\tImdsHostSecret: virt.ImdsHostSecret,\n\t\tClientIps:      inst.PrivateIps,\n\t\tNode:           types.NewNode(node.Self),\n\t\tInstance:       types.NewInstance(inst),\n\t\tVpc:            types.NewVpc(vc),\n\t\tSubnet:         types.NewSubnet(subnet),\n\t\tPods:           types.NewPods(pods, podUnitsMap, deployments),\n\t\tSecrets:        types.NewSecrets(secrs),\n\t\tCertificates:   types.NewCertificates(certs),\n\t\tDomains:        domains,\n\t}\n\n\tif spc != nil {\n\t\tconf.Spec = spc.Id\n\t\tconf.SpecData = spc.Data\n\t\tconf.Journals = types.NewJournals(spc)\n\t}\n\n\treturn\n}\n\nfunc SetConfigs(cnfs map[bson.ObjectID]*types.Config) {\n\tcurConfsLock.Lock()\n\tcurConfs = cnfs\n\tcurConfsLock.Unlock()\n}\n\nfunc GetConfigs() (\n\tcnfs map[bson.ObjectID]*types.Config) {\n\n\tcurConfsLock.Lock()\n\tcnfs = curConfs\n\tcurConfsLock.Unlock()\n\treturn\n}\n"
  },
  {
    "path": "imds/imds.go",
    "content": "package imds\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/advisory\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/utils\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/journal\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/store\"\n\t\"github.com/pritunl/pritunl-cloud/telemetry\"\n\tpritunlutils \"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/tools/errors\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\thashes     = map[bson.ObjectID]uint32{}\n\thashesLock = sync.Mutex{}\n\tcounter    = atomic.Uint64{}\n)\n\nconst (\n\tcounterMax = 2000000000\n)\n\nfunc mergeUpdateDetails(db *database.Database, instId bson.ObjectID,\n\tupdates []*telemetry.Update) (err error) {\n\n\tif len(updates) == 0 {\n\t\treturn\n\t}\n\n\tcoll := db.Instances()\n\tinst := &instance.Instance{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\": instId,\n\t}, database.FindOneProject(\"guest.updates\")).Decode(inst)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\terr = database.IgnoreNotFoundError(err)\n\t\treturn\n\t}\n\n\tif inst.Guest == nil {\n\t\treturn\n\t}\n\n\tdetailsMap := map[string][]*advisory.Advisory{}\n\tfor _, upd := range inst.Guest.Updates {\n\t\tif upd.Advisory != \"\" && len(upd.Details) > 0 {\n\t\t\tdetailsMap[upd.Advisory] = upd.Details\n\t\t}\n\t}\n\n\tfor _, upd := range updates {\n\t\tif details, ok := detailsMap[upd.Advisory]; ok {\n\t\t\tupd.Details = details\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Sync(db *database.Database, namespace string,\n\tinstId, deplyId bson.ObjectID, conf *types.Config) (err error) {\n\n\tsockPath := paths.GetImdsSockPath(instId)\n\n\texists, err := utils.Exists(sockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !exists {\n\t\treturn\n\t}\n\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(_ context.Context,\n\t\t\t\t_, _ string) (net.Conn, error) {\n\n\t\t\t\treturn net.Dial(\"unix\", sockPath)\n\t\t\t},\n\t\t},\n\t\tTimeout: 6 * time.Second,\n\t}\n\n\tvar body io.Reader\n\n\thashesLock.Lock()\n\tcurHash := hashes[instId]\n\thashesLock.Unlock()\n\n\tif conf != nil && curHash != conf.Hash {\n\t\treqDataBuf := &bytes.Buffer{}\n\t\terr = json.NewEncoder(reqDataBuf).Encode(conf)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"agent: Failed to parse request data\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tbody = reqDataBuf\n\t}\n\n\treq, err := http.NewRequest(\"PUT\", \"http://unix/sync\", body)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"agent: Failed to create imds request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"pritunl-imds\")\n\treq.Header.Set(\"Auth-Token\", conf.ImdsHostSecret)\n\tif body != nil {\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t}\n\n\tresp, e := client.Do(req)\n\tif e != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(e, \"agent: Imds request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tbody := \"\"\n\t\tdata, _ := ioutil.ReadAll(resp.Body)\n\t\tif data != nil {\n\t\t\tbody = string(data)\n\t\t}\n\n\t\terrData := &errortypes.ErrorData{}\n\t\terr = json.Unmarshal(data, errData)\n\t\tif err != nil || errData.Error == \"\" {\n\t\t\terrData = nil\n\t\t}\n\n\t\tif errData != nil && errData.Message != \"\" {\n\t\t\tbody = errData.Message\n\t\t}\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"agent: Imds host sync error %d - %s\",\n\t\t\t\tresp.StatusCode, body),\n\t\t}\n\t\treturn\n\t}\n\n\tste := &types.State{}\n\terr = json.NewDecoder(resp.Body).Decode(ste)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"agent: Failed to decode imds host sync resp\"),\n\t\t}\n\t\treturn\n\t}\n\n\thashesLock.Lock()\n\thashes[instId] = ste.Hash\n\thashesLock.Unlock()\n\n\tif ste.Status != \"\" {\n\t\tcoll := db.Instances()\n\n\t\tdata := bson.M{\n\t\t\t\"guest.status\":    ste.Status,\n\t\t\t\"guest.timestamp\": time.Now(),\n\t\t\t\"guest.heartbeat\": ste.Timestamp,\n\t\t\t\"guest.memory\":    ste.Memory,\n\t\t\t\"guest.hugepages\": ste.HugePages,\n\t\t\t\"guest.load1\":     ste.Load1,\n\t\t\t\"guest.load5\":     ste.Load5,\n\t\t\t\"guest.load15\":    ste.Load15,\n\t\t}\n\n\t\tif ste.DhcpIp != nil {\n\t\t\tdata[\"dhcp_ip\"] = ste.DhcpIp.String()\n\t\t}\n\t\tif ste.DhcpIp6 != nil {\n\t\t\tdata[\"dhcp_ip6\"] = ste.DhcpIp6.String()\n\t\t}\n\n\t\tif ste.Updates != nil {\n\t\t\terr = mergeUpdateDetails(db, instId, ste.Updates)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdata[\"guest.updates\"] = ste.Updates\n\t\t}\n\n\t\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\t\"_id\": instId,\n\t\t}, bson.M{\n\t\t\t\"$set\": data,\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvar kind int32\n\t\tvar resource bson.ObjectID\n\t\tif !deplyId.IsZero() {\n\t\t\tkind = journal.DeploymentAgent\n\t\t\tresource = deplyId\n\t\t} else {\n\t\t\tkind = journal.InstanceAgent\n\t\t\tresource = instId\n\t\t}\n\n\t\tfor _, entry := range ste.Output {\n\t\t\tif entry.Level < 1 || entry.Level > 9 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(entry.Message) > 100000 {\n\t\t\t\tentry.Message = entry.Message[:100000]\n\t\t\t}\n\n\t\t\tjrnl := &journal.Journal{\n\t\t\t\tResource:  resource,\n\t\t\t\tKind:      kind,\n\t\t\t\tLevel:     entry.Level,\n\t\t\t\tTimestamp: entry.Timestamp,\n\t\t\t\tCount:     int32(counter.Add(1) % counterMax),\n\t\t\t\tMessage:   entry.Message,\n\t\t\t}\n\n\t\t\terr = jrnl.Insert(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif ste.Journals != nil {\n\t\t\tindexes := map[string]int32{}\n\t\t\tfor _, jrnl := range conf.Journals {\n\t\t\t\tindexes[jrnl.Key] = jrnl.Index\n\t\t\t}\n\n\t\t\tfor key, output := range ste.Journals {\n\t\t\t\tindex := indexes[key]\n\t\t\t\tif index == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfor _, entry := range output {\n\t\t\t\t\tif entry.Level < 1 || entry.Level > 9 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif len(entry.Message) > 100000 {\n\t\t\t\t\t\tentry.Message = entry.Message[:100000]\n\t\t\t\t\t}\n\n\t\t\t\t\tjrnl := &journal.Journal{\n\t\t\t\t\t\tResource:  resource,\n\t\t\t\t\t\tKind:      index,\n\t\t\t\t\t\tLevel:     entry.Level,\n\t\t\t\t\t\tTimestamp: entry.Timestamp,\n\t\t\t\t\t\tCount:     int32(counter.Add(1) % counterMax),\n\t\t\t\t\t\tMessage:   entry.Message,\n\t\t\t\t\t}\n\n\t\t\t\t\terr = jrnl.Insert(db)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcurIp := \"\"\n\t\tcurIp6 := \"\"\n\t\tcurIpPrefix := \"\"\n\t\tcurIpPrefix6 := \"\"\n\t\tcurIpCached := false\n\t\tcurIpCached6 := false\n\t\tnewIp := \"\"\n\t\tnewIp6 := \"\"\n\t\tnewIpPrefix := \"\"\n\t\tnewIpPrefix6 := \"\"\n\t\tclearIpCache := false\n\n\t\tif ste.DhcpIp != nil {\n\t\t\taddrStore, ok := store.GetAddress(instId)\n\t\t\tif ok {\n\t\t\t\tcurIpCached = true\n\t\t\t\tcurIp = addrStore.Addr\n\t\t\t\tif ste.DhcpIface == ste.DhcpIface6 {\n\t\t\t\t\tcurIpCached6 = true\n\t\t\t\t\tcurIp6 = addrStore.Addr6\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\taddress, address6, e := iproute.AddressGetIfaceMod(\n\t\t\t\t\tnamespace, ste.DhcpIface)\n\t\t\t\tif e == nil && address != nil {\n\t\t\t\t\tcurIpCached = false\n\t\t\t\t\tcurIp = address.Local\n\t\t\t\t\tcurIpPrefix = fmt.Sprintf(\n\t\t\t\t\t\t\"%s/%d\", address.Local, address.Prefix)\n\t\t\t\t\tif ste.DhcpIface == ste.DhcpIface6 && address6 != nil {\n\t\t\t\t\t\tcurIpCached6 = false\n\t\t\t\t\t\tcurIp6 = address6.Local\n\t\t\t\t\t\tcurIpPrefix6 = fmt.Sprintf(\n\t\t\t\t\t\t\t\"%s/%d\", address6.Local, address6.Prefix)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tnewIpPrefix = ste.DhcpIp.String()\n\t\t\tnewIp = strings.Split(newIpPrefix, \"/\")[0]\n\t\t}\n\n\t\tif ste.DhcpIp6 != nil {\n\t\t\tif curIp6 == \"\" {\n\t\t\t\taddrStore, ok := store.GetAddress(instId)\n\t\t\t\tif ok {\n\t\t\t\t\tcurIpCached6 = true\n\t\t\t\t\tcurIp6 = addrStore.Addr6\n\t\t\t\t} else {\n\t\t\t\t\t_, address6, e := iproute.AddressGetIfaceMod(\n\t\t\t\t\t\tnamespace, ste.DhcpIface6)\n\t\t\t\t\tif e == nil && address6 != nil {\n\t\t\t\t\t\tcurIpCached6 = false\n\t\t\t\t\t\tcurIp6 = address6.Local\n\t\t\t\t\t\tcurIpPrefix6 = fmt.Sprintf(\n\t\t\t\t\t\t\t\"%s/%d\", address6.Local, address6.Prefix)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tnewIpPrefix6 = ste.DhcpIp6.String()\n\t\t\tnewIp6 = strings.Split(newIpPrefix6, \"/\")[0]\n\t\t}\n\n\t\tif newIp != \"\" && newIp != curIp {\n\t\t\tif curIpCached {\n\t\t\t\taddress, address6, e := iproute.AddressGetIfaceMod(\n\t\t\t\t\tnamespace, ste.DhcpIface)\n\t\t\t\tif e == nil && address != nil {\n\t\t\t\t\tcurIpCached = false\n\t\t\t\t\tcurIp = address.Local\n\t\t\t\t\tcurIpPrefix = fmt.Sprintf(\n\t\t\t\t\t\t\"%s/%d\", address.Local, address.Prefix)\n\t\t\t\t\tif ste.DhcpIface == ste.DhcpIface6 && address6 != nil {\n\t\t\t\t\t\tcurIpCached6 = false\n\t\t\t\t\t\tcurIp6 = address6.Local\n\t\t\t\t\t\tcurIpPrefix6 = fmt.Sprintf(\n\t\t\t\t\t\t\t\"%s/%d\", address6.Local, address6.Prefix)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif newIp != curIp {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance\":  instId.Hex(),\n\t\t\t\t\t\"namespace\": namespace,\n\t\t\t\t\t\"cur_ip\":    curIpPrefix,\n\t\t\t\t\t\"new_ip\":    newIpPrefix,\n\t\t\t\t}).Info(\"imds: Updating instance DHCP IPv4 address\")\n\n\t\t\t\tif curIpPrefix != \"\" {\n\t\t\t\t\t_, err = pritunlutils.ExecCombinedOutputLogged(\n\t\t\t\t\t\t[]string{\"File exists\", \"Cannot assign\"},\n\t\t\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\t\t\"del\", curIpPrefix,\n\t\t\t\t\t\t\"dev\", ste.DhcpIface,\n\t\t\t\t\t)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_, err = pritunlutils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"File exists\", \"already assigned\"},\n\t\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\t\"add\", newIpPrefix,\n\t\t\t\t\t\"dev\", ste.DhcpIface,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif ste.DhcpGateway != nil {\n\t\t\t\t\t_, err = pritunlutils.ExecCombinedOutputLogged(\n\t\t\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\t\t\"ip\", \"route\",\n\t\t\t\t\t\t\"add\", \"default\",\n\t\t\t\t\t\t\"via\", ste.DhcpGateway.String(),\n\t\t\t\t\t\t\"dev\", ste.DhcpIface,\n\t\t\t\t\t)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tclearIpCache = true\n\t\t\t}\n\t\t}\n\n\t\tif newIp6 != \"\" && newIp6 != curIp6 {\n\t\t\tif curIpCached6 {\n\t\t\t\t_, address6, e := iproute.AddressGetIfaceMod(\n\t\t\t\t\tnamespace, ste.DhcpIface6)\n\t\t\t\tif e == nil && address6 != nil {\n\t\t\t\t\tcurIpCached6 = false\n\t\t\t\t\tcurIp6 = address6.Local\n\t\t\t\t\tcurIpPrefix6 = fmt.Sprintf(\n\t\t\t\t\t\t\"%s/%d\", address6.Local, address6.Prefix)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif newIp6 != curIp6 {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance\":  instId.Hex(),\n\t\t\t\t\t\"namespace\": namespace,\n\t\t\t\t\t\"cur_ip6\":   curIpPrefix6,\n\t\t\t\t\t\"new_ip6\":   newIpPrefix6,\n\t\t\t\t}).Info(\"imds: Updating instance DHCP IPv6 address\")\n\n\t\t\t\tif curIpPrefix6 != \"\" {\n\t\t\t\t\t_, err = pritunlutils.ExecCombinedOutputLogged(\n\t\t\t\t\t\t[]string{\"File exists\", \"Cannot assign\"},\n\t\t\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\t\t\"del\", curIpPrefix6,\n\t\t\t\t\t\t\"dev\", ste.DhcpIface6,\n\t\t\t\t\t)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_, err = pritunlutils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"File exists\", \"already assigned\"},\n\t\t\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\t\"add\", newIpPrefix6,\n\t\t\t\t\t\"dev\", ste.DhcpIface6,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tclearIpCache = true\n\t\t\t}\n\t\t}\n\n\t\tif clearIpCache {\n\t\t\tstore.RemAddress(instId)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Pull(db *database.Database, instId, deplyId bson.ObjectID,\n\timdsHostSecret string, journals []*types.Journal) (err error) {\n\n\tsockPath := paths.GetImdsSockPath(instId)\n\n\texists, err := utils.Exists(sockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !exists {\n\t\treturn\n\t}\n\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(_ context.Context,\n\t\t\t\t_, _ string) (net.Conn, error) {\n\n\t\t\t\treturn net.Dial(\"unix\", sockPath)\n\t\t\t},\n\t\t},\n\t\tTimeout: 6 * time.Second,\n\t}\n\n\treq, err := http.NewRequest(\"GET\", \"http://unix/sync\", nil)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"agent: Failed to create imds request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"pritunl-imds\")\n\treq.Header.Set(\"Auth-Token\", imdsHostSecret)\n\n\tresp, e := client.Do(req)\n\tif e != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(e, \"agent: Imds request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tbody := \"\"\n\t\tdata, _ := ioutil.ReadAll(resp.Body)\n\t\tif data != nil {\n\t\t\tbody = string(data)\n\t\t}\n\n\t\terrData := &errortypes.ErrorData{}\n\t\terr = json.Unmarshal(data, errData)\n\t\tif err != nil || errData.Error == \"\" {\n\t\t\terrData = nil\n\t\t}\n\n\t\tif errData != nil && errData.Message != \"\" {\n\t\t\tbody = errData.Message\n\t\t}\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"agent: Imds host sync error %d - %s\",\n\t\t\t\tresp.StatusCode, body),\n\t\t}\n\t\treturn\n\t}\n\n\tste := &types.State{}\n\terr = json.NewDecoder(resp.Body).Decode(ste)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"agent: Failed to decode imds host sync resp\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif ste.Status != \"\" {\n\t\tcoll := db.Instances()\n\n\t\tdata := bson.M{\n\t\t\t\"guest.status\":    ste.Status,\n\t\t\t\"guest.timestamp\": time.Now(),\n\t\t\t\"guest.heartbeat\": ste.Timestamp,\n\t\t\t\"guest.memory\":    ste.Memory,\n\t\t\t\"guest.hugepages\": ste.HugePages,\n\t\t\t\"guest.load1\":     ste.Load1,\n\t\t\t\"guest.load5\":     ste.Load5,\n\t\t\t\"guest.load15\":    ste.Load15,\n\t\t}\n\n\t\tif ste.DhcpIp != nil {\n\t\t\tdata[\"dhcp_ip\"] = ste.DhcpIp.String()\n\t\t}\n\t\tif ste.DhcpIp6 != nil {\n\t\t\tdata[\"dhcp_ip6\"] = ste.DhcpIp6.String()\n\t\t}\n\n\t\tif ste.Updates != nil {\n\t\t\terr = mergeUpdateDetails(db, instId, ste.Updates)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdata[\"guest.updates\"] = ste.Updates\n\t\t}\n\n\t\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\t\"_id\": instId,\n\t\t}, bson.M{\n\t\t\t\"$set\": data,\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvar kind int32\n\t\tvar resource bson.ObjectID\n\t\tif !deplyId.IsZero() {\n\t\t\tkind = journal.DeploymentAgent\n\t\t\tresource = deplyId\n\t\t} else {\n\t\t\tkind = journal.InstanceAgent\n\t\t\tresource = instId\n\t\t}\n\n\t\tfor _, entry := range ste.Output {\n\t\t\tif entry.Level < 1 || entry.Level > 9 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(entry.Message) > 100000 {\n\t\t\t\tentry.Message = entry.Message[:100000]\n\t\t\t}\n\n\t\t\tjrnl := &journal.Journal{\n\t\t\t\tResource:  resource,\n\t\t\t\tKind:      kind,\n\t\t\t\tLevel:     entry.Level,\n\t\t\t\tTimestamp: entry.Timestamp,\n\t\t\t\tCount:     int32(counter.Add(1) % counterMax),\n\t\t\t\tMessage:   entry.Message,\n\t\t\t}\n\n\t\t\terr = jrnl.Insert(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif ste.Journals != nil {\n\t\t\tindexes := map[string]int32{}\n\t\t\tfor _, jrnl := range journals {\n\t\t\t\tindexes[jrnl.Key] = jrnl.Index\n\t\t\t}\n\n\t\t\tfor key, output := range ste.Journals {\n\t\t\t\tindex := indexes[key]\n\t\t\t\tif index == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfor _, entry := range output {\n\t\t\t\t\tif entry.Level < 1 || entry.Level > 9 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif len(entry.Message) > 100000 {\n\t\t\t\t\t\tentry.Message = entry.Message[:100000]\n\t\t\t\t\t}\n\n\t\t\t\t\tjrnl := &journal.Journal{\n\t\t\t\t\t\tResource:  resource,\n\t\t\t\t\t\tKind:      index,\n\t\t\t\t\t\tLevel:     entry.Level,\n\t\t\t\t\t\tTimestamp: entry.Timestamp,\n\t\t\t\t\t\tCount:     int32(counter.Add(1) % counterMax),\n\t\t\t\t\t\tMessage:   entry.Message,\n\t\t\t\t\t}\n\n\t\t\t\t\terr = jrnl.Insert(db)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc State(db *database.Database, instId bson.ObjectID,\n\timdsHostSecret string) (ste *types.State, err error) {\n\n\tsockPath := paths.GetImdsSockPath(instId)\n\n\texists, err := utils.Exists(sockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !exists {\n\t\treturn\n\t}\n\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(_ context.Context,\n\t\t\t\t_, _ string) (net.Conn, error) {\n\n\t\t\t\treturn net.Dial(\"unix\", sockPath)\n\t\t\t},\n\t\t},\n\t\tTimeout: 6 * time.Second,\n\t}\n\n\treq, err := http.NewRequest(\"GET\", \"http://unix/state\", nil)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"agent: Failed to create imds request\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"pritunl-imds\")\n\treq.Header.Set(\"Auth-Token\", imdsHostSecret)\n\n\tresp, e := client.Do(req)\n\tif e != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(e, \"agent: Imds request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tbody := \"\"\n\t\tdata, _ := ioutil.ReadAll(resp.Body)\n\t\tif data != nil {\n\t\t\tbody = string(data)\n\t\t}\n\n\t\terrData := &errortypes.ErrorData{}\n\t\terr = json.Unmarshal(data, errData)\n\t\tif err != nil || errData.Error == \"\" {\n\t\t\terrData = nil\n\t\t}\n\n\t\tif errData != nil && errData.Message != \"\" {\n\t\t\tbody = errData.Message\n\t\t}\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"agent: Imds host sync error %d - %s\",\n\t\t\t\tresp.StatusCode, body),\n\t\t}\n\t\treturn\n\t}\n\n\tste = &types.State{}\n\terr = json.NewDecoder(resp.Body).Decode(ste)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"agent: Failed to decode imds host sync resp\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "imds/resource/resource.go",
    "content": "package resource\n\nimport (\n\t\"strings\"\n\n\t\"github.com/pritunl/pritunl-cloud/finder\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/config\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n)\n\nfunc Query(resrc string, keys ...string) (val string, err error) {\n\tvar resrcInf interface{}\n\n\tkey := \"\"\n\tisJson := false\n\n\tswitch resrc {\n\tcase finder.NodeKind:\n\t\tif len(keys) != 2 || keys[0] != \"self\" {\n\t\t\tbreak\n\t\t}\n\t\tkey = keys[1]\n\t\tresrcInf = config.Config.Node\n\t\tbreak\n\tcase finder.InstanceKind:\n\t\tif len(keys) != 2 || keys[0] != \"self\" {\n\t\t\tbreak\n\t\t}\n\t\tkey = keys[1]\n\t\tresrcInf = config.Config.Instance\n\t\tbreak\n\tcase finder.VpcKind:\n\t\tif len(keys) != 2 || keys[0] != \"self\" {\n\t\t\tbreak\n\t\t}\n\t\tkey = keys[1]\n\t\tresrcInf = config.Config.Vpc\n\t\tbreak\n\tcase finder.SubnetKind:\n\t\tif len(keys) != 2 || keys[0] != \"self\" {\n\t\t\tbreak\n\t\t}\n\t\tkey = keys[1]\n\t\tresrcInf = config.Config.Subnet\n\t\tbreak\n\tcase finder.SecretKind:\n\t\tif len(keys) != 2 {\n\t\t\tbreak\n\t\t}\n\t\tkey = keys[1]\n\n\t\tfor _, secr := range config.Config.Secrets {\n\t\t\tif secr.Name == keys[0] {\n\t\t\t\tresrcInf = secr\n\t\t\t\tif secr.Type == secret.Json &&\n\t\t\t\t\tstrings.HasPrefix(key, \"data.\") {\n\n\t\t\t\t\tisJson = true\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase finder.CertificateKind:\n\t\tif len(keys) != 2 {\n\t\t\tbreak\n\t\t}\n\t\tkey = keys[1]\n\n\t\tfor _, cert := range config.Config.Certificates {\n\t\t\tif cert.Name == keys[0] {\n\t\t\t\tresrcInf = cert\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase finder.PodKind:\n\t\tif len(keys) == 2 {\n\t\t\tkey = keys[1]\n\t\t} else if len(keys) == 4 {\n\t\t\tkey = keys[3]\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, pd := range config.Config.Pods {\n\t\t\tif pd.Name == keys[0] {\n\t\t\t\tif len(keys) == 4 {\n\t\t\t\t\tif keys[1] == finder.UnitKind {\n\t\t\t\t\t\tfor _, unit := range pd.Units {\n\t\t\t\t\t\t\tif unit.Name == keys[2] {\n\t\t\t\t\t\t\t\tresrcInf = unit\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tresrcInf = pd\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase finder.UnitKind:\n\t\tif len(keys) != 2 {\n\t\t\tbreak\n\t\t}\n\t\tkey = keys[1]\n\n\t\tfor _, pd := range config.Config.Pods {\n\t\t\tfor _, unit := range pd.Units {\n\t\t\t\tif unit.Name == keys[0] {\n\t\t\t\t\tresrcInf = unit\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbreak\n\tdefault:\n\t\treturn\n\t}\n\n\tif resrcInf == nil {\n\t\treturn\n\t}\n\n\tval, err = selector(resrcInf, key, isJson)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "imds/resource/utils.go",
    "content": "package resource\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc selector(v interface{}, key string, isJson bool) (val string, err error) {\n\tvalRef := reflect.ValueOf(v)\n\n\tif valRef.Kind() != reflect.Ptr || valRef.IsNil() {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"Selector input invalid\"),\n\t\t}\n\t\treturn\n\t}\n\n\telm := valRef.Elem()\n\tif elm.Kind() != reflect.Struct {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"Selector kind invalid\"),\n\t\t}\n\t\treturn\n\t}\n\n\tjsonKey := \"\"\n\tif isJson {\n\t\tkeys := strings.SplitN(key, \".\", 2)\n\t\tif len(keys) == 2 {\n\t\t\tkey = keys[0]\n\t\t\tjsonKey = keys[1]\n\t\t} else {\n\t\t\tisJson = false\n\t\t}\n\t}\n\n\ttyp := elm.Type()\n\n\tfor i := 0; i < elm.NumField(); i++ {\n\t\tfield := typ.Field(i)\n\t\tjsonTag := field.Tag.Get(\"json\")\n\n\t\tif jsonTag == key {\n\t\t\tfieldVal := elm.Field(i)\n\n\t\t\tif fieldVal.Kind() == reflect.Slice {\n\t\t\t\tvar elements []string\n\n\t\t\t\tfor j := 0; j < fieldVal.Len(); j++ {\n\t\t\t\t\telements = append(elements,\n\t\t\t\t\t\tselectString(fieldVal.Index(j).Interface()))\n\t\t\t\t}\n\n\t\t\t\tval = strings.Join(elements, \",\")\n\t\t\t} else {\n\t\t\t\tval = selectString(elm.Field(i).Interface())\n\n\t\t\t\tif isJson && jsonKey != \"\" {\n\t\t\t\t\tvar jsonData map[string]any\n\t\t\t\t\terr = json.Unmarshal([]byte(val), &jsonData)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tval = \"\"\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tjsonValue, exists := jsonData[jsonKey]\n\t\t\t\t\tif !exists {\n\t\t\t\t\t\tval = \"\"\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tval = jsonValString(jsonValue)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc jsonValString(value any) string {\n\tswitch val := value.(type) {\n\tcase string:\n\t\treturn val\n\tcase bool:\n\t\treturn strconv.FormatBool(val)\n\tcase float64:\n\t\treturn strconv.FormatFloat(val, 'f', -1, 64)\n\tcase int:\n\t\treturn strconv.Itoa(val)\n\tcase int64:\n\t\treturn strconv.FormatInt(val, 10)\n\tcase nil:\n\t\treturn \"\"\n\tdefault:\n\t\tjsonBytes, _ := json.Marshal(val)\n\t\treturn string(jsonBytes)\n\t}\n}\n\nfunc selectString(obj interface{}) string {\n\tif oid, ok := obj.(bson.ObjectID); ok {\n\t\treturn oid.Hex()\n\t}\n\n\tval := reflect.ValueOf(obj)\n\tval = reflect.Indirect(val)\n\n\tmethod := val.MethodByName(\"String\")\n\tif method.IsValid() &&\n\t\tmethod.Type().NumIn() == 0 &&\n\t\tmethod.Type().NumOut() == 1 &&\n\t\tmethod.Type().Out(0).Kind() == reflect.String {\n\n\t\tresult := method.Call(nil)\n\t\treturn result[0].String()\n\t}\n\n\treturn fmt.Sprintf(\"%v\", obj)\n}\n"
  },
  {
    "path": "imds/server/config/config.go",
    "content": "package config\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n)\n\nvar (\n\tConfig = &types.Config{}\n)\n"
  },
  {
    "path": "imds/server/constants/constants.go",
    "content": "package constants\n\nimport (\n\t\"time\"\n)\n\nconst (\n\tVersion     = \"1.0.3229.20\"\n\tConfRefresh = 500 * time.Millisecond\n)\n\nvar (\n\tSock         = \"\"\n\tHost         = \"127.0.0.1\"\n\tPort         = 80\n\tClient       = \"127.0.0.1\"\n\tClientSecret = \"\"\n\tDhcpSecret   = \"\"\n\tHostSecret   = \"\"\n\tInterrupt    = false\n)\n"
  },
  {
    "path": "imds/server/errortypes/errortypes.go",
    "content": "package errortypes\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype UnknownError struct {\n\terrors.DropboxError\n}\n\ntype NotFoundError struct {\n\terrors.DropboxError\n}\n\ntype ReadError struct {\n\terrors.DropboxError\n}\n\ntype WriteError struct {\n\terrors.DropboxError\n}\n\ntype ParseError struct {\n\terrors.DropboxError\n}\n\ntype AuthenticationError struct {\n\terrors.DropboxError\n}\n\ntype VerificationError struct {\n\terrors.DropboxError\n}\n\ntype ApiError struct {\n\terrors.DropboxError\n}\n\ntype DatabaseError struct {\n\terrors.DropboxError\n}\n\ntype RequestError struct {\n\terrors.DropboxError\n}\n\ntype ConnectionError struct {\n\terrors.DropboxError\n}\n\ntype TimeoutError struct {\n\terrors.DropboxError\n}\n\ntype ExecError struct {\n\terrors.DropboxError\n}\n\ntype NetworkError struct {\n\terrors.DropboxError\n}\n\ntype TypeError struct {\n\terrors.DropboxError\n}\n\ntype ErrorData struct {\n\tError   string `json:\"error\"`\n\tMessage string `json:\"error_msg\"`\n}\n"
  },
  {
    "path": "imds/server/handlers/certificate.go",
    "content": "package handlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/config\"\n)\n\nfunc certificatesGet(c *gin.Context) {\n\tc.JSON(200, config.Config.Certificates)\n}\n"
  },
  {
    "path": "imds/server/handlers/dhcp.go",
    "content": "package handlers\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/dhcpc\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/state\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc dhcpPut(c *gin.Context) {\n\tdata := &dhcpc.Lease{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tstate.Global.State.DhcpIface = data.Iface\n\tstate.Global.State.DhcpIface6 = data.Iface6\n\tstate.Global.State.DhcpIp = data.Address\n\tstate.Global.State.DhcpGateway = data.Gateway\n\tstate.Global.State.DhcpIp6 = data.Address6\n\n\tc.JSON(200, map[string]string{})\n}\n"
  },
  {
    "path": "imds/server/handlers/handlers.go",
    "content": "package handlers\n\nimport (\n\t\"crypto/subtle\"\n\t\"net/http\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/constants\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\ntype AuthenticationError struct {\n\tError   string `json:\"error\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc Recovery(c *gin.Context) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr := &errortypes.UnknownError{\n\t\t\t\terrors.Newf(\"handlers: Handler panic %s\", r),\n\t\t\t}\n\t\t\tlogger.WithFields(logger.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"handlers: Handler panic\")\n\t\t\tc.Writer.WriteHeader(http.StatusInternalServerError)\n\t\t}\n\t}()\n\n\tc.Next()\n}\n\nfunc Errors(c *gin.Context) {\n\tc.Next()\n\tfor _, err := range c.Errors {\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"handlers: Handler error\")\n\t}\n}\n\nfunc AuthVirt(c *gin.Context) {\n\ttoken := c.Request.Header.Get(\"Auth-Token\")\n\tif token == \"\" {\n\t\ttoken = c.Query(\"token\")\n\t}\n\n\t// TODO config.Config.ClientIps not loaded\n\t// addr := utils.StripPort(c.Request.RemoteAddr)\n\t// if len(config.Config.ClientIps) != 0 && config.Config.ClientIps[0] == \"\" &&\n\t// \t!utils.StringsContains(config.Config.ClientIps, addr) {\n\n\t// \tc.AbortWithStatusJSON(401, &AuthenticationError{\n\t// \t\tError:   \"authentication\",\n\t// \t\tMessage: \"Source IP address invalid\",\n\t// \t})\n\t// \treturn\n\t// }\n\n\tif c.Request.Header.Get(\"Origin\") != \"\" ||\n\t\tc.Request.Header.Get(\"Referer\") != \"\" ||\n\t\tc.Request.Header.Get(\"User-Agent\") != \"pritunl-imds\" ||\n\t\tconstants.ClientSecret == \"\" ||\n\t\t(subtle.ConstantTimeCompare([]byte(token),\n\t\t\t[]byte(constants.ClientSecret)) != 1) {\n\n\t\tc.AbortWithStatus(401)\n\t\treturn\n\t}\n\tc.Next()\n}\n\nfunc AuthDhcp(c *gin.Context) {\n\ttoken := c.Request.Header.Get(\"Auth-Token\")\n\tif token == \"\" {\n\t\ttoken = c.Query(\"token\")\n\t}\n\n\tif c.Request.Header.Get(\"Origin\") != \"\" ||\n\t\tc.Request.Header.Get(\"Referer\") != \"\" ||\n\t\tc.Request.Header.Get(\"User-Agent\") != \"pritunl-dhcp\" ||\n\t\tconstants.DhcpSecret == \"\" ||\n\t\t(subtle.ConstantTimeCompare([]byte(token),\n\t\t\t[]byte(constants.DhcpSecret)) != 1) {\n\n\t\tc.AbortWithStatus(401)\n\t\treturn\n\t}\n\tc.Next()\n}\n\nfunc AuthHost(c *gin.Context) {\n\ttoken := c.Request.Header.Get(\"Auth-Token\")\n\tif token == \"\" {\n\t\ttoken = c.Query(\"token\")\n\t}\n\n\tif c.Request.Header.Get(\"Origin\") != \"\" ||\n\t\tc.Request.Header.Get(\"Referer\") != \"\" ||\n\t\tc.Request.Header.Get(\"User-Agent\") != \"pritunl-imds\" ||\n\t\tconstants.HostSecret == \"\" ||\n\t\t(subtle.ConstantTimeCompare([]byte(token),\n\t\t\t[]byte(constants.HostSecret)) != 1) {\n\n\t\tc.AbortWithStatus(401)\n\t\treturn\n\t}\n\tc.Next()\n}\n\nfunc RegisterVirt(engine *gin.Engine) {\n\tengine.Use(Recovery)\n\tengine.Use(Errors)\n\n\tvirtGroup := engine.Group(\"\")\n\tvirtGroup.Use(AuthVirt)\n\n\tdhcpGroup := engine.Group(\"\")\n\tdhcpGroup.Use(AuthDhcp)\n\n\tvirtGroup.GET(\"/query/:resource\", queryGet)\n\tvirtGroup.GET(\"/query/:resource/:key1\", queryGet)\n\tvirtGroup.GET(\"/query/:resource/:key1/:key2\", queryGet)\n\tvirtGroup.GET(\"/query/:resource/:key1/:key2/:key3\", queryGet)\n\tvirtGroup.GET(\"/query/:resource/:key1/:key2/:key3/:key4\", queryGet)\n\tvirtGroup.GET(\"/node\", nodeGet)\n\tvirtGroup.GET(\"/instance\", instanceGet)\n\tvirtGroup.GET(\"/vpc\", vpcGet)\n\tvirtGroup.GET(\"/subnet\", subnetGet)\n\tvirtGroup.GET(\"/certificate\", certificatesGet)\n\tvirtGroup.GET(\"/secret\", secretsGet)\n\tvirtGroup.PUT(\"/sync\", syncPut)\n\n\tdhcpGroup.PUT(\"/dhcp\", dhcpPut)\n}\n\nfunc RegisterHost(engine *gin.Engine) {\n\tengine.Use(AuthHost)\n\tengine.Use(Recovery)\n\tengine.Use(Errors)\n\n\tengine.PUT(\"/sync\", hostSyncPut)\n\tengine.GET(\"/sync\", hostSyncGet)\n\tengine.GET(\"/state\", hostStateGet)\n}\n"
  },
  {
    "path": "imds/server/handlers/instance.go",
    "content": "package handlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/config\"\n)\n\nfunc instanceGet(c *gin.Context) {\n\tc.JSON(200, config.Config.Instance)\n}\n"
  },
  {
    "path": "imds/server/handlers/node.go",
    "content": "package handlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/config\"\n)\n\nfunc nodeGet(c *gin.Context) {\n\tc.JSON(200, config.Config.Node)\n}\n"
  },
  {
    "path": "imds/server/handlers/query.go",
    "content": "package handlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/imds/resource\"\n)\n\nfunc queryGet(c *gin.Context) {\n\tresrc := c.Param(\"resource\")\n\tkey1 := c.Param(\"key1\")\n\tkey2 := c.Param(\"key2\")\n\tkey3 := c.Param(\"key3\")\n\tkey4 := c.Param(\"key4\")\n\n\tkeys := []string{}\n\tif key1 != \"\" {\n\t\tkeys = append(keys, key1)\n\t\tif key2 != \"\" {\n\t\t\tkeys = append(keys, key2)\n\t\t\tif key3 != \"\" {\n\t\t\t\tkeys = append(keys, key3)\n\t\t\t\tif key4 != \"\" {\n\t\t\t\t\tkeys = append(keys, key4)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tval, err := resource.Query(resrc, keys...)\n\tif err != nil {\n\t\tc.AbortWithError(500, err)\n\t\treturn\n\t}\n\n\tc.String(200, val)\n}\n"
  },
  {
    "path": "imds/server/handlers/secret.go",
    "content": "package handlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/config\"\n)\n\nfunc secretsGet(c *gin.Context) {\n\tc.JSON(200, config.Config.Secrets)\n}\n"
  },
  {
    "path": "imds/server/handlers/sync.go",
    "content": "package handlers\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/dnss\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/config\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/state\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/telemetry\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tlastSecurity     = time.Now().Add(-7 * time.Minute)\n\tlastSecurityLock sync.Mutex\n)\n\ntype syncRespData struct {\n\tSpec     string           `json:\"spec\"`\n\tHash     uint32           `json:\"hash\"`\n\tJournals []*types.Journal `json:\"journals\"`\n}\n\nfunc syncPut(c *gin.Context) {\n\tdata := &types.State{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif !state.Global.State.Final() {\n\t\tstate.Global.State.Status = data.Status\n\t}\n\tstate.Global.State.Timestamp = time.Now()\n\tstate.Global.State.Memory = data.Memory\n\tstate.Global.State.HugePages = data.HugePages\n\tstate.Global.State.Load1 = data.Load1\n\tstate.Global.State.Load5 = data.Load5\n\tstate.Global.State.Load15 = data.Load15\n\n\tif data.Updates != nil {\n\t\ttelemetry.Updates.Set(data.Updates)\n\t}\n\n\tif data.Output != nil {\n\t\tfor _, entry := range data.Output {\n\t\t\tstate.Global.AppendOutput(entry)\n\t\t}\n\t}\n\n\tif data.Journals != nil {\n\t\tfor key, output := range data.Journals {\n\t\t\tfor _, entry := range output {\n\t\t\t\tstate.Global.AppendJournalOutput(key, entry)\n\t\t\t}\n\t\t}\n\t}\n\n\tif data.Hash != config.Config.Hash {\n\t\tc.JSON(200, &syncRespData{\n\t\t\tSpec:     config.Config.SpecData,\n\t\t\tHash:     config.Config.Hash,\n\t\t\tJournals: config.Config.Journals,\n\t\t})\n\t} else {\n\t\tc.JSON(200, &syncRespData{\n\t\t\tHash:     config.Config.Hash,\n\t\t\tJournals: config.Config.Journals,\n\t\t})\n\t}\n}\n\nfunc hostSyncPut(c *gin.Context) {\n\tdata := &types.Config{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif data.Hash != 0 {\n\t\tconfig.Config = data\n\t\tdnss.LoadConfig(data.Domains)\n\t}\n\n\tste := state.Global.State.Copy()\n\tste.Hash = config.Config.Hash\n\tste.Output = state.Global.GetOutput()\n\tste.Journals = state.Global.GetJournals()\n\n\tupdates, ok := telemetry.Updates.Get()\n\tif ok {\n\t\tste.Updates = updates\n\t} else {\n\t\tste.Updates = nil\n\t}\n\n\tc.JSON(200, ste)\n}\n\nfunc hostSyncGet(c *gin.Context) {\n\tste := state.Global.State.Copy()\n\tste.Output = state.Global.GetOutput()\n\tste.Journals = state.Global.GetJournals()\n\n\tupdates, ok := telemetry.Updates.Get()\n\tif ok {\n\t\tste.Updates = updates\n\t} else {\n\t\tste.Updates = nil\n\t}\n\n\tc.JSON(200, ste)\n}\n\nfunc hostStateGet(c *gin.Context) {\n\tste := state.Global.State.Copy()\n\n\tc.JSON(200, ste)\n}\n"
  },
  {
    "path": "imds/server/handlers/vpc.go",
    "content": "package handlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/config\"\n)\n\nfunc vpcGet(c *gin.Context) {\n\tc.JSON(200, config.Config.Vpc)\n}\n\nfunc subnetGet(c *gin.Context) {\n\tc.JSON(200, config.Config.Subnet)\n}\n"
  },
  {
    "path": "imds/server/router/router.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/dnss\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/constants\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/handlers\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\ntype Router struct {\n\tvirtServer *http.Server\n\thostServer *http.Server\n\tdnsServer  *dnss.Server\n}\n\nfunc (r *Router) Run() (err error) {\n\tlogger.WithFields(logger.Fields{\n\t\t\"host\":    constants.Host,\n\t\t\"port\":    constants.Port,\n\t\t\"sock\":    constants.Sock,\n\t\t\"version\": constants.Version,\n\t}).Info(\"main: Starting imds server\")\n\n\twaiters := &sync.WaitGroup{}\n\n\twaiters.Add(1)\n\tgo func() {\n\t\tdefer waiters.Done()\n\n\t\te := r.virtServer.ListenAndServe()\n\t\tif e != nil {\n\t\t\te = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(e, \"main: Server listen error\"),\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\terr = e\n\t\t\t}\n\t\t\tr.Shutdown()\n\t\t\treturn\n\t\t}\n\t}()\n\n\twaiters.Add(1)\n\tgo func() {\n\t\tdefer waiters.Done()\n\n\t\t_ = os.Remove(constants.Sock)\n\n\t\tlistener, e := net.Listen(\"unix\", constants.Sock)\n\t\tif e != nil {\n\t\t\te = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(e, \"main: Failed to create unix socket\"),\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\terr = e\n\t\t\t}\n\t\t\tr.Shutdown()\n\t\t\treturn\n\t\t}\n\n\t\te = r.hostServer.Serve(listener)\n\t\tif e != nil {\n\t\t\te = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(e, \"main: Server listen error\"),\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\terr = e\n\t\t\t}\n\t\t\tr.Shutdown()\n\t\t\treturn\n\t\t}\n\t}()\n\n\twaiters.Add(1)\n\tgo func() {\n\t\tdefer waiters.Done()\n\n\t\te := r.dnsServer.ListenUdp()\n\t\tif e != nil {\n\t\t\tif err == nil {\n\t\t\t\terr = e\n\t\t\t}\n\t\t\tr.Shutdown()\n\t\t\treturn\n\t\t}\n\t}()\n\n\twaiters.Add(1)\n\tgo func() {\n\t\tdefer waiters.Done()\n\n\t\te := r.dnsServer.ListenTcp()\n\t\tif e != nil {\n\t\t\tif err == nil {\n\t\t\t\terr = e\n\t\t\t}\n\t\t\tr.Shutdown()\n\t\t\treturn\n\t\t}\n\t}()\n\n\twaiters.Wait()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (r *Router) Shutdown() {\n\tdefer func() {\n\t\trecover()\n\t}()\n\n\twebCtx, webCancel := context.WithTimeout(\n\t\tcontext.Background(),\n\t\t1*time.Second,\n\t)\n\tdefer webCancel()\n\n\t_ = r.virtServer.Shutdown(webCtx)\n\t_ = r.virtServer.Close()\n\n\t_ = r.hostServer.Shutdown(webCtx)\n\t_ = r.hostServer.Close()\n\n\t_ = r.dnsServer.Shutdown()\n}\n\nfunc (r *Router) Init() {\n\tgin.SetMode(gin.ReleaseMode)\n\n\tvirtRouter := gin.New()\n\thandlers.RegisterVirt(virtRouter)\n\n\tr.virtServer = &http.Server{\n\t\tAddr: fmt.Sprintf(\n\t\t\t\"%s:%d\",\n\t\t\tconstants.Host,\n\t\t\tconstants.Port,\n\t\t),\n\t\tHandler:        virtRouter,\n\t\tReadTimeout:    30 * time.Second,\n\t\tWriteTimeout:   30 * time.Second,\n\t\tIdleTimeout:    60 * time.Second,\n\t\tMaxHeaderBytes: 4096,\n\t}\n\n\thostRouter := gin.New()\n\thandlers.RegisterHost(hostRouter)\n\n\tr.hostServer = &http.Server{\n\t\tAddr:           \"127.0.0.1:99999\",\n\t\tHandler:        hostRouter,\n\t\tReadTimeout:    30 * time.Second,\n\t\tWriteTimeout:   30 * time.Second,\n\t\tIdleTimeout:    60 * time.Second,\n\t\tMaxHeaderBytes: 4096,\n\t}\n\n\tr.dnsServer = dnss.NewServer(fmt.Sprintf(\n\t\t\"%s:53\",\n\t\tconstants.Host,\n\t))\n\n\treturn\n}\n"
  },
  {
    "path": "imds/server/server.go",
    "content": "package server\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/pritunl/pritunl-cloud/imds/server/constants\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/router\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/state\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\nfunc Main() (err error) {\n\tconstants.ClientSecret = os.Getenv(\"CLIENT_SECRET\")\n\tconstants.DhcpSecret = os.Getenv(\"DHCP_SECRET\")\n\tconstants.HostSecret = os.Getenv(\"HOST_SECRET\")\n\tos.Unsetenv(\"CLIENT_SECRET\")\n\tos.Unsetenv(\"DHCP_SECRET\")\n\tos.Unsetenv(\"HOST_SECRET\")\n\n\tlogger.Init(\n\t\tlogger.SetTimeFormat(\"\"),\n\t)\n\n\tlogger.AddHandler(func(record *logger.Record) {\n\t\tfmt.Print(record.String())\n\t})\n\n\thost := \"\"\n\tflag.StringVar(&host, \"host\", \"127.0.0.1\", \"Server bind address\")\n\n\tport := 0\n\tflag.IntVar(&port, \"port\", 80, \"Server bind port\")\n\n\tclient := \"\"\n\tflag.StringVar(&client, \"client\", \"127.0.0.1\", \"Client address\")\n\n\tsockPath := \"\"\n\tflag.StringVar(&sockPath, \"sock\", \"\", \"Socket path\")\n\n\tflag.Parse()\n\n\tconstants.Host = strings.Split(host, \"/\")[0]\n\tconstants.Port = port\n\tconstants.Sock = sockPath\n\tconstants.Client = client\n\n\troutr := &router.Router{}\n\troutr.Init()\n\n\terr = state.Init()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = routr.Run()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "imds/server/state/state.go",
    "content": "package state\n\nimport (\n\t\"sync\"\n\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n)\n\nvar Global = &Store{\n\tState:    &types.State{},\n\toutput:   make(chan *types.Entry, 10000),\n\tjournals: map[string]chan *types.Entry{},\n}\n\ntype Store struct {\n\tState    *types.State\n\toutput   chan *types.Entry\n\tjournals map[string]chan *types.Entry\n\tlock     sync.RWMutex\n}\n\nfunc (s *Store) AppendOutput(entry *types.Entry) {\n\tif len(s.output) > 9000 {\n\t\treturn\n\t}\n\ts.output <- entry\n}\n\nfunc (s *Store) GetOutput() (entries []*types.Entry) {\n\tfor {\n\t\tselect {\n\t\tcase entry := <-s.output:\n\t\t\tentries = append(entries, entry)\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (s *Store) AppendJournalOutput(key string, entry *types.Entry) {\n\ts.lock.Lock()\n\toutput, exists := s.journals[key]\n\tif !exists {\n\t\toutput = make(chan *types.Entry, 10000)\n\t\ts.journals[key] = output\n\t}\n\ts.lock.Unlock()\n\n\tif len(output) > 9000 {\n\t\treturn\n\t}\n\toutput <- entry\n}\n\nfunc (s *Store) GetJournals() (journals map[string][]*types.Entry) {\n\tjournals = map[string][]*types.Entry{}\n\n\ts.lock.RLock()\n\tkeys := make([]string, 0, len(s.journals))\n\toutputs := make(map[string]chan *types.Entry)\n\tfor key, output := range s.journals {\n\t\tkeys = append(keys, key)\n\t\toutputs[key] = output\n\t}\n\ts.lock.RUnlock()\n\n\tfor _, key := range keys {\n\t\toutput := outputs[key]\n\t\tif output == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar entries []*types.Entry\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase entry := <-output:\n\t\t\t\tentries = append(entries, entry)\n\t\t\tdefault:\n\t\t\t\tif len(entries) > 0 {\n\t\t\t\t\tjournals[key] = entries\n\t\t\t\t}\n\t\t\t\tgoto nextKey\n\t\t\t}\n\t\t}\n\tnextKey:\n\t}\n\n\treturn\n}\n\nfunc Init() (err error) {\n\treturn\n}\n"
  },
  {
    "path": "imds/server/utils/files.go",
    "content": "package utils\n\nimport (\n\t\"bufio\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nvar invalidPaths = set.NewSet(\"/\", \"\", \".\", \"./\")\n\nfunc Chmod(pth string, mode os.FileMode) (err error) {\n\terr = os.Chmod(pth, mode)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to chmod %s\", pth),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Exists(pth string) (exists bool, err error) {\n\t_, err = os.Stat(pth)\n\tif err == nil {\n\t\texists = true\n\t\treturn\n\t}\n\n\tif os.IsNotExist(err) {\n\t\terr = nil\n\t\treturn\n\t}\n\n\terr = &errortypes.ReadError{\n\t\terrors.Wrapf(err, \"utils: Failed to stat %s\", pth),\n\t}\n\treturn\n}\n\nfunc ExistsDir(pth string) (exists bool, err error) {\n\tstat, err := os.Stat(pth)\n\tif err == nil {\n\t\texists = stat.IsDir()\n\t\treturn\n\t}\n\n\tif os.IsNotExist(err) {\n\t\terr = nil\n\t\treturn\n\t}\n\n\terr = &errortypes.ReadError{\n\t\terrors.Wrapf(err, \"utils: Failed to stat %s\", pth),\n\t}\n\treturn\n}\n\nfunc ExistsFile(pth string) (exists bool, err error) {\n\tstat, err := os.Stat(pth)\n\tif err == nil {\n\t\texists = !stat.IsDir()\n\t\treturn\n\t}\n\n\tif os.IsNotExist(err) {\n\t\terr = nil\n\t\treturn\n\t}\n\n\terr = &errortypes.ReadError{\n\t\terrors.Wrapf(err, \"utils: Failed to stat %s\", pth),\n\t}\n\treturn\n}\n\nfunc ExistsMkdir(pth string, perm os.FileMode) (err error) {\n\texists, err := ExistsDir(pth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !exists {\n\t\terr = os.MkdirAll(pth, perm)\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to mkdir %s\", pth),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc ExistsRemove(pth string) (err error) {\n\texists, err := Exists(pth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif exists {\n\t\terr = os.RemoveAll(pth)\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to rm %s\", pth),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Remove(path string) (err error) {\n\tif invalidPaths.Contains(path) {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Invalid remove path '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\terr = os.Remove(path)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to remove '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveAll(path string) (err error) {\n\tif invalidPaths.Contains(path) {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Invalid remove path '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\terr = os.RemoveAll(path)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to remove '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ContainsDir(pth string) (hasDir bool, err error) {\n\texists, err := ExistsDir(pth)\n\tif !exists {\n\t\treturn\n\t}\n\n\tentries, err := ioutil.ReadDir(pth)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"queue: Failed to read dir %s\", pth),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\thasDir = true\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Open(path string, perm os.FileMode) (file *os.File, err error) {\n\tfile, err = os.OpenFile(path, os.O_RDWR|os.O_TRUNC, perm)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to open '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Read(path string) (data string, err error) {\n\tdataByt, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to read '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\tdata = string(dataByt)\n\treturn\n}\n\nfunc ReadLines(path string) (lines []string, err error) {\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to open '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr = file.Close()\n\t\tif err != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to read '%s'\", path),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\tlines = []string{}\n\treader := bufio.NewReader(file)\n\tfor {\n\t\tline, e := reader.ReadString('\\n')\n\t\tif e != nil {\n\t\t\tbreak\n\t\t}\n\t\tlines = append(lines, strings.Trim(line, \"\\n\"))\n\t}\n\n\treturn\n}\n\nfunc Write(path string, data string, perm os.FileMode) (err error) {\n\tfile, err := Open(path, perm)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr = file.Close()\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to write '%s'\", path),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\t_, err = file.WriteString(data)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to write to file '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Create(path string, perm os.FileMode) (file *os.File, err error) {\n\tfile, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to create '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc CreateWrite(path string, data string, perm os.FileMode) (err error) {\n\tfile, err := Create(path, perm)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr = file.Close()\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to write '%s'\", path),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\t_, err = file.WriteString(data)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to write to file '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "imds/server/utils/misc.go",
    "content": "package utils\n\nfunc StringsContains(val []string, str string) bool {\n\tif val == nil {\n\t\treturn false\n\t}\n\tfor _, v := range val {\n\t\tif v == str {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "imds/server/utils/request.go",
    "content": "package utils\n\nimport (\n\t\"strings\"\n)\n\nfunc StripPort(hostport string) string {\n\tcolon := strings.IndexByte(hostport, ':')\n\tif colon == -1 {\n\t\treturn hostport\n\t}\n\n\tn := strings.Count(hostport, \":\")\n\tif n > 1 {\n\t\tif i := strings.IndexByte(hostport, ']'); i != -1 {\n\t\t\treturn strings.TrimPrefix(hostport[:i], \"[\")\n\t\t}\n\t\treturn hostport\n\t}\n\n\treturn hostport[:colon]\n}\n"
  },
  {
    "path": "imds/systemd.go",
    "content": "package imds\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/features\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/permission\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst systemdNamespaceTemplate = `[Unit]\nDescription=Pritunl Cloud IMDS Server\nAfter=network.target\n\n[Service]\nType=simple\nUser=%s\nEnvironment=\"CLIENT_SECRET=%s\"\nEnvironment=\"DHCP_SECRET=%s\"\nEnvironment=\"HOST_SECRET=%s\"\nExecStart=/usr/bin/pritunl-cloud -sock=%s -host=%s -port=%d imds-server\nTimeoutStopSec=5\nRestart=always\nRestartSec=3\nPrivateTmp=true\nProtectHome=true\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\nNetworkNamespacePath=/var/run/netns/%s\nAmbientCapabilities=CAP_NET_BIND_SERVICE\n`\n\nconst systemdTemplate = `[Unit]\nDescription=Pritunl Cloud IMDS Server\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nEnvironment=\"CLIENT_SECRET=%s\"\nEnvironment=\"DHCP_SECRET=%s\"\nEnvironment=\"HOST_SECRET=%s\"\nExecStart=/usr/sbin/ip netns exec %s /usr/bin/pritunl-cloud -sock=%s -host=%s -port=%d imds-server\nTimeoutStopSec=5\nRestart=always\nRestartSec=3\nPrivateTmp=true\nProtectHome=true\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\nAmbientCapabilities=CAP_NET_BIND_SERVICE\n`\n\nfunc WriteService(vmId bson.ObjectID,\n\tnamespace, clientSecret, dhcpSecret, hostSecret string,\n\tsystemdNamespace bool) (err error) {\n\n\tunitPath := paths.GetUnitPathImds(vmId)\n\tsockPath := paths.GetImdsSockPath(vmId)\n\n\tif clientSecret == \"\" || dhcpSecret == \"\" || hostSecret == \"\" {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"imds: Cannot start imds with empty secret\"),\n\t\t}\n\t\treturn\n\t}\n\n\toutput := \"\"\n\tif systemdNamespace {\n\t\toutput = fmt.Sprintf(\n\t\t\tsystemdNamespaceTemplate,\n\t\t\tpermission.GetUserName(vmId),\n\t\t\tclientSecret,\n\t\t\tdhcpSecret,\n\t\t\thostSecret,\n\t\t\tsockPath,\n\t\t\tsettings.Hypervisor.ImdsAddress,\n\t\t\tsettings.Hypervisor.ImdsPort,\n\t\t\tnamespace,\n\t\t)\n\t} else {\n\t\toutput = fmt.Sprintf(\n\t\t\tsystemdTemplate,\n\t\t\tclientSecret,\n\t\t\tdhcpSecret,\n\t\t\thostSecret,\n\t\t\tnamespace,\n\t\t\tsockPath,\n\t\t\tsettings.Hypervisor.ImdsAddress,\n\t\t\tsettings.Hypervisor.ImdsPort,\n\t\t)\n\t}\n\n\terr = utils.CreateWrite(unitPath, output, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Start(db *database.Database, virt *vm.VirtualMachine) (err error) {\n\tnamespace := vm.GetNamespace(virt.Id, 0)\n\n\thasSystemdNamespace := features.HasSystemdNamespace()\n\tunit := paths.GetUnitNameImds(virt.Id)\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\":           virt.Id.Hex(),\n\t\t\"systemd_unit\": unit,\n\t}).Info(\"imds: Starting virtual machine imds server\")\n\n\t_ = systemd.Stop(unit)\n\n\terr = WriteService(virt.Id, namespace, virt.ImdsClientSecret,\n\t\tvirt.ImdsDhcpSecret, virt.ImdsHostSecret, hasSystemdNamespace)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Reload()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Start(unit)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Restart(instId bson.ObjectID) (err error) {\n\tunit := paths.GetUnitNameImds(instId)\n\n\t_ = systemd.Restart(unit)\n\n\treturn\n}\n\nfunc Stop(virt *vm.VirtualMachine) (err error) {\n\tunit := paths.GetUnitNameImds(virt.Id)\n\n\t_ = systemd.Stop(unit)\n\n\treturn\n}\n"
  },
  {
    "path": "imds/types/certificate.go",
    "content": "package types\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n)\n\ntype Certificate struct {\n\tId          bson.ObjectID `json:\"id\"`\n\tName        string        `json:\"name\"`\n\tType        string        `json:\"type\"`\n\tKey         string        `json:\"key\"`\n\tCertificate string        `json:\"certificate\"`\n}\n\nfunc NewCertificates(certs []*certificate.Certificate) []*Certificate {\n\tdatas := []*Certificate{}\n\n\tfor _, cert := range certs {\n\t\tif cert == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tdata := &Certificate{\n\t\t\tId:          cert.Id,\n\t\t\tName:        cert.Name,\n\t\t\tType:        cert.Type,\n\t\t\tKey:         cert.Key,\n\t\t\tCertificate: cert.Certificate,\n\t\t}\n\n\t\tdatas = append(datas, data)\n\t}\n\n\treturn datas\n}\n"
  },
  {
    "path": "imds/types/config.go",
    "content": "package types\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\n// Cannot contain maps for encode order\ntype Config struct {\n\tSpec           bson.ObjectID  `json:\"spec\"`\n\tSpecData       string         `json:\"spec_data\" gob:\"-\"`\n\tImdsHostSecret string         `json:\"-\"`\n\tClientIps      []string       `json:\"client_ips\"`\n\tNode           *Node          `json:\"node\"`\n\tInstance       *Instance      `json:\"instance\"`\n\tVpc            *Vpc           `json:\"vpc\"`\n\tSubnet         *Subnet        `json:\"subnet\"`\n\tCertificates   []*Certificate `json:\"certificates\"`\n\tSecrets        []*Secret      `json:\"secrets\"`\n\tPods           []*Pod         `json:\"pods\"`\n\tJournals       []*Journal     `json:\"journals\"`\n\tDomains        []*Domain      `json:\"domains\"`\n\tHash           uint32         `json:\"hash\"`\n}\n\nfunc (c *Config) ComputeHash() (err error) {\n\tc.Hash = 0\n\n\tconfHash, err := utils.CrcHash(c)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tc.Hash = confHash\n\treturn\n}\n"
  },
  {
    "path": "imds/types/constants.go",
    "content": "package types\n\nconst (\n\tInitializing   = \"initializing\"\n\tReloadingClean = \"reloading_clean\"\n\tReloadingFault = \"reloading_fault\"\n\tRunning        = \"running\"\n\tFault          = \"fault\"\n\tOffline        = \"offline\"\n\tImaged         = \"imaged\"\n)\n"
  },
  {
    "path": "imds/types/domain.go",
    "content": "package types\n\nimport (\n\t\"net\"\n)\n\ntype Domain struct {\n\tDomain string `json:\"domain\"`\n\tType   string `json:\"type\"`\n\tIp     net.IP `json:\"ip\"`\n\tTarget string `json:\"target\"`\n}\n"
  },
  {
    "path": "imds/types/instance.go",
    "content": "package types\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n)\n\ntype Instance struct {\n\tId                  bson.ObjectID `json:\"id\"`\n\tOrganization        bson.ObjectID `json:\"organization\"`\n\tZone                bson.ObjectID `json:\"zone\"`\n\tVpc                 bson.ObjectID `json:\"vpc\"`\n\tSubnet              bson.ObjectID `json:\"subnet\"`\n\tCloudSubnet         string        `json:\"cloud_subnet\"`\n\tCloudVnic           string        `json:\"cloud_vnic\"`\n\tImage               bson.ObjectID `json:\"image\"`\n\tState               string        `json:\"state\"`\n\tTimestamp           time.Time     `json:\"timestamp\"`\n\tAction              string        `json:\"action\"`\n\tUefi                bool          `json:\"uefi\"`\n\tSecureBoot          bool          `json:\"secure_boot\"`\n\tTpm                 bool          `json:\"tpm\"`\n\tDhcpServer          bool          `json:\"dhcp_server\"`\n\tCloudType           string        `json:\"cloud_type\"`\n\tSystemKind          string        `json:\"system_kind\"`\n\tDeleteProtection    bool          `json:\"delete_protection\"`\n\tSkipSourceDestCheck bool          `json:\"skip_source_dest_check\"`\n\tQemuVersion         string        `json:\"qemu_version\"`\n\tPublicIps           []string      `json:\"public_ips\"`\n\tPublicIps6          []string      `json:\"public_ips6\"`\n\tPrivateIps          []string      `json:\"private_ips\"`\n\tPrivateIps6         []string      `json:\"private_ips6\"`\n\tGatewayIps          []string      `json:\"gateway_ips\"`\n\tGatewayIps6         []string      `json:\"gateway_ips6\"`\n\tCloudPrivateIps     []string      `json:\"cloud_private_ips\"`\n\tCloudPublicIps      []string      `json:\"cloud_public_ips\"`\n\tCloudPublicIps6     []string      `json:\"cloud_public_ips6\"`\n\tHostIps             []string      `json:\"host_ips\"`\n\tNodePortIps         []string      `json:\"node_port_ips\"`\n\tNetworkNamespace    string        `json:\"network_namespace\"`\n\tNoPublicAddress     bool          `json:\"no_public_address\"`\n\tNoPublicAddress6    bool          `json:\"no_public_address6\"`\n\tNoHostAddress       bool          `json:\"no_host_address\"`\n\tNode                bson.ObjectID `json:\"node\"`\n\tShape               bson.ObjectID `json:\"shape\"`\n\tName                string        `json:\"name\"`\n\tRootEnabled         bool          `json:\"root_enabled\"`\n\tMemory              int           `json:\"memory\"`\n\tProcessors          int           `json:\"processors\"`\n\tRoles               []string      `json:\"roles\"`\n\tVnc                 bool          `json:\"vnc\"`\n\tSpice               bool          `json:\"spice\"`\n\tGui                 bool          `json:\"gui\"`\n\tDeployment          bson.ObjectID `json:\"deployment\"`\n}\n\nfunc NewInstance(inst *instance.Instance) *Instance {\n\tif inst == nil {\n\t\treturn &Instance{}\n\t}\n\n\treturn &Instance{\n\t\tId:                  inst.Id,\n\t\tOrganization:        inst.Organization,\n\t\tZone:                inst.Zone,\n\t\tVpc:                 inst.Vpc,\n\t\tSubnet:              inst.Subnet,\n\t\tCloudSubnet:         inst.CloudSubnet,\n\t\tCloudVnic:           inst.CloudVnic,\n\t\tImage:               inst.Image,\n\t\tState:               inst.State,\n\t\tTimestamp:           inst.Timestamp,\n\t\tAction:              inst.Action,\n\t\tUefi:                inst.Uefi,\n\t\tSecureBoot:          inst.SecureBoot,\n\t\tTpm:                 inst.Tpm,\n\t\tDhcpServer:          inst.DhcpServer,\n\t\tCloudType:           inst.CloudType,\n\t\tSystemKind:          inst.SystemKind,\n\t\tDeleteProtection:    inst.DeleteProtection,\n\t\tSkipSourceDestCheck: inst.SkipSourceDestCheck,\n\t\tQemuVersion:         inst.QemuVersion,\n\t\tPublicIps:           inst.PublicIps,\n\t\tPublicIps6:          inst.PublicIps6,\n\t\tPrivateIps:          inst.PrivateIps,\n\t\tPrivateIps6:         inst.PrivateIps6,\n\t\tGatewayIps:          inst.GatewayIps,\n\t\tGatewayIps6:         inst.GatewayIps6,\n\t\tCloudPrivateIps:     inst.CloudPrivateIps,\n\t\tCloudPublicIps:      inst.CloudPublicIps,\n\t\tCloudPublicIps6:     inst.CloudPublicIps6,\n\t\tHostIps:             inst.HostIps,\n\t\tNodePortIps:         inst.NodePortIps,\n\t\tNetworkNamespace:    inst.NetworkNamespace,\n\t\tNoPublicAddress:     inst.NoPublicAddress,\n\t\tNoPublicAddress6:    inst.NoPublicAddress6,\n\t\tNoHostAddress:       inst.NoHostAddress,\n\t\tNode:                inst.Node,\n\t\tShape:               inst.Shape,\n\t\tName:                inst.Name,\n\t\tRootEnabled:         inst.RootEnabled,\n\t\tMemory:              inst.Memory,\n\t\tProcessors:          inst.Processors,\n\t\tRoles:               inst.Roles,\n\t\tVnc:                 inst.Vnc,\n\t\tSpice:               inst.Spice,\n\t\tGui:                 inst.Gui,\n\t\tDeployment:          inst.Deployment,\n\t}\n}\n"
  },
  {
    "path": "imds/types/journal.go",
    "content": "package types\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n)\n\ntype Journal struct {\n\tIndex int32  `json:\"index\"`\n\tKey   string `json:\"key\"`\n\tType  string `json:\"type\"`\n\tUnit  string `json:\"unit\"`\n\tPath  string `json:\"path\"`\n}\n\nfunc NewJournals(spc *spec.Spec) []*Journal {\n\tif spc == nil || spc.Journal == nil {\n\t\treturn nil\n\t}\n\n\tjrnls := []*Journal{}\n\tfor _, jrnl := range spc.Journal.Inputs {\n\t\tjrnls = append(jrnls, &Journal{\n\t\t\tIndex: jrnl.Index,\n\t\t\tKey:   jrnl.Key,\n\t\t\tType:  jrnl.Type,\n\t\t\tUnit:  jrnl.Unit,\n\t\t\tPath:  jrnl.Path,\n\t\t})\n\t}\n\n\treturn jrnls\n}\n"
  },
  {
    "path": "imds/types/node.go",
    "content": "package types\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\ntype Node struct {\n\tId         bson.ObjectID `json:\"id\"`\n\tName       string        `json:\"name\"`\n\tPublicIps  []string      `json:\"public_ips\"`\n\tPublicIps6 []string      `json:\"public_ips6\"`\n}\n\nfunc NewNode(nde *node.Node) *Node {\n\tif nde == nil {\n\t\treturn &Node{}\n\t}\n\n\treturn &Node{\n\t\tId:         nde.Id,\n\t\tName:       nde.Name,\n\t\tPublicIps:  nde.PublicIps,\n\t\tPublicIps6: nde.PublicIps6,\n\t}\n}\n"
  },
  {
    "path": "imds/types/pod.go",
    "content": "package types\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n)\n\ntype Pod struct {\n\tId    bson.ObjectID `json:\"id\"`\n\tName  string        `json:\"name\"`\n\tUnits []*Unit       `json:\"units\"`\n}\n\ntype Unit struct {\n\tId                       bson.ObjectID `json:\"id\"`\n\tName                     string        `json:\"name\"`\n\tKind                     string        `json:\"kind\"`\n\tCount                    int           `json:\"count\"`\n\tPublicIps                []string      `json:\"public_ips\"`\n\tPublicIps6               []string      `json:\"public_ips6\"`\n\tHealthyPublicIps         []string      `json:\"healthy_public_ips\"`\n\tHealthyPublicIps6        []string      `json:\"healthy_public_ips6\"`\n\tUnhealthyPublicIps       []string      `json:\"unhealthy_public_ips\"`\n\tUnhealthyPublicIps6      []string      `json:\"unhealthy_public_ips6\"`\n\tPrivateIps               []string      `json:\"private_ips\"`\n\tPrivateIps6              []string      `json:\"private_ips6\"`\n\tHealthyPrivateIps        []string      `json:\"healthy_private_ips\"`\n\tHealthyPrivateIps6       []string      `json:\"healthy_private_ips6\"`\n\tUnhealthyPrivateIps      []string      `json:\"unhealthy_private_ips\"`\n\tUnhealthyPrivateIps6     []string      `json:\"unhealthy_private_ips6\"`\n\tCloudPublicIps           []string      `json:\"cloud_public_ips\"`\n\tCloudPublicIps6          []string      `json:\"cloud_public_ips6\"`\n\tCloudPrivateIps          []string      `json:\"cloud_private_ips\"`\n\tHealthyCloudPublicIps    []string      `json:\"healthy_cloud_public_ips\"`\n\tHealthyCloudPublicIps6   []string      `json:\"healthy_cloud_public_ips6\"`\n\tHealthyCloudPrivateIps   []string      `json:\"healthy_cloud_private_ips\"`\n\tUnhealthyCloudPublicIps  []string      `json:\"unhealthy_cloud_public_ips\"`\n\tUnhealthyCloudPublicIps6 []string      `json:\"unhealthy_cloud_public_ips6\"`\n\tUnhealthyCloudPrivateIps []string      `json:\"unhealthy_cloud_private_ips\"`\n}\n\nfunc NewPods(pods []*pod.Pod, podUnitsMap map[bson.ObjectID][]*unit.Unit,\n\tdeployments map[bson.ObjectID]*deployment.Deployment) []*Pod {\n\n\tdatas := []*Pod{}\n\n\tfor _, pd := range pods {\n\t\tif pd == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tunits := []*Unit{}\n\t\tfor _, pdUnit := range podUnitsMap[pd.Id] {\n\t\t\tunit := &Unit{\n\t\t\t\tId:                       pdUnit.Id,\n\t\t\t\tName:                     pdUnit.Name,\n\t\t\t\tKind:                     pdUnit.Kind,\n\t\t\t\tCount:                    pdUnit.Count,\n\t\t\t\tPublicIps:                []string{},\n\t\t\t\tPublicIps6:               []string{},\n\t\t\t\tHealthyPublicIps:         []string{},\n\t\t\t\tHealthyPublicIps6:        []string{},\n\t\t\t\tUnhealthyPublicIps:       []string{},\n\t\t\t\tUnhealthyPublicIps6:      []string{},\n\t\t\t\tPrivateIps:               []string{},\n\t\t\t\tPrivateIps6:              []string{},\n\t\t\t\tHealthyPrivateIps:        []string{},\n\t\t\t\tHealthyPrivateIps6:       []string{},\n\t\t\t\tUnhealthyPrivateIps:      []string{},\n\t\t\t\tUnhealthyPrivateIps6:     []string{},\n\t\t\t\tCloudPublicIps:           []string{},\n\t\t\t\tCloudPublicIps6:          []string{},\n\t\t\t\tCloudPrivateIps:          []string{},\n\t\t\t\tHealthyCloudPublicIps:    []string{},\n\t\t\t\tHealthyCloudPublicIps6:   []string{},\n\t\t\t\tHealthyCloudPrivateIps:   []string{},\n\t\t\t\tUnhealthyCloudPublicIps:  []string{},\n\t\t\t\tUnhealthyCloudPublicIps6: []string{},\n\t\t\t\tUnhealthyCloudPrivateIps: []string{},\n\t\t\t}\n\n\t\t\tfor _, unitDeplyId := range pdUnit.Deployments {\n\t\t\t\tdeply := deployments[unitDeplyId]\n\n\t\t\t\tif deply != nil {\n\t\t\t\t\tdata := deply.InstanceData\n\t\t\t\t\tif data == nil {\n\t\t\t\t\t\tdata = &deployment.InstanceData{}\n\t\t\t\t\t}\n\n\t\t\t\t\tpublicIps := data.PublicIps\n\t\t\t\t\tif publicIps == nil {\n\t\t\t\t\t\tpublicIps = []string{}\n\t\t\t\t\t}\n\t\t\t\t\tpublicIps6 := data.PublicIps6\n\t\t\t\t\tif publicIps6 == nil {\n\t\t\t\t\t\tpublicIps6 = []string{}\n\t\t\t\t\t}\n\t\t\t\t\thealthyPublicIps := []string{}\n\t\t\t\t\tunhealthyPublicIps := []string{}\n\t\t\t\t\thealthyPublicIps6 := []string{}\n\t\t\t\t\tunhealthyPublicIps6 := []string{}\n\n\t\t\t\t\tprivateIps := data.PrivateIps\n\t\t\t\t\tif privateIps == nil {\n\t\t\t\t\t\tprivateIps = []string{}\n\t\t\t\t\t}\n\t\t\t\t\tprivateIps6 := data.PrivateIps6\n\t\t\t\t\tif privateIps6 == nil {\n\t\t\t\t\t\tprivateIps6 = []string{}\n\t\t\t\t\t}\n\t\t\t\t\thealthyPrivateIps := []string{}\n\t\t\t\t\tunhealthyPrivateIps := []string{}\n\t\t\t\t\thealthyPrivateIps6 := []string{}\n\t\t\t\t\tunhealthyPrivateIps6 := []string{}\n\n\t\t\t\t\tcloudPublicIps := data.CloudPublicIps\n\t\t\t\t\tif cloudPublicIps == nil {\n\t\t\t\t\t\tcloudPublicIps = []string{}\n\t\t\t\t\t}\n\t\t\t\t\tcloudPublicIps6 := data.CloudPublicIps6\n\t\t\t\t\tif cloudPublicIps6 == nil {\n\t\t\t\t\t\tcloudPublicIps6 = []string{}\n\t\t\t\t\t}\n\t\t\t\t\tcloudPrivateIps := data.CloudPrivateIps\n\t\t\t\t\tif cloudPrivateIps == nil {\n\t\t\t\t\t\tcloudPrivateIps = []string{}\n\t\t\t\t\t}\n\t\t\t\t\thealthyCloudPublicIps := []string{}\n\t\t\t\t\tunhealthyCloudPublicIps := []string{}\n\t\t\t\t\thealthyCloudPublicIps6 := []string{}\n\t\t\t\t\tunhealthyCloudPublicIps6 := []string{}\n\t\t\t\t\thealthyCloudPrivateIps := []string{}\n\t\t\t\t\tunhealthyCloudPrivateIps := []string{}\n\n\t\t\t\t\tif deply.IsHealthy() {\n\t\t\t\t\t\thealthyPublicIps = publicIps\n\t\t\t\t\t\thealthyPublicIps6 = publicIps6\n\t\t\t\t\t\thealthyPrivateIps = privateIps\n\t\t\t\t\t\thealthyPrivateIps6 = privateIps6\n\t\t\t\t\t\thealthyCloudPublicIps = cloudPublicIps\n\t\t\t\t\t\thealthyCloudPublicIps6 = cloudPublicIps6\n\t\t\t\t\t\thealthyCloudPrivateIps = cloudPrivateIps\n\t\t\t\t\t} else {\n\t\t\t\t\t\tunhealthyPublicIps = publicIps\n\t\t\t\t\t\tunhealthyPublicIps6 = publicIps6\n\t\t\t\t\t\tunhealthyPrivateIps = privateIps\n\t\t\t\t\t\tunhealthyPrivateIps6 = privateIps6\n\t\t\t\t\t\tunhealthyCloudPublicIps = cloudPublicIps\n\t\t\t\t\t\tunhealthyCloudPublicIps6 = cloudPublicIps6\n\t\t\t\t\t\tunhealthyCloudPrivateIps = cloudPrivateIps\n\t\t\t\t\t}\n\n\t\t\t\t\tunit.PublicIps = append(\n\t\t\t\t\t\tunit.PublicIps, publicIps...)\n\t\t\t\t\tunit.PublicIps6 = append(\n\t\t\t\t\t\tunit.PublicIps6, publicIps6...)\n\t\t\t\t\tunit.HealthyPublicIps = append(\n\t\t\t\t\t\tunit.HealthyPublicIps, healthyPublicIps...)\n\t\t\t\t\tunit.HealthyPublicIps6 = append(\n\t\t\t\t\t\tunit.HealthyPublicIps6, healthyPublicIps6...)\n\t\t\t\t\tunit.UnhealthyPublicIps = append(\n\t\t\t\t\t\tunit.UnhealthyPublicIps, unhealthyPublicIps...)\n\t\t\t\t\tunit.UnhealthyPublicIps6 = append(\n\t\t\t\t\t\tunit.UnhealthyPublicIps6, unhealthyPublicIps6...)\n\n\t\t\t\t\tunit.PrivateIps = append(\n\t\t\t\t\t\tunit.PrivateIps, privateIps...)\n\t\t\t\t\tunit.PrivateIps6 = append(\n\t\t\t\t\t\tunit.PrivateIps6, privateIps6...)\n\t\t\t\t\tunit.HealthyPrivateIps = append(\n\t\t\t\t\t\tunit.HealthyPrivateIps, healthyPrivateIps...)\n\t\t\t\t\tunit.HealthyPrivateIps6 = append(\n\t\t\t\t\t\tunit.HealthyPrivateIps6, healthyPrivateIps6...)\n\t\t\t\t\tunit.UnhealthyPrivateIps = append(\n\t\t\t\t\t\tunit.UnhealthyPrivateIps, unhealthyPrivateIps...)\n\t\t\t\t\tunit.UnhealthyPrivateIps6 = append(\n\t\t\t\t\t\tunit.UnhealthyPrivateIps6, unhealthyPrivateIps6...)\n\n\t\t\t\t\tunit.CloudPublicIps = append(\n\t\t\t\t\t\tunit.CloudPublicIps, cloudPublicIps...)\n\t\t\t\t\tunit.HealthyCloudPublicIps = append(\n\t\t\t\t\t\tunit.HealthyCloudPublicIps,\n\t\t\t\t\t\thealthyCloudPublicIps...,\n\t\t\t\t\t)\n\t\t\t\t\tunit.UnhealthyCloudPublicIps = append(\n\t\t\t\t\t\tunit.UnhealthyCloudPublicIps,\n\t\t\t\t\t\tunhealthyCloudPublicIps...,\n\t\t\t\t\t)\n\t\t\t\t\tunit.CloudPublicIps6 = append(\n\t\t\t\t\t\tunit.CloudPublicIps6, cloudPublicIps6...)\n\t\t\t\t\tunit.HealthyCloudPublicIps6 = append(\n\t\t\t\t\t\tunit.HealthyCloudPublicIps6,\n\t\t\t\t\t\thealthyCloudPublicIps6...,\n\t\t\t\t\t)\n\t\t\t\t\tunit.UnhealthyCloudPublicIps6 = append(\n\t\t\t\t\t\tunit.UnhealthyCloudPublicIps6,\n\t\t\t\t\t\tunhealthyCloudPublicIps6...,\n\t\t\t\t\t)\n\t\t\t\t\tunit.CloudPrivateIps = append(\n\t\t\t\t\t\tunit.CloudPrivateIps, cloudPrivateIps...)\n\t\t\t\t\tunit.HealthyCloudPrivateIps = append(\n\t\t\t\t\t\tunit.HealthyCloudPrivateIps,\n\t\t\t\t\t\thealthyCloudPrivateIps...,\n\t\t\t\t\t)\n\t\t\t\t\tunit.UnhealthyCloudPrivateIps = append(\n\t\t\t\t\t\tunit.UnhealthyCloudPrivateIps,\n\t\t\t\t\t\tunhealthyCloudPrivateIps...,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tunits = append(units, unit)\n\t\t}\n\n\t\tdata := &Pod{\n\t\t\tId:    pd.Id,\n\t\t\tName:  pd.Name,\n\t\t\tUnits: units,\n\t\t}\n\n\t\tdatas = append(datas, data)\n\t}\n\n\treturn datas\n}\n"
  },
  {
    "path": "imds/types/secret.go",
    "content": "package types\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n)\n\ntype Secret struct {\n\tId         bson.ObjectID `json:\"id\"`\n\tName       string        `json:\"name\"`\n\tType       string        `json:\"type\"`\n\tKey        string        `json:\"key\"`\n\tValue      string        `json:\"value\"`\n\tData       string        `json:\"data\"`\n\tRegion     string        `json:\"region\"`\n\tPublicKey  string        `json:\"public_key\"`\n\tPrivateKey string        `json:\"private_key\"`\n}\n\nfunc NewSecrets(secrs []*secret.Secret) []*Secret {\n\tdatas := []*Secret{}\n\n\tfor _, secr := range secrs {\n\t\tif secr == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tdata := &Secret{\n\t\t\tId:         secr.Id,\n\t\t\tName:       secr.Name,\n\t\t\tType:       secr.Type,\n\t\t\tKey:        secr.Key,\n\t\t\tValue:      secr.Value,\n\t\t\tData:       secr.Data,\n\t\t\tRegion:     secr.Region,\n\t\t\tPublicKey:  secr.PublicKey,\n\t\t\tPrivateKey: secr.PrivateKey,\n\t\t}\n\n\t\tdatas = append(datas, data)\n\t}\n\n\treturn datas\n}\n"
  },
  {
    "path": "imds/types/state.go",
    "content": "package types\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/telemetry\"\n)\n\ntype State struct {\n\tHash        uint32              `json:\"hash\"`\n\tStatus      string              `json:\"status\"`\n\tMemory      float64             `json:\"memory\"`\n\tHugePages   float64             `json:\"hugepages\"`\n\tLoad1       float64             `json:\"load1\"`\n\tLoad5       float64             `json:\"load5\"`\n\tLoad15      float64             `json:\"load15\"`\n\tDhcpIface   string              `json:\"dhcp_iface\"`\n\tDhcpIface6  string              `json:\"dhcp_iface6\"`\n\tDhcpIp      *net.IPNet          `json:\"dhcp_ip\"`\n\tDhcpIp6     *net.IPNet          `json:\"dhcp_ip6\"`\n\tDhcpGateway net.IP              `json:\"dhcp_gateway\"`\n\tUpdates     []*telemetry.Update `json:\"updates\"`\n\tTimestamp   time.Time           `json:\"timestamp\"`\n\tOutput      []*Entry            `json:\"output,omitempty\"`\n\tJournals    map[string][]*Entry `json:\"journals,omitempty\"`\n}\n\nfunc (s *State) Final() bool {\n\tif s.Status == Imaged {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (s *State) Copy() *State {\n\treturn &State{\n\t\tHash:        s.Hash,\n\t\tStatus:      s.Status,\n\t\tMemory:      s.Memory,\n\t\tHugePages:   s.HugePages,\n\t\tLoad1:       s.Load1,\n\t\tLoad5:       s.Load5,\n\t\tLoad15:      s.Load15,\n\t\tDhcpIface:   s.DhcpIface,\n\t\tDhcpIface6:  s.DhcpIface6,\n\t\tDhcpIp:      s.DhcpIp,\n\t\tDhcpIp6:     s.DhcpIp6,\n\t\tDhcpGateway: s.DhcpGateway,\n\t\tUpdates:     s.Updates,\n\t\tTimestamp:   s.Timestamp,\n\t}\n}\n\ntype Entry struct {\n\tTimestamp time.Time `json:\"t\"`\n\tLevel     int32     `json:\"l\"`\n\tMessage   string    `json:\"m\"`\n}\n\nconst (\n\tError = 3\n\tInfo  = 5\n)\n"
  },
  {
    "path": "imds/types/vpc.go",
    "content": "package types\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\ntype Vpc struct {\n\tId       bson.ObjectID `json:\"id\"`\n\tName     string        `json:\"name\"`\n\tVpcId    int           `json:\"vpc_id\"`\n\tNetwork  string        `json:\"network\"`\n\tNetwork6 string        `json:\"network6\"`\n\tSubnets  []*Subnet     `json:\"subnets\"`\n\tRoutes   []*Route      `json:\"routes\"`\n}\n\ntype Subnet struct {\n\tId      bson.ObjectID `json:\"id\"`\n\tName    string        `json:\"name\"`\n\tNetwork string        `json:\"network\"`\n}\n\nfunc (s *Subnet) String() string {\n\treturn s.Network\n}\n\ntype Route struct {\n\tDestination string `json:\"destination\"`\n\tTarget      string `json:\"target\"`\n}\n\nfunc (r *Route) String() string {\n\treturn fmt.Sprintf(\"%s via %s\", r.Destination, r.Target)\n}\n\nfunc NewSubnet(subnet *vpc.Subnet) *Subnet {\n\tif subnet == nil {\n\t\treturn &Subnet{}\n\t}\n\treturn &Subnet{\n\t\tId:      subnet.Id,\n\t\tName:    subnet.Name,\n\t\tNetwork: subnet.Network,\n\t}\n}\n\nfunc NewRoute(subnet *vpc.Route) *Route {\n\tif subnet == nil {\n\t\treturn &Route{}\n\t}\n\treturn &Route{\n\t\tDestination: subnet.Destination,\n\t\tTarget:      subnet.Target,\n\t}\n}\n\nfunc NewVpc(vpc *vpc.Vpc) *Vpc {\n\tif vpc == nil {\n\t\treturn &Vpc{}\n\t}\n\n\tvpc.Json()\n\n\tsubnets := []*Subnet{}\n\tif vpc.Subnets != nil {\n\t\tfor _, subnet := range vpc.Subnets {\n\t\t\tsubnets = append(subnets, NewSubnet(subnet))\n\t\t}\n\t}\n\n\troutes := []*Route{}\n\tif vpc.Routes != nil {\n\t\tfor _, route := range vpc.Routes {\n\t\t\troutes = append(routes, NewRoute(route))\n\t\t}\n\t}\n\n\treturn &Vpc{\n\t\tId:       vpc.Id,\n\t\tName:     vpc.Name,\n\t\tVpcId:    vpc.VpcId,\n\t\tNetwork:  vpc.Network,\n\t\tNetwork6: vpc.Network6,\n\t\tSubnets:  subnets,\n\t\tRoutes:   routes,\n\t}\n}\n"
  },
  {
    "path": "info/instance.go",
    "content": "package info\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/tools/set\"\n)\n\nfunc NewInstance(stat *state.State, inst *instance.Instance) (\n\tinf *instance.Info) {\n\n\tinf = &instance.Info{\n\t\tTimestamp: time.Now().Add(time.Duration(\n\t\t\tutils.RandInt(0, 10)) * time.Second),\n\t\tDisks:         []string{},\n\t\tFirewallRules: map[string]string{},\n\t\tAuthorities:   []string{},\n\t\tCloudSubnets:  []*node.CloudSubnet{},\n\t}\n\n\tnde := stat.Node()\n\tif inst.Node != nde.Id {\n\t\treturn\n\t}\n\n\tinf.Node = nde.Name\n\tif len(nde.PublicIps) > 0 {\n\t\tinf.NodePublicIp = nde.PublicIps[0]\n\t}\n\tinf.Iscsi = nde.Iscsi\n\tinf.Isos = nde.LocalIsos\n\tinf.CloudSubnets = nde.GetCloudSubnetsName()\n\tif nde.UsbPassthrough {\n\t\tinf.UsbDevices = nde.UsbDevices\n\t}\n\tif nde.PciDevices != nil {\n\t\tinf.PciDevices = nde.PciDevices\n\t}\n\tif nde.InstanceDrives != nil {\n\t\tinf.DriveDevices = nde.InstanceDrives\n\t}\n\n\tdc := stat.NodeDatacenter()\n\tif dc != nil {\n\t\tinf.Mtu = dc.GetInstanceMtu()\n\t}\n\n\tinstDisks := stat.GetInstaceDisks(inst.Id)\n\tfor _, dsk := range instDisks {\n\t\tinf.Disks = append(\n\t\t\tinf.Disks,\n\t\t\tfmt.Sprintf(\"%s: %s\", dsk.Index, dsk.Name),\n\t\t)\n\t}\n\n\tfirewallRulesKeys := []string{}\n\tfirewallRules := map[string]set.Set{}\n\tnamespaces := stat.GetInstanceNamespaces(inst.Id)\n\tfirewalls := stat.Firewalls()\n\tfor _, namespace := range namespaces {\n\t\tfor _, rule := range firewalls[namespace] {\n\t\t\tkey := rule.Protocol\n\t\t\tif rule.Port != \"\" {\n\t\t\t\tkey += \":\" + rule.Port\n\t\t\t}\n\n\t\t\trules := firewallRules[key]\n\t\t\tif rules == nil {\n\t\t\t\trules = set.NewSet()\n\t\t\t\tfirewallRules[key] = rules\n\t\t\t\tfirewallRulesKeys = append(\n\t\t\t\t\tfirewallRulesKeys,\n\t\t\t\t\tkey,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tfor _, sourceIp := range rule.SourceIps {\n\t\t\t\trules.Add(sourceIp)\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Strings(firewallRulesKeys)\n\tfor _, key := range firewallRulesKeys {\n\t\trules := firewallRules[key]\n\n\t\tvals := []string{}\n\t\tfor rule := range rules.Iter() {\n\t\t\tvals = append(vals, rule.(string))\n\t\t}\n\t\tsort.Strings(vals)\n\n\t\tinf.FirewallRules[key] = strings.Join(vals, \", \")\n\t}\n\n\tauthrs := stat.GetInstaceAuthorities(inst.Organization, inst.Roles)\n\tfor _, authr := range authrs {\n\t\tinf.Authorities = append(inf.Authorities, authr.Name)\n\t}\n\tsort.Strings(inf.Authorities)\n\n\treturn\n}\n"
  },
  {
    "path": "instance/constants.go",
    "content": "package instance\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\nconst (\n\tStarting     = \"starting\"\n\tRunning      = \"running\"\n\tStopped      = \"stopped\"\n\tFailed       = \"failed\"\n\tUpdating     = \"updating\"\n\tProvisioning = \"provisioning\"\n\tBridge       = \"bridge\"\n\tVxlan        = \"vxlan\"\n\n\tStart       = \"start\"\n\tStop        = \"stop\"\n\tCleanup     = \"cleanup\"\n\tRestart     = \"restart\"\n\tDestroy     = \"destroy\"\n\tLinux       = \"linux\"\n\tLinuxLegacy = \"linux_legacy\"\n\tBSD         = \"bsd\"\n\n\tAlpineLinux = \"alpinelinux\"\n\tArchLinux   = \"archlinux\"\n\tRedHat      = \"redhat\"\n\tFedora      = \"fedora\"\n\tUbuntu      = \"ubuntu\"\n\tFreeBSD     = \"freebsd\"\n\n\tHostPath = \"host_path\"\n)\n\nvar (\n\tValidStates = set.NewSet(\n\t\tStarting,\n\t\tRunning,\n\t\tStopped,\n\t\tFailed,\n\t\tUpdating,\n\t\tProvisioning,\n\t\tBridge,\n\t\tVxlan,\n\t)\n\tValidActions = set.NewSet(\n\t\tStart,\n\t\tStop,\n\t\tCleanup,\n\t\tRestart,\n\t\tDestroy,\n\t)\n\tValidCloudTypes = set.NewSet(\n\t\tLinux,\n\t\tLinuxLegacy,\n\t\tBSD,\n\t)\n\tValidSystemKinds = set.NewSet(\n\t\tAlpineLinux,\n\t\tArchLinux,\n\t\tRedHat,\n\t\tFedora,\n\t\tUbuntu,\n\t\tFreeBSD,\n\t)\n)\n"
  },
  {
    "path": "instance/errortypes.go",
    "content": "package instance\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype VncDialError struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "instance/instance.go",
    "content": "package instance\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/drive\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/iscsi\"\n\t\"github.com/pritunl/pritunl-cloud/iso\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/pci\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/telemetry\"\n\t\"github.com/pritunl/pritunl-cloud/tpm\"\n\t\"github.com/pritunl/pritunl-cloud/usb\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar scriptReg = regexp.MustCompile(\"^#!\")\n\ntype Instance struct {\n\tId                  bson.ObjectID       `bson:\"_id,omitempty\" json:\"id\"`\n\tOrganization        bson.ObjectID       `bson:\"organization\" json:\"organization\"`\n\tUnixId              int                 `bson:\"unix_id\" json:\"unix_id\"`\n\tDatacenter          bson.ObjectID       `bson:\"datacenter\" json:\"datacenter\"`\n\tZone                bson.ObjectID       `bson:\"zone\" json:\"zone\"`\n\tVpc                 bson.ObjectID       `bson:\"vpc\" json:\"vpc\"`\n\tSubnet              bson.ObjectID       `bson:\"subnet\" json:\"subnet\"`\n\tCreated             time.Time           `bson:\"created\" json:\"created\"`\n\tGuest               *GuestData          `bson:\"guest,omitempty\" json:\"guest\"`\n\tCloudSubnet         string              `bson:\"cloud_subnet\" json:\"cloud_subnet\"`\n\tCloudVnic           string              `bson:\"cloud_vnic\" json:\"cloud_vnic\"`\n\tCloudVnicAttach     string              `bson:\"cloud_vnic_attach\" json:\"cloud_vnic_attach\"`\n\tImage               bson.ObjectID       `bson:\"image\" json:\"image\"`\n\tImageBacking        bool                `bson:\"image_backing\" json:\"image_backing\"`\n\tDiskType            string              `bson:\"disk_type\" json:\"disk_type\"`\n\tDiskPool            bson.ObjectID       `bson:\"disk_pool\" json:\"disk_pool\"`\n\tStatus              string              `bson:\"-\" json:\"status\"`\n\tStatusInfo          *StatusInfo         `bson:\"status_info,omitempty\" json:\"status_info\"`\n\tUptime              string              `bson:\"-\" json:\"uptime\"`\n\tState               string              `bson:\"state\" json:\"state\"`\n\tAction              string              `bson:\"action\" json:\"action\"`\n\tPublicMac           string              `bson:\"-\" json:\"public_mac\"`\n\tTimestamp           time.Time           `bson:\"timestamp\" json:\"timestamp\"`\n\tRestart             bool                `bson:\"restart\" json:\"restart\"`\n\tRestartReason       string              `bson:\"restart_reason\" json:\"restart_reason\"`\n\tRestartBlockIp      bool                `bson:\"restart_block_ip\" json:\"restart_block_ip\"`\n\tUefi                bool                `bson:\"uefi\" json:\"uefi\"`\n\tSecureBoot          bool                `bson:\"secure_boot\" json:\"secure_boot\"`\n\tTpm                 bool                `bson:\"tpm\" json:\"tpm\"`\n\tTpmSecret           string              `bson:\"tpm_secret\" json:\"-\"`\n\tDhcpServer          bool                `bson:\"dhcp_server\" json:\"dhcp_server\"`\n\tCloudType           string              `bson:\"cloud_type\" json:\"cloud_type\"`\n\tCloudScript         string              `bson:\"cloud_script\" json:\"cloud_script\"`\n\tSystemKind          string              `bson:\"system_kind\" json:\"system_kind\"`\n\tDeleteProtection    bool                `bson:\"delete_protection\" json:\"delete_protection\"`\n\tSkipSourceDestCheck bool                `bson:\"skip_source_dest_check\" json:\"skip_source_dest_check\"`\n\tQemuVersion         string              `bson:\"qemu_version\" json:\"qemu_version\"`\n\tPublicIps           []string            `bson:\"public_ips\" json:\"public_ips\"`\n\tPublicIps6          []string            `bson:\"public_ips6\" json:\"public_ips6\"`\n\tPrivateIps          []string            `bson:\"private_ips\" json:\"private_ips\"`\n\tPrivateIps6         []string            `bson:\"private_ips6\" json:\"private_ips6\"`\n\tGatewayIps          []string            `bson:\"gateway_ips\" json:\"gateway_ips\"`\n\tGatewayIps6         []string            `bson:\"gateway_ips6\" json:\"gateway_ips6\"`\n\tCloudPrivateIps     []string            `bson:\"cloud_private_ips\" json:\"cloud_private_ips\"`\n\tCloudPublicIps      []string            `bson:\"cloud_public_ips\" json:\"cloud_public_ips\"`\n\tCloudPublicIps6     []string            `bson:\"cloud_public_ips6\" json:\"cloud_public_ips6\"`\n\tHostIps             []string            `bson:\"host_ips\" json:\"host_ips\"`\n\tNodePortIps         []string            `bson:\"node_port_ips\" json:\"node_port_ips\"`\n\tNodePorts           []*nodeport.Mapping `bson:\"node_ports,omitempty\" json:\"node_ports\"`\n\tDhcpIp              string              `bson:\"dhcp_ip\" json:\"dhcp_ip\"`\n\tDhcpIp6             string              `bson:\"dhcp_ip6\" json:\"dhcp_ip6\"`\n\tNetworkNamespace    string              `bson:\"network_namespace\" json:\"network_namespace\"`\n\tNoPublicAddress     bool                `bson:\"no_public_address\" json:\"no_public_address\"`\n\tNoPublicAddress6    bool                `bson:\"no_public_address6\" json:\"no_public_address6\"`\n\tNoHostAddress       bool                `bson:\"no_host_address\" json:\"no_host_address\"`\n\tNode                bson.ObjectID       `bson:\"node\" json:\"node\"`\n\tShape               bson.ObjectID       `bson:\"shape\" json:\"shape\"`\n\tName                string              `bson:\"name\" json:\"name\"`\n\tComment             string              `bson:\"comment\" json:\"comment\"`\n\tRootEnabled         bool                `bson:\"root_enabled\" json:\"root_enabled\"`\n\tRootPasswd          string              `bson:\"root_passwd\" json:\"root_passwd\"`\n\tInitDiskSize        int                 `bson:\"init_disk_size\" json:\"init_disk_size\"`\n\tMemory              int                 `bson:\"memory\" json:\"memory\"`\n\tProcessors          int                 `bson:\"processors\" json:\"processors\"`\n\tRoles               []string            `bson:\"roles\" json:\"roles\"`\n\tIsos                []*iso.Iso          `bson:\"isos,omitempty\" json:\"isos\"`\n\tUsbDevices          []*usb.Device       `bson:\"usb_devices,omitempty\" json:\"usb_devices\"`\n\tPciDevices          []*pci.Device       `bson:\"pci_devices,omitempty\" json:\"pci_devices\"`\n\tDriveDevices        []*drive.Device     `bson:\"drive_devices,omitempty\" json:\"drive_devices\"`\n\tIscsiDevices        []*iscsi.Device     `bson:\"iscsi_devices,omitempty\" json:\"iscsi_devices\"`\n\tMounts              []*Mount            `bson:\"mounts,omitempty\" json:\"mounts\"`\n\tVnc                 bool                `bson:\"vnc\" json:\"vnc\"`\n\tVncPassword         string              `bson:\"vnc_password\" json:\"vnc_password\"`\n\tVncDisplay          int                 `bson:\"vnc_display\" json:\"vnc_display\"`\n\tSpice               bool                `bson:\"spice\" json:\"spice\"`\n\tSpicePassword       string              `bson:\"spice_password\" json:\"spice_password\"`\n\tSpicePort           int                 `bson:\"spice_port\" json:\"spice_port\"`\n\tGui                 bool                `bson:\"gui\" json:\"gui\"`\n\tDeployment          bson.ObjectID       `bson:\"deployment\" json:\"deployment\"`\n\tInfo                *Info               `bson:\"info,omitempty\" json:\"info\"`\n\tVirt                *vm.VirtualMachine  `bson:\"-\" json:\"-\"`\n\n\tcurVpc              bson.ObjectID                       `bson:\"-\" json:\"-\"`\n\tcurSubnet           bson.ObjectID                       `bson:\"-\" json:\"-\"`\n\tcurDeleteProtection bool                                `bson:\"-\" json:\"-\"`\n\tcurAction           string                              `bson:\"-\" json:\"-\"`\n\tcurNoPublicAddress  bool                                `bson:\"-\" json:\"-\"`\n\tcurNoHostAddress    bool                                `bson:\"-\" json:\"-\"`\n\tcurNodePorts        map[bson.ObjectID]*nodeport.Mapping `bson:\"-\" json:\"-\"`\n\tremovedNodePorts    []bson.ObjectID                     `bson:\"-\" json:\"-\"`\n\tnewId               bool                                `bson:\"-\" json:\"-\"`\n}\n\ntype Completion struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tZone         bson.ObjectID `bson:\"zone\" json:\"zone\"`\n\tVpc          bson.ObjectID `bson:\"vpc\" json:\"vpc\"`\n\tSubnet       bson.ObjectID `bson:\"subnet\" json:\"subnet\"`\n\tNode         bson.ObjectID `bson:\"node\" json:\"node\"`\n}\n\ntype Mount struct {\n\tName     string `bson:\"name\" json:\"name\"`\n\tType     string `bson:\"type\" json:\"type\"`\n\tPath     string `bson:\"path\" json:\"path\"`\n\tHostPath string `bson:\"host_path\" json:\"host_path\"`\n}\n\ntype StatusInfo struct {\n\tDownloadProgress int     `bson:\"download_progress,omitempty\" json:\"download_progress\"`\n\tDownloadSpeed    float64 `bson:\"download_speed,omitempty\" json:\"download_speed\"`\n}\n\ntype GuestData struct {\n\tStatus    string              `bson:\"status\" json:\"status\"`\n\tTimestamp time.Time           `bson:\"timestamp\" json:\"timestamp\"`\n\tHeartbeat time.Time           `bson:\"heartbeat\" json:\"heartbeat\"`\n\tMemory    float64             `bson:\"memory\" json:\"memory\"`\n\tHugePages float64             `bson:\"hugepages\" json:\"hugepages\"`\n\tLoad1     float64             `bson:\"load1\" json:\"load1\"`\n\tLoad5     float64             `bson:\"load5\" json:\"load5\"`\n\tLoad15    float64             `bson:\"load15\" json:\"load15\"`\n\tUpdates   []*telemetry.Update `bson:\"updates\" json:\"updates\"`\n}\n\ntype Info struct {\n\tNode          string              `bson:\"node\" json:\"node\"`\n\tNodePublicIp  string              `bson:\"node_public_ip\" json:\"node_public_ip\"`\n\tMtu           int                 `bson:\"mtu\" json:\"mtu\"`\n\tIscsi         bool                `bson:\"iscsi\" json:\"iscsi\"`\n\tDisks         []string            `bson:\"disks\" json:\"disks\"`\n\tFirewallRules map[string]string   `bson:\"firewall_rules\" json:\"firewall_rules\"`\n\tAuthorities   []string            `bson:\"authorities\" json:\"authorities\"`\n\tIsos          []*iso.Iso          `bson:\"isos\" json:\"isos\"`\n\tUsbDevices    []*usb.Device       `bson:\"usb_devices\" json:\"usb_devices\"`\n\tPciDevices    []*pci.Device       `bson:\"pci_devices\" json:\"pci_devices\"`\n\tDriveDevices  []*drive.Device     `bson:\"drive_devices\" json:\"drive_devices\"`\n\tCloudSubnets  []*node.CloudSubnet `bson:\"cloud_subnets\" json:\"cloud_subnets\"`\n\tTimestamp     time.Time           `bson:\"timestamp\" json:\"timestamp\"`\n}\n\nfunc (i *Instance) GenerateId() (err error) {\n\tif !i.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"instance: Instance already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\ti.newId = true\n\ti.Id = bson.NewObjectID()\n\n\treturn\n}\n\nfunc (i *Instance) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\ti.Name = utils.FilterName(i.Name)\n\n\tif i.Action == \"\" {\n\t\ti.Action = Start\n\t}\n\n\tif i.Action != Start {\n\t\ti.Restart = false\n\t\ti.RestartReason = \"\"\n\t\ti.RestartBlockIp = false\n\t}\n\n\tif i.State != \"\" && !ValidStates.Contains(i.State) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_state\",\n\t\t\tMessage: \"Invalid instance state\",\n\t\t}\n\t\treturn\n\t}\n\n\tif !ValidActions.Contains(i.Action) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_action\",\n\t\t\tMessage: \"Invalid instance action\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.Organization.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"organization_required\",\n\t\t\tMessage: \"Missing required organization\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.Zone.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"zone_required\",\n\t\t\tMessage: \"Missing required zone\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.Node.IsZero() {\n\t\tif i.Shape.IsZero() {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"node_required\",\n\t\t\t\tMessage: \"Missing required node\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tshpe, e := shape.Get(db, i.Shape)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif !shpe.Flexible {\n\t\t\ti.Processors = shpe.Processors\n\t\t\ti.Memory = shpe.Memory\n\t\t}\n\n\t\tnde, e := shpe.FindNode(db, i.Processors, i.Memory)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\ti.Node = nde.Id\n\t\ti.DiskType = shpe.DiskType\n\t\ti.DiskPool = shpe.DiskPool\n\t}\n\n\tif i.Vpc.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"vpc_required\",\n\t\t\tMessage: \"Missing required VPC\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.UnixId == 0 {\n\t\ti.GenerateUnixId()\n\t}\n\n\tswitch i.DiskType {\n\tcase disk.Lvm:\n\t\tif i.DiskPool.IsZero() {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"pool_required\",\n\t\t\t\tMessage: \"Missing required disk pool\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase disk.Qcow2, \"\":\n\t\ti.DiskType = disk.Qcow2\n\t}\n\n\tvc, err := vpc.Get(db, i.Vpc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif i.Subnet.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"vpc_subnet_required\",\n\t\t\tMessage: \"Missing required VPC subnet\",\n\t\t}\n\t\treturn\n\t}\n\n\tsub := vc.GetSubnet(i.Subnet)\n\tif sub == nil {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"vpc_subnet_missing\",\n\t\t\tMessage: \"VPC subnet does not exist\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.InitDiskSize != 0 && i.InitDiskSize < 10 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"init_disk_size_invalid\",\n\t\t\tMessage: \"Disk size below minimum\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.Memory < 256 {\n\t\ti.Memory = 256\n\t}\n\n\tif i.Processors < 1 {\n\t\ti.Processors = 1\n\t}\n\n\tif i.Roles == nil {\n\t\ti.Roles = []string{}\n\t}\n\n\tif i.PublicIps == nil {\n\t\ti.PublicIps = []string{}\n\t}\n\n\tif i.PublicIps6 == nil {\n\t\ti.PublicIps6 = []string{}\n\t}\n\n\tif i.PrivateIps == nil {\n\t\ti.PrivateIps = []string{}\n\t}\n\n\tif i.PrivateIps6 == nil {\n\t\ti.PrivateIps6 = []string{}\n\t}\n\n\tif i.CloudType == \"\" {\n\t\ti.CloudType = Linux\n\t}\n\tif !ValidCloudTypes.Contains(i.CloudType) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_cloud_type\",\n\t\t\tMessage: \"Invalid cloud init type\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.SystemKind != \"\" && !ValidSystemKinds.Contains(i.SystemKind) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_system_kind\",\n\t\t\tMessage: \"Instance system kind invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.CloudScript != \"\" && !scriptReg.MatchString(i.CloudScript) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_cloud_script\",\n\t\t\tMessage: \"Startup script missing shebang on first line\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.TpmSecret == \"\" {\n\t\ti.TpmSecret, err = tpm.GenerateSecret()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tnde, err := node.Get(db, i.Node)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif i.Datacenter == bson.NilObjectID {\n\t\ti.Datacenter = nde.Datacenter\n\t}\n\n\tif i.Datacenter != vc.Datacenter {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"vpc_invalid_datacenter\",\n\t\t\tMessage: \"VPC must be in same datacenter as instance\",\n\t\t}\n\t\treturn\n\t}\n\n\tif i.CloudSubnet != \"\" {\n\t\tmatch := false\n\t\tfor _, subnet := range nde.CloudSubnets {\n\t\t\tif subnet == i.CloudSubnet {\n\t\t\t\tmatch = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !match {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"cloud_subnet_invalid\",\n\t\t\t\tMessage: \"Invalid Cloud subnet\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tif i.RootEnabled {\n\t\tif i.RootPasswd == \"\" {\n\t\t\ti.RootPasswd, err = utils.RandPasswd(8)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\ti.RootPasswd = \"\"\n\t}\n\n\tif i.Isos == nil {\n\t\ti.Isos = []*iso.Iso{}\n\t} else {\n\t\tfor _, is := range i.Isos {\n\t\t\tis.Name = utils.FilterRelPath(is.Name)\n\t\t}\n\t}\n\n\tif i.UsbDevices == nil {\n\t\ti.UsbDevices = []*usb.Device{}\n\t} else {\n\t\tfor _, device := range i.UsbDevices {\n\t\t\tdevice.Name = \"\"\n\t\t\tdevice.Vendor = usb.FilterId(device.Vendor)\n\t\t\tdevice.Product = usb.FilterId(device.Product)\n\t\t\tdevice.Bus = usb.FilterAddr(device.Bus)\n\t\t\tdevice.Address = usb.FilterAddr(device.Address)\n\n\t\t\tif (device.Vendor == \"\" || device.Product == \"\") &&\n\t\t\t\t(device.Bus == \"\" || device.Address == \"\") {\n\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"usb_device_invalid\",\n\t\t\t\t\tMessage: \"Invalid USB device\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tavailable, e := usb.Available(db, i.Id, i.Node, device)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !available {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"usb_device_unavailable\",\n\t\t\t\t\tMessage: \"USB device in use by another instance\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif i.PciDevices == nil {\n\t\ti.PciDevices = []*pci.Device{}\n\t} else {\n\t\tfor _, device := range i.PciDevices {\n\t\t\tdevice.Name = \"\"\n\t\t\tdevice.Class = \"\"\n\t\t\tdevice.Driver = \"\"\n\n\t\t\tif !pci.CheckSlot(device.Slot) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"pci_device_slot_invalid\",\n\t\t\t\t\tMessage: \"Invalid PCI slot\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tinstanceDrives := set.NewSet()\n\tnodeInstanceDrives := nde.InstanceDrives\n\tfor _, device := range nodeInstanceDrives {\n\t\tinstanceDrives.Add(device.Id)\n\t}\n\n\tif i.DriveDevices == nil {\n\t\ti.DriveDevices = []*drive.Device{}\n\t} else {\n\t\tfor _, device := range i.DriveDevices {\n\t\t\tif !instanceDrives.Contains(device.Id) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"drive_invalid\",\n\t\t\t\t\tMessage: \"Instance drive not available\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tiscsiDevices := []*iscsi.Device{}\n\tif i.IscsiDevices != nil {\n\t\tfor _, device := range i.IscsiDevices {\n\t\t\tif device.Uri == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\terrData, err = device.Parse()\n\t\t\tif err != nil || errData != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tiscsiDevices = append(iscsiDevices, device)\n\t\t}\n\t}\n\ti.IscsiDevices = iscsiDevices\n\n\tnewMounts := []*Mount{}\n\tfor _, mount := range i.Mounts {\n\t\tif mount.Name == \"\" && mount.HostPath == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tmount.Name = utils.FilterNameCmd(mount.Name)\n\t\tmount.Type = HostPath\n\t\tmount.Path = utils.FilterPath(mount.Path)\n\t\tmount.HostPath = utils.FilterPath(mount.HostPath)\n\n\t\tif mount.Name == \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"missing_mount_name\",\n\t\t\t\tMessage: \"Missing required mount name\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif mount.HostPath == \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"mount_host_path_invalid\",\n\t\t\t\tMessage: \"Mount host path invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tnewMounts = append(newMounts, mount)\n\t}\n\ti.Mounts = newMounts\n\n\tif i.Vnc {\n\t\tif i.VncPassword == \"\" {\n\t\t\ti.VncPassword, err = utils.RandPasswd(32)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\ti.VncPassword = \"\"\n\t}\n\n\tif i.Spice {\n\t\tif i.SpicePassword == \"\" {\n\t\t\ti.SpicePassword, err = utils.RandPasswd(32)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\ti.SpicePassword = \"\"\n\t}\n\n\texternalNodePorts := set.NewSet()\n\tfor _, mapping := range i.NodePorts {\n\t\textPortKey := fmt.Sprintf(\"%s:%d\",\n\t\t\tmapping.Protocol, mapping.ExternalPort)\n\n\t\tif !mapping.Delete {\n\t\t\tif externalNodePorts.Contains(extPortKey) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"node_port_external_duplicate\",\n\t\t\t\t\tMessage: \"Duplicate external node port\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\texternalNodePorts.Add(extPortKey)\n\n\t\terrData, err = mapping.Validate(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tavailable, e := nodeport.Available(db, i.Datacenter, i.Organization,\n\t\t\tmapping.Protocol, mapping.ExternalPort)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif !available {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"node_port_unavailable\",\n\t\t\t\tMessage: \"External node port is unavailable\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) GenerateUnixId() {\n\ti.UnixId = rand.Intn(55500) + 10000\n}\n\nfunc (i *Instance) InitUnixId(db *database.Database) (err error) {\n\tif i.UnixId != 0 {\n\t\treturn\n\t}\n\n\ti.GenerateUnixId()\n\n\terr = i.CommitFields(db, set.NewSet(\"unix_id\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) GenerateSpicePort() {\n\t// Spice 15000 - 19999\n\ti.SpicePort = rand.Intn(4999) + 15000\n}\n\nfunc (i *Instance) InitSpicePort(db *database.Database) (err error) {\n\tif i.SpicePort != 0 {\n\t\treturn\n\t}\n\n\ti.GenerateSpicePort()\n\n\tcoll := db.Instances()\n\n\tfor n := 0; n < 10000; n++ {\n\t\terr = coll.CommitFields(i.Id, i, set.NewSet(\"spice_port\"))\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\t\ti.GenerateSpicePort()\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\n\t\treturn\n\t}\n\n\terr = &errortypes.WriteError{\n\t\terrors.New(\"instance: Failed to commit unique spice port\"),\n\t}\n\treturn\n}\n\nfunc (i *Instance) GenerateVncDisplay() {\n\t// VNC 10001 - 14999 (+5900)\n\t// VNC WebSocket 20001 - 24999 (+15900)\n\ti.VncDisplay = rand.Intn(4999) + 4101\n}\n\nfunc (i *Instance) InitVncDisplay(db *database.Database) (err error) {\n\tif i.VncDisplay != 0 {\n\t\treturn\n\t}\n\n\ti.GenerateVncDisplay()\n\n\tcoll := db.Instances()\n\n\tfor n := 0; n < 10000; n++ {\n\t\terr = coll.CommitFields(i.Id, i, set.NewSet(\"vnc_display\"))\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\t\ti.GenerateVncDisplay()\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"instance.change\")\n\n\t\treturn\n\t}\n\n\terr = &errortypes.WriteError{\n\t\terrors.New(\"instance: Failed to commit unique vnc port\"),\n\t}\n\treturn\n}\n\nfunc (i *Instance) Format() {\n}\n\nfunc (i *Instance) Json(short bool) {\n\tswitch i.Action {\n\tcase Start:\n\t\tif i.Restart || i.RestartBlockIp {\n\t\t\ti.Status = \"Restart Required\"\n\t\t\tif i.RestartReason != \"\" {\n\t\t\t\ti.Status += fmt.Sprintf(\" (%s)\", i.RestartReason)\n\t\t\t}\n\t\t} else {\n\t\t\tswitch i.State {\n\t\t\tcase vm.Starting:\n\t\t\t\ti.Status = \"Starting\"\n\t\t\t\tbreak\n\t\t\tcase vm.Running:\n\t\t\t\ti.Status = \"Running\"\n\t\t\t\tbreak\n\t\t\tcase vm.Stopped:\n\t\t\t\ti.Status = \"Starting\"\n\t\t\t\tbreak\n\t\t\tcase vm.Failed:\n\t\t\t\ti.Status = \"Starting\"\n\t\t\t\tbreak\n\t\t\tcase vm.Updating:\n\t\t\t\ti.Status = \"Updating\"\n\t\t\t\tbreak\n\t\t\tcase vm.Provisioning:\n\t\t\t\ti.Status = \"Provisioning\"\n\t\t\t\tbreak\n\t\t\tcase \"\":\n\t\t\t\ti.Status = \"Provisioning\"\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tbreak\n\tcase Cleanup:\n\t\tswitch i.State {\n\t\tcase vm.Starting:\n\t\t\ti.Status = \"Stopping\"\n\t\t\tbreak\n\t\tcase vm.Running:\n\t\t\ti.Status = \"Stopping\"\n\t\t\tbreak\n\t\tcase vm.Stopped:\n\t\t\ti.Status = \"Stopping\"\n\t\t\tbreak\n\t\tcase vm.Failed:\n\t\t\ti.Status = \"Stopping\"\n\t\t\tbreak\n\t\tcase vm.Updating:\n\t\t\ti.Status = \"Updating\"\n\t\t\tbreak\n\t\tcase vm.Provisioning:\n\t\t\ti.Status = \"Stopping\"\n\t\t\tbreak\n\t\tcase \"\":\n\t\t\ti.Status = \"Stopping\"\n\t\t\tbreak\n\t\t}\n\t\tbreak\n\tcase Stop:\n\t\tswitch i.State {\n\t\tcase vm.Starting:\n\t\t\ti.Status = \"Stopping\"\n\t\t\tbreak\n\t\tcase vm.Running:\n\t\t\ti.Status = \"Stopping\"\n\t\t\tbreak\n\t\tcase vm.Stopped:\n\t\t\ti.Status = \"Stopped\"\n\t\t\tbreak\n\t\tcase vm.Failed:\n\t\t\ti.Status = \"Failed\"\n\t\t\tbreak\n\t\tcase vm.Updating:\n\t\t\ti.Status = \"Updating\"\n\t\t\tbreak\n\t\tcase vm.Provisioning:\n\t\t\ti.Status = \"Stopped\"\n\t\t\tbreak\n\t\tcase \"\":\n\t\t\ti.Status = \"Stopped\"\n\t\t\tbreak\n\t\t}\n\t\tbreak\n\tcase Restart:\n\t\ti.Status = \"Restarting\"\n\t\tbreak\n\tcase Destroy:\n\t\ti.Status = \"Destroying\"\n\t\tbreak\n\t}\n\n\tif !i.IsActive() && i.Guest != nil {\n\t\ti.Guest.Timestamp = time.Time{}\n\t\ti.Guest.Heartbeat = time.Time{}\n\t\ti.Guest.Memory = 0\n\t\ti.Guest.HugePages = 0\n\t\ti.Guest.Load1 = 0\n\t\ti.Guest.Load5 = 0\n\t\ti.Guest.Load15 = 0\n\t\ti.Guest.Updates = []*telemetry.Update{}\n\t}\n\n\ti.PublicMac = vm.GetMacAddrExternal(i.Id, i.Vpc)\n\tif i.Timestamp.IsZero() || !i.IsActive() {\n\t\ti.Uptime = \"\"\n\t} else {\n\t\tif short {\n\t\t\ti.Uptime = systemd.FormatUptimeShort(i.Timestamp)\n\t\t} else {\n\t\t\ti.Uptime = systemd.FormatUptime(i.Timestamp)\n\t\t}\n\t}\n\n\tif i.IscsiDevices != nil {\n\t\tfor _, device := range i.IscsiDevices {\n\t\t\tdevice.Json()\n\t\t}\n\t}\n}\n\nfunc (i *Instance) IsActive() bool {\n\treturn i.Action == Start || i.State == vm.Running ||\n\t\ti.State == vm.Starting || i.State == vm.Provisioning\n}\n\nfunc (i *Instance) IsIpv6Only() bool {\n\treturn (node.Self.NetworkMode == node.Disabled || i.NoPublicAddress) &&\n\t\t(node.Self.NetworkMode6 != node.Disabled && !i.NoPublicAddress6) &&\n\t\t(node.Self.NoHostNetwork || i.NoHostAddress)\n}\n\nfunc (i *Instance) PreCommit() {\n\ti.curVpc = i.Vpc\n\ti.curSubnet = i.Subnet\n\ti.curDeleteProtection = i.DeleteProtection\n\ti.curAction = i.Action\n\ti.curNoPublicAddress = i.NoPublicAddress\n\ti.curNoHostAddress = i.NoHostAddress\n\n\tnodePortMap := map[bson.ObjectID]*nodeport.Mapping{}\n\tfor _, mapping := range i.NodePorts {\n\t\tnodePortMap[mapping.NodePort] = mapping\n\t}\n\ti.curNodePorts = nodePortMap\n}\n\nfunc (i *Instance) UpsertNodePorts(newNodePorts []*nodeport.Mapping) {\n\tif len(i.NodePorts) == 0 {\n\t\ti.NodePorts = newNodePorts\n\t\treturn\n\t}\n\n\tprocessed := make(map[int]bool)\n\tnewMappings := []*nodeport.Mapping{}\n\n\tfor _, newMapping := range newNodePorts {\n\t\tmatched := false\n\n\t\tif newMapping.ExternalPort != 0 {\n\t\t\tfor x, curMapping := range i.NodePorts {\n\t\t\t\tif curMapping.Protocol == newMapping.Protocol &&\n\t\t\t\t\tcurMapping.InternalPort == newMapping.InternalPort &&\n\t\t\t\t\tcurMapping.ExternalPort == newMapping.ExternalPort {\n\n\t\t\t\t\tnewMapping.NodePort = curMapping.NodePort\n\t\t\t\t\tnewMappings = append(newMappings, newMapping)\n\n\t\t\t\t\tprocessed[x] = true\n\t\t\t\t\tmatched = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor x, curMapping := range i.NodePorts {\n\t\t\t\tif curMapping.Protocol == newMapping.Protocol &&\n\t\t\t\t\tcurMapping.InternalPort == newMapping.InternalPort &&\n\t\t\t\t\t!processed[x] {\n\n\t\t\t\t\tnewMapping.NodePort = curMapping.NodePort\n\t\t\t\t\tnewMapping.ExternalPort = curMapping.ExternalPort\n\t\t\t\t\tnewMappings = append(newMappings, newMapping)\n\n\t\t\t\t\tprocessed[x] = true\n\t\t\t\t\tmatched = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !matched {\n\t\t\tnewMappings = append(newMappings, newMapping)\n\t\t}\n\t}\n\n\ti.NodePorts = newMappings\n}\n\nfunc (i *Instance) SyncNodePorts(db *database.Database) (err error) {\n\tnewNodePorts := []*nodeport.Mapping{}\n\tnewNodePortIds := set.NewSet()\n\texternalPorts := set.NewSet()\n\n\tif i.curNodePorts == nil {\n\t\ti.curNodePorts = map[bson.ObjectID]*nodeport.Mapping{}\n\t}\n\n\tfor _, mapping := range i.NodePorts {\n\t\tif !mapping.NodePort.IsZero() {\n\t\t\tcurMapping := i.curNodePorts[mapping.NodePort]\n\t\t\tif curMapping == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tnewNodePortIds.Add(curMapping.NodePort)\n\n\t\t\tif mapping.Delete {\n\t\t\t\ti.removedNodePorts = append(\n\t\t\t\t\ti.removedNodePorts, curMapping.NodePort)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcurMapping.InternalPort = mapping.InternalPort\n\t\t\tmapping = curMapping\n\t\t}\n\n\t\tvar errData *errortypes.ErrorData\n\t\tvar ndePort *nodeport.NodePort\n\t\tif mapping.ExternalPort != 0 {\n\t\t\tndePort, err = nodeport.GetPort(db, i.Datacenter, i.Organization,\n\t\t\t\tmapping.Protocol, mapping.ExternalPort)\n\t\t\tif err != nil {\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\tndePort = nil\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ndePort == nil {\n\t\t\tndePort, errData, err = nodeport.New(db,\n\t\t\t\ti.Datacenter, i.Organization,\n\t\t\t\tmapping.Protocol, mapping.ExternalPort)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif errData != nil {\n\t\t\t\terr = errData.GetError()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tmapping.NodePort = ndePort.Id\n\t\tmapping.ExternalPort = ndePort.Port\n\n\t\textPortKey := fmt.Sprintf(\"%s:%d\",\n\t\t\tmapping.Protocol, mapping.ExternalPort)\n\t\tif externalPorts.Contains(extPortKey) {\n\t\t\tcontinue\n\t\t}\n\t\texternalPorts.Add(extPortKey)\n\n\t\tnewNodePorts = append(newNodePorts, mapping)\n\t}\n\ti.NodePorts = newNodePorts\n\n\tfor _, mapping := range i.curNodePorts {\n\t\tif newNodePortIds.Contains(mapping.NodePort) {\n\t\t\tcontinue\n\t\t}\n\t\ti.removedNodePorts = append(i.removedNodePorts, mapping.NodePort)\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) PostCommit(db *database.Database) (\n\tdskChange bool, err error) {\n\n\terr = i.SyncNodePorts(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif (!i.curVpc.IsZero() && i.curVpc != i.Vpc) ||\n\t\t(!i.curSubnet.IsZero() && i.curSubnet != i.Subnet) {\n\n\t\ti.DhcpIp = \"\"\n\t\ti.DhcpIp6 = \"\"\n\n\t\terr = vpc.RemoveInstanceIp(db, i.Id, i.curVpc)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif i.curDeleteProtection != i.DeleteProtection {\n\t\tdskChange = true\n\n\t\terr = disk.SetDeleteProtection(db, i.Id, i.DeleteProtection)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif i.curAction != i.Action && (i.Action == Stop || i.Action == Start ||\n\t\ti.Action == Restart) {\n\n\t\ti.Restart = false\n\t\ti.RestartBlockIp = false\n\t}\n\n\tif i.curNoPublicAddress != i.NoPublicAddress && i.NoPublicAddress {\n\t\terr = block.RemoveInstanceIpsType(db, i.Id, block.External)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif i.curNoHostAddress != i.NoHostAddress && i.NoHostAddress {\n\t\terr = block.RemoveInstanceIpsType(db, i.Id, block.Host)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) Cleanup(db *database.Database) (err error) {\n\tfor _, mapping := range i.NodePorts {\n\t\tndePort, e := nodeport.Get(db, mapping.NodePort)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = ndePort.Sync(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, ndePortId := range i.removedNodePorts {\n\t\tndePort, e := nodeport.Get(db, ndePortId)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = ndePort.Sync(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) Commit(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\n\terr = coll.Commit(i.Id, i)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Instances()\n\n\tif fields.Contains(\"unix_id\") {\n\t\tfor n := 0; n < 10000; n++ {\n\t\t\terr = coll.CommitFields(i.Id, i, fields)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\t\t\ti.GenerateUnixId()\n\t\t\t\t\terr = nil\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.New(\"instance: Failed to commit unique unix id\"),\n\t\t}\n\t\treturn\n\t} else {\n\t\terr = coll.CommitFields(i.Id, i, fields)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) Insert(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\n\tif !i.Id.IsZero() && !i.newId {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"instance: Instance already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\ti.Created = time.Now()\n\n\tfor n := 0; n < 2000; n++ {\n\t\tresp, e := coll.InsertOne(db, i)\n\t\tif e != nil {\n\t\t\terr = database.ParseError(e)\n\t\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\t\ti.GenerateUnixId()\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\ti.Id = resp.InsertedID.(bson.ObjectID)\n\t\treturn\n\t}\n\n\terr = &errortypes.WriteError{\n\t\terrors.New(\"instance: Failed to insert unique unix id\"),\n\t}\n\treturn\n}\n\nfunc (i *Instance) LoadVirt(poolsMap map[bson.ObjectID]*pool.Pool,\n\tdisks []*disk.Disk) {\n\n\ti.Virt = &vm.VirtualMachine{\n\t\tId:           i.Id,\n\t\tOrganization: i.Organization,\n\t\tUnixId:       i.UnixId,\n\t\tDiskType:     i.DiskType,\n\t\tDiskPool:     i.DiskPool,\n\t\tImage:        i.Image,\n\t\tProcessors:   i.Processors,\n\t\tMemory:       i.Memory,\n\t\tHugepages:    node.Self.Hugepages,\n\t\tVnc:          i.Vnc,\n\t\tVncDisplay:   i.VncDisplay,\n\t\tSpice:        i.Spice,\n\t\tSpicePort:    i.SpicePort,\n\t\tGui:          i.Gui,\n\t\tDisks:        []*vm.Disk{},\n\t\tNetworkAdapters: []*vm.NetworkAdapter{\n\t\t\t&vm.NetworkAdapter{\n\t\t\t\tType:       vm.Bridge,\n\t\t\t\tMacAddress: vm.GetMacAddr(i.Id, i.Vpc),\n\t\t\t\tVpc:        i.Vpc,\n\t\t\t\tSubnet:     i.Subnet,\n\t\t\t},\n\t\t},\n\t\tCloudSubnet:      i.CloudSubnet,\n\t\tCloudVnic:        i.CloudVnic,\n\t\tCloudVnicAttach:  i.CloudVnicAttach,\n\t\tDhcpIp:           i.DhcpIp,\n\t\tDhcpIp6:          i.DhcpIp6,\n\t\tUefi:             i.Uefi,\n\t\tSecureBoot:       i.SecureBoot,\n\t\tTpm:              i.Tpm,\n\t\tDhcpServer:       i.DhcpServer,\n\t\tDeployment:       i.Deployment,\n\t\tCloudType:        i.CloudType,\n\t\tSystemKind:       i.SystemKind,\n\t\tNoPublicAddress:  i.NoPublicAddress,\n\t\tNoPublicAddress6: i.NoPublicAddress6,\n\t\tNoHostAddress:    i.NoHostAddress,\n\t\tIsos:             []*vm.Iso{},\n\t\tUsbDevices:       []*vm.UsbDevice{},\n\t\tPciDevices:       []*vm.PciDevice{},\n\t\tDriveDevices:     []*vm.DriveDevice{},\n\t\tIscsiDevices:     []*vm.IscsiDevice{},\n\t\tMounts:           []*vm.Mount{},\n\t}\n\n\tif disks != nil {\n\t\tfor _, dsk := range disks {\n\t\t\tswitch dsk.Type {\n\t\t\tcase disk.Lvm:\n\t\t\t\tif poolsMap == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tpl := poolsMap[dsk.Pool]\n\t\t\t\tif pl == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\ti.Virt.DriveDevices = append(\n\t\t\t\t\ti.Virt.DriveDevices,\n\t\t\t\t\t&vm.DriveDevice{\n\t\t\t\t\t\tId:     dsk.Id.Hex(),\n\t\t\t\t\t\tType:   vm.Lvm,\n\t\t\t\t\t\tVgName: pl.VgName,\n\t\t\t\t\t\tLvName: dsk.Id.Hex(),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tcase disk.Qcow2, \"\":\n\t\t\t\tindex, err := strconv.Atoi(dsk.Index)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\ti.Virt.Disks = append(i.Virt.Disks, &vm.Disk{\n\t\t\t\t\tId:    dsk.Id,\n\t\t\t\t\tIndex: index,\n\t\t\t\t\tPath:  paths.GetDiskPath(dsk.Id),\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, is := range i.Isos {\n\t\ti.Virt.Isos = append(i.Virt.Isos, &vm.Iso{\n\t\t\tName: is.Name,\n\t\t})\n\t}\n\n\tif node.Self.UsbPassthrough && i.UsbDevices != nil {\n\t\tfor _, device := range i.UsbDevices {\n\t\t\tusbDevice, _ := usb.GetDevice(\n\t\t\t\tdevice.Bus, device.Address,\n\t\t\t\tdevice.Vendor, device.Product,\n\t\t\t)\n\n\t\t\tif usbDevice != nil {\n\t\t\t\ti.Virt.UsbDevices = append(i.Virt.UsbDevices, &vm.UsbDevice{\n\t\t\t\t\tVendor:  usbDevice.Vendor,\n\t\t\t\t\tProduct: usbDevice.Product,\n\t\t\t\t\tBus:     usbDevice.Bus,\n\t\t\t\t\tAddress: usbDevice.Address,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tif node.Self.PciPassthrough && i.PciDevices != nil {\n\t\tfor _, device := range i.PciDevices {\n\t\t\ti.Virt.PciDevices = append(i.Virt.PciDevices, &vm.PciDevice{\n\t\t\t\tSlot: device.Slot,\n\t\t\t})\n\t\t}\n\t}\n\n\tinstanceDrives := set.NewSet()\n\tnodeInstanceDrives := node.Self.InstanceDrives\n\tif nodeInstanceDrives != nil {\n\t\tfor _, device := range nodeInstanceDrives {\n\t\t\tinstanceDrives.Add(device.Id)\n\t\t}\n\t}\n\n\tif i.DriveDevices != nil {\n\t\tfor _, device := range i.DriveDevices {\n\t\t\tif instanceDrives.Contains(device.Id) {\n\t\t\t\ti.Virt.DriveDevices = append(\n\t\t\t\t\ti.Virt.DriveDevices,\n\t\t\t\t\t&vm.DriveDevice{\n\t\t\t\t\t\tId:   device.Id,\n\t\t\t\t\t\tType: vm.Physical,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tif node.Self.Iscsi && i.IscsiDevices != nil {\n\t\tfor _, device := range i.IscsiDevices {\n\t\t\ti.Virt.IscsiDevices = append(\n\t\t\t\ti.Virt.IscsiDevices,\n\t\t\t\t&vm.IscsiDevice{\n\t\t\t\t\tUri: device.QemuUri(),\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\tfor _, mount := range i.Mounts {\n\t\ti.Virt.Mounts = append(\n\t\t\ti.Virt.Mounts,\n\t\t\t&vm.Mount{\n\t\t\t\tName:     mount.Name,\n\t\t\t\tType:     mount.Type,\n\t\t\t\tPath:     mount.Path,\n\t\t\t\tHostPath: mount.HostPath,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) Changed(curVirt *vm.VirtualMachine) (bool, string) {\n\tcurCloudType := curVirt.CloudType\n\tif curCloudType == \"\" {\n\t\tcurCloudType = Linux\n\t}\n\tcloudType := i.Virt.CloudType\n\tif cloudType == \"\" {\n\t\tcloudType = Linux\n\t}\n\n\tif i.Virt.Memory != curVirt.Memory {\n\t\treturn true, \"Memory size changed\"\n\t}\n\tif i.Virt.Hugepages != curVirt.Hugepages {\n\t\treturn true, \"Hugepages changed\"\n\t}\n\tif i.Virt.Processors != curVirt.Processors {\n\t\treturn true, \"Processor count changed\"\n\t}\n\tif i.Virt.Vnc != curVirt.Vnc {\n\t\treturn true, \"VNC changed\"\n\t}\n\tif i.Virt.VncDisplay != curVirt.VncDisplay {\n\t\treturn true, \"VNC display changed\"\n\t}\n\tif i.Virt.Spice != curVirt.Spice {\n\t\treturn true, \"SPICE changed\"\n\t}\n\tif i.Virt.SpicePort != curVirt.SpicePort {\n\t\treturn true, \"SPICE port changed\"\n\t}\n\tif i.Virt.Gui != curVirt.Gui {\n\t\treturn true, \"GUI changed\"\n\t}\n\tif i.Virt.Uefi != curVirt.Uefi {\n\t\treturn true, \"UEFI changed\"\n\t}\n\tif i.Virt.SecureBoot != curVirt.SecureBoot {\n\t\treturn true, \"Secure boot changed\"\n\t}\n\tif i.Virt.Tpm != curVirt.Tpm {\n\t\treturn true, \"TPM changed\"\n\t}\n\tif i.Virt.DhcpServer != curVirt.DhcpServer {\n\t\treturn true, \"DHCP server changed\"\n\t}\n\tif cloudType != curCloudType {\n\t\treturn true, \"Cloud type changed\"\n\t}\n\tif i.Virt.NoPublicAddress != curVirt.NoPublicAddress {\n\t\treturn true, \"Public address changed\"\n\t}\n\tif i.Virt.NoPublicAddress6 != curVirt.NoPublicAddress6 {\n\t\treturn true, \"Public IPv6 changed\"\n\t}\n\tif i.Virt.NoHostAddress != curVirt.NoHostAddress {\n\t\treturn true, \"Host address changed\"\n\t}\n\n\tfor i, adapter := range i.Virt.NetworkAdapters {\n\t\tif len(curVirt.NetworkAdapters) <= i {\n\t\t\treturn true, \"Network adapters changed\"\n\t\t}\n\n\t\tif adapter.Vpc != curVirt.NetworkAdapters[i].Vpc {\n\t\t\treturn true, \"VPC changed\"\n\t\t}\n\n\t\tif adapter.Subnet != curVirt.NetworkAdapters[i].Subnet {\n\t\t\treturn true, \"Subnet changed\"\n\t\t}\n\t}\n\n\tif i.Virt.Isos != nil {\n\t\tif len(i.Virt.Isos) > 0 && curVirt.Isos == nil {\n\t\t\treturn true, \"ISO devices changed\"\n\t\t}\n\n\t\tfor i, device := range i.Virt.Isos {\n\t\t\tif len(curVirt.Isos) <= i {\n\t\t\t\treturn true, \"ISO devices changed\"\n\t\t\t}\n\n\t\t\tif device.Name != curVirt.Isos[i].Name {\n\t\t\t\treturn true, \"ISO device changed\"\n\t\t\t}\n\t\t}\n\t}\n\n\tif i.Virt.PciDevices != nil {\n\t\tif len(i.Virt.PciDevices) > 0 && curVirt.PciDevices == nil {\n\t\t\treturn true, \"PCI devices changed\"\n\t\t}\n\n\t\tfor i, device := range i.Virt.PciDevices {\n\t\t\tif len(curVirt.PciDevices) <= i {\n\t\t\t\treturn true, \"PCI devices changed\"\n\t\t\t}\n\n\t\t\tif device.Slot != curVirt.PciDevices[i].Slot {\n\t\t\t\treturn true, \"PCI device slot changed\"\n\t\t\t}\n\t\t}\n\t}\n\n\tif i.Virt.DriveDevices != nil {\n\t\tif len(i.Virt.DriveDevices) > 0 && curVirt.DriveDevices == nil {\n\t\t\treturn true, \"Drive devices changed\"\n\t\t}\n\n\t\tfor i, device := range i.Virt.DriveDevices {\n\t\t\tif len(curVirt.DriveDevices) <= i {\n\t\t\t\treturn true, \"Drive devices changed\"\n\t\t\t}\n\n\t\t\tif device.Id != curVirt.DriveDevices[i].Id {\n\t\t\t\treturn true, \"Drive device changed\"\n\t\t\t}\n\t\t}\n\t}\n\n\tif i.Virt.IscsiDevices != nil {\n\t\tif len(i.Virt.IscsiDevices) > 0 && curVirt.IscsiDevices == nil {\n\t\t\treturn true, \"iSCSI devices changed\"\n\t\t}\n\n\t\tfor i, device := range i.Virt.IscsiDevices {\n\t\t\tif len(curVirt.IscsiDevices) <= i {\n\t\t\t\treturn true, \"iSCSI devices changed\"\n\t\t\t}\n\n\t\t\tif device.Uri != curVirt.IscsiDevices[i].Uri {\n\t\t\t\treturn true, \"iSCSI URI changed\"\n\t\t\t}\n\t\t}\n\t}\n\n\tif i.Virt.Mounts != nil {\n\t\tif len(i.Virt.Mounts) > 0 && curVirt.Mounts == nil {\n\t\t\treturn true, \"Mounts changed\"\n\t\t}\n\n\t\tfor i, mount := range i.Virt.Mounts {\n\t\t\tif len(curVirt.Mounts) <= i {\n\t\t\t\treturn true, \"Mounts changed\"\n\t\t\t}\n\n\t\t\tif mount.Name != curVirt.Mounts[i].Name {\n\t\t\t\treturn true, \"Mount name changed\"\n\t\t\t}\n\t\t\tif mount.Type != curVirt.Mounts[i].Type {\n\t\t\t\treturn true, \"Mount type changed\"\n\t\t\t}\n\t\t\tif mount.Path != curVirt.Mounts[i].Path {\n\t\t\t\treturn true, \"Mount path changed\"\n\t\t\t}\n\t\t\tif mount.HostPath != curVirt.Mounts[i].HostPath {\n\t\t\t\treturn true, \"Mount host path changed\"\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false, \"\"\n}\n\nfunc (i *Instance) DiskChanged(curVirt *vm.VirtualMachine) (\n\taddDisks, remDisks []*vm.Disk) {\n\n\taddDisks = []*vm.Disk{}\n\tremDisks = []*vm.Disk{}\n\n\tif !curVirt.DisksAvailable {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": curVirt.Id.Hex(),\n\t\t}).Warn(\"qemu: Ignoring disk state\")\n\t\treturn\n\t}\n\n\tdisks := map[bson.ObjectID]*vm.Disk{}\n\tcurDisks := set.NewSet()\n\n\tfor _, dsk := range i.Virt.Disks {\n\t\tdisks[dsk.Id] = dsk\n\t}\n\n\tfor _, dsk := range curVirt.Disks {\n\t\tnewDsk := disks[dsk.Id]\n\n\t\tif newDsk == nil || dsk.Index != newDsk.Index {\n\t\t\tremDisks = append(remDisks, dsk)\n\t\t} else {\n\t\t\tcurDisks.Add(dsk.Id)\n\t\t}\n\t}\n\n\tfor _, dsk := range i.Virt.Disks {\n\t\tif !curDisks.Contains(dsk.Id) {\n\t\t\taddDisks = append(addDisks, dsk)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) UsbChanged(curVirt *vm.VirtualMachine) (\n\taddUsbs, remUsbs []*vm.UsbDevice) {\n\n\taddUsbs = []*vm.UsbDevice{}\n\tremUsbs = []*vm.UsbDevice{}\n\n\tif !node.Self.UsbPassthrough {\n\t\treturn\n\t}\n\n\tif !curVirt.UsbDevicesAvailable {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": curVirt.Id.Hex(),\n\t\t}).Warn(\"qemu: Ignoring USB state\")\n\t\treturn\n\t}\n\n\tusbs := set.NewSet()\n\tusbsMap := map[string]*vm.UsbDevice{}\n\tcurUsbs := set.NewSet()\n\tcurUsbsMap := map[string]*vm.UsbDevice{}\n\n\tif curVirt.UsbDevices != nil {\n\t\tfor _, device := range curVirt.UsbDevices {\n\t\t\tkey := device.Key()\n\t\t\tcurUsbs.Add(key)\n\t\t\tcurUsbsMap[key] = device\n\t\t}\n\t}\n\n\tif i.Virt.UsbDevices != nil {\n\t\tfor _, device := range i.Virt.UsbDevices {\n\t\t\tkey := device.Key()\n\t\t\tusbs.Add(key)\n\t\t\tusbsMap[key] = device\n\t\t}\n\t}\n\n\taddUsbsSet := usbs.Copy()\n\taddUsbsSet.Subtract(curUsbs)\n\tremUsbsSet := curUsbs.Copy()\n\tremUsbsSet.Subtract(usbs)\n\n\tfor deviceInf := range addUsbsSet.Iter() {\n\t\tdevice := usbsMap[deviceInf.(string)]\n\t\taddUsbs = append(addUsbs, device)\n\t}\n\tfor deviceInf := range remUsbsSet.Iter() {\n\t\tdevice := curUsbsMap[deviceInf.(string)]\n\t\tremUsbs = append(remUsbs, device)\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) VncConnect(db *database.Database,\n\trw http.ResponseWriter, r *http.Request) (err error) {\n\n\tnde, err := node.Get(db, i.Node)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvncHost := \"\"\n\tif nde.Id == node.Self.Id {\n\t\tvncHost = \"127.0.0.1\"\n\t}\n\tif vncHost == \"\" && len(nde.PrivateIps) > 0 {\n\t\tvncHost = nde.PrivateIps[nde.DefaultInterface]\n\t\tif vncHost == \"\" {\n\t\t\tfor _, privIp := range nde.PrivateIps {\n\t\t\t\tif privIp != \"\" {\n\t\t\t\t\tvncHost = privIp\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif vncHost == \"\" && len(nde.PublicIps) > 0 {\n\t\tvncHost = nde.PublicIps[0]\n\t}\n\tif vncHost == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"instance: Node missing IP for VNC\"),\n\t\t}\n\t\treturn\n\t}\n\n\twsUrl := fmt.Sprintf(\n\t\t\"ws://%s:%d\",\n\t\tvncHost,\n\t\ti.VncDisplay+15900,\n\t)\n\n\tvar backConn *websocket.Conn\n\tvar backResp *http.Response\n\n\tdialer := &websocket.Dialer{\n\t\tHandshakeTimeout: 10 * time.Second,\n\t}\n\n\theader := http.Header{}\n\theader.Set(\n\t\t\"Sec-Websocket-Protocol\",\n\t\tr.Header.Get(\"Sec-Websocket-Protocol\"),\n\t)\n\n\tbackConn, backResp, err = dialer.Dial(wsUrl, header)\n\tif err != nil {\n\t\tif backResp != nil {\n\t\t\terr = &VncDialError{\n\t\t\t\terrors.Wrapf(err, \"instance: WebSocket dial error %d\",\n\t\t\t\t\tbackResp.StatusCode),\n\t\t\t}\n\t\t} else {\n\t\t\terr = &VncDialError{\n\t\t\t\terrors.Wrap(err, \"instance: WebSocket dial error\"),\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\tdefer backConn.Close()\n\n\twsUpgrader := &websocket.Upgrader{\n\t\tHandshakeTimeout: time.Duration(\n\t\t\tsettings.Router.HandshakeTimeout) * time.Second,\n\t\tReadBufferSize:  2048,\n\t\tWriteBufferSize: 2048,\n\t\tCheckOrigin: func(r *http.Request) bool {\n\t\t\treturn true\n\t\t},\n\t}\n\n\tupgradeHeader := http.Header{}\n\tval := backResp.Header.Get(\"Sec-Websocket-Protocol\")\n\tif val != \"\" {\n\t\tupgradeHeader.Set(\"Sec-Websocket-Protocol\", val)\n\t}\n\n\tfrontConn, err := wsUpgrader.Upgrade(rw, r, upgradeHeader)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"instance: WebSocket upgrade error\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer frontConn.Close()\n\n\twait := make(chan bool, 4)\n\tgo func() {\n\t\tdefer func() {\n\t\t\trec := recover()\n\t\t\tif rec != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"panic\": rec,\n\t\t\t\t}).Error(\"instance: WebSocket VNC back panic\")\n\t\t\t\twait <- true\n\t\t\t}\n\t\t}()\n\n\t\tfor {\n\t\t\tmsgType, msg, err := frontConn.ReadMessage()\n\t\t\tif err != nil {\n\t\t\t\tcloseMsg := websocket.FormatCloseMessage(\n\t\t\t\t\twebsocket.CloseNormalClosure, fmt.Sprintf(\"%v\", err))\n\t\t\t\tif e, ok := err.(*websocket.CloseError); ok {\n\t\t\t\t\tif e.Code != websocket.CloseNoStatusReceived {\n\t\t\t\t\t\tcloseMsg = websocket.FormatCloseMessage(e.Code, e.Text)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_ = backConn.WriteMessage(websocket.CloseMessage, closeMsg)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\terr = backConn.WriteMessage(msgType, msg)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ReadError{\n\t\t\t\t\terrors.Wrap(err, \"instance: WebSocket VNC write error\"),\n\t\t\t\t}\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"instance: WebSocket VNC back write error\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\twait <- true\n\t}()\n\tgo func() {\n\t\tdefer func() {\n\t\t\trec := recover()\n\t\t\tif rec != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"panic\": rec,\n\t\t\t\t}).Error(\"instance: WebSocket VNC front panic\")\n\t\t\t\twait <- true\n\t\t\t}\n\t\t}()\n\n\t\tfor {\n\t\t\tmsgType, msg, err := backConn.ReadMessage()\n\t\t\tif err != nil {\n\t\t\t\tcloseMsg := websocket.FormatCloseMessage(\n\t\t\t\t\twebsocket.CloseNormalClosure, fmt.Sprintf(\"%v\", err))\n\t\t\t\tif e, ok := err.(*websocket.CloseError); ok {\n\t\t\t\t\tif e.Code != websocket.CloseNoStatusReceived {\n\t\t\t\t\t\tcloseMsg = websocket.FormatCloseMessage(e.Code, e.Text)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_ = frontConn.WriteMessage(websocket.CloseMessage, closeMsg)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\terr = frontConn.WriteMessage(msgType, msg)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ReadError{\n\t\t\t\t\terrors.Wrap(err, \"instance: WebSocket VNC write error\"),\n\t\t\t\t}\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"instance: WebSocket VNC back write error\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\twait <- true\n\t}()\n\t<-wait\n\n\treturn\n}\n"
  },
  {
    "path": "instance/utils.go",
    "content": "package instance\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/journal\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc Get(db *database.Database, instId bson.ObjectID) (\n\tinst *Instance, err error) {\n\n\tcoll := db.Instances()\n\tinst = &Instance{}\n\n\terr = coll.FindOneId(instId, inst)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, instId bson.ObjectID) (\n\tinst *Instance, err error) {\n\n\tcoll := db.Instances()\n\tinst = &Instance{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          instId,\n\t\t\"organization\": orgId,\n\t}).Decode(inst)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (inst *Instance, err error) {\n\tcoll := db.Instances()\n\tinst = &Instance{}\n\n\terr = coll.FindOne(db, query).Decode(inst)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExistsIp(db *database.Database, addr string) (exists bool, err error) {\n\tcoll := db.Instances()\n\n\tn, err := coll.CountDocuments(db, &bson.M{\n\t\t\"$or\": []bson.M{\n\t\t\t{\"public_ips\": addr},\n\t\t\t{\"public_ips6\": addr},\n\t\t\t{\"cloud_private_ips\": addr},\n\t\t\t{\"cloud_public_ips\": addr},\n\t\t\t{\"cloud_public_ips6\": addr},\n\t\t\t{\"host_ips\": addr},\n\t\t\t{\"node_port_ips\": addr},\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif n > 0 {\n\t\texists = true\n\t}\n\n\treturn\n}\n\nfunc ExistsOrg(db *database.Database, orgId, instId bson.ObjectID) (\n\texists bool, err error) {\n\n\tcoll := db.Instances()\n\n\tn, err := coll.CountDocuments(db, &bson.M{\n\t\t\"_id\":          instId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif n > 0 {\n\t\texists = true\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tinsts []*Instance, err error) {\n\n\tcoll := db.Instances()\n\tinsts = []*Instance{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tinst := &Instance{}\n\t\terr = cursor.Decode(inst)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tinsts = append(insts, inst)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllRoles(db *database.Database, query *bson.M) (\n\tinsts []*Instance, rolesSet set.Set, err error) {\n\n\tcoll := db.Instances()\n\tinsts = []*Instance{}\n\trolesSet = set.NewSet()\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tinst := &Instance{}\n\t\terr = cursor.Decode(inst)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tinsts = append(insts, inst)\n\n\t\tfor _, role := range inst.Roles {\n\t\t\trolesSet.Add(role)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllVirt(db *database.Database, query *bson.M,\n\tpools []*pool.Pool, disks []*disk.Disk) (\n\tinsts []*Instance, err error) {\n\n\tpoolsMap := map[bson.ObjectID]*pool.Pool{}\n\tfor _, pl := range pools {\n\t\tpoolsMap[pl.Id] = pl\n\t}\n\n\tinstanceDisks := map[bson.ObjectID][]*disk.Disk{}\n\tif disks != nil {\n\t\tfor _, dsk := range disks {\n\t\t\tif dsk.Action == disk.Destroy {\n\t\t\t\tif dsk.DeleteProtection {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t\t\t}).Info(\"instance: Delete protection ignore disk detach\")\n\t\t\t\t} else {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else if dsk.State != disk.Available &&\n\t\t\t\tdsk.State != disk.Attached {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdsks := instanceDisks[dsk.Instance]\n\t\t\tif dsks == nil {\n\t\t\t\tdsks = []*disk.Disk{}\n\t\t\t}\n\t\t\tinstanceDisks[dsk.Instance] = append(dsks, dsk)\n\t\t}\n\t}\n\n\tcoll := db.Instances()\n\tinsts = []*Instance{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tinst := &Instance{}\n\t\terr = cursor.Decode(inst)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tinst.LoadVirt(poolsMap, instanceDisks[inst.Id])\n\t\tinsts = append(insts, inst)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllVirtMapped(db *database.Database, query *bson.M,\n\tpools []*pool.Pool, instanceDisks map[bson.ObjectID][]*disk.Disk) (\n\tinsts []*Instance, err error) {\n\n\tcoll := db.Instances()\n\tinsts = []*Instance{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tpoolsMap := map[bson.ObjectID]*pool.Pool{}\n\tfor _, pl := range pools {\n\t\tpoolsMap[pl.Id] = pl\n\t}\n\n\tfor cursor.Next(db) {\n\t\tinst := &Instance{}\n\t\terr = cursor.Decode(inst)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvirtDsks := []*disk.Disk{}\n\n\t\tdsks := instanceDisks[inst.Id]\n\t\tif dsks != nil {\n\t\t\tfor _, dsk := range dsks {\n\t\t\t\tif dsk.Action == disk.Destroy {\n\t\t\t\t\tif dsk.DeleteProtection {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t\t\t\t}).Info(\"instance: Delete protection ignore disk detach\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t} else if dsk.State != disk.Available &&\n\t\t\t\t\tdsk.State != disk.Attached {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvirtDsks = append(virtDsks, dsk)\n\t\t\t}\n\t\t}\n\n\t\tinst.LoadVirt(poolsMap, virtDsks)\n\t\tinsts = append(insts, inst)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc LoadAllVirt(insts []*Instance, pools []*pool.Pool,\n\tinstanceDisks map[bson.ObjectID][]*disk.Disk) []*Instance {\n\n\tpoolsMap := map[bson.ObjectID]*pool.Pool{}\n\tfor _, pl := range pools {\n\t\tpoolsMap[pl.Id] = pl\n\t}\n\n\tfor _, inst := range insts {\n\t\tvirtDsks := []*disk.Disk{}\n\n\t\tdsks := instanceDisks[inst.Id]\n\t\tfor _, dsk := range dsks {\n\t\t\tif dsk.Action == disk.Destroy {\n\t\t\t\tif dsk.DeleteProtection {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"disk_id\": dsk.Id.Hex(),\n\t\t\t\t\t}).Info(\"instance: Delete protection ignore disk detach\")\n\t\t\t\t} else {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else if dsk.State != disk.Available &&\n\t\t\t\tdsk.State != disk.Attached {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvirtDsks = append(virtDsks, dsk)\n\t\t}\n\n\t\tinst.LoadVirt(poolsMap, virtDsks)\n\t}\n\n\treturn insts\n}\n\nfunc GetAllName(db *database.Database, query *bson.M) (\n\tinstances []*Instance, err error) {\n\n\tcoll := db.Instances()\n\tinstances = []*Instance{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetProjection(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}),\n\t)\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tinst := &Instance{}\n\t\terr = cursor.Decode(inst)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tinstances = append(instances, inst)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (insts []*Instance, count int64, err error) {\n\n\tcoll := db.Instances()\n\tinsts = []*Instance{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tinst := &Instance{}\n\t\terr = cursor.Decode(inst)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tinsts = append(insts, inst)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, instId bson.ObjectID) (err error) {\n\tinst, err := Get(db, instId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif inst.DeleteProtection {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": instId.Hex(),\n\t\t}).Info(\"instance: Delete protection ignore instance remove\")\n\t\treturn\n\t}\n\n\terr = block.RemoveInstanceIps(db, instId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = vpc.RemoveInstanceIps(db, instId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = journal.RemoveAll(db, instId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcoll := db.Instances()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": instId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\t_ = inst.Cleanup(db)\n\n\treturn\n}\n\nfunc Delete(db *database.Database, instId bson.ObjectID) (err error) {\n\tcoll := db.Instances()\n\n\terr = coll.UpdateId(instId, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Destroy,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeleteOrg(db *database.Database, orgId, instId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Instances()\n\n\terr = coll.UpdateId(instId, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Destroy,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeleteMulti(db *database.Database, instIds []bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Instances()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": instIds,\n\t\t},\n\t\t\"delete_protection\": &bson.M{\n\t\t\t\"$ne\": true,\n\t\t},\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Destroy,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeleteMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tinstIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Instances()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": instIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t\t\"delete_protection\": &bson.M{\n\t\t\t\"$ne\": true,\n\t\t},\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": Destroy,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc UpdateMulti(db *database.Database, instIds []bson.ObjectID,\n\tdoc *bson.M) (err error) {\n\n\tcoll := db.Instances()\n\n\tquery := &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": instIds,\n\t\t},\n\t\t\"action\": &bson.M{\n\t\t\t\"$ne\": Destroy,\n\t\t},\n\t}\n\n\tif (*doc)[\"action\"] == Destroy {\n\t\t(*query)[\"delete_protection\"] = &bson.M{\n\t\t\t\"$ne\": true,\n\t\t}\n\t}\n\n\t_, err = coll.UpdateMany(db, query, &bson.M{\n\t\t\"$set\": doc,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc UpdateMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tinstIds []bson.ObjectID, doc *bson.M) (err error) {\n\n\tcoll := db.Instances()\n\n\tquery := &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": instIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t\t\"action\": &bson.M{\n\t\t\t\"$ne\": Destroy,\n\t\t},\n\t}\n\n\tif (*doc)[\"action\"] == Destroy {\n\t\t(*query)[\"delete_protection\"] = &bson.M{\n\t\t\t\"$ne\": true,\n\t\t}\n\t}\n\n\t_, err = coll.UpdateMany(db, query, &bson.M{\n\t\t\"$set\": doc,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc SetAction(db *database.Database, instId bson.ObjectID,\n\taction string) (err error) {\n\n\tcoll := db.Instances()\n\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\": instId,\n\t\t\"action\": &bson.M{\n\t\t\t\"$ne\": Destroy,\n\t\t},\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\": action,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc SetDownloadProgress(db *database.Database,\n\tinstId bson.ObjectID, progress int, speedMb float64) (err error) {\n\n\tcoll := db.Instances()\n\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\": instId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"status_info.download_progress\": progress,\n\t\t\t\"status_info.download_speed\":    speedMb,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "interfaces/interfaces.go",
    "content": "package interfaces\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tcurVxlan   = false\n\tifaces     = map[string]set.Set{}\n\tifacesLock = sync.Mutex{}\n\tlastChange time.Time\n)\n\nfunc getIfaces(bridge string) (ifacesSet set.Set, err error) {\n\tifacesSet = set.NewSet()\n\n\tifaces, err := iproute.IfaceGetBridgeIfaces(\"\", bridge)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, iface := range ifaces {\n\t\tifacesSet.Add(iface.Name)\n\t}\n\n\treturn\n}\n\nfunc SyncIfaces(vxlan bool) {\n\tnde := node.Self\n\n\tif vxlan == curVxlan && time.Since(lastChange) < 10*time.Second {\n\t\treturn\n\t}\n\tcurVxlan = vxlan\n\n\tifacesNew := map[string]set.Set{}\n\n\texternalIfaces := nde.ExternalInterfaces\n\tinternalIfaces := nde.InternalInterfaces\n\tblocks := nde.Blocks\n\n\tfor _, iface := range externalIfaces {\n\t\tifaceSet, err := getIfaces(iface)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"bridge\": iface,\n\t\t\t\t\"error\":  err,\n\t\t\t}).Error(\"interfaces: Bridge ifaces get failed\")\n\t\t} else {\n\t\t\tifacesNew[iface] = ifaceSet\n\t\t}\n\t}\n\n\tfor _, iface := range internalIfaces {\n\t\tifaceSet, err := getIfaces(iface)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"bridge\": iface,\n\t\t\t\t\"error\":  err,\n\t\t\t}).Error(\"interfaces: Bridge ifaces get failed\")\n\t\t} else {\n\t\t\tifacesNew[iface] = ifaceSet\n\t\t}\n\n\t\tbrIface := vm.GetHostBridgeIface(iface)\n\t\tifaceSet, err = getIfaces(brIface)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"bridge\": brIface,\n\t\t\t\t\"error\":  err,\n\t\t\t}).Error(\"interfaces: Bridge ifaces get failed\")\n\t\t} else {\n\t\t\tifacesNew[brIface] = ifaceSet\n\t\t}\n\t}\n\n\tfor _, blck := range blocks {\n\t\tif blck.Interface == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tifaceSet, err := getIfaces(blck.Interface)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"bridge\": blck.Interface,\n\t\t\t\t\"error\":  err,\n\t\t\t}).Error(\"interfaces: Bridge ifaces get failed\")\n\t\t} else {\n\t\t\tifacesNew[blck.Interface] = ifaceSet\n\t\t}\n\t}\n\n\tifacesLock.Lock()\n\tlastChange = time.Now()\n\tifaces = ifacesNew\n\tifacesLock.Unlock()\n\n\treturn\n}\n\nfunc GetExternal(virtIface string) (externalIface string) {\n\texternalIfaces := node.Self.ExternalInterfaces\n\n\tif externalIfaces != nil {\n\t\tcurLen := 0\n\n\t\tfor _, iface := range externalIfaces {\n\t\t\tifacesSetLen := 0\n\n\t\t\tifacesLock.Lock()\n\t\t\tifacesSet := ifaces[iface]\n\t\t\tif ifacesSet != nil {\n\t\t\t\tifacesSetLen = ifacesSet.Len()\n\t\t\t}\n\t\t\tifacesLock.Unlock()\n\n\t\t\tif ifacesSet == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif externalIface == \"\" || ifacesSetLen < curLen {\n\t\t\t\tcurLen = ifacesSetLen\n\t\t\t\texternalIface = iface\n\t\t\t}\n\t\t}\n\n\t\tif externalIface != \"\" {\n\t\t\tifacesLock.Lock()\n\t\t\tlastChange = time.Now()\n\t\t\tifacesSet := ifaces[externalIface]\n\t\t\tif ifacesSet != nil {\n\t\t\t\tifacesSet.Add(virtIface)\n\t\t\t}\n\t\t\tifacesLock.Unlock()\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc HasExternal() (exists bool) {\n\texternalIfaces := node.Self.ExternalInterfaces\n\texternalIface := \"\"\n\n\tif len(externalIfaces) > 0 {\n\t\texternalIface = externalIfaces[0]\n\t}\n\n\tif externalIface != \"\" {\n\t\texists = true\n\t}\n\n\treturn\n}\n\nfunc GetInternal(virtIface string, vxlan bool) (internalIface string) {\n\tinternalIfaces := node.Self.InternalInterfaces\n\n\tif internalIfaces != nil {\n\t\tcurLen := 0\n\n\t\tfor _, iface := range internalIfaces {\n\t\t\tif vxlan {\n\t\t\t\tiface = vm.GetHostBridgeIface(iface)\n\t\t\t}\n\n\t\t\tifacesSetLen := 0\n\n\t\t\tifacesLock.Lock()\n\t\t\tifacesSet := ifaces[iface]\n\t\t\tif ifacesSet != nil {\n\t\t\t\tifacesSetLen = ifacesSet.Len()\n\t\t\t}\n\t\t\tifacesLock.Unlock()\n\n\t\t\tif ifacesSet == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif internalIface == \"\" || ifacesSetLen < curLen {\n\t\t\t\tcurLen = ifacesSetLen\n\t\t\t\tinternalIface = iface\n\t\t\t}\n\t\t}\n\n\t\tif internalIface != \"\" {\n\t\t\tifacesLock.Lock()\n\t\t\tlastChange = time.Now()\n\t\t\tifacesSet := ifaces[internalIface]\n\t\t\tif ifacesSet != nil {\n\t\t\t\tifacesSet.Add(virtIface)\n\t\t\t}\n\t\t\tifacesLock.Unlock()\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GetBridges(nde *node.Node) (bridges set.Set) {\n\tbridges = set.NewSet()\n\n\texternalIfaces := nde.ExternalInterfaces\n\tfor _, iface := range externalIfaces {\n\t\tbridges.Add(iface)\n\t}\n\n\tinternalIfaces := nde.InternalInterfaces\n\tfor _, iface := range internalIfaces {\n\t\tbridges.Add(iface)\n\t}\n\n\tndeBlocks := nde.Blocks\n\tfor _, blck := range ndeBlocks {\n\t\tif blck.Interface == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tbridges.Add(blck.Interface)\n\t}\n\n\tndeBlocks6 := nde.Blocks6\n\tfor _, blck := range ndeBlocks6 {\n\t\tif blck.Interface == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tbridges.Add(blck.Interface)\n\t}\n\n\treturn\n}\n\nfunc GetBridgesInternal(nde *node.Node) (bridges set.Set) {\n\tbridges = set.NewSet()\n\n\tinternalIfaces := nde.InternalInterfaces\n\tfor _, iface := range internalIfaces {\n\t\tbridges.Add(iface)\n\t}\n\n\treturn\n}\n\nfunc GetBridgesExternal(nde *node.Node) (bridges set.Set) {\n\tbridges = set.NewSet()\n\n\texternalIfaces := nde.ExternalInterfaces\n\tfor _, iface := range externalIfaces {\n\t\tbridges.Add(iface)\n\t}\n\n\tndeBlocks := nde.Blocks\n\tfor _, blck := range ndeBlocks {\n\t\tif blck.Interface == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tbridges.Add(blck.Interface)\n\t}\n\n\tndeBlocks6 := nde.Blocks6\n\tfor _, blck := range ndeBlocks6 {\n\t\tif blck.Interface == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tbridges.Add(blck.Interface)\n\t}\n\n\treturn\n}\n\nfunc RemoveVirtIface(virtIface string) {\n\tif virtIface == \"\" {\n\t\treturn\n\t}\n\n\tifacesLock.Lock()\n\tlastChange = time.Now()\n\tfor iface, ifaceSet := range ifaces {\n\t\tifaceSet.Remove(virtIface)\n\t\tifaces[iface] = ifaceSet\n\t}\n\tifacesLock.Unlock()\n}\n"
  },
  {
    "path": "ip/interface.go",
    "content": "package ip\n\ntype Interface struct {\n\tName    string `bson:\"name\" json:\"name\"`\n\tAddress string `bson:\"address\" json:\"address\"`\n}\n"
  },
  {
    "path": "ip/ip.go",
    "content": "package ip\n\nimport (\n\t\"encoding/json\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tcache     = map[string]map[string]*Iface{}\n\tcacheTime = map[string]time.Time{}\n\tcacheLock = sync.Mutex{}\n)\n\ntype Iface struct {\n\tIfindex   int      `json:\"ifindex\"`\n\tIfname    string   `json:\"ifname\"`\n\tFlags     []string `json:\"flags\"`\n\tMtu       int      `json:\"mtu\"`\n\tQdisc     string   `json:\"qdisc\"`\n\tOperstate string   `json:\"operstate\"`\n\tGroup     string   `json:\"group\"`\n\tTxqlen    int      `json:\"txqlen\"`\n\tLinkType  string   `json:\"link_type\"`\n\tAddress   string   `json:\"address\"`\n\tBroadcast string   `json:\"broadcast\"`\n\tAddrInfo  []struct {\n\t\tFamily            string `json:\"family\"`\n\t\tLocal             string `json:\"local\"`\n\t\tPrefixlen         int    `json:\"prefixlen\"`\n\t\tScope             string `json:\"scope\"`\n\t\tLabel             string `json:\"label,omitempty\"`\n\t\tValidLifeTime     int64  `json:\"valid_life_time\"`\n\t\tPreferredLifeTime int64  `json:\"preferred_life_time\"`\n\t\tBroadcast         string `json:\"broadcast,omitempty\"`\n\t\tDynamic           bool   `json:\"dynamic,omitempty\"`\n\t\tMngtmpaddr        bool   `json:\"mngtmpaddr,omitempty\"`\n\t} `json:\"addr_info\"`\n\tLink        string `json:\"link,omitempty\"`\n\tMaster      string `json:\"master,omitempty\"`\n\tLinkIndex   int    `json:\"link_index,omitempty\"`\n\tLinkNetnsid int    `json:\"link_netnsid,omitempty\"`\n}\n\nfunc (iface *Iface) GetAddress() string {\n\tvar addrs []string\n\n\tfor _, addr := range iface.AddrInfo {\n\t\tif addr.Family != \"inet\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tip := net.ParseIP(addr.Local)\n\t\tif ip == nil || ip.IsLoopback() {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch addr.Scope {\n\t\tcase \"global\":\n\t\t\taddrs = append([]string{addr.Local}, addrs...)\n\t\tcase \"link\":\n\t\t\taddrs = append(addrs, addr.Local)\n\t\t}\n\t}\n\n\tif len(addrs) > 0 {\n\t\treturn addrs[0]\n\t}\n\treturn \"\"\n}\n\nfunc (iface *Iface) GetAddress6() string {\n\tvar addrs []string\n\n\tfor _, addr := range iface.AddrInfo {\n\t\tif addr.Family != \"inet6\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tip := net.ParseIP(addr.Local)\n\t\tif ip == nil || ip.IsLoopback() || ip.IsLinkLocalUnicast() {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch addr.Scope {\n\t\tcase \"global\":\n\t\t\taddrs = append([]string{addr.Local}, addrs...)\n\t\tcase \"link\":\n\t\t\taddrs = append(addrs, addr.Local)\n\t\t}\n\t}\n\n\tif len(addrs) > 0 {\n\t\treturn addrs[0]\n\t}\n\treturn \"\"\n}\n\nfunc GetIfaces(namespace string) (ifaces []*Iface, err error) {\n\toutput := \"\"\n\n\tif namespace == \"\" {\n\t\toutput, err = utils.ExecOutput(\n\t\t\t\"\",\n\t\t\t\"ip\", \"-j\", \"address\",\n\t\t)\n\t} else {\n\t\toutput, err = utils.ExecOutput(\n\t\t\t\"\",\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"-j\", \"address\",\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\n\tifaces = []*Iface{}\n\n\terr = json.Unmarshal([]byte(output), &ifaces)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"ip: Failed to parse ip json output\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetIfacesCached(namespace string) (ifacesMap map[string]*Iface, err error) {\n\tcacheLock.Lock()\n\tif time.Since(cacheTime[namespace]) < 5*time.Minute {\n\t\tifacesMap = cache[namespace]\n\t\tcacheLock.Unlock()\n\t\treturn\n\t}\n\tcacheLock.Unlock()\n\n\tifaces, err := GetIfaces(namespace)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tifacesMap = map[string]*Iface{}\n\tfor _, iface := range ifaces {\n\t\tifacesMap[iface.Ifname] = iface\n\t}\n\n\tcacheLock.Lock()\n\tcache[namespace] = ifacesMap\n\tcacheTime[namespace] = time.Now()\n\tcacheLock.Unlock()\n\n\treturn\n}\n\nfunc ClearIfacesCache(namespace string) {\n\tcacheLock.Lock()\n\tcache[namespace] = map[string]*Iface{}\n\tcacheTime[namespace] = time.Time{}\n\tcacheLock.Unlock()\n}\n"
  },
  {
    "path": "iproute/address.go",
    "content": "package iproute\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Address struct {\n\tFamily     string `json:\"family\"`\n\tLocal      string `json:\"local\"`\n\tPrefix     int    `json:\"prefixlen\"`\n\tScope      string `json:\"scope\"`\n\tLabel      string `json:\"label\"`\n\tDynamic    bool   `json:\"dynamic\"`\n\tDeprecated bool   `json:\"deprecated\"`\n}\n\ntype AddressIface struct {\n\tName      string     `json:\"ifname\"`\n\tState     string     `json:\"operstate\"`\n\tAddresses []*Address `json:\"addr_info\"`\n}\n\nfunc AddressGetIface(namespace, name string) (\n\taddress, address6 *Address, err error) {\n\n\tifaces := []*AddressIface{}\n\n\tlabel := \"\"\n\tif strings.Contains(name, \":\") {\n\t\tlabel = name\n\t\tname = strings.Split(name, \":\")[0]\n\t}\n\n\tvar output string\n\tif namespace != \"\" {\n\t\toutput, err = utils.ExecOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"No such file or directory\",\n\t\t\t\t\"does not exist\",\n\t\t\t\t\"setting the network namespace\",\n\t\t\t},\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"--json\",\n\t\t\t\"addr\", \"show\",\n\t\t\t\"dev\", name,\n\t\t)\n\t} else {\n\t\toutput, err = utils.ExecOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"No such file or directory\",\n\t\t\t\t\"does not exist\",\n\t\t\t\t\"setting the network namespace\",\n\t\t\t},\n\t\t\t\"ip\", \"--json\",\n\t\t\t\"addr\", \"show\",\n\t\t\t\"dev\", name,\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif output == \"\" {\n\t\treturn\n\t}\n\n\terr = json.Unmarshal([]byte(output), &ifaces)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"iproute: Failed to parse interface address\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdynamic6 := false\n\tif label != \"\" {\n\t\tfor _, iface := range ifaces {\n\t\t\tif iface.Name == name && iface.Addresses != nil {\n\t\t\t\tfor _, addr := range iface.Addresses {\n\t\t\t\t\tif addr.Label == label && addr.Scope == \"global\" &&\n\t\t\t\t\t\t!addr.Deprecated {\n\n\t\t\t\t\t\tif address == nil && addr.Family == \"inet\" {\n\t\t\t\t\t\t\taddress = addr\n\t\t\t\t\t\t} else if addr.Family == \"inet6\" {\n\t\t\t\t\t\t\tif addr.Dynamic && !dynamic6 {\n\t\t\t\t\t\t\t\taddress6 = addr\n\t\t\t\t\t\t\t\tdynamic6 = true\n\t\t\t\t\t\t\t} else if address6 == nil {\n\t\t\t\t\t\t\t\taddress6 = addr\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, iface := range ifaces {\n\t\tif iface.Name == name && iface.Addresses != nil {\n\t\t\tfor _, addr := range iface.Addresses {\n\t\t\t\tif addr.Scope == \"global\" && !addr.Deprecated {\n\t\t\t\t\tif address == nil && addr.Family == \"inet\" {\n\t\t\t\t\t\taddress = addr\n\t\t\t\t\t} else if addr.Family == \"inet6\" {\n\t\t\t\t\t\tif addr.Dynamic && !dynamic6 {\n\t\t\t\t\t\t\taddress6 = addr\n\t\t\t\t\t\t\tdynamic6 = true\n\t\t\t\t\t\t} else if address6 == nil {\n\t\t\t\t\t\t\taddress6 = addr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc AddressGetIfaceMod(namespace, name string) (\n\taddress, address6 *Address, err error) {\n\n\tifaces := []*AddressIface{}\n\n\tlabel := \"\"\n\tif strings.Contains(name, \":\") {\n\t\tlabel = name\n\t\tname = strings.Split(name, \":\")[0]\n\t}\n\n\tnameMod := name + \"x\"\n\tnameMod6 := name + \"y\"\n\tvar addressMod *Address\n\tvar address6Mod *Address\n\tvar addressMod6 *Address\n\tvar address6Mod6 *Address\n\n\tvar output string\n\tif namespace != \"\" {\n\t\toutput, err = utils.ExecOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"No such file or directory\",\n\t\t\t\t\"does not exist\",\n\t\t\t\t\"setting the network namespace\",\n\t\t\t},\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"--json\",\n\t\t\t\"addr\", \"show\",\n\t\t)\n\t} else {\n\t\toutput, err = utils.ExecOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"No such file or directory\",\n\t\t\t\t\"does not exist\",\n\t\t\t\t\"setting the network namespace\",\n\t\t\t},\n\t\t\t\"ip\", \"--json\",\n\t\t\t\"addr\", \"show\",\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif output == \"\" {\n\t\treturn\n\t}\n\n\terr = json.Unmarshal([]byte(output), &ifaces)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"iproute: Failed to parse interface address\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdynamic6 := false\n\tif label != \"\" {\n\t\tfor _, iface := range ifaces {\n\t\t\tif strings.HasPrefix(iface.Name, name) && iface.Addresses != nil {\n\t\t\t\tfor _, addr := range iface.Addresses {\n\t\t\t\t\tif addr.Label == label && addr.Scope == \"global\" &&\n\t\t\t\t\t\t!addr.Deprecated {\n\n\t\t\t\t\t\tif address == nil && addr.Family == \"inet\" {\n\t\t\t\t\t\t\taddress = addr\n\t\t\t\t\t\t} else if addr.Family == \"inet6\" {\n\t\t\t\t\t\t\tif addr.Dynamic && !dynamic6 {\n\t\t\t\t\t\t\t\taddress6 = addr\n\t\t\t\t\t\t\t\tdynamic6 = true\n\t\t\t\t\t\t\t} else if address6 == nil {\n\t\t\t\t\t\t\t\taddress6 = addr\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, iface := range ifaces {\n\t\tif iface.Name == name && iface.Addresses != nil {\n\t\t\tfor _, addr := range iface.Addresses {\n\t\t\t\tif addr.Scope == \"global\" && !addr.Deprecated {\n\t\t\t\t\tif address == nil && addr.Family == \"inet\" {\n\t\t\t\t\t\taddress = addr\n\t\t\t\t\t} else if addr.Family == \"inet6\" {\n\t\t\t\t\t\tif addr.Dynamic && !dynamic6 {\n\t\t\t\t\t\t\taddress6 = addr\n\t\t\t\t\t\t\tdynamic6 = true\n\t\t\t\t\t\t} else if address6 == nil {\n\t\t\t\t\t\t\taddress6 = addr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif iface.Name == nameMod && iface.Addresses != nil {\n\t\t\tfor _, addr := range iface.Addresses {\n\t\t\t\tif addr.Scope == \"global\" && !addr.Deprecated {\n\t\t\t\t\tif addressMod == nil && addr.Family == \"inet\" {\n\t\t\t\t\t\taddressMod = addr\n\t\t\t\t\t} else if addr.Family == \"inet6\" {\n\t\t\t\t\t\tif addr.Dynamic && !dynamic6 {\n\t\t\t\t\t\t\taddressMod6 = addr\n\t\t\t\t\t\t\tdynamic6 = true\n\t\t\t\t\t\t} else if addressMod6 == nil {\n\t\t\t\t\t\t\taddressMod6 = addr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif iface.Name == nameMod6 && iface.Addresses != nil {\n\t\t\tfor _, addr := range iface.Addresses {\n\t\t\t\tif addr.Scope == \"global\" && !addr.Deprecated {\n\t\t\t\t\tif address6Mod == nil && addr.Family == \"inet\" {\n\t\t\t\t\t\taddress6Mod = addr\n\t\t\t\t\t} else if addr.Family == \"inet6\" {\n\t\t\t\t\t\tif addr.Dynamic && !dynamic6 {\n\t\t\t\t\t\t\taddress6Mod6 = addr\n\t\t\t\t\t\t\tdynamic6 = true\n\t\t\t\t\t\t} else if address6Mod6 == nil {\n\t\t\t\t\t\t\taddress6Mod6 = addr\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif address6Mod6 != nil {\n\t\taddress6 = address6Mod6\n\t} else if addressMod6 != nil {\n\t\taddress6 = addressMod6\n\t}\n\n\tif addressMod != nil {\n\t\taddress = addressMod\n\t} else if address6Mod != nil {\n\t\taddress = address6Mod\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "iproute/bridge.go",
    "content": "package iproute\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc BridgeAdd(namespace, name string) (err error) {\n\tif namespace != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"File exists\",\n\t\t\t},\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"add\", name,\n\t\t\t\"type\", \"bridge\",\n\t\t\t\"stp_state\", \"0\",\n\t\t\t\"forward_delay\", \"0\",\n\t\t)\n\t} else {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"File exists\",\n\t\t\t},\n\t\t\t\"ip\", \"link\",\n\t\t\t\"add\", name,\n\t\t\t\"type\", \"bridge\",\n\t\t\t\"stp_state\", \"0\",\n\t\t\t\"forward_delay\", \"0\",\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc BridgeDelete(namespace, name string) (err error) {\n\tif namespace != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"File exists\",\n\t\t\t},\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"delete\", name,\n\t\t\t\"type\", \"bridge\",\n\t\t)\n\t} else {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"File exists\",\n\t\t\t},\n\t\t\t\"ip\", \"link\",\n\t\t\t\"delete\", name,\n\t\t\t\"type\", \"bridge\",\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "iproute/iface.go",
    "content": "package iproute\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Iface struct {\n\tName  string `json:\"ifname\"`\n\tState string `json:\"operstate\"`\n}\n\nfunc IfaceGetAll(namespace string) (ifaces []*Iface, err error) {\n\tifaces = []*Iface{}\n\n\tvar output string\n\tif namespace != \"\" {\n\t\toutput, err = utils.ExecOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"--json\", \"--brief\",\n\t\t\t\"link\", \"show\",\n\t\t)\n\t} else {\n\t\toutput, err = utils.ExecOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"--json\", \"--brief\",\n\t\t\t\"link\", \"show\",\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = json.Unmarshal([]byte(output), &ifaces)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"iproute: Failed to prase ifaces\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc IfaceGetBridges(namespace string) (ifaces []*Iface, err error) {\n\tifaces = []*Iface{}\n\n\tvar output string\n\tif namespace != \"\" {\n\t\toutput, err = utils.ExecOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"--json\", \"--brief\",\n\t\t\t\"link\", \"show\",\n\t\t\t\"type\", \"bridge\",\n\t\t)\n\t} else {\n\t\toutput, err = utils.ExecOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"--json\", \"--brief\",\n\t\t\t\"link\", \"show\",\n\t\t\t\"type\", \"bridge\",\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = json.Unmarshal([]byte(output), &ifaces)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"iproute: Failed to prase bridges\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc IfaceGetBridgeIfaces(namespace, bridge string) (\n\tifaces []*Iface, err error) {\n\n\tifaces = []*Iface{}\n\n\tvar output string\n\tif namespace != \"\" {\n\t\toutput, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"does not exist\",\n\t\t\t},\n\t\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\t\"ip\", \"--json\", \"--brief\",\n\t\t\t\"link\", \"show\",\n\t\t\t\"master\", bridge,\n\t\t)\n\t} else {\n\t\toutput, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\n\t\t\t\t\"does not exist\",\n\t\t\t},\n\t\t\t\"ip\", \"--json\", \"--brief\",\n\t\t\t\"link\", \"show\",\n\t\t\t\"master\", bridge,\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif output == \"\" {\n\t\treturn\n\t}\n\n\terr = json.Unmarshal([]byte(output), &ifaces)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"iproute: Failed to prase bridges\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "ipset/names.go",
    "content": "package ipset\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Names struct {\n\tNamespace string\n\tSets      set.Set\n}\n\nfunc (n *Names) Apply(curNames *Names) (err error) {\n\tif curNames != nil {\n\t\tfor nameInf := range curNames.Sets.Iter() {\n\t\t\tname := nameInf.(string)\n\n\t\t\tif !n.Sets.Contains(name) {\n\t\t\t\tif n.Namespace == \"0\" {\n\t\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t\t[]string{\"not exist\"},\n\t\t\t\t\t\t\"ipset\", \"destroy\",\n\t\t\t\t\t\tname,\n\t\t\t\t\t)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t\t[]string{\"not exist\"},\n\t\t\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\t\t\"ipset\", \"destroy\",\n\t\t\t\t\t\tname,\n\t\t\t\t\t)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "ipset/sets.go",
    "content": "package ipset\n\nimport (\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Sets struct {\n\tNamespace string\n\tSets      map[string]set.Set\n}\n\nfunc (s *Sets) Apply(curSets *Sets) (err error) {\n\tnamesSet := set.NewSet()\n\n\tfor name, ipSet := range s.Sets {\n\t\tnamesSet.Add(name)\n\n\t\tvar curIpSet set.Set\n\t\tif curSets != nil {\n\t\t\tcurIpSet = curSets.Sets[name]\n\t\t}\n\t\tif curIpSet == nil {\n\t\t\tcurIpSet = set.NewSet()\n\t\t}\n\n\t\tcreated := false\n\n\t\tfor ipInf := range ipSet.Iter() {\n\t\t\tip := ipInf.(string)\n\n\t\t\tif curIpSet.Contains(ip) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !created {\n\t\t\t\tfamily := \"inet\"\n\t\t\t\tif strings.HasPrefix(name, \"pr6\") {\n\t\t\t\t\tfamily = \"inet6\"\n\t\t\t\t}\n\n\t\t\t\tif s.Namespace == \"0\" {\n\t\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t\t[]string{\"already exists\"},\n\t\t\t\t\t\t\"ipset\", \"create\",\n\t\t\t\t\t\tname, \"hash:net\",\n\t\t\t\t\t\t\"family\", family,\n\t\t\t\t\t)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t\t[]string{\"already exists\"},\n\t\t\t\t\t\t\"ip\", \"netns\", \"exec\", s.Namespace,\n\t\t\t\t\t\t\"ipset\", \"create\",\n\t\t\t\t\t\tname, \"hash:net\",\n\t\t\t\t\t\t\"family\", family,\n\t\t\t\t\t)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcreated = true\n\t\t\t}\n\n\t\t\tif s.Namespace == \"0\" {\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"already added\"},\n\t\t\t\t\t\"ipset\", \"add\",\n\t\t\t\t\tname, ip,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"already added\"},\n\t\t\t\t\t\"ip\", \"netns\", \"exec\", s.Namespace,\n\t\t\t\t\t\"ipset\", \"add\",\n\t\t\t\t\tname, ip,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdelIpSet := curIpSet.Copy()\n\t\tdelIpSet.Subtract(ipSet)\n\n\t\tfor ipInf := range delIpSet.Iter() {\n\t\t\tip := ipInf.(string)\n\n\t\t\tif s.Namespace == \"0\" {\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\n\t\t\t\t\t\t\"not exist\",\n\t\t\t\t\t\t\"not added\",\n\t\t\t\t\t},\n\t\t\t\t\t\"ipset\", \"del\",\n\t\t\t\t\tname, ip,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\n\t\t\t\t\t\t\"not exist\",\n\t\t\t\t\t\t\"not added\",\n\t\t\t\t\t},\n\t\t\t\t\t\"ip\", \"netns\", \"exec\", s.Namespace,\n\t\t\t\t\t\"ipset\", \"del\",\n\t\t\t\t\tname, ip,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "ipset/state.go",
    "content": "package ipset\n\nimport (\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n)\n\ntype State struct {\n\tNamespaces map[string]*Sets\n}\n\nfunc (s *State) AddIngress(namespace string, ingress []*firewall.Rule) {\n\tsets := s.Namespaces[namespace]\n\tif sets == nil {\n\t\tsets = &Sets{\n\t\t\tNamespace: namespace,\n\t\t\tSets:      map[string]set.Set{},\n\t\t}\n\t\ts.Namespaces[namespace] = sets\n\t}\n\n\tfor _, rule := range ingress {\n\t\tname := rule.SetName(false)\n\t\tname6 := rule.SetName(true)\n\n\t\tif name == \"\" || name6 == \"\" || rule.Protocol == firewall.Multicast ||\n\t\t\trule.Protocol == firewall.Broadcast {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, sourceIp := range rule.SourceIps {\n\t\t\tif sourceIp == \"0.0.0.0/0\" || sourceIp == \"::/0\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\truleName := \"\"\n\t\t\tipv6 := strings.Contains(sourceIp, \":\")\n\t\t\tif ipv6 {\n\t\t\t\tsourceIp = strings.Replace(sourceIp, \"/128\", \"\", 1)\n\t\t\t\truleName = name6\n\t\t\t} else {\n\t\t\t\tsourceIp = strings.Replace(sourceIp, \"/32\", \"\", 1)\n\t\t\t\truleName = name\n\t\t\t}\n\n\t\t\truleSet := sets.Sets[ruleName]\n\t\t\tif ruleSet == nil {\n\t\t\t\truleSet = set.NewSet()\n\t\t\t\tsets.Sets[ruleName] = ruleSet\n\t\t\t}\n\n\t\t\truleSet.Add(sourceIp)\n\t\t}\n\t}\n}\n\nfunc (s *State) AddSourceDestCheck(namespace, addr6 string) {\n\tsets := s.Namespaces[namespace]\n\tif sets == nil {\n\t\tsets = &Sets{\n\t\t\tNamespace: namespace,\n\t\t\tSets:      map[string]set.Set{},\n\t\t}\n\t\ts.Namespaces[namespace] = sets\n\t}\n\n\tsdcSet := set.NewSet()\n\tif addr6 != \"\" {\n\t\tsdcSet.Add(addr6)\n\t}\n\tsdcSet.Add(\"fe80::/10\")\n\tsets.Sets[\"pr6_sdc\"] = sdcSet\n}\n\nfunc (s *State) AddMember(namespace string, ruleName, member string) {\n\tif strings.HasPrefix(ruleName, \"prx\") {\n\t\treturn\n\t}\n\n\tsets := s.Namespaces[namespace]\n\tif sets == nil {\n\t\tsets = &Sets{\n\t\t\tNamespace: namespace,\n\t\t\tSets:      map[string]set.Set{},\n\t\t}\n\t\ts.Namespaces[namespace] = sets\n\t}\n\n\truleSet := sets.Sets[ruleName]\n\tif ruleSet == nil {\n\t\truleSet = set.NewSet()\n\t\tsets.Sets[ruleName] = ruleSet\n\t}\n\n\truleSet.Add(member)\n}\n\ntype NamesState struct {\n\tNamespaces map[string]*Names\n}\n\nfunc (n *NamesState) AddIngress(namespace string, ingress []*firewall.Rule) {\n\tsets := n.Namespaces[namespace]\n\tif sets == nil {\n\t\tsets = &Names{\n\t\t\tNamespace: namespace,\n\t\t\tSets:      set.NewSet(),\n\t\t}\n\t\tn.Namespaces[namespace] = sets\n\t}\n\n\tfor _, rule := range ingress {\n\t\tname := rule.SetName(false)\n\t\tname6 := rule.SetName(true)\n\n\t\tif name == \"\" || name6 == \"\" || rule.Protocol == firewall.Multicast ||\n\t\t\trule.Protocol == firewall.Broadcast {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, sourceIp := range rule.SourceIps {\n\t\t\tif sourceIp == \"0.0.0.0/0\" || sourceIp == \"::/0\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tipv6 := strings.Contains(sourceIp, \":\")\n\t\t\tif ipv6 {\n\t\t\t\tsets.Sets.Add(name6)\n\t\t\t} else {\n\t\t\t\tsets.Sets.Add(name)\n\t\t\t}\n\n\t\t}\n\t}\n}\n\nfunc (n *NamesState) AddSourceDestCheck(namespace string) {\n\tsets := n.Namespaces[namespace]\n\tif sets == nil {\n\t\tsets = &Names{\n\t\t\tNamespace: namespace,\n\t\t\tSets:      set.NewSet(),\n\t\t}\n\t\tn.Namespaces[namespace] = sets\n\t}\n\n\tsets.Sets.Add(\"pr6_sdc\")\n}\n\nfunc (n *NamesState) AddName(namespace string, ruleName string) {\n\tif strings.HasPrefix(ruleName, \"prx\") {\n\t\treturn\n\t}\n\n\tsets := n.Namespaces[namespace]\n\tif sets == nil {\n\t\tsets = &Names{\n\t\t\tNamespace: namespace,\n\t\t\tSets:      set.NewSet(),\n\t\t}\n\t\tn.Namespaces[namespace] = sets\n\t}\n\n\tsets.Sets.Add(ruleName)\n}\n"
  },
  {
    "path": "ipset/utils.go",
    "content": "package ipset\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tcurState      *State\n\tcurNamesState *NamesState\n\tstateLock     = utils.NewTimeoutLock(3 * time.Minute)\n)\n\nfunc UpdateState(instances []*instance.Instance, namespaces []string,\n\tnodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule) (\n\terr error) {\n\n\tlockId := stateLock.Lock()\n\tdefer stateLock.Unlock(lockId)\n\n\tnewState := &State{\n\t\tNamespaces: map[string]*Sets{},\n\t}\n\n\tif nodeFirewall != nil {\n\t\tnewState.AddIngress(\"0\", nodeFirewall)\n\t}\n\n\tfor _, inst := range instances {\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor i := range inst.Virt.NetworkAdapters {\n\t\t\tnamespace := vm.GetNamespace(inst.Id, i)\n\n\t\t\tingress := firewalls[namespace]\n\t\t\tif ingress == nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"namespace\":   namespace,\n\t\t\t\t}).Warn(\"ipset: Failed to load instance firewall rules\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\taddr6 := \"\"\n\t\t\tif inst.PrivateIps6 != nil && len(inst.PrivateIps6) != 0 {\n\t\t\t\taddr6 = inst.PrivateIps6[0]\n\t\t\t}\n\n\t\t\tnewState.AddIngress(namespace, ingress)\n\t\t\tif !inst.SkipSourceDestCheck {\n\t\t\t\tnewState.AddSourceDestCheck(namespace, addr6)\n\t\t\t}\n\t\t}\n\t}\n\n\terr = applyState(curState, newState, namespaces)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcurState = newState\n\n\treturn\n}\n\nfunc applyState(oldState, newState *State, namespaces []string) (err error) {\n\tnamespacesSet := set.NewSet()\n\tfor _, namespace := range namespaces {\n\t\tnamespacesSet.Add(namespace)\n\t}\n\n\tfor _, ipSet := range newState.Namespaces {\n\t\tif ipSet.Namespace != \"0\" && !namespacesSet.Contains(\n\t\t\tipSet.Namespace) {\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\"ip\", \"netns\",\n\t\t\t\t\"add\", ipSet.Namespace,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tcurSet := oldState.Namespaces[ipSet.Namespace]\n\n\t\terr = ipSet.Apply(curSet)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc UpdateNamesState(instances []*instance.Instance,\n\tnodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule) (\n\terr error) {\n\n\tlockId := stateLock.Lock()\n\tdefer stateLock.Unlock(lockId)\n\n\tnewNamesState := &NamesState{\n\t\tNamespaces: map[string]*Names{},\n\t}\n\n\tif nodeFirewall != nil {\n\t\tnewNamesState.AddIngress(\"0\", nodeFirewall)\n\t}\n\n\tfor _, inst := range instances {\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor i := range inst.Virt.NetworkAdapters {\n\t\t\tnamespace := vm.GetNamespace(inst.Id, i)\n\n\t\t\tingress := firewalls[namespace]\n\t\t\tif ingress == nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\t\"namespace\":   namespace,\n\t\t\t\t}).Warn(\"ipset: Failed to load instance firewall rules\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnewNamesState.AddIngress(namespace, ingress)\n\t\t\tif !inst.SkipSourceDestCheck {\n\t\t\t\tnewNamesState.AddSourceDestCheck(namespace)\n\t\t\t}\n\t\t}\n\t}\n\n\terr = applyNamesState(curNamesState, newNamesState)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcurNamesState = newNamesState\n\n\treturn\n}\n\nfunc applyNamesState(oldNamesState, newNamesState *NamesState) (err error) {\n\tfor _, ipSet := range newNamesState.Namespaces {\n\t\tcurSet := oldNamesState.Namespaces[ipSet.Namespace]\n\n\t\terr = ipSet.Apply(curSet)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc loadIpset(namespace string, state *State, namesState *NamesState) (\n\terr error) {\n\n\toutput := \"\"\n\tif namespace == \"0\" {\n\t\toutput, err = utils.ExecOutput(\"\", \"ipset\", \"list\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\toutput, err = utils.ExecOutput(\"\",\n\t\t\t\"ip\", \"netns\", \"exec\", namespace, \"ipset\", \"list\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tcurName := \"\"\n\tisMembers := false\n\tfor _, line := range strings.Split(output, \"\\n\") {\n\t\tif strings.HasPrefix(line, \"Name:\") {\n\t\t\tcurName = strings.TrimSpace(strings.SplitN(line, \":\", 2)[1])\n\t\t\tisMembers = false\n\t\t} else if isMembers {\n\t\t\tif line == \"\" {\n\t\t\t\tisMembers = false\n\t\t\t} else {\n\t\t\t\tmember := strings.TrimSpace(line)\n\t\t\t\tstate.AddMember(namespace, curName, member)\n\t\t\t\tnamesState.AddName(namespace, curName)\n\t\t\t}\n\t\t} else if strings.HasPrefix(line, \"Members:\") {\n\t\t\tisMembers = true\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Init(namespaces []string, instances []*instance.Instance,\n\tnodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule) (\n\terr error) {\n\n\tstate := &State{\n\t\tNamespaces: map[string]*Sets{},\n\t}\n\tnamesState := &NamesState{\n\t\tNamespaces: map[string]*Names{},\n\t}\n\n\terr = loadIpset(\"0\", state, namesState)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, namespace := range namespaces {\n\t\terr = loadIpset(namespace, state, namesState)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tcurState = state\n\tcurNamesState = namesState\n\n\terr = UpdateState(instances, namespaces, nodeFirewall, firewalls)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc InitNames(namespaces []string, instances []*instance.Instance,\n\tnodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule) (\n\terr error) {\n\n\terr = UpdateNamesState(instances, nodeFirewall, firewalls)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "iptables/iptables.go",
    "content": "package iptables\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/ipvs\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tcurState  *State\n\tstateLock = utils.NewTimeoutLock(3 * time.Minute)\n)\n\nfunc (r *Rules) newCommand() (cmd []string) {\n\tchain := \"\"\n\tif r.Interface == \"host\" {\n\t\tchain = \"INPUT\"\n\t} else {\n\t\tchain = \"FORWARD\"\n\t}\n\n\tcmd = []string{\n\t\tchain,\n\t}\n\n\treturn\n}\n\nfunc (r *Rules) newCommandNatPre() (cmd []string) {\n\tcmd = []string{\n\t\t\"PREROUTING\",\n\t}\n\n\treturn\n}\n\nfunc (r *Rules) newCommandNatPost() (cmd []string) {\n\tcmd = []string{\n\t\t\"POSTROUTING\",\n\t}\n\n\treturn\n}\n\nfunc (r *Rules) newCommandMap() (cmd []string) {\n\tcmd = []string{\n\t\t\"PREROUTING\",\n\t}\n\n\treturn\n}\n\nfunc (r *Rules) newCommandMapPost() (cmd []string) {\n\tcmd = []string{\n\t\t\"POSTROUTING\",\n\t}\n\n\treturn\n}\n\nfunc (r *Rules) commentCommand(inCmd []string, hold bool) (cmd []string) {\n\tcomment := \"\"\n\tif hold {\n\t\tcomment = \"pritunl_cloud_hold\"\n\t} else {\n\t\tcomment = \"pritunl_cloud_rule\"\n\t}\n\n\tcmd = append(inCmd,\n\t\t\"-m\", \"comment\",\n\t\t\"--comment\", comment,\n\t)\n\n\treturn\n}\n\nfunc (r *Rules) commentCommandHeader(inCmd []string) (cmd []string) {\n\tcmd = append(inCmd,\n\t\t\"-m\", \"comment\",\n\t\t\"--comment\", \"pritunl_cloud_head\",\n\t)\n\n\treturn\n}\n\nfunc (r *Rules) commentCommandSdc(inCmd []string) (cmd []string) {\n\tcmd = append(inCmd,\n\t\t\"-m\", \"comment\",\n\t\t\"--comment\", \"pritunl_cloud_sdc\",\n\t)\n\n\treturn\n}\n\nfunc (r *Rules) commentCommandNat(inCmd []string) (cmd []string) {\n\tcmd = append(inCmd,\n\t\t\"-m\", \"comment\",\n\t\t\"--comment\", \"pritunl_cloud_nat\",\n\t)\n\n\treturn\n}\n\nfunc (r *Rules) commentCommandMap(inCmd []string) (cmd []string) {\n\tcmd = append(inCmd,\n\t\t\"-m\", \"comment\",\n\t\t\"--comment\", \"pritunl_cloud_map\",\n\t)\n\n\treturn\n}\n\nfunc (r *Rules) run(table string, cmds [][]string,\n\tipCmd string, ipv6 bool) (err error) {\n\n\tiptablesCmd := getIptablesCmd(ipv6)\n\n\tfor _, cmd := range cmds {\n\t\tcmd = append([]string{ipCmd}, cmd...)\n\t\tif table != \"\" {\n\t\t\tcmd = append([]string{\"-t\", table}, cmd...)\n\t\t}\n\n\t\tif r.Namespace != \"0\" {\n\t\t\tcmd = append([]string{\n\t\t\t\t\"netns\", \"exec\", r.Namespace,\n\t\t\t\tiptablesCmd,\n\t\t\t}, cmd...)\n\t\t}\n\n\t\tfor i := 0; i < 3; i++ {\n\t\t\toutput := \"\"\n\t\t\tif r.Namespace == \"0\" {\n\t\t\t\tLock()\n\t\t\t\toutput, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\n\t\t\t\t\t\t\"matching rule exist\",\n\t\t\t\t\t}, iptablesCmd, cmd...)\n\t\t\t\tUnlock()\n\t\t\t} else {\n\t\t\t\tLock()\n\t\t\t\toutput, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\n\t\t\t\t\t\t\"matching rule exist\",\n\t\t\t\t\t\t\"Cannot open network namespace\",\n\t\t\t\t\t}, \"ip\", cmd...)\n\t\t\t\tUnlock()\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tif i < 2 {\n\t\t\t\t\terr = nil\n\t\t\t\t\ttime.Sleep(250 * time.Millisecond)\n\t\t\t\t\tcontinue\n\t\t\t\t} else if cmd[len(cmd)-1] == \"ACCEPT\" {\n\t\t\t\t\terr = nil\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"ipv6\":    ipv6,\n\t\t\t\t\t\t\"command\": cmd,\n\t\t\t\t\t\t\"output\":  output,\n\t\t\t\t\t}).Error(\"iptables: Ignoring invalid iptables command\")\n\t\t\t\t} else {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"ipv6\":    ipv6,\n\t\t\t\t\t\t\"command\": cmd,\n\t\t\t\t\t\t\"output\":  output,\n\t\t\t\t\t}).Warn(\"iptables: Failed to run iptables command\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (r *Rules) Apply(diff *RulesDiff) (err error) {\n\tif diff == nil || diff.HeaderDiff {\n\t\terr = r.run(\"\", r.Header, \"-A\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.Header6Diff {\n\t\terr = r.run(\"\", r.Header6, \"-A\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.SourceDestCheckDiff {\n\t\terr = r.run(\"\", r.SourceDestCheck, \"-A\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.SourceDestCheck6Diff {\n\t\terr = r.run(\"\", r.SourceDestCheck6, \"-A\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.IngressDiff {\n\t\terr = r.run(\"\", r.Ingress, \"-A\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.Ingress6Diff {\n\t\terr = r.run(\"\", r.Ingress6, \"-A\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.NatsDiff {\n\t\terr = r.run(\"nat\", r.Nats, \"-A\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.Nats6Diff {\n\t\terr = r.run(\"nat\", r.Nats6, \"-A\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.MapsDiff {\n\t\terr = r.run(\"nat\", r.Maps, \"-A\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.Maps6Diff {\n\t\terr = r.run(\"nat\", r.Maps6, \"-A\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif diff == nil || diff.IngressDiff {\n\t\terr = r.run(\"\", r.Holds, \"-D\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Holds = [][]string{}\n\t}\n\n\tif diff == nil || diff.Ingress6Diff {\n\t\terr = r.run(\"\", r.Holds6, \"-D\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Holds6 = [][]string{}\n\t}\n\n\treturn\n}\n\nfunc (r *Rules) Hold() (err error) {\n\tcmd := r.newCommand()\n\tif r.Interface != \"host\" {\n\t\tif strings.HasPrefix(r.Interface, \"e\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface+\"+\",\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"h\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface,\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"m\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface,\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"i\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface,\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"o\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface,\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"p\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-m\", \"physdev\",\n\t\t\t\t\"--physdev-out\", r.Interface,\n\t\t\t\t\"--physdev-is-bridged\",\n\t\t\t)\n\t\t} else {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"iptables: Unknown interface type %s\",\n\t\t\t\t\tr.Interface),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\tcmd = r.commentCommand(cmd, true)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\tr.Holds = append(r.Holds, cmd)\n\n\tcmd = r.newCommand()\n\tif r.Interface != \"host\" {\n\t\tif strings.HasPrefix(r.Interface, \"e\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface+\"+\",\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"h\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface,\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"m\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface,\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"i\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface,\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"o\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", r.Interface,\n\t\t\t)\n\t\t} else if strings.HasPrefix(r.Interface, \"p\") {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-m\", \"physdev\",\n\t\t\t\t\"--physdev-out\", r.Interface,\n\t\t\t\t\"--physdev-is-bridged\",\n\t\t\t)\n\t\t} else {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"iptables: Unknown interface type %s\",\n\t\t\t\t\tr.Interface),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\tcmd = r.commentCommand(cmd, true)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\tr.Holds6 = append(r.Holds6, cmd)\n\n\terr = r.run(\"\", r.Holds, \"-A\", false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = r.run(\"\", r.Holds6, \"-A\", true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (r *Rules) Remove(diff *RulesDiff) (err error) {\n\tif diff == nil || diff.HeaderDiff {\n\t\terr = r.run(\"\", r.Header, \"-D\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Header = [][]string{}\n\t}\n\n\tif diff == nil || diff.Header6Diff {\n\t\terr = r.run(\"\", r.Header6, \"-D\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Header6 = [][]string{}\n\t}\n\n\tif diff == nil || diff.SourceDestCheckDiff {\n\t\terr = r.run(\"\", r.SourceDestCheck, \"-D\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.SourceDestCheck = [][]string{}\n\t}\n\n\tif diff == nil || diff.SourceDestCheck6Diff {\n\t\terr = r.run(\"\", r.SourceDestCheck6, \"-D\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.SourceDestCheck6 = [][]string{}\n\t}\n\n\tif diff == nil || diff.IngressDiff {\n\t\terr = r.run(\"\", r.Ingress, \"-D\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Ingress = [][]string{}\n\t}\n\n\tif diff == nil || diff.Ingress6Diff {\n\t\terr = r.run(\"\", r.Ingress6, \"-D\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Ingress6 = [][]string{}\n\t}\n\n\tif diff == nil || diff.NatsDiff {\n\t\terr = r.run(\"nat\", r.Nats, \"-D\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Nats = [][]string{}\n\t}\n\n\tif diff == nil || diff.Nats6Diff {\n\t\terr = r.run(\"nat\", r.Nats6, \"-D\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Nats6 = [][]string{}\n\t}\n\n\tif diff == nil || diff.MapsDiff {\n\t\terr = r.run(\"nat\", r.Maps, \"-D\", false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Maps = [][]string{}\n\t}\n\n\tif diff == nil || diff.Maps6Diff {\n\t\terr = r.run(\"nat\", r.Maps6, \"-D\", true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr.Maps6 = [][]string{}\n\t}\n\n\terr = r.run(\"\", r.Holds, \"-D\", false)\n\tif err != nil {\n\t\treturn\n\t}\n\tr.Holds = [][]string{}\n\n\terr = r.run(\"\", r.Holds6, \"-D\", true)\n\tif err != nil {\n\t\treturn\n\t}\n\tr.Holds6 = [][]string{}\n\n\treturn\n}\n\nfunc generateVirt(vc *vpc.Vpc, namespace, iface, addr, addr6 string,\n\tsourceDestCheck bool, ingress []*firewall.Rule) (rules *Rules) {\n\n\trules = &Rules{\n\t\tNamespace:        namespace,\n\t\tInterface:        iface,\n\t\tHeader:           [][]string{},\n\t\tHeader6:          [][]string{},\n\t\tSourceDestCheck:  [][]string{},\n\t\tSourceDestCheck6: [][]string{},\n\t\tIngress:          [][]string{},\n\t\tIngress6:         [][]string{},\n\t\tMaps:             [][]string{},\n\t\tMaps6:            [][]string{},\n\t\tHolds:            [][]string{},\n\t\tHolds6:           [][]string{},\n\t}\n\n\tcmd := rules.newCommand()\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t)\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"133\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t)\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"134\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t)\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"135\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t)\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"136\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tif sourceDestCheck {\n\t\tif addr != \"\" {\n\t\t\tcmd := rules.newCommand()\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"!\", \"-s\", addr+\"/32\",\n\t\t\t)\n\t\t\tif rules.Interface != \"host\" {\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", \"physdev\",\n\t\t\t\t\t\"--physdev-in\", rules.Interface,\n\t\t\t\t\t\"--physdev-is-bridged\",\n\t\t\t\t)\n\t\t\t}\n\t\t\tcmd = rules.commentCommandSdc(cmd)\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-j\", \"DROP\",\n\t\t\t)\n\t\t\trules.SourceDestCheck = append(rules.SourceDestCheck, cmd)\n\t\t}\n\n\t\tcmd := rules.newCommand()\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"set\",\n\t\t\t\"!\", \"--match-set\", \"pr6_sdc\", \"src\",\n\t\t)\n\t\tif rules.Interface != \"host\" {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-m\", \"physdev\",\n\t\t\t\t\"--physdev-in\", rules.Interface,\n\t\t\t\t\"--physdev-is-bridged\",\n\t\t\t)\n\t\t}\n\t\tcmd = rules.commentCommandSdc(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"DROP\",\n\t\t)\n\t\trules.SourceDestCheck6 = append(rules.SourceDestCheck6, cmd)\n\n\t\tif addr != \"\" {\n\t\t\tcmd = rules.newCommand()\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"!\", \"-d\", addr+\"/32\",\n\t\t\t)\n\t\t\tif rules.Interface != \"host\" {\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", \"physdev\",\n\t\t\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\t\t\"--physdev-is-bridged\",\n\t\t\t\t)\n\t\t\t}\n\t\t\tcmd = rules.commentCommandSdc(cmd)\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-j\", \"DROP\",\n\t\t\t)\n\t\t\trules.SourceDestCheck = append(rules.SourceDestCheck, cmd)\n\t\t}\n\n\t\tcmd = rules.newCommand()\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"set\",\n\t\t\t\"!\", \"--match-set\", \"pr6_sdc\", \"dst\",\n\t\t)\n\t\tif rules.Interface != \"host\" {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-m\", \"physdev\",\n\t\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\t\"--physdev-is-bridged\",\n\t\t\t)\n\t\t}\n\t\tcmd = rules.commentCommandSdc(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"DROP\",\n\t\t)\n\t\trules.SourceDestCheck6 = append(rules.SourceDestCheck6, cmd)\n\t}\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"RELATED,ESTABLISHED\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"RELATED,ESTABLISHED\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Ingress6 = append(rules.Ingress6, cmd)\n\n\tfor _, rule := range ingress {\n\t\tall4 := false\n\t\tall6 := false\n\t\tset4 := false\n\t\tset6 := false\n\t\tsetName := rule.SetName(false)\n\t\tsetName6 := rule.SetName(true)\n\n\t\tif setName == \"\" || setName6 == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif rule.Protocol == firewall.Multicast ||\n\t\t\trule.Protocol == firewall.Broadcast {\n\n\t\t\tcmd = rules.newCommand()\n\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-p\", \"udp\",\n\t\t\t\t\"-m\", \"pkttype\",\n\t\t\t\t\"--pkt-type\", rule.Protocol,\n\t\t\t)\n\n\t\t\tif rules.Interface != \"host\" {\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", \"physdev\",\n\t\t\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\t\t\"--physdev-is-bridged\",\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-m\", \"udp\",\n\t\t\t\t\"--dport\", strings.Replace(rule.Port, \"-\", \":\", 1),\n\t\t\t)\n\n\t\t\tcmd = rules.commentCommand(cmd, false)\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-j\", \"ACCEPT\",\n\t\t\t)\n\n\t\t\trules.Ingress = append(rules.Ingress, cmd)\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, sourceIp := range rule.SourceIps {\n\t\t\tipv6 := strings.Contains(sourceIp, \":\")\n\n\t\t\tif sourceIp == \"0.0.0.0/0\" {\n\t\t\t\tif all4 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tall4 = true\n\t\t\t} else if sourceIp == \"::/0\" {\n\t\t\t\tif all6 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tall6 = true\n\t\t\t} else {\n\t\t\t\tif ipv6 {\n\t\t\t\t\tif set6 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tset6 = true\n\t\t\t\t} else {\n\t\t\t\t\tif set4 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tset4 = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcmd = rules.newCommand()\n\n\t\t\tswitch rule.Protocol {\n\t\t\tcase firewall.All:\n\t\t\t\tbreak\n\t\t\tcase firewall.Icmp:\n\t\t\t\tif ipv6 {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-p\", \"ipv6-icmp\",\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-p\", \"icmp\",\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase firewall.Multicast, firewall.Broadcast:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-p\", \"udp\",\n\t\t\t\t\t\"-m\", \"pkttype\",\n\t\t\t\t\t\"--pkt-type\", rule.Protocol,\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tcase firewall.Tcp, firewall.Udp:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-p\", rule.Protocol,\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif sourceIp != \"0.0.0.0/0\" && sourceIp != \"::/0\" &&\n\t\t\t\trule.Protocol != firewall.Multicast &&\n\t\t\t\trule.Protocol != firewall.Broadcast {\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-m\", \"set\",\n\t\t\t\t\t\t\"--match-set\", setName6, \"src\",\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-m\", \"set\",\n\t\t\t\t\t\t\"--match-set\", setName, \"src\",\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif rules.Interface != \"host\" {\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", \"physdev\",\n\t\t\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\t\t\"--physdev-is-bridged\",\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tswitch rule.Protocol {\n\t\t\tcase firewall.Multicast, firewall.Broadcast:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", \"udp\",\n\t\t\t\t\t\"--dport\", strings.Replace(rule.Port, \"-\", \":\", 1),\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tcase firewall.Tcp, firewall.Udp:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", rule.Protocol,\n\t\t\t\t\t\"--dport\", strings.Replace(rule.Port, \"-\", \":\", 1),\n\t\t\t\t\t\"-m\", \"conntrack\",\n\t\t\t\t\t\"--ctstate\", \"NEW\",\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif rule.Protocol == firewall.Multicast ||\n\t\t\t\trule.Protocol == firewall.Broadcast {\n\n\t\t\t\tcmd = rules.commentCommandHeader(cmd)\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-j\", \"ACCEPT\",\n\t\t\t\t)\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\trules.Header6 = append(rules.Header6, cmd)\n\t\t\t\t} else {\n\t\t\t\t\trules.Header = append(rules.Header, cmd)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcmd = rules.commentCommand(cmd, false)\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-j\", \"ACCEPT\",\n\t\t\t\t)\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\trules.Ingress6 = append(rules.Ingress6, cmd)\n\t\t\t\t} else {\n\t\t\t\t\trules.Ingress = append(rules.Ingress, cmd)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"INVALID\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"INVALID\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress6 = append(rules.Ingress6, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-m\", \"physdev\",\n\t\t\t\"--physdev-out\", rules.Interface,\n\t\t\t\"--physdev-is-bridged\",\n\t\t)\n\t}\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress6 = append(rules.Ingress6, cmd)\n\n\tif vc != nil && vc.Maps != nil {\n\t\tfor _, mp := range vc.Maps {\n\t\t\tif mp.Type != vpc.Destination {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif strings.Contains(mp.Target, \":\") {\n\t\t\t\tif addr6 != \"\" {\n\t\t\t\t\tcmd = rules.newCommandMap()\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-s\", addr+\"/128\",\n\t\t\t\t\t\t\"-d\", mp.Destination,\n\t\t\t\t\t)\n\t\t\t\t\tcmd = rules.commentCommandMap(cmd)\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-j\", \"DNAT\",\n\t\t\t\t\t\t\"--to-destination\", mp.Target,\n\t\t\t\t\t)\n\t\t\t\t\trules.Maps6 = append(rules.Maps6, cmd)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif addr != \"\" {\n\t\t\t\t\tcmd = rules.newCommandMap()\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-s\", addr+\"/32\",\n\t\t\t\t\t\t\"-d\", mp.Destination,\n\t\t\t\t\t)\n\t\t\t\t\tcmd = rules.commentCommandMap(cmd)\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-j\", \"DNAT\",\n\t\t\t\t\t\t\"--to-destination\", mp.Target,\n\t\t\t\t\t)\n\t\t\t\t\trules.Maps = append(rules.Maps, cmd)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc generateInternal(namespace, iface string, nat, nat6, dhcp, dhcp6 bool,\n\tnatAddr, natPubAddr, natAddr6, natPubAddr6 string,\n\tingress []*firewall.Rule) (rules *Rules) {\n\n\trules = &Rules{\n\t\tNamespace:        namespace,\n\t\tInterface:        iface,\n\t\tHeader:           [][]string{},\n\t\tHeader6:          [][]string{},\n\t\tSourceDestCheck:  [][]string{},\n\t\tSourceDestCheck6: [][]string{},\n\t\tIngress:          [][]string{},\n\t\tIngress6:         [][]string{},\n\t\tMaps:             [][]string{},\n\t\tMaps6:            [][]string{},\n\t\tHolds:            [][]string{},\n\t\tHolds6:           [][]string{},\n\t}\n\n\tif strings.HasPrefix(iface, \"e\") {\n\t\tiface = iface + \"+\"\n\t}\n\n\tif nat && natAddr != \"\" && natPubAddr != \"\" {\n\t\tcmd := rules.newCommandNatPre()\n\t\tcmd = append(cmd,\n\t\t\t\"-d\", natPubAddr+\"/32\",\n\t\t)\n\t\tcmd = rules.commentCommandNat(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"DNAT\",\n\t\t\t\"--to-destination\", natAddr,\n\t\t)\n\t\trules.Nats = append(rules.Nats, cmd)\n\n\t\tcmd = rules.newCommandNatPost()\n\t\tcmd = append(cmd,\n\t\t\t\"-s\", natAddr+\"/32\",\n\t\t\t\"-d\", natAddr+\"/32\",\n\t\t)\n\t\tcmd = rules.commentCommandNat(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"SNAT\",\n\t\t\t\"--to-source\", natPubAddr,\n\t\t)\n\t\trules.Nats = append(rules.Nats, cmd)\n\n\t\tcmd = rules.newCommandNatPost()\n\t\tcmd = append(cmd,\n\t\t\t\"-s\", natAddr+\"/32\",\n\t\t\t\"-o\", iface,\n\t\t)\n\t\tcmd = rules.commentCommandNat(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"MASQUERADE\",\n\t\t)\n\t\trules.Nats = append(rules.Nats, cmd)\n\t}\n\n\tif nat6 && natAddr6 != \"\" && natPubAddr6 != \"\" {\n\t\tcmd := rules.newCommandNatPre()\n\t\tcmd = append(cmd,\n\t\t\t\"-d\", natPubAddr6+\"/128\",\n\t\t)\n\t\tcmd = rules.commentCommandNat(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"DNAT\",\n\t\t\t\"--to-destination\", natAddr6,\n\t\t)\n\t\trules.Nats6 = append(rules.Nats6, cmd)\n\n\t\tcmd = rules.newCommandNatPost()\n\t\tcmd = append(cmd,\n\t\t\t\"-s\", natAddr6+\"/128\",\n\t\t\t\"-d\", natAddr6+\"/128\",\n\t\t)\n\t\tcmd = rules.commentCommandNat(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"SNAT\",\n\t\t\t\"--to-source\", natPubAddr6,\n\t\t)\n\t\trules.Nats6 = append(rules.Nats6, cmd)\n\n\t\tcmd = rules.newCommandNatPost()\n\t\tcmd = append(cmd,\n\t\t\t\"-s\", natAddr6+\"/128\",\n\t\t\t\"-o\", iface,\n\t\t)\n\t\tcmd = rules.commentCommandNat(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"MASQUERADE\",\n\t\t)\n\t\trules.Nats6 = append(rules.Nats6, cmd)\n\t}\n\n\tcmd := rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"133\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"134\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"135\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"136\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"RELATED,ESTABLISHED\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"RELATED,ESTABLISHED\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Ingress6 = append(rules.Ingress6, cmd)\n\n\tfor _, rule := range ingress {\n\t\tall4 := false\n\t\tall6 := false\n\t\tset4 := false\n\t\tset6 := false\n\t\tsetName := rule.SetName(false)\n\t\tsetName6 := rule.SetName(true)\n\n\t\tif setName == \"\" || setName6 == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif rule.Protocol == firewall.Multicast ||\n\t\t\trule.Protocol == firewall.Broadcast {\n\n\t\t\tcmd = rules.newCommand()\n\n\t\t\tif iface != \"host\" {\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-i\", iface,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-p\", \"udp\",\n\t\t\t\t\"-m\", \"pkttype\",\n\t\t\t\t\"--pkt-type\", rule.Protocol,\n\t\t\t)\n\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-m\", \"udp\",\n\t\t\t\t\"--dport\", strings.Replace(rule.Port, \"-\", \":\", 1),\n\t\t\t)\n\n\t\t\tcmd = rules.commentCommand(cmd, false)\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-j\", \"ACCEPT\",\n\t\t\t)\n\n\t\t\trules.Ingress = append(rules.Ingress, cmd)\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, sourceIp := range rule.SourceIps {\n\t\t\tipv6 := strings.Contains(sourceIp, \":\")\n\n\t\t\tif sourceIp == \"0.0.0.0/0\" {\n\t\t\t\tif all4 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tall4 = true\n\t\t\t} else if sourceIp == \"::/0\" {\n\t\t\t\tif all6 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tall6 = true\n\t\t\t} else {\n\t\t\t\tif ipv6 {\n\t\t\t\t\tif set6 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tset6 = true\n\t\t\t\t} else {\n\t\t\t\t\tif set4 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tset4 = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcmd = rules.newCommand()\n\n\t\t\tif iface != \"host\" {\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-i\", iface,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tswitch rule.Protocol {\n\t\t\tcase firewall.All:\n\t\t\t\tbreak\n\t\t\tcase firewall.Icmp:\n\t\t\t\tif ipv6 {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-p\", \"ipv6-icmp\",\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-p\", \"icmp\",\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase firewall.Multicast, firewall.Broadcast:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-p\", \"udp\",\n\t\t\t\t\t\"-m\", \"pkttype\",\n\t\t\t\t\t\"--pkt-type\", rule.Protocol,\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tcase firewall.Tcp, firewall.Udp:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-p\", rule.Protocol,\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif sourceIp != \"0.0.0.0/0\" && sourceIp != \"::/0\" &&\n\t\t\t\trule.Protocol != firewall.Multicast &&\n\t\t\t\trule.Protocol != firewall.Broadcast {\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-m\", \"set\",\n\t\t\t\t\t\t\"--match-set\", setName6, \"src\",\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-m\", \"set\",\n\t\t\t\t\t\t\"--match-set\", setName, \"src\",\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch rule.Protocol {\n\t\t\tcase firewall.Multicast, firewall.Broadcast:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", \"udp\",\n\t\t\t\t\t\"--dport\", strings.Replace(rule.Port, \"-\", \":\", 1),\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tcase firewall.Tcp, firewall.Udp:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", rule.Protocol,\n\t\t\t\t\t\"--dport\", strings.Replace(rule.Port, \"-\", \":\", 1),\n\t\t\t\t\t\"-m\", \"conntrack\",\n\t\t\t\t\t\"--ctstate\", \"NEW\",\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif rule.Protocol == firewall.Multicast ||\n\t\t\t\trule.Protocol == firewall.Broadcast {\n\n\t\t\t\tcmd = rules.commentCommandHeader(cmd)\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-j\", \"ACCEPT\",\n\t\t\t\t)\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\trules.Header6 = append(rules.Header6, cmd)\n\t\t\t\t} else {\n\t\t\t\t\trules.Header = append(rules.Header, cmd)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcmd = rules.commentCommand(cmd, false)\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-j\", \"ACCEPT\",\n\t\t\t\t)\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\trules.Ingress6 = append(rules.Ingress6, cmd)\n\t\t\t\t} else {\n\t\t\t\t\trules.Ingress = append(rules.Ingress, cmd)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tcmd = rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"INVALID\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"INVALID\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress6 = append(rules.Ingress6, cmd)\n\n\tcmd = rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif iface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t)\n\t}\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress6 = append(rules.Ingress6, cmd)\n\n\treturn\n}\n\nfunc generateNodePort(namespace, iface string, addr, nodePortGateway string,\n\tfirewallMaps []*firewall.Mapping) (rules *Rules) {\n\n\trules = &Rules{\n\t\tNamespace:        namespace,\n\t\tInterface:        iface,\n\t\tHeader:           [][]string{},\n\t\tHeader6:          [][]string{},\n\t\tSourceDestCheck:  [][]string{},\n\t\tSourceDestCheck6: [][]string{},\n\t\tIngress:          [][]string{},\n\t\tIngress6:         [][]string{},\n\t\tMaps:             [][]string{},\n\t\tMaps6:            [][]string{},\n\t\tHolds:            [][]string{},\n\t\tHolds6:           [][]string{},\n\t}\n\n\tcmd := rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"RELATED,ESTABLISHED\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tfor _, mapping := range firewallMaps {\n\t\tif mapping.Protocol != firewall.Tcp &&\n\t\t\tmapping.Protocol != firewall.Udp {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tcmd = rules.newCommandMap()\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", iface,\n\t\t\t\"-p\", mapping.Protocol,\n\t\t\t\"-m\", mapping.Protocol,\n\t\t\t\"--dport\", fmt.Sprintf(\"%d\", mapping.ExternalPort),\n\t\t)\n\t\tcmd = rules.commentCommandMap(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"DNAT\",\n\t\t\t\"--to-destination\", fmt.Sprintf(\n\t\t\t\t\"%s:%d\",\n\t\t\t\taddr,\n\t\t\t\tmapping.InternalPort,\n\t\t\t),\n\t\t)\n\t\trules.Maps = append(rules.Maps, cmd)\n\n\t\tcmd = rules.newCommand()\n\t\tcmd = append(cmd,\n\t\t\t\"-s\", nodePortGateway+\"/32\",\n\t\t)\n\n\t\tif rules.Interface != \"host\" {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-i\", rules.Interface,\n\t\t\t)\n\t\t}\n\n\t\tcmd = append(cmd,\n\t\t\t\"-p\", mapping.Protocol,\n\t\t\t\"-m\", mapping.Protocol,\n\t\t\t\"--dport\", fmt.Sprintf(\"%d\", mapping.InternalPort),\n\t\t\t\"-m\", \"conntrack\",\n\t\t\t\"--ctstate\", \"NEW\",\n\t\t)\n\n\t\tcmd = rules.commentCommand(cmd, false)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"ACCEPT\",\n\t\t)\n\n\t\trules.Ingress = append(rules.Ingress, cmd)\n\t}\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"INVALID\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", rules.Interface,\n\t\t)\n\t}\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\treturn\n}\n\nfunc generateHost(namespace, iface string, nodePortNetwork bool,\n\tnodePortGateway, defaultIface string, externalIfaces, puiblicIps []string,\n\tingress []*firewall.Rule,\n\tnodePortMappings map[string][]*firewall.Mapping) (rules *Rules) {\n\n\trules = &Rules{\n\t\tNamespace:        namespace,\n\t\tInterface:        iface,\n\t\tHeader:           [][]string{},\n\t\tHeader6:          [][]string{},\n\t\tSourceDestCheck:  [][]string{},\n\t\tSourceDestCheck6: [][]string{},\n\t\tIngress:          [][]string{},\n\t\tIngress6:         [][]string{},\n\t\tHolds:            [][]string{},\n\t\tHolds6:           [][]string{},\n\t\tIpvs:             ipvs.New(),\n\t}\n\n\tif rules.Interface == \"host\" {\n\t\tcmd := rules.newCommand()\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", \"lo\",\n\t\t)\n\t\tcmd = rules.commentCommand(cmd, false)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"ACCEPT\",\n\t\t)\n\t\trules.Ingress = append(rules.Ingress, cmd)\n\t}\n\n\tif rules.Interface == \"host\" {\n\t\tcmd := rules.newCommand()\n\t\tcmd = append(cmd,\n\t\t\t\"-i\", \"lo\",\n\t\t)\n\t\tcmd = rules.commentCommand(cmd, false)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"ACCEPT\",\n\t\t)\n\t\trules.Ingress6 = append(rules.Ingress6, cmd)\n\t}\n\n\tcmd := rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"133\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"134\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"135\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-p\", \"ipv6-icmp\",\n\t\t\"-m\", \"icmp6\",\n\t\t\"--icmpv6-type\", \"136\",\n\t)\n\tcmd = rules.commentCommandHeader(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Header6 = append(rules.Header6, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"RELATED,ESTABLISHED\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"RELATED,ESTABLISHED\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"ACCEPT\",\n\t)\n\trules.Ingress6 = append(rules.Ingress6, cmd)\n\n\tfor _, rule := range ingress {\n\t\tall4 := false\n\t\tall6 := false\n\t\tset4 := false\n\t\tset6 := false\n\t\tsetName := rule.SetName(false)\n\t\tsetName6 := rule.SetName(true)\n\n\t\tif setName == \"\" || setName6 == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif rule.Protocol == firewall.Multicast ||\n\t\t\trule.Protocol == firewall.Broadcast {\n\n\t\t\tcmd = rules.newCommand()\n\n\t\t\tif rules.Interface != \"host\" {\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-o\", rules.Interface,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-p\", \"udp\",\n\t\t\t\t\"-m\", \"pkttype\",\n\t\t\t\t\"--pkt-type\", rule.Protocol,\n\t\t\t)\n\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-m\", \"udp\",\n\t\t\t\t\"--dport\", strings.Replace(rule.Port, \"-\", \":\", 1),\n\t\t\t)\n\n\t\t\tcmd = rules.commentCommand(cmd, false)\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-j\", \"ACCEPT\",\n\t\t\t)\n\n\t\t\trules.Ingress = append(rules.Ingress, cmd)\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, sourceIp := range rule.SourceIps {\n\t\t\tipv6 := strings.Contains(sourceIp, \":\")\n\n\t\t\tif sourceIp == \"0.0.0.0/0\" {\n\t\t\t\tif all4 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tall4 = true\n\t\t\t} else if sourceIp == \"::/0\" {\n\t\t\t\tif all6 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tall6 = true\n\t\t\t} else {\n\t\t\t\tif ipv6 {\n\t\t\t\t\tif set6 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tset6 = true\n\t\t\t\t} else {\n\t\t\t\t\tif set4 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tset4 = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcmd = rules.newCommand()\n\n\t\t\tif rules.Interface != \"host\" {\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-o\", rules.Interface,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tswitch rule.Protocol {\n\t\t\tcase firewall.All:\n\t\t\t\tbreak\n\t\t\tcase firewall.Icmp:\n\t\t\t\tif ipv6 {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-p\", \"ipv6-icmp\",\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-p\", \"icmp\",\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase firewall.Multicast, firewall.Broadcast:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-p\", \"udp\",\n\t\t\t\t\t\"-m\", \"pkttype\",\n\t\t\t\t\t\"--pkt-type\", rule.Protocol,\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tcase firewall.Tcp, firewall.Udp:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-p\", rule.Protocol,\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif sourceIp != \"0.0.0.0/0\" && sourceIp != \"::/0\" &&\n\t\t\t\trule.Protocol != firewall.Multicast &&\n\t\t\t\trule.Protocol != firewall.Broadcast {\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-m\", \"set\",\n\t\t\t\t\t\t\"--match-set\", setName6, \"src\",\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-m\", \"set\",\n\t\t\t\t\t\t\"--match-set\", setName, \"src\",\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch rule.Protocol {\n\t\t\tcase firewall.Multicast, firewall.Broadcast:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", \"udp\",\n\t\t\t\t\t\"--dport\", strings.Replace(rule.Port, \"-\", \":\", 1),\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\tcase firewall.Tcp, firewall.Udp:\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-m\", rule.Protocol,\n\t\t\t\t\t\"--dport\", strings.Replace(rule.Port, \"-\", \":\", 1),\n\t\t\t\t\t\"-m\", \"conntrack\",\n\t\t\t\t\t\"--ctstate\", \"NEW\",\n\t\t\t\t)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif rule.Protocol == firewall.Multicast ||\n\t\t\t\trule.Protocol == firewall.Broadcast {\n\n\t\t\t\tcmd = rules.commentCommandHeader(cmd)\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-j\", \"ACCEPT\",\n\t\t\t\t)\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\trules.Header6 = append(rules.Header6, cmd)\n\t\t\t\t} else {\n\t\t\t\t\trules.Header = append(rules.Header, cmd)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcmd = rules.commentCommand(cmd, false)\n\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\"-j\", \"ACCEPT\",\n\t\t\t\t)\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\trules.Ingress6 = append(rules.Ingress6, cmd)\n\t\t\t\t} else {\n\t\t\t\t\trules.Ingress = append(rules.Ingress, cmd)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"INVALID\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = append(cmd,\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"INVALID\",\n\t)\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress6 = append(rules.Ingress6, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress = append(rules.Ingress, cmd)\n\n\tcmd = rules.newCommand()\n\tif rules.Interface != \"host\" {\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", rules.Interface,\n\t\t)\n\t}\n\tcmd = rules.commentCommand(cmd, false)\n\tcmd = append(cmd,\n\t\t\"-j\", \"DROP\",\n\t)\n\trules.Ingress6 = append(rules.Ingress6, cmd)\n\n\tif nodePortNetwork {\n\t\tnodePortKeys := make([]string, 0, len(nodePortMappings))\n\t\tfor k := range nodePortMappings {\n\t\t\tnodePortKeys = append(nodePortKeys, k)\n\t\t}\n\t\tsort.Strings(nodePortKeys)\n\n\t\tfor _, nodePortAddr := range nodePortKeys {\n\t\t\tmappings := nodePortMappings[nodePortAddr]\n\t\t\tfor _, mapping := range mappings {\n\t\t\t\tif mapping.Protocol != firewall.Tcp &&\n\t\t\t\t\tmapping.Protocol != firewall.Udp {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif mapping.Ipvs {\n\t\t\t\t\tipvsProtocol := \"\"\n\t\t\t\t\tif mapping.Protocol == firewall.Udp {\n\t\t\t\t\t\tipvsProtocol = ipvs.Udp\n\t\t\t\t\t} else {\n\t\t\t\t\t\tipvsProtocol = ipvs.Tcp\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, publicIp := range puiblicIps {\n\t\t\t\t\t\trules.Ipvs.AddTarget(publicIp, mapping.Address,\n\t\t\t\t\t\t\tmapping.ExternalPort, ipvsProtocol)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Alternative to full SNAT\n\t\t\t\t\t// cmd = rules.newCommandMapPost()\n\t\t\t\t\t// cmd = append(cmd,\n\t\t\t\t\t// \t\"-m\", \"ipvs\",\n\t\t\t\t\t// \t\"--vproto\", protocolIndex(mapping.Protocol),\n\t\t\t\t\t// \t\"--vport\", fmt.Sprintf(\"%d\", mapping.ExternalPort),\n\t\t\t\t\t// \t\"--vdir\", \"ORIGINAL\",\n\t\t\t\t\t// )\n\t\t\t\t\t// cmd = rules.commentCommandMap(cmd)\n\t\t\t\t\t// cmd = append(cmd,\n\t\t\t\t\t// \t\"-j\", \"SNAT\",\n\t\t\t\t\t// \t\"--to-source\", nodePortGateway,\n\t\t\t\t\t// )\n\t\t\t\t\t// rules.Maps = append(rules.Maps, cmd)\n\t\t\t\t} else {\n\t\t\t\t\tnodePortIfaces := []string{}\n\t\t\t\t\tif defaultIface != \"\" {\n\t\t\t\t\t\tnodePortIfaces = append(nodePortIfaces, defaultIface)\n\t\t\t\t\t}\n\t\t\t\t\tfor _, iface := range externalIfaces {\n\t\t\t\t\t\tif iface != defaultIface {\n\t\t\t\t\t\t\tnodePortIfaces = append(nodePortIfaces, iface)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, externalIface := range nodePortIfaces {\n\t\t\t\t\t\tcmd = rules.newCommandMap()\n\t\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\t\"-i\", externalIface,\n\t\t\t\t\t\t\t\"-p\", mapping.Protocol,\n\t\t\t\t\t\t\t\"-m\", mapping.Protocol,\n\t\t\t\t\t\t\t\"--dport\", fmt.Sprintf(\"%d\", mapping.ExternalPort),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tcmd = rules.commentCommandMap(cmd)\n\t\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\t\"-j\", \"DNAT\",\n\t\t\t\t\t\t\t\"--to-destination\", fmt.Sprintf(\n\t\t\t\t\t\t\t\t\"%s:%d\",\n\t\t\t\t\t\t\t\tnodePortAddr,\n\t\t\t\t\t\t\t\tmapping.ExternalPort,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t\trules.Maps = append(rules.Maps, cmd)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcmd = rules.newCommandMapPost()\n\t\tcmd = append(cmd,\n\t\t\t\"-o\", settings.Hypervisor.NodePortNetworkName,\n\t\t)\n\t\tcmd = rules.commentCommandMap(cmd)\n\t\tcmd = append(cmd,\n\t\t\t\"-j\", \"SNAT\",\n\t\t\t\"--to-source\", nodePortGateway,\n\t\t)\n\t\trules.Maps = append(rules.Maps, cmd)\n\t}\n\n\treturn\n}\n\nfunc generateHostNodePort(namespace, iface string,\n\tnodePortGateway, defaultIface string, externalIfaces, puiblicIps []string,\n\tnodePortMappings map[string][]*firewall.Mapping) (rules *Rules) {\n\n\trules = &Rules{\n\t\tNamespace:        namespace,\n\t\tInterface:        iface,\n\t\tHeader:           [][]string{},\n\t\tHeader6:          [][]string{},\n\t\tSourceDestCheck:  [][]string{},\n\t\tSourceDestCheck6: [][]string{},\n\t\tIngress:          [][]string{},\n\t\tIngress6:         [][]string{},\n\t\tMaps:             [][]string{},\n\t\tMaps6:            [][]string{},\n\t\tHolds:            [][]string{},\n\t\tHolds6:           [][]string{},\n\t\tIpvs:             ipvs.New(),\n\t}\n\n\tnodePortKeys := make([]string, 0, len(nodePortMappings))\n\tfor k := range nodePortMappings {\n\t\tnodePortKeys = append(nodePortKeys, k)\n\t}\n\tsort.Strings(nodePortKeys)\n\n\tfor _, nodePortAddr := range nodePortKeys {\n\t\tmappings := nodePortMappings[nodePortAddr]\n\t\tfor _, mapping := range mappings {\n\t\t\tif mapping.Protocol != firewall.Tcp &&\n\t\t\t\tmapping.Protocol != firewall.Udp {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif mapping.Ipvs {\n\t\t\t\tipvsProtocol := \"\"\n\t\t\t\tif mapping.Protocol == firewall.Udp {\n\t\t\t\t\tipvsProtocol = ipvs.Udp\n\t\t\t\t} else {\n\t\t\t\t\tipvsProtocol = ipvs.Tcp\n\t\t\t\t}\n\n\t\t\t\tfor _, publicIp := range puiblicIps {\n\t\t\t\t\trules.Ipvs.AddTarget(publicIp, mapping.Address,\n\t\t\t\t\t\tmapping.ExternalPort, ipvsProtocol)\n\t\t\t\t}\n\n\t\t\t\t// Alternative to full SNAT\n\t\t\t\t// cmd = rules.newCommandMapPost()\n\t\t\t\t// cmd = append(cmd,\n\t\t\t\t// \t\"-m\", \"ipvs\",\n\t\t\t\t// \t\"--vproto\", protocolIndex(mapping.Protocol),\n\t\t\t\t// \t\"--vport\", fmt.Sprintf(\"%d\", mapping.ExternalPort),\n\t\t\t\t// \t\"--vdir\", \"ORIGINAL\",\n\t\t\t\t// )\n\t\t\t\t// cmd = rules.commentCommandMap(cmd)\n\t\t\t\t// cmd = append(cmd,\n\t\t\t\t// \t\"-j\", \"SNAT\",\n\t\t\t\t// \t\"--to-source\", nodePortGateway,\n\t\t\t\t// )\n\t\t\t\t// rules.Maps = append(rules.Maps, cmd)\n\t\t\t} else {\n\t\t\t\tnodePortIfaces := []string{}\n\t\t\t\tif defaultIface != \"\" {\n\t\t\t\t\tnodePortIfaces = append(nodePortIfaces, defaultIface)\n\t\t\t\t}\n\t\t\t\tfor _, iface := range externalIfaces {\n\t\t\t\t\tif iface != defaultIface {\n\t\t\t\t\t\tnodePortIfaces = append(nodePortIfaces, iface)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor _, externalIface := range nodePortIfaces {\n\t\t\t\t\tcmd := rules.newCommandMap()\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-i\", externalIface,\n\t\t\t\t\t\t\"-p\", mapping.Protocol,\n\t\t\t\t\t\t\"-m\", mapping.Protocol,\n\t\t\t\t\t\t\"--dport\", fmt.Sprintf(\"%d\", mapping.ExternalPort),\n\t\t\t\t\t)\n\t\t\t\t\tcmd = rules.commentCommandMap(cmd)\n\t\t\t\t\tcmd = append(cmd,\n\t\t\t\t\t\t\"-j\", \"DNAT\",\n\t\t\t\t\t\t\"--to-destination\", fmt.Sprintf(\n\t\t\t\t\t\t\t\"%s:%d\",\n\t\t\t\t\t\t\tnodePortAddr,\n\t\t\t\t\t\t\tmapping.ExternalPort,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\trules.Maps = append(rules.Maps, cmd)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tcmd := rules.newCommandMapPost()\n\tcmd = append(cmd,\n\t\t\"-o\", settings.Hypervisor.NodePortNetworkName,\n\t)\n\tcmd = rules.commentCommandMap(cmd)\n\tcmd = append(cmd,\n\t\t\"-j\", \"SNAT\",\n\t\t\"--to-source\", nodePortGateway,\n\t)\n\trules.Maps = append(rules.Maps, cmd)\n\n\treturn\n}\n"
  },
  {
    "path": "iptables/lock.go",
    "content": "package iptables\n\nimport (\n\t\"sync\"\n)\n\nvar lock = sync.Mutex{}\n\nfunc Lock() {\n\tlock.Lock()\n}\n\nfunc Unlock() {\n\tlock.Unlock()\n}\n"
  },
  {
    "path": "iptables/rules.go",
    "content": "package iptables\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/ipvs\"\n)\n\ntype Rules struct {\n\tNamespace        string\n\tInterface        string\n\tHeader           [][]string\n\tHeader6          [][]string\n\tSourceDestCheck  [][]string\n\tSourceDestCheck6 [][]string\n\tIngress          [][]string\n\tIngress6         [][]string\n\tNats             [][]string\n\tNats6            [][]string\n\tMaps             [][]string\n\tMaps6            [][]string\n\tHolds            [][]string\n\tHolds6           [][]string\n\tIpvs             *ipvs.State\n}\n\ntype RulesDiff struct {\n\tHeaderDiff           bool\n\tHeader6Diff          bool\n\tSourceDestCheckDiff  bool\n\tSourceDestCheck6Diff bool\n\tIngressDiff          bool\n\tIngress6Diff         bool\n\tNatsDiff             bool\n\tNats6Diff            bool\n\tMapsDiff             bool\n\tMaps6Diff            bool\n\tHoldsDiff            bool\n\tHolds6Diff           bool\n}\n"
  },
  {
    "path": "iptables/state.go",
    "content": "package iptables\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype State struct {\n\tInterfaces map[string]*Rules\n}\n\nfunc LoadState(nodeSelf *node.Node, vpcs []*vpc.Vpc,\n\tinstances []*instance.Instance, nodeFirewall []*firewall.Rule,\n\tfirewalls map[string][]*firewall.Rule,\n\tfirewallMaps map[string][]*firewall.Mapping) (state *State) {\n\n\tvpcsMap := map[bson.ObjectID]*vpc.Vpc{}\n\tfor _, vc := range vpcs {\n\t\tvpcsMap[vc.Id] = vc\n\t}\n\n\tnodeNetworkMode := node.Self.NetworkMode\n\tif nodeNetworkMode == \"\" {\n\t\tnodeNetworkMode = node.Dhcp\n\t}\n\tnodeNetworkMode6 := node.Self.NetworkMode6\n\tif nodeNetworkMode6 == \"\" {\n\t\tnodeNetworkMode6 = node.Dhcp\n\t}\n\tnodePortNetwork := !node.Self.NoNodePortNetwork\n\n\tstate = &State{\n\t\tInterfaces: map[string]*Rules{},\n\t}\n\n\thostNodePortMappings := map[string][]*firewall.Mapping{}\n\tnodePortGateway, err := block.GetNodePortGateway()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, inst := range instances {\n\t\tif !inst.IsActive() {\n\t\t\tcontinue\n\t\t}\n\n\t\tnamespace := vm.GetNamespace(inst.Id, 0)\n\t\tiface := vm.GetIface(inst.Id, 0)\n\t\tifaceHost := vm.GetIfaceHost(inst.Id, 1)\n\t\tifaceNodePort := vm.GetIfaceNodePort(inst.Id, 1)\n\t\tifaceExternal := vm.GetIfaceExternal(inst.Id, 0)\n\n\t\taddr := \"\"\n\t\taddr6 := \"\"\n\t\tpubAddr := \"\"\n\t\tpubAddr6 := \"\"\n\t\tif len(inst.PrivateIps) != 0 {\n\t\t\taddr = inst.PrivateIps[0]\n\t\t}\n\t\tif len(inst.PrivateIps6) != 0 {\n\t\t\taddr6 = inst.PrivateIps6[0]\n\t\t}\n\t\tif len(inst.PublicIps) != 0 {\n\t\t\tpubAddr = inst.PublicIps[0]\n\t\t}\n\t\tif len(inst.PublicIps6) != 0 {\n\t\t\tpubAddr6 = inst.PublicIps6[0]\n\t\t} else if len(inst.CloudPublicIps6) != 0 {\n\t\t\tpubAddr6 = inst.CloudPublicIps6[0]\n\t\t}\n\n\t\tnodePortAddr := \"\"\n\t\tif len(inst.NodePortIps) != 0 {\n\t\t\tnodePortAddr = inst.NodePortIps[0]\n\t\t}\n\t\thostNodePortMappings[nodePortAddr] = firewallMaps[namespace]\n\n\t\tcloudAddr := \"\"\n\t\tcloudIface := vm.GetIfaceCloud(inst.Id, 0)\n\t\tif len(inst.CloudPrivateIps) != 0 {\n\t\t\tcloudAddr = inst.CloudPrivateIps[0]\n\t\t}\n\n\t\t_, ok := state.Interfaces[namespace+\"-\"+iface]\n\t\tif ok {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"namespace\": namespace,\n\t\t\t\t\"interface\": iface,\n\t\t\t}).Error(\"iptables: Virtual interface conflict\")\n\t\t\tcontinue\n\t\t}\n\n\t\tingress := firewalls[namespace]\n\t\tif ingress == nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"namespace\":   namespace,\n\t\t\t}).Warn(\"iptables: Failed to load instance firewall rules\")\n\t\t\tcontinue\n\t\t}\n\n\t\tdhcp := false\n\t\tdhcp6 := false\n\n\t\tif nodeNetworkMode == node.Dhcp {\n\t\t\tdhcp = true\n\t\t}\n\t\tif nodeNetworkMode6 == node.Dhcp {\n\t\t\tdhcp6 = true\n\t\t}\n\n\t\tnat6 := false\n\t\tif nodeNetworkMode != node.Disabled &&\n\t\t\tnodeNetworkMode != node.Cloud {\n\n\t\t\tif nodeNetworkMode6 != node.Disabled &&\n\t\t\t\tnodeNetworkMode6 != node.Cloud {\n\n\t\t\t\tnat6 = true\n\t\t\t}\n\n\t\t\trules := generateInternal(namespace, ifaceExternal,\n\t\t\t\ttrue, nat6, dhcp, dhcp6, addr, pubAddr, addr6, pubAddr6,\n\t\t\t\tingress)\n\t\t\tstate.Interfaces[namespace+\"-\"+ifaceExternal] = rules\n\t\t} else if nodeNetworkMode6 != node.Disabled &&\n\t\t\tnodeNetworkMode6 != node.Cloud {\n\n\t\t\trules := generateInternal(namespace, ifaceExternal,\n\t\t\t\tfalse, true, dhcp, dhcp6, addr, pubAddr, addr6, pubAddr6,\n\t\t\t\tingress)\n\t\t\tstate.Interfaces[namespace+\"-\"+ifaceExternal] = rules\n\t\t}\n\n\t\tif nodeNetworkMode == node.Cloud {\n\t\t\tif nodeNetworkMode6 == node.Cloud {\n\t\t\t\tnat6 = true\n\t\t\t}\n\n\t\t\trules := generateInternal(namespace, cloudIface,\n\t\t\t\ttrue, nat6, false, false, addr, cloudAddr, addr6, pubAddr6,\n\t\t\t\tingress)\n\n\t\t\tstate.Interfaces[namespace+\"-\"+cloudIface] = rules\n\t\t}\n\n\t\trules := generateInternal(namespace, ifaceHost,\n\t\t\tfalse, false, false, false, \"\", \"\", \"\", \"\", ingress)\n\t\tstate.Interfaces[namespace+\"-\"+ifaceHost] = rules\n\n\t\tif nodePortNetwork {\n\t\t\trules := generateNodePort(namespace, ifaceNodePort,\n\t\t\t\taddr, nodePortGateway, firewallMaps[namespace])\n\t\t\tstate.Interfaces[namespace+\"-\"+ifaceNodePort] = rules\n\t\t}\n\n\t\trules = generateVirt(vpcsMap[inst.Vpc], namespace, iface, addr,\n\t\t\taddr6, !inst.SkipSourceDestCheck, ingress)\n\t\tstate.Interfaces[namespace+\"-\"+iface] = rules\n\t}\n\n\tif nodeFirewall != nil {\n\t\tstate.Interfaces[\"0-host\"] = generateHost(\"0\", \"host\",\n\t\t\t!nodeSelf.NoNodePortNetwork, nodePortGateway,\n\t\t\tnodeSelf.DefaultInterface, nodeSelf.ExternalInterfaces,\n\t\t\tnodeSelf.PublicIps, nodeFirewall, hostNodePortMappings)\n\t} else if !nodeSelf.NoNodePortNetwork {\n\t\tstate.Interfaces[\"0-host\"] = generateHostNodePort(\"0\", \"host\",\n\t\t\tnodePortGateway, nodeSelf.DefaultInterface,\n\t\t\tnodeSelf.ExternalInterfaces, nodeSelf.PublicIps,\n\t\t\thostNodePortMappings)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "iptables/update.go",
    "content": "package iptables\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/ipvs\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Update struct {\n\tOldState         *State\n\tNewState         *State\n\tNamespaces       []string\n\tFailedNamespaces set.Set\n}\n\nfunc (u *Update) Apply() {\n\tchanged := false\n\tvar removed []string\n\toldIfaces := set.NewSet()\n\tnewIfaces := set.NewSet()\n\n\tnamespacesSet := set.NewSet()\n\tfor _, namespace := range u.Namespaces {\n\t\tnamespacesSet.Add(namespace)\n\t}\n\n\tfor iface := range u.OldState.Interfaces {\n\t\toldIfaces.Add(iface)\n\t}\n\tfor iface := range u.NewState.Interfaces {\n\t\tnewIfaces.Add(iface)\n\t}\n\n\toldIfaces.Subtract(newIfaces)\n\tfor iface := range oldIfaces.Iter() {\n\t\tremoved = append(removed, iface.(string))\n\t\terr := u.OldState.Interfaces[iface.(string)].Remove(nil)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"iface\": iface,\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"iptables: Failed to delete removed interface iptables\")\n\t\t}\n\t}\n\n\tif removed != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"ifaces\": removed,\n\t\t}).Info(\"iptables: Removed iptables\")\n\t}\n\n\tfor _, rules := range u.NewState.Interfaces {\n\t\tif u.FailedNamespaces.Contains(rules.Namespace) {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"namespace\": rules.Namespace,\n\t\t\t}).Warn(\"iptables: Skipping failed namespace\")\n\t\t\tcontinue\n\t\t}\n\n\t\tif rules.Namespace != \"0\" &&\n\t\t\t!namespacesSet.Contains(rules.Namespace) {\n\n\t\t\t_, err := utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\"ip\", \"netns\",\n\t\t\t\t\"add\", rules.Namespace,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"namespace\": rules.Namespace,\n\t\t\t\t\t\"error\":     err,\n\t\t\t\t}).Error(\"iptables: Namespace add error\")\n\n\t\t\t\tu.FailedNamespaces.Add(rules.Namespace)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tvar diff *RulesDiff\n\t\toldRules := u.OldState.Interfaces[rules.Namespace+\"-\"+rules.Interface]\n\t\tif oldRules != nil {\n\t\t\tdiff = diffRules(oldRules, rules)\n\t\t\tif diff == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !changed {\n\t\t\t\tchanged = true\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"ingress\":  diff.IngressDiff,\n\t\t\t\t\t\"ingress6\": diff.Ingress6Diff,\n\t\t\t\t\t\"nats\":     diff.NatsDiff,\n\t\t\t\t\t\"nats6\":    diff.Nats6Diff,\n\t\t\t\t\t\"maps\":     diff.MapsDiff,\n\t\t\t\t\t\"maps6\":    diff.Maps6Diff,\n\t\t\t\t\t\"holds\":    diff.HoldsDiff,\n\t\t\t\t\t\"holds6\":   diff.Holds6Diff,\n\t\t\t\t}).Info(\"iptables: Updating iptables\")\n\t\t\t}\n\n\t\t\tif (diff.IngressDiff || diff.Ingress6Diff) &&\n\t\t\t\trules.Interface != \"host\" {\n\n\t\t\t\terr := rules.Hold()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"namespace\": rules.Namespace,\n\t\t\t\t\t\t\"error\":     err,\n\t\t\t\t\t}).Error(\"iptables: Namespace hold error\")\n\n\t\t\t\t\tu.FailedNamespaces.Add(rules.Namespace)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\terr := oldRules.Remove(diff)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"namespace\": rules.Namespace,\n\t\t\t\t\t\"error\":     err,\n\t\t\t\t}).Error(\"iptables: Namespace remove error\")\n\n\t\t\t\tu.FailedNamespaces.Add(rules.Namespace)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif !changed {\n\t\t\tchanged = true\n\t\t\tlogrus.Info(\"iptables: Updating iptables\")\n\t\t}\n\n\t\terr := rules.Apply(diff)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"namespace\": rules.Namespace,\n\t\t\t\t\"error\":     err,\n\t\t\t}).Error(\"iptables: Namespace apply error\")\n\n\t\t\tu.FailedNamespaces.Add(rules.Namespace)\n\t\t\tcontinue\n\t\t}\n\t}\n\n\thostIface := u.NewState.Interfaces[\"0-host\"]\n\tif hostIface != nil && hostIface.Ipvs != nil {\n\t\terr := ipvs.UpdateState(u.NewState.Interfaces[\"0-host\"].Ipvs)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"storage: Failed to update ipvs state\")\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (u *Update) Recover() {\n\tif u.FailedNamespaces.Contains(\"0\") {\n\t\terr := RecoverNode()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed to recover node iptables, retrying\")\n\t\t\ttime.Sleep(10 * time.Second)\n\t\t}\n\t}\n\n\tif u.FailedNamespaces.Len() > 0 {\n\t\tlogrus.Error(\"deploy: Failed to update iptables, \" +\n\t\t\t\"reloading state\")\n\n\t\ttime.Sleep(10 * time.Second)\n\n\t\terr := u.reload()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"deploy: Failed to recover iptables\")\n\t\t}\n\t}\n}\n\nfunc (u *Update) reload() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tnodeDatacenter, err := node.Self.GetDatacenter(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvpcs := []*vpc.Vpc{}\n\tif !nodeDatacenter.IsZero() {\n\t\tvpcs, err = vpc.GetDatacenter(db, nodeDatacenter)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tnamespaces, err := utils.GetNamespaces()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tinstances, err := instance.GetAllVirt(db, &bson.M{\n\t\t\"node\": node.Self.Id,\n\t}, nil, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tspecRules, nodePortsMap, err := firewall.GetSpecRulesSlow(\n\t\tdb, node.Self.Id, instances)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnodeFirewall, firewalls, firewallMaps, _, err := firewall.GetAllIngress(\n\t\tdb, node.Self, instances, specRules, nodePortsMap)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = Init(namespaces, vpcs, instances, nodeFirewall,\n\t\tfirewalls, firewallMaps)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ApplyUpdate(newState *State, namespaces []string, recover bool) {\n\tlockId := stateLock.Lock()\n\n\tupdate := &Update{\n\t\tOldState:         curState,\n\t\tNewState:         newState,\n\t\tNamespaces:       namespaces,\n\t\tFailedNamespaces: set.NewSet(),\n\t}\n\n\tupdate.Apply()\n\n\tcurState = newState\n\n\tstateLock.Unlock(lockId)\n\n\tif recover {\n\t\tupdate.Recover()\n\t}\n\n\treturn\n}\n\nfunc UpdateState(nodeSelf *node.Node, vpcs []*vpc.Vpc,\n\tinstances []*instance.Instance, namespaces []string,\n\tnodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule,\n\tfirewallMaps map[string][]*firewall.Mapping) {\n\n\tnewState := LoadState(nodeSelf, vpcs, instances, nodeFirewall,\n\t\tfirewalls, firewallMaps)\n\n\tApplyUpdate(newState, namespaces, false)\n\n\treturn\n}\n\nfunc UpdateStateRecover(nodeSelf *node.Node, vpcs []*vpc.Vpc,\n\tinstances []*instance.Instance, namespaces []string,\n\tnodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule,\n\tfirewallMaps map[string][]*firewall.Mapping) {\n\n\tnewState := LoadState(nodeSelf, vpcs, instances, nodeFirewall,\n\t\tfirewalls, firewallMaps)\n\n\tApplyUpdate(newState, namespaces, true)\n\n\treturn\n}\n"
  },
  {
    "path": "iptables/utils.go",
    "content": "package iptables\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc diffCmd(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn true\n\t}\n\n\tfor i := range a {\n\t\tif a[i] != b[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc diffRules(a, b *Rules) *RulesDiff {\n\tdiff := &RulesDiff{}\n\tchanged := false\n\n\tif len(a.Header) != len(b.Header) {\n\t\tdiff.HeaderDiff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Header {\n\t\t\tif diffCmd(a.Header[i], b.Header[i]) {\n\t\t\t\tdiff.HeaderDiff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.Header6) != len(b.Header6) {\n\t\tdiff.Header6Diff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Header6 {\n\t\t\tif diffCmd(a.Header6[i], b.Header6[i]) {\n\t\t\t\tdiff.Header6Diff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.SourceDestCheck) != len(b.SourceDestCheck) {\n\t\tdiff.SourceDestCheckDiff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.SourceDestCheck {\n\t\t\tif diffCmd(a.SourceDestCheck[i], b.SourceDestCheck[i]) {\n\t\t\t\tdiff.SourceDestCheckDiff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.SourceDestCheck6) != len(b.SourceDestCheck6) {\n\t\tdiff.SourceDestCheck6Diff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.SourceDestCheck6 {\n\t\t\tif diffCmd(a.SourceDestCheck6[i], b.SourceDestCheck6[i]) {\n\t\t\t\tdiff.SourceDestCheck6Diff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.Ingress) != len(b.Ingress) {\n\t\tdiff.IngressDiff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Ingress {\n\t\t\tif diffCmd(a.Ingress[i], b.Ingress[i]) {\n\t\t\t\tdiff.IngressDiff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.Ingress6) != len(b.Ingress6) {\n\t\tdiff.Ingress6Diff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Ingress6 {\n\t\t\tif diffCmd(a.Ingress6[i], b.Ingress6[i]) {\n\t\t\t\tdiff.Ingress6Diff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.Nats) != len(b.Nats) {\n\t\tdiff.NatsDiff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Nats {\n\t\t\tif diffCmd(a.Nats[i], b.Nats[i]) {\n\t\t\t\tdiff.NatsDiff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.Nats6) != len(b.Nats6) {\n\t\tdiff.Nats6Diff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Nats6 {\n\t\t\tif diffCmd(a.Nats6[i], b.Nats6[i]) {\n\t\t\t\tdiff.Nats6Diff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.Maps) != len(b.Maps) {\n\t\tdiff.MapsDiff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Maps {\n\t\t\tif diffCmd(a.Maps[i], b.Maps[i]) {\n\t\t\t\tdiff.MapsDiff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.Maps6) != len(b.Maps6) {\n\t\tdiff.Maps6Diff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Maps6 {\n\t\t\tif diffCmd(a.Maps6[i], b.Maps6[i]) {\n\t\t\t\tdiff.Maps6Diff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.Holds) != len(b.Holds) {\n\t\tdiff.HoldsDiff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Holds {\n\t\t\tif diffCmd(a.Holds[i], b.Holds[i]) {\n\t\t\t\tdiff.HoldsDiff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(a.Holds6) != len(b.Holds6) {\n\t\tdiff.Holds6Diff = true\n\t\tchanged = true\n\t} else {\n\t\tfor i := range a.Holds6 {\n\t\t\tif diffCmd(a.Holds6[i], b.Holds6[i]) {\n\t\t\t\tdiff.Holds6Diff = true\n\t\t\t\tchanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif !changed {\n\t\treturn nil\n\t}\n\n\treturn diff\n}\n\nfunc getIptablesCmd(ipv6 bool) string {\n\tif ipv6 {\n\t\treturn \"ip6tables\"\n\t} else {\n\t\treturn \"iptables\"\n\t}\n}\n\nfunc loadIptables(namespace, instIface string, state *State,\n\tipv6 bool) (err error) {\n\n\tLock()\n\tdefer Unlock()\n\n\tiptablesCmd := getIptablesCmd(ipv6)\n\n\toutput := \"\"\n\tif namespace == \"0\" {\n\t\toutput, err = utils.ExecOutput(\"\", iptablesCmd, \"-S\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\toutput, err = utils.ExecOutput(\"\",\n\t\t\t\"ip\", \"netns\", \"exec\", namespace, iptablesCmd, \"-S\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, line := range strings.Split(output, \"\\n\") {\n\t\truleComment := strings.Contains(line, \"pritunl_cloud_rule\")\n\t\tholdComment := strings.Contains(line, \"pritunl_cloud_hold\")\n\t\theadComment := strings.Contains(line, \"pritunl_cloud_head\")\n\t\tsdcComment := strings.Contains(line, \"pritunl_cloud_sdc\")\n\n\t\tif !ruleComment && !holdComment && !headComment && !sdcComment {\n\t\t\tcontinue\n\t\t}\n\n\t\tcmd := strings.Fields(line)\n\t\tif len(cmd) < 3 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"iptables_rule\": line,\n\t\t\t}).Error(\"iptables: Invalid iptables state\")\n\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"iptables: Invalid iptables state\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tcmd = cmd[1:]\n\n\t\tiface := \"\"\n\t\tif sdcComment {\n\t\t\tif cmd[0] != \"FORWARD\" {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t}).Error(\"iptables: Invalid iptables sdc chain\")\n\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.New(\"iptables: Invalid iptables sdc chain\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor i, item := range cmd {\n\t\t\t\tif item == \"--physdev-in\" || item == \"--physdev-out\" {\n\t\t\t\t\tif len(cmd) < i+2 {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t\t\t}).Error(\"iptables: Invalid iptables sdc interface\")\n\n\t\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\t\terrors.New(\n\t\t\t\t\t\t\t\t\"iptables: Invalid iptables sdc interface\"),\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tiface = strings.Trim(cmd[i+1], \"+\")\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t} else if namespace != \"0\" {\n\t\t\tif cmd[0] != \"FORWARD\" {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t}).Error(\"iptables: Invalid iptables chain\")\n\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.New(\"iptables: Invalid iptables chain\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor i, item := range cmd {\n\t\t\t\tif item == \"--physdev-out\" || item == \"-o\" || item == \"-i\" {\n\t\t\t\t\tif len(cmd) < i+2 {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t\t\t}).Error(\"iptables: Invalid iptables interface\")\n\n\t\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\t\terrors.New(\n\t\t\t\t\t\t\t\t\"iptables: Invalid iptables interface\"),\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tiface = strings.Trim(cmd[i+1], \"+\")\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tiface = \"host\"\n\n\t\t\tif cmd[0] != \"INPUT\" {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t}).Error(\"iptables: Invalid iptables chain\")\n\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.New(\"iptables: Invalid iptables chain\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif iface == \"\" {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"iptables_rule\": line,\n\t\t\t}).Error(\"iptables: Missing iptables interface\")\n\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"iptables: Missing iptables interface\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\trules := state.Interfaces[namespace+\"-\"+iface]\n\t\tif rules == nil {\n\t\t\trules = &Rules{\n\t\t\t\tNamespace:        namespace,\n\t\t\t\tInterface:        iface,\n\t\t\t\tHeader:           [][]string{},\n\t\t\t\tHeader6:          [][]string{},\n\t\t\t\tSourceDestCheck:  [][]string{},\n\t\t\t\tSourceDestCheck6: [][]string{},\n\t\t\t\tIngress:          [][]string{},\n\t\t\t\tIngress6:         [][]string{},\n\t\t\t\tMaps:             [][]string{},\n\t\t\t\tMaps6:            [][]string{},\n\t\t\t\tHolds:            [][]string{},\n\t\t\t\tHolds6:           [][]string{},\n\t\t\t}\n\t\t\tstate.Interfaces[namespace+\"-\"+iface] = rules\n\t\t}\n\n\t\tif holdComment {\n\t\t\tif ipv6 {\n\t\t\t\trules.Holds6 = append(rules.Holds6, cmd)\n\t\t\t} else {\n\t\t\t\trules.Holds = append(rules.Holds, cmd)\n\t\t\t}\n\t\t} else if sdcComment {\n\t\t\tif ipv6 {\n\t\t\t\trules.SourceDestCheck6 = append(rules.SourceDestCheck6, cmd)\n\t\t\t} else {\n\t\t\t\trules.SourceDestCheck = append(rules.SourceDestCheck, cmd)\n\t\t\t}\n\t\t} else {\n\t\t\tif headComment {\n\t\t\t\tif ipv6 {\n\t\t\t\t\trules.Header6 = append(rules.Header6, cmd)\n\t\t\t\t} else {\n\t\t\t\t\trules.Header = append(rules.Header, cmd)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ipv6 {\n\t\t\t\t\trules.Ingress6 = append(rules.Ingress6, cmd)\n\t\t\t\t} else {\n\t\t\t\t\trules.Ingress = append(rules.Ingress, cmd)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif namespace == \"0\" {\n\t\toutput, err = utils.ExecOutput(\"\", iptablesCmd, \"-S\", \"-t\", \"nat\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\toutput, err = utils.ExecOutput(\"\",\n\t\t\t\"ip\", \"netns\", \"exec\", namespace, iptablesCmd, \"-S\", \"-t\", \"nat\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tpostIface := \"\"\n\tnatRules := [][]string{}\n\n\tfor _, line := range strings.Split(output, \"\\n\") {\n\t\tnatComment := strings.Contains(line, \"pritunl_cloud_nat\")\n\t\tmapComment := strings.Contains(line, \"pritunl_cloud_map\")\n\n\t\tif !natComment && !mapComment {\n\t\t\tcontinue\n\t\t}\n\n\t\tcmd := strings.Fields(line)\n\t\tif len(cmd) < 3 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"iptables_rule\": line,\n\t\t\t}).Error(\"iptables: Invalid iptables state\")\n\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"iptables: Invalid iptables state\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tcmd = cmd[1:]\n\n\t\tif mapComment && namespace == \"0\" {\n\t\t\tif cmd[0] != \"PREROUTING\" && cmd[0] != \"POSTROUTING\" {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t}).Error(\"iptables: Invalid iptables map chain\")\n\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.New(\"iptables: Invalid iptables map chain\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trules := state.Interfaces[namespace+\"-host\"]\n\t\t\tif rules == nil {\n\t\t\t\trules = &Rules{\n\t\t\t\t\tNamespace:        namespace,\n\t\t\t\t\tInterface:        \"host\",\n\t\t\t\t\tHeader:           [][]string{},\n\t\t\t\t\tHeader6:          [][]string{},\n\t\t\t\t\tSourceDestCheck:  [][]string{},\n\t\t\t\t\tSourceDestCheck6: [][]string{},\n\t\t\t\t\tIngress:          [][]string{},\n\t\t\t\t\tIngress6:         [][]string{},\n\t\t\t\t\tMaps:             [][]string{},\n\t\t\t\t\tMaps6:            [][]string{},\n\t\t\t\t\tHolds:            [][]string{},\n\t\t\t\t\tHolds6:           [][]string{},\n\t\t\t\t}\n\t\t\t\tstate.Interfaces[namespace+\"-host\"] = rules\n\t\t\t}\n\n\t\t\tif ipv6 {\n\t\t\t\trules.Maps6 = append(rules.Maps6, cmd)\n\t\t\t} else {\n\t\t\t\trules.Maps = append(rules.Maps, cmd)\n\t\t\t}\n\t\t} else if mapComment {\n\t\t\tiface := instIface\n\n\t\t\tfor i, item := range cmd {\n\t\t\t\tif item == \"-i\" {\n\t\t\t\t\tif len(cmd) < i+2 {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t\t\t}).Error(\"iptables: Invalid iptables interface\")\n\n\t\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\t\terrors.New(\n\t\t\t\t\t\t\t\t\"iptables: Invalid iptables interface\"),\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tiface = strings.Trim(cmd[i+1], \"+\")\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif iface == \"\" {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"namespace\":     namespace,\n\t\t\t\t\t\"iface\":         iface,\n\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t}).Error(\"iptables: Missing instance iface for map\")\n\t\t\t} else {\n\t\t\t\tif cmd[0] != \"PREROUTING\" {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t\t}).Error(\"iptables: Invalid iptables map chain\")\n\n\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\terrors.New(\"iptables: Invalid iptables map chain\"),\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\trules := state.Interfaces[namespace+\"-\"+iface]\n\t\t\t\tif rules == nil {\n\t\t\t\t\trules = &Rules{\n\t\t\t\t\t\tNamespace:        namespace,\n\t\t\t\t\t\tInterface:        iface,\n\t\t\t\t\t\tHeader:           [][]string{},\n\t\t\t\t\t\tHeader6:          [][]string{},\n\t\t\t\t\t\tSourceDestCheck:  [][]string{},\n\t\t\t\t\t\tSourceDestCheck6: [][]string{},\n\t\t\t\t\t\tIngress:          [][]string{},\n\t\t\t\t\t\tIngress6:         [][]string{},\n\t\t\t\t\t\tMaps:             [][]string{},\n\t\t\t\t\t\tMaps6:            [][]string{},\n\t\t\t\t\t\tHolds:            [][]string{},\n\t\t\t\t\t\tHolds6:           [][]string{},\n\t\t\t\t\t}\n\t\t\t\t\tstate.Interfaces[namespace+\"-\"+iface] = rules\n\t\t\t\t}\n\n\t\t\t\tif ipv6 {\n\t\t\t\t\trules.Maps6 = append(rules.Maps6, cmd)\n\t\t\t\t} else {\n\t\t\t\t\trules.Maps = append(rules.Maps, cmd)\n\t\t\t\t}\n\t\t\t}\n\t\t} else if natComment {\n\t\t\tif cmd[0] != \"PREROUTING\" && cmd[0] != \"POSTROUTING\" {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t}).Error(\"iptables: Invalid iptables map chain\")\n\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.New(\"iptables: Invalid iptables map chain\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif cmd[0] == \"POSTROUTING\" {\n\t\t\t\tfor i, item := range cmd {\n\t\t\t\t\tif item == \"-o\" {\n\t\t\t\t\t\tif len(cmd) < i+2 {\n\t\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t\t\t\t}).Error(\"iptables: Invalid iptables addr\")\n\n\t\t\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\t\t\terrors.New(\n\t\t\t\t\t\t\t\t\t\"iptables: Invalid iptables addr\"),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpostIface = strings.Trim(cmd[i+1], \"+\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tnatRules = append(natRules, cmd)\n\t\t}\n\t}\n\n\tcloudPostIface := \"\"\n\tcloudNatRules := [][]string{}\n\n\tfor _, line := range strings.Split(output, \"\\n\") {\n\t\tif !strings.Contains(line, \"pritunl_cloud_cloud_nat\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tcmd := strings.Fields(line)\n\t\tif len(cmd) < 3 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"iptables_rule\": line,\n\t\t\t}).Error(\"iptables: Invalid iptables state\")\n\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"iptables: Invalid iptables state\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tcmd = cmd[1:]\n\n\t\tif cmd[0] != \"PREROUTING\" && cmd[0] != \"POSTROUTING\" {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"iptables_rule\": line,\n\t\t\t}).Error(\"iptables: Invalid iptables map chain\")\n\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"iptables: Invalid iptables map chain\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif cmd[0] == \"POSTROUTING\" {\n\t\t\tfor i, item := range cmd {\n\t\t\t\tif item == \"-o\" {\n\t\t\t\t\tif len(cmd) < i+2 {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"iptables_rule\": line,\n\t\t\t\t\t\t}).Error(\"iptables: Invalid iptables addr\")\n\n\t\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\t\terrors.New(\n\t\t\t\t\t\t\t\t\"iptables: Invalid iptables addr\"),\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tcloudPostIface = strings.Trim(cmd[i+1], \"+\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcloudNatRules = append(cloudNatRules, cmd)\n\t}\n\n\tif postIface != \"\" {\n\t\trules := state.Interfaces[namespace+\"-\"+postIface]\n\t\tif rules == nil {\n\t\t\trules = &Rules{\n\t\t\t\tNamespace:        namespace,\n\t\t\t\tInterface:        postIface,\n\t\t\t\tHeader:           [][]string{},\n\t\t\t\tHeader6:          [][]string{},\n\t\t\t\tSourceDestCheck:  [][]string{},\n\t\t\t\tSourceDestCheck6: [][]string{},\n\t\t\t\tIngress:          [][]string{},\n\t\t\t\tIngress6:         [][]string{},\n\t\t\t\tHolds:            [][]string{},\n\t\t\t\tHolds6:           [][]string{},\n\t\t\t}\n\t\t\tstate.Interfaces[namespace+\"-\"+postIface] = rules\n\t\t}\n\n\t\tif ipv6 {\n\t\t\trules.Nats6 = append(rules.Nats6, natRules...)\n\t\t} else {\n\t\t\trules.Nats = append(rules.Nats, natRules...)\n\t\t}\n\t}\n\n\tif cloudPostIface != \"\" {\n\t\trules := state.Interfaces[namespace+\"-\"+cloudPostIface]\n\t\tif rules == nil {\n\t\t\trules = &Rules{\n\t\t\t\tNamespace:        namespace,\n\t\t\t\tInterface:        cloudPostIface,\n\t\t\t\tHeader:           [][]string{},\n\t\t\t\tHeader6:          [][]string{},\n\t\t\t\tSourceDestCheck:  [][]string{},\n\t\t\t\tSourceDestCheck6: [][]string{},\n\t\t\t\tIngress:          [][]string{},\n\t\t\t\tIngress6:         [][]string{},\n\t\t\t\tHolds:            [][]string{},\n\t\t\t\tHolds6:           [][]string{},\n\t\t\t}\n\t\t\tstate.Interfaces[namespace+\"-\"+cloudPostIface] = rules\n\t\t}\n\n\t\tif ipv6 {\n\t\t\trules.Nats6 = append(rules.Nats6, cloudNatRules...)\n\t\t} else {\n\t\t\trules.Nats = append(rules.Nats, cloudNatRules...)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RecoverNode() (err error) {\n\tcmds := [][]string{}\n\n\tif !node.Self.Firewall {\n\t\treturn\n\t}\n\n\tcmds = append(cmds, []string{\n\t\t\"-I\", \"INPUT\", \"1\",\n\t\t\"-m\", \"comment\",\n\t\t\"--comment\", \"pritunl_cloud_rule\",\n\t\t\"-j\", \"DROP\",\n\t})\n\tcmds = append(cmds, []string{\n\t\t\"-I\", \"INPUT\", \"1\",\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"INVALID\",\n\t\t\"-m\", \"comment\",\n\t\t\"--comment\", \"pritunl_cloud_rule\",\n\t\t\"-j\", \"DROP\",\n\t})\n\tcmds = append(cmds, []string{\n\t\t\"-I\", \"INPUT\", \"1\",\n\t\t\"-m\", \"conntrack\",\n\t\t\"--ctstate\", \"RELATED,ESTABLISHED\",\n\t\t\"-m\", \"comment\", \"--comment\", \"pritunl_cloud_rule\",\n\t\t\"-j\", \"ACCEPT\",\n\t})\n\n\tfor _, cmd := range cmds {\n\t\tLock()\n\t\toutput, e := utils.ExecCombinedOutput(\"\", \"iptables\", cmd...)\n\t\tUnlock()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"command\": cmd,\n\t\t\t\t\"output\":  output,\n\t\t\t\t\"error\":   err,\n\t\t\t}).Error(\"iptables: Failed to add iptables recover rule\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, cmd := range cmds {\n\t\tLock()\n\t\toutput, e := utils.ExecCombinedOutput(\"\", \"ip6tables\", cmd...)\n\t\tUnlock()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"command\": cmd,\n\t\t\t\t\"output\":  output,\n\t\t\t\t\"error\":   err,\n\t\t\t}).Error(\"iptables: Failed to add ip6tables recover rule\")\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Init(namespaces []string, vpcs []*vpc.Vpc,\n\tinstances []*instance.Instance, nodeFirewall []*firewall.Rule,\n\tfirewalls map[string][]*firewall.Rule,\n\tfirewallMaps map[string][]*firewall.Mapping) (err error) {\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil, \"sysctl\", \"-w\", \"net.ipv6.conf.all.accept_ra=2\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil, \"sysctl\", \"-w\", \"net.ipv6.conf.default.accept_ra=2\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tinterfaces, err := utils.GetInterfaces()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, iface := range interfaces {\n\t\tif len(iface) == 14 && (strings.HasPrefix(iface, \"v\") ||\n\t\t\tstrings.HasPrefix(iface, \"x\")) {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tutils.ExecCombinedOutput(\"\",\n\t\t\t\"sysctl\", \"-w\",\n\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.accept_ra=2\", iface),\n\t\t)\n\t}\n\n\tutils.ExecCombinedOutput(\n\t\t\"\", \"sysctl\", \"-w\", \"net.bridge.bridge-nf-call-iptables=1\",\n\t)\n\tutils.ExecCombinedOutput(\n\t\t\"\", \"sysctl\", \"-w\", \"net.bridge.bridge-nf-call-ip6tables=1\",\n\t)\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil, \"sysctl\", \"-w\", \"net.ipv4.ip_forward=1\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil, \"sysctl\", \"-w\", \"net.ipv6.conf.all.forwarding=1\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstate := &State{\n\t\tInterfaces: map[string]*Rules{},\n\t}\n\n\terr = loadIptables(\"0\", \"\", state, false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = loadIptables(\"0\", \"\", state, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnamespaceMap := map[string]*instance.Instance{}\n\tfor _, inst := range instances {\n\t\tnamespaceMap[vm.GetNamespace(inst.Id, 0)] = inst\n\t}\n\n\tfor _, namespace := range namespaces {\n\t\tinstIface := \"\"\n\t\tinst := namespaceMap[namespace]\n\t\tif inst != nil {\n\t\t\tinstIface = vm.GetIface(inst.Id, 0)\n\t\t}\n\n\t\terr = loadIptables(namespace, instIface, state, false)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = loadIptables(namespace, instIface, state, true)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tcurState = state\n\n\tUpdateState(node.Self, vpcs, instances,\n\t\tnamespaces, nodeFirewall, firewalls, firewallMaps)\n\n\treturn\n}\n\nfunc protocolIndex(proto string) string {\n\tswitch proto {\n\tcase \"icmp\":\n\t\treturn \"1\"\n\tcase \"tcp\":\n\t\treturn \"6\"\n\tcase \"udp\":\n\t\treturn \"17\"\n\tdefault:\n\t\treturn \"0\"\n\t}\n}\n"
  },
  {
    "path": "ipvs/constants.go",
    "content": "package ipvs\n\nconst (\n\tTcp = \"-t\"\n\tUdp = \"-u\"\n\n\tRoundRobin = \"rr\"\n)\n"
  },
  {
    "path": "ipvs/ipvs.go",
    "content": "package ipvs\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pritunl/tools/commander\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tcurState *State\n)\n\ntype State struct {\n\tServices map[string]*Service\n}\n\nfunc (s *State) Print() string {\n\tvar output strings.Builder\n\n\toutput.WriteString(\"IPVS Configuration:\\n\")\n\toutput.WriteString(\"=================\\n\\n\")\n\n\tif len(s.Services) == 0 {\n\t\toutput.WriteString(\"No services configured.\\n\")\n\t\treturn output.String()\n\t}\n\n\tserviceKeys := make([]string, 0, len(s.Services))\n\tfor key := range s.Services {\n\t\tserviceKeys = append(serviceKeys, key)\n\t}\n\tsort.Strings(serviceKeys)\n\n\tfor _, key := range serviceKeys {\n\t\tservice := s.Services[key]\n\t\toutput.WriteString(fmt.Sprintf(\"Service: %s%s\\n\", service.Protocol, service.Key()))\n\t\toutput.WriteString(fmt.Sprintf(\"  Key: %s\\n\", key))\n\t\toutput.WriteString(fmt.Sprintf(\"  Scheduler: %s\\n\", service.Scheduler))\n\n\t\tif len(service.Targets) == 0 {\n\t\t\toutput.WriteString(\"  No targets configured.\\n\")\n\t\t} else {\n\t\t\toutput.WriteString(\"  Targets:\\n\")\n\n\t\t\tsort.Slice(service.Targets, func(i, j int) bool {\n\t\t\t\tif service.Targets[i].Address != service.Targets[j].Address {\n\t\t\t\t\treturn service.Targets[i].Address < service.Targets[j].Address\n\t\t\t\t}\n\t\t\t\treturn service.Targets[i].Port < service.Targets[j].Port\n\t\t\t})\n\n\t\t\tfor _, target := range service.Targets {\n\t\t\t\tmasq := \"No\"\n\t\t\t\tif target.Masquerade {\n\t\t\t\t\tmasq = \"Yes\"\n\t\t\t\t}\n\t\t\t\toutput.WriteString(fmt.Sprintf(\"    - %s (Weight: %d, Masquerade: %s)\\n\",\n\t\t\t\t\ttarget.Key(), target.Weight, masq))\n\t\t\t}\n\t\t}\n\t\toutput.WriteString(\"\\n\")\n\t}\n\n\treturn output.String()\n}\n\nfunc (s *State) AddTarget(serviceAddr, targetAddr string,\n\tport int, protocol string) (err error) {\n\n\tserviceKey := fmt.Sprintf(\"%s%s:%d\", protocol, serviceAddr, port)\n\tservice := s.Services[serviceKey]\n\tif service == nil {\n\t\tservice = &Service{\n\t\t\tScheduler: RoundRobin,\n\t\t\tProtocol:  protocol,\n\t\t\tAddress:   serviceAddr,\n\t\t\tPort:      port,\n\t\t}\n\t\ts.Services[serviceKey] = service\n\t}\n\n\ttarget := &Target{\n\t\tService:    service,\n\t\tAddress:    targetAddr,\n\t\tPort:       port,\n\t\tWeight:     1,\n\t\tMasquerade: true,\n\t}\n\tservice.Targets = append(service.Targets, target)\n\n\treturn\n}\n\nfunc UpdateState(newState *State) (err error) {\n\tupdated := false\n\n\tif curState == nil {\n\t\tvar state *State\n\t\tstate, err = LoadState()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tcurState = state\n\t}\n\n\tfor serviceKey, service := range curState.Services {\n\t\tnewService := newState.Services[serviceKey]\n\n\t\tif newService == nil {\n\t\t\tif !updated {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"reason\": \"unknown_service\",\n\t\t\t\t}).Info(\"ipvs: Updating ipvs state\")\n\t\t\t\tupdated = true\n\t\t\t}\n\t\t\terr = service.Delete()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, target := range service.Targets {\n\t\t\tfound := false\n\t\t\tfor _, newTarget := range newService.Targets {\n\t\t\t\tif target.Address == newTarget.Address &&\n\t\t\t\t\ttarget.Port == newTarget.Port {\n\n\t\t\t\t\tif target.Weight != newTarget.Weight ||\n\t\t\t\t\t\ttarget.Masquerade != newTarget.Masquerade {\n\n\t\t\t\t\t\tif !updated {\n\t\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\t\"reason\": \"weight_masquerade\",\n\t\t\t\t\t\t\t}).Info(\"ipvs: Updating ipvs state\")\n\t\t\t\t\t\t\tupdated = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\terr = target.Delete()\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfound = false\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !found {\n\t\t\t\tif !updated {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"reason\": \"target_unknown\",\n\t\t\t\t\t}).Info(\"ipvs: Updating ipvs state\")\n\t\t\t\t\tupdated = true\n\t\t\t\t}\n\t\t\t\terr = target.Delete()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif service.Scheduler != newService.Scheduler {\n\t\t\tif !updated {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"reason\": \"scheduler\",\n\t\t\t\t}).Info(\"ipvs: Updating ipvs state\")\n\t\t\t\tupdated = true\n\t\t\t}\n\t\t\terr = service.Delete()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = newService.Add()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor _, target := range newService.Targets {\n\t\t\t\ttarget.Service = newService\n\t\t\t\terr = target.Add()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor serviceKey, newService := range newState.Services {\n\t\tservice := curState.Services[serviceKey]\n\n\t\tif service == nil {\n\t\t\tif !updated {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"reason\": \"new_service\",\n\t\t\t\t}).Info(\"ipvs: Updating ipvs state\")\n\t\t\t\tupdated = true\n\t\t\t}\n\t\t\terr = newService.Add()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor _, target := range newService.Targets {\n\t\t\t\ttarget.Service = newService\n\t\t\t\terr = target.Add()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t} else if service.Scheduler == newService.Scheduler {\n\t\t\tfor _, newTarget := range newService.Targets {\n\t\t\t\tfound := false\n\t\t\t\tneedsUpdate := false\n\n\t\t\t\tfor _, target := range service.Targets {\n\t\t\t\t\tif target.Address == newTarget.Address &&\n\t\t\t\t\t\ttarget.Port == newTarget.Port {\n\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t\tif target.Weight != newTarget.Weight ||\n\t\t\t\t\t\t\ttarget.Masquerade != newTarget.Masquerade {\n\n\t\t\t\t\t\t\tneedsUpdate = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif !found || needsUpdate {\n\t\t\t\t\tnewTarget.Service = service\n\t\t\t\t\tif !updated {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"reason\": \"new_target\",\n\t\t\t\t\t\t}).Info(\"ipvs: Updating ipvs state\")\n\t\t\t\t\t\tupdated = true\n\t\t\t\t\t}\n\t\t\t\t\terr = newTarget.Add()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tcurState = newState\n\treturn\n}\n\nfunc LoadState() (state *State, err error) {\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName: \"ipvsadm-save\",\n\t\tArgs: []string{\n\t\t\t\"-n\",\n\t\t},\n\t\tTimeout: 10 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tlogrus.WithFields(resp.Map()).Error(\"ipvs: Failed to load state\")\n\t\treturn\n\t}\n\n\tstate = &State{\n\t\tServices: map[string]*Service{},\n\t}\n\n\tfor _, line := range strings.Split(string(resp.Output), \"\\n\") {\n\t\tline = strings.TrimSpace(line)\n\t\tif line == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tparts := strings.Fields(line)\n\t\tif len(parts) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif parts[0] == \"-A\" {\n\t\t\tserviceKey := \"\"\n\t\t\tservice := &Service{\n\t\t\t\tTargets: []*Target{},\n\t\t\t}\n\n\t\t\tfor i := 0; i < len(parts); i++ {\n\t\t\t\tswitch parts[i] {\n\t\t\t\tcase \"-t\":\n\t\t\t\t\tif i+1 < len(parts) {\n\t\t\t\t\t\tserviceKey = parts[i+1]\n\t\t\t\t\t\taddrPort := strings.Split(serviceKey, \":\")\n\t\t\t\t\t\tserviceKey = Tcp + serviceKey\n\n\t\t\t\t\t\tservice.Protocol = Tcp\n\t\t\t\t\t\tif len(addrPort) == 2 {\n\t\t\t\t\t\t\tservice.Address = addrPort[0]\n\t\t\t\t\t\t\tservice.Port, _ = strconv.Atoi(addrPort[1])\n\t\t\t\t\t\t}\n\t\t\t\t\t\ti++\n\t\t\t\t\t}\n\t\t\t\tcase \"-u\":\n\t\t\t\t\tif i+1 < len(parts) {\n\t\t\t\t\t\tserviceKey = parts[i+1]\n\t\t\t\t\t\taddrPort := strings.Split(serviceKey, \":\")\n\t\t\t\t\t\tserviceKey = Udp + serviceKey\n\n\t\t\t\t\t\tservice.Protocol = Udp\n\t\t\t\t\t\tif len(addrPort) == 2 {\n\t\t\t\t\t\t\tservice.Address = addrPort[0]\n\t\t\t\t\t\t\tservice.Port, _ = strconv.Atoi(addrPort[1])\n\t\t\t\t\t\t}\n\t\t\t\t\t\ti++\n\t\t\t\t\t}\n\t\t\t\tcase \"-s\":\n\t\t\t\t\tif i+1 < len(parts) {\n\t\t\t\t\t\tswitch parts[i+1] {\n\t\t\t\t\t\tcase \"rr\":\n\t\t\t\t\t\t\tservice.Scheduler = RoundRobin\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\ti++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif serviceKey != \"\" {\n\t\t\t\tstate.Services[serviceKey] = service\n\t\t\t}\n\t\t} else if parts[0] == \"-a\" {\n\t\t\ttarget := &Target{}\n\n\t\t\tfor i := 0; i < len(parts); i++ {\n\t\t\t\tswitch parts[i] {\n\t\t\t\tcase \"-t\":\n\t\t\t\t\tif i+1 < len(parts) {\n\t\t\t\t\t\ttarget.Service = state.Services[Tcp+parts[i+1]]\n\t\t\t\t\t\ti++\n\t\t\t\t\t}\n\t\t\t\tcase \"-u\":\n\t\t\t\t\tif i+1 < len(parts) {\n\t\t\t\t\t\ttarget.Service = state.Services[Udp+parts[i+1]]\n\t\t\t\t\t\ti++\n\t\t\t\t\t}\n\t\t\t\tcase \"-r\":\n\t\t\t\t\tif i+1 < len(parts) {\n\t\t\t\t\t\taddrPort := strings.Split(parts[i+1], \":\")\n\t\t\t\t\t\tif len(addrPort) == 2 {\n\t\t\t\t\t\t\ttarget.Address = addrPort[0]\n\t\t\t\t\t\t\ttarget.Port, _ = strconv.Atoi(addrPort[1])\n\t\t\t\t\t\t}\n\t\t\t\t\t\ti++\n\t\t\t\t\t}\n\t\t\t\tcase \"-w\":\n\t\t\t\t\tif i+1 < len(parts) {\n\t\t\t\t\t\ttarget.Weight, _ = strconv.Atoi(parts[i+1])\n\t\t\t\t\t\ti++\n\t\t\t\t\t}\n\t\t\t\tcase \"-m\":\n\t\t\t\t\ttarget.Masquerade = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif target.Service != nil {\n\t\t\t\ttarget.Service.Targets = append(target.Service.Targets, target)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc New() *State {\n\treturn &State{\n\t\tServices: map[string]*Service{},\n\t}\n}\n"
  },
  {
    "path": "ipvs/service.go",
    "content": "package ipvs\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/pritunl/tools/commander\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\thasSysctl = false\n)\n\ntype Service struct {\n\tScheduler string\n\tProtocol  string\n\tAddress   string\n\tPort      int\n\tTargets   []*Target\n}\n\nfunc (s *Service) Key() string {\n\treturn fmt.Sprintf(\"%s:%d\", s.Address, s.Port)\n}\n\nfunc (s *Service) Add() (err error) {\n\tif s.Scheduler == \"\" {\n\t\ts.Scheduler = RoundRobin\n\t}\n\n\tif !hasSysctl {\n\t\tresp, err := commander.Exec(&commander.Opt{\n\t\t\tName: \"sysctl\",\n\t\t\tArgs: []string{\n\t\t\t\t\"-w\", \"net.ipv4.vs.conntrack=1\",\n\t\t\t},\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(resp.Map()).Error(\n\t\t\t\t\"ipvs: Failed to set ipvs sysctl\")\n\t\t\terr = nil\n\t\t}\n\n\t\thasSysctl = true\n\t}\n\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName: \"ipvsadm\",\n\t\tArgs: []string{\n\t\t\t\"-A\",\n\t\t\ts.Protocol, s.Key(),\n\t\t\t\"-s\", s.Scheduler,\n\t\t},\n\t\tTimeout: 10 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tlogrus.WithFields(resp.Map()).Error(\"ipvs: Failed to add service\")\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Service) Delete() (err error) {\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName: \"ipvsadm\",\n\t\tArgs: []string{\n\t\t\t\"-D\",\n\t\t\ts.Protocol, s.Key(),\n\t\t},\n\t\tTimeout: 10 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tlogrus.WithFields(resp.Map()).Error(\"ipvs: Failed to remove service\")\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "ipvs/target.go",
    "content": "package ipvs\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/pritunl/tools/commander\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Target struct {\n\tService    *Service\n\tAddress    string\n\tPort       int\n\tWeight     int\n\tMasquerade bool\n}\n\nfunc (t *Target) Key() string {\n\treturn fmt.Sprintf(\"%s:%d\", t.Address, t.Port)\n}\n\nfunc (t *Target) Add() (err error) {\n\tif t.Weight == 0 {\n\t\tt.Weight = 1\n\t}\n\n\targs := []string{\n\t\t\"-a\",\n\t\tt.Service.Protocol, t.Service.Key(),\n\t\t\"-r\", t.Key(),\n\t}\n\n\tif t.Masquerade {\n\t\targs = append(args, \"-m\")\n\t}\n\n\targs = append(args, \"-w\", fmt.Sprintf(\"%d\", t.Weight))\n\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName:    \"ipvsadm\",\n\t\tArgs:    args,\n\t\tTimeout: 10 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tlogrus.WithFields(resp.Map()).Error(\"ipvs: Failed to add target\")\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (t *Target) Delete() (err error) {\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName: \"ipvsadm\",\n\t\tArgs: []string{\n\t\t\t\"-d\",\n\t\t\tt.Service.Protocol, t.Service.Key(),\n\t\t\t\"-r\", t.Key(),\n\t\t},\n\t\tTimeout: 10 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tlogrus.WithFields(resp.Map()).Error(\"ipvs: Failed to remove target\")\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "iscsi/iscsi.go",
    "content": "package iscsi\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype Device struct {\n\tHost     string `bson:\"host\" json:\"host\"`\n\tPort     int    `bson:\"port\" json:\"port\"`\n\tIqn      string `bson:\"iqn\" json:\"iqn\"`\n\tLun      string `bson:\"lun\" json:\"lun\"`\n\tUsername string `bson:\"username\" json:\"username\"`\n\tPassword string `bson:\"password\" json:\"password\"`\n\tUri      string `bson:\"-\" json:\"uri\"`\n}\n\nfunc (d *Device) Json() {\n\thost := \"\"\n\tif d.Port != 0 {\n\t\thost = fmt.Sprintf(\"%s:%d\", d.Host, d.Port)\n\t} else {\n\t\thost = fmt.Sprintf(\"%s\", d.Host)\n\t}\n\n\turi := url.URL{\n\t\tScheme: \"iscsi\",\n\t\tHost:   host,\n\t\tPath:   fmt.Sprintf(\"%s/%s\", d.Iqn, d.Lun),\n\t}\n\n\tif d.Username != \"\" && d.Password != \"\" {\n\t\turi.User = url.UserPassword(d.Username, d.Password)\n\t}\n\n\td.Uri = uri.String()\n}\n\nfunc (d *Device) QemuUri() (uriStr string) {\n\thost := \"\"\n\tif d.Port != 0 {\n\t\thost = fmt.Sprintf(\"%s:%d\", d.Host, d.Port)\n\t} else {\n\t\thost = fmt.Sprintf(\"%s\", d.Host)\n\t}\n\n\turi := url.URL{\n\t\tScheme: \"iscsi\",\n\t\tHost:   host,\n\t\tPath:   fmt.Sprintf(\"%s/%s\", d.Iqn, d.Lun),\n\t}\n\n\tif d.Username != \"\" && d.Password != \"\" {\n\t\turi.User = url.UserPassword(d.Username, d.Password)\n\t}\n\n\turiStr = uri.String()\n\n\turiStr = strings.Replace(uriStr, \"%\", \"\", -1)\n\n\tif d.Username != \"\" && d.Password != \"\" && len(uriStr) > 8 {\n\t\turiStr = uriStr[:8] + strings.Replace(uriStr[8:], \":\", \"%%\", 1)\n\t}\n\n\treturn\n}\n\nfunc (d *Device) Parse() (errData *errortypes.ErrorData, err error) {\n\tif d.Uri == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_iscsi_uri\",\n\t\t\tMessage: \"Invalid iSCSI URI\",\n\t\t}\n\t\treturn\n\t}\n\n\turi, err := url.Parse(d.Uri)\n\tif err != nil {\n\t\terr = nil\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_iscsi_uri\",\n\t\t\tMessage: \"Invalid iSCSI URI\",\n\t\t}\n\t\treturn\n\t}\n\n\tif uri.Scheme != \"iscsi\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_iscsi_uri\",\n\t\t\tMessage: \"Invalid iSCSI URI\",\n\t\t}\n\t\treturn\n\t}\n\n\tport := 0\n\tportStr := uri.Port()\n\tif portStr != \"\" {\n\t\tport, err = strconv.Atoi(portStr)\n\t\tif err != nil {\n\t\t\terr = nil\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_iscsi_port\",\n\t\t\t\tMessage: \"Invalid iSCSI port\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\thost := uri.Hostname()\n\tif host == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_iscsi_uri\",\n\t\t\tMessage: \"Invalid iSCSI URI\",\n\t\t}\n\t\treturn\n\t}\n\n\tusername := \"\"\n\tpassword := \"\"\n\tif uri.User != nil {\n\t\tusername = uri.User.Username()\n\t\tpassword, _ = uri.User.Password()\n\n\t\tif username != \"\" || password != \"\" {\n\t\t\tif username == \"\" {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_iscsi_username\",\n\t\t\t\t\tMessage: \"Missing iSCSI username\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif password == \"\" {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_iscsi_password\",\n\t\t\t\t\tMessage: \"Missing iSCSI password\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tpath := strings.Split(uri.Path, \"/\")\n\tif len(path) != 3 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"missing_iscsi_iqn_lun\",\n\t\t\tMessage: \"Missing iSCSI IQN and LUN\",\n\t\t}\n\t\treturn\n\t}\n\n\tiqn := path[1]\n\tif iqn == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_iscsi_iqn\",\n\t\t\tMessage: \"Invalid iSCSI IQN\",\n\t\t}\n\t\treturn\n\t}\n\n\tlun := path[2]\n\tif lun == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_iscsi_lun\",\n\t\t\tMessage: \"Invalid iSCSI LUN\",\n\t\t}\n\t\treturn\n\t}\n\n\td.Host = host\n\td.Port = port\n\td.Iqn = iqn\n\td.Lun = lun\n\td.Username = username\n\td.Password = password\n\td.Uri = \"\"\n\n\td.Json()\n\tif strings.Contains(d.Uri, \"%\") {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_iscsi_uri\",\n\t\t\tMessage: \"Invalid iSCSI URI, cannot contain % character\",\n\t\t}\n\t\treturn\n\t}\n\td.Uri = \"\"\n\n\treturn\n}\n"
  },
  {
    "path": "iso/iso.go",
    "content": "package iso\n\nimport (\n\t\"io/ioutil\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tsyncLast  time.Time\n\tsyncLock  sync.Mutex\n\tsyncCache []*Iso\n)\n\ntype Iso struct {\n\tName string `bson:\"name\" json:\"name\"`\n}\n\nfunc GetIsos(isoDir string) (isos []*Iso, err error) {\n\tif time.Since(syncLast) < 30*time.Second {\n\t\tisos = syncCache\n\t\treturn\n\t}\n\n\tsyncLock.Lock()\n\tdefer syncLock.Unlock()\n\n\terr = utils.ExistsMkdir(isoDir, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tisoFiles, err := ioutil.ReadDir(isoDir)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"backup: Failed to read isos directory\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, item := range isoFiles {\n\t\tfilename := item.Name()\n\t\tfilenameFilt := utils.FilterRelPath(filename)\n\n\t\tif filenameFilt != filename {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"name\": filename,\n\t\t\t}).Error(\"iso: Invalid ISO filename\")\n\t\t}\n\n\t\tiso := &Iso{\n\t\t\tName: filenameFilt,\n\t\t}\n\t\tisos = append(isos, iso)\n\t}\n\n\tsyncCache = isos\n\tsyncLast = time.Now()\n\n\treturn\n}\n"
  },
  {
    "path": "journal/constants.go",
    "content": "package journal\n\nconst (\n\tInstanceAgent   = 1\n\tDeploymentAgent = 2\n)\n\nconst (\n\tPanic    = 1\n\tCritical = 2\n\tError    = 3\n\tWarning  = 4\n\tInfo     = 5\n\tDebug    = 6\n\tTrace    = 7\n)\n"
  },
  {
    "path": "journal/journal.go",
    "content": "package journal\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\ntype Journal struct {\n\tId        bson.ObjectID     `bson:\"_id,omitempty\" json:\"id\"`\n\tResource  bson.ObjectID     `bson:\"r\" json:\"r\"`\n\tKind      int32             `bson:\"k\" json:\"k\"`\n\tLevel     int32             `bson:\"l\" json:\"l\"`\n\tTimestamp time.Time         `bson:\"t\" json:\"t\"`\n\tCount     int32             `bson:\"c\" json:\"-\"`\n\tMessage   string            `bson:\"m\" json:\"m\"`\n\tFields    map[string]string `bson:\"f,omitempty\" json:\"f\"`\n}\n\nfunc (j *Journal) String() string {\n\treturn fmt.Sprintf(\n\t\t\"[%s] %s\\n\",\n\t\tj.Timestamp.Format(\"2006-01-02 15:04:05\"),\n\t\tj.Message,\n\t)\n}\n\nfunc (j *Journal) Insert(db *database.Database) (err error) {\n\tcoll := db.Journal()\n\n\tif j.Level == 0 {\n\t\tj.Level = Info\n\t}\n\n\t_, err = coll.InsertOne(db, j)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "journal/store.go",
    "content": "package journal\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\ntype KindGenerator interface {\n\tGetKind(db *database.Database, key string) (kind int32, err error)\n}\n"
  },
  {
    "path": "journal/utils.go",
    "content": "package journal\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nfunc GetOutput(c context.Context, db *database.Database,\n\tresource bson.ObjectID, kind int32) (output []string, err error) {\n\n\tcoll := db.Journal()\n\n\tlimit := int64(settings.Hypervisor.JournalDisplayLimit)\n\n\tcursor, err := coll.Find(\n\t\tc,\n\t\t&bson.M{\n\t\t\t\"r\": resource,\n\t\t\t\"k\": kind,\n\t\t},\n\t\toptions.Find().\n\t\t\tSetLimit(limit).\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"t\", -1},\n\t\t\t\t{\"c\", -1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(c)\n\n\toutputRevrse := []string{}\n\tfor cursor.Next(c) {\n\t\tjrnl := &Journal{}\n\t\terr = cursor.Decode(jrnl)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\toutputRevrse = append(outputRevrse, jrnl.String())\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tfor i := len(outputRevrse) - 1; i >= 0; i-- {\n\t\tif i == 0 {\n\t\t\toutput = append(output, strings.TrimSuffix(outputRevrse[i], \"\\n\"))\n\t\t} else {\n\t\t\toutput = append(output, outputRevrse[i])\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, resource bson.ObjectID,\n\tkind int) (err error) {\n\n\tcoll := db.Journal()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"r\": resource,\n\t\t\"k\": kind,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveAll(db *database.Database, resource bson.ObjectID) (err error) {\n\tcoll := db.Journal()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"r\": resource,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "lock/lvm.go",
    "content": "package lock\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\ntype LvmLocker struct {\n\tId        string        `bson:\"_id\" json:\"_id\"`\n\tNode      bson.ObjectID `bson:\"node\" json:\"node\"`\n\tTimestamp time.Time     `bson:\"timestamp\" json:\"timestamp\"`\n}\n\nfunc LvmLock(db *database.Database, vgName, lvName string) (\n\tacquired bool, err error) {\n\n\tcoll := db.LvmLock()\n\n\tdoc := &LvmLocker{\n\t\tId:        fmt.Sprintf(\"%s/%s\", vgName, lvName),\n\t\tNode:      node.Self.Id,\n\t\tTimestamp: time.Now(),\n\t}\n\n\t_, err = coll.InsertOne(db, doc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\terr = nil\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\tacquired = true\n\n\treturn\n}\n\nfunc LvmRelock(db *database.Database, vgName, lvName string) (err error) {\n\tcoll := db.LvmLock()\n\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"id\":   fmt.Sprintf(\"%s/%s\", vgName, lvName),\n\t\t\"node\": node.Self.Id,\n\t}, &bson.M{\n\t\t\"timestamp\": time.Now(),\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc LvmUnlock(db *database.Database, vgName, lvName string) (err error) {\n\tcoll := db.LvmLock()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"id\":   fmt.Sprintf(\"%s/%s\", vgName, lvName),\n\t\t\"node\": node.Self.Id,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "log/constants.go",
    "content": "package log\n\nconst (\n\tDebug   = \"debug\"\n\tInfo    = \"info\"\n\tWarning = \"warning\"\n\tError   = \"error\"\n\tFatal   = \"fatal\"\n\tPanic   = \"panic\"\n\tUnknown = \"unknown\"\n)\n"
  },
  {
    "path": "log/log.go",
    "content": "package log\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n)\n\nvar published = false\n\ntype Entry struct {\n\tId        bson.ObjectID          `bson:\"_id,omitempty\" json:\"id\"`\n\tLevel     string                 `bson:\"level\" json:\"level\"`\n\tTimestamp time.Time              `bson:\"timestamp\" json:\"timestamp\"`\n\tMessage   string                 `bson:\"message\" json:\"message\"`\n\tStack     string                 `bson:\"stack\" json:\"stack\"`\n\tFields    map[string]interface{} `bson:\"fields\" json:\"fields\"`\n}\n\nfunc (e *Entry) Insert(db *database.Database) (err error) {\n\tcoll := db.Logs()\n\n\tif !e.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"log: Entry already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = coll.InsertOne(db, e)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tpublished = true\n\n\treturn\n}\n\nfunc publish() {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tevent.PublishDispatch(db, \"log.change\")\n}\n\nfunc initSender() {\n\tfor {\n\t\ttime.Sleep(1500 * time.Millisecond)\n\t\tif published {\n\t\t\tpublished = false\n\t\t\tpublish()\n\t\t}\n\t}\n}\n\nfunc init() {\n\tmodule := requires.New(\"log\")\n\tmodule.After(\"logger\")\n\n\tmodule.Handler = func() (err error) {\n\t\tgo initSender()\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "log/utils.go",
    "content": "package log\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, logId bson.ObjectID) (\n\tentry *Entry, err error) {\n\n\tcoll := db.Logs()\n\tentry = &Entry{}\n\n\terr = coll.FindOneId(logId, entry)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M, page, pageCount int64) (\n\tentries []*Entry, count int64, err error) {\n\n\tcoll := db.Logs()\n\tentries = []*Entry{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"$natural\", -1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tentry := &Entry{}\n\t\terr = cursor.Decode(entry)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tentries = append(entries, entry)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Clear(db *database.Database) (err error) {\n\tcoll := db.Logs()\n\n\t_, err = coll.DeleteMany(db, &bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"log.change\")\n\n\treturn\n}\n"
  },
  {
    "path": "logger/database.go",
    "content": "package logger\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/log\"\n)\n\nvar (\n\tdatabaseBuffer = make(chan *logrus.Entry, 128)\n)\n\ntype databaseSender struct{}\n\nfunc (s *databaseSender) Init() {}\n\nfunc (s *databaseSender) Parse(entry *logrus.Entry) {\n\tif len(buffer) <= 32 {\n\t\tdatabaseBuffer <- entry\n\t}\n}\n\nfunc databaseSend(entry *logrus.Entry) (err error) {\n\tlevel := \"\"\n\n\tdb := database.GetDatabase()\n\tif db == nil {\n\t\treturn\n\t}\n\tdefer db.Close()\n\n\tswitch entry.Level {\n\tcase logrus.DebugLevel:\n\t\tlevel = log.Debug\n\t\tbreak\n\tcase logrus.WarnLevel:\n\t\tlevel = log.Warning\n\t\tbreak\n\tcase logrus.InfoLevel:\n\t\tlevel = log.Info\n\t\tbreak\n\tcase logrus.ErrorLevel:\n\t\tlevel = log.Error\n\t\tbreak\n\tcase logrus.FatalLevel:\n\t\tlevel = log.Fatal\n\t\tbreak\n\tcase logrus.PanicLevel:\n\t\tlevel = log.Panic\n\t\tbreak\n\tdefault:\n\t\tlevel = log.Unknown\n\t}\n\n\tent := &log.Entry{\n\t\tLevel:     level,\n\t\tTimestamp: entry.Time,\n\t\tMessage:   entry.Message,\n\t\tFields:    map[string]interface{}{},\n\t}\n\n\tfor key, val := range entry.Data {\n\t\tif key == \"error\" {\n\t\t\tent.Stack = fmt.Sprintf(\"%s\", val)\n\t\t} else {\n\t\t\tent.Fields[key] = val\n\t\t}\n\t}\n\n\terr = ent.Insert(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc initDatabaseSender() {\n\tgo func() {\n\t\tfor {\n\t\t\tentry := <-databaseBuffer\n\n\t\t\tif constants.Interrupt {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif strings.HasPrefix(entry.Message, \"logger:\") {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\terr := databaseSend(entry)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"logger: Database send error\")\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc init() {\n\tsenders = append(senders, &databaseSender{})\n}\n"
  },
  {
    "path": "logger/file.go",
    "content": "package logger\n\nimport (\n\t\"os\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype fileSender struct{}\n\nfunc (s *fileSender) Init() {}\n\nfunc (s *fileSender) Parse(entry *logrus.Entry) {\n\terr := s.send(entry)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"logger: File send error\")\n\t}\n}\n\nfunc (s *fileSender) send(entry *logrus.Entry) (err error) {\n\tmsg := formatPlain(entry)\n\n\tfile, err := os.OpenFile(constants.LogPath,\n\t\tos.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"logger: Failed to open log file\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer file.Close()\n\n\tstat, err := file.Stat()\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"logger: Failed to stat log file\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif stat.Size() >= 5000000 {\n\t\tos.Remove(constants.LogPath2)\n\t\terr = os.Rename(constants.LogPath, constants.LogPath2)\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(err, \"logger: Failed to rotate log file\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfile.Close()\n\t\tfile, err = os.OpenFile(constants.LogPath,\n\t\t\tos.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(err, \"logger: Failed to open log file\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\t_, err = file.Write(msg)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"logger: Failed to write to log file\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tsenders = append(senders, &fileSender{})\n}\n"
  },
  {
    "path": "logger/formatter.go",
    "content": "package logger\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/colorize\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tblueArrow    = colorize.ColorString(\"▶\", colorize.BlueBold, colorize.None)\n\twhiteDiamond = colorize.ColorString(\"◆\", colorize.WhiteBold, colorize.None)\n)\n\nfunc format(entry *logrus.Entry) (output []byte) {\n\tmsg := fmt.Sprintf(\"%s%s %s %s\",\n\t\tformatTime(entry.Time),\n\t\tformatLevel(entry.Level),\n\t\tblueArrow,\n\t\tentry.Message,\n\t)\n\n\tkeys := []string{}\n\n\tvar errStr string\n\tfor key, val := range entry.Data {\n\t\tif key == \"error\" {\n\t\t\terrStr = fmt.Sprintf(\"%s\", val)\n\t\t\tcontinue\n\t\t} else if key == \"error_data\" {\n\t\t\tif val != nil && !reflect.ValueOf(val).IsNil() {\n\t\t\t\tif errData, ok := val.(*errortypes.ErrorData); ok {\n\t\t\t\t\tentry.Data[\"error_key\"] = errData.Error\n\t\t\t\t\tentry.Data[\"error_msg\"] = errData.Message\n\t\t\t\t\tkeys = append(keys, \"error_key\", \"error_msg\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tkeys = append(keys, key)\n\t}\n\n\tsort.Strings(keys)\n\n\tfor _, key := range keys {\n\t\tmsg += fmt.Sprintf(\" %s %s=%v\", whiteDiamond,\n\t\t\tcolorize.ColorString(key, colorize.CyanBold, colorize.None),\n\t\t\tcolorize.ColorString(fmt.Sprintf(\"%#v\", entry.Data[key]),\n\t\t\t\tcolorize.GreenBold, colorize.None))\n\t}\n\n\tif errStr != \"\" {\n\t\tmsg += \"\\n\" + colorize.ColorString(errStr, colorize.Red, colorize.None)\n\t}\n\n\tif string(msg[len(msg)-1]) != \"\\n\" {\n\t\tmsg += \"\\n\"\n\t}\n\n\toutput = []byte(msg)\n\n\treturn\n}\n\nfunc formatPlain(entry *logrus.Entry) (output []byte) {\n\tmsg := fmt.Sprintf(\"%s%s ▶ %s\",\n\t\tentry.Time.Format(\"[2006-01-02 15:04:05]\"),\n\t\tformatLevelPlain(entry.Level),\n\t\tentry.Message,\n\t)\n\n\tkeys := []string{}\n\n\tvar errStr string\n\tfor key, val := range entry.Data {\n\t\tif key == \"error\" {\n\t\t\terrStr = fmt.Sprintf(\"%s\", val)\n\t\t\tcontinue\n\t\t} else if key == \"error_data\" {\n\t\t\tif val != nil && !reflect.ValueOf(val).IsNil() {\n\t\t\t\tif errData, ok := val.(*errortypes.ErrorData); ok {\n\t\t\t\t\tentry.Data[\"error_key\"] = errData.Error\n\t\t\t\t\tentry.Data[\"error_msg\"] = errData.Message\n\t\t\t\t\tkeys = append(keys, \"error_key\", \"error_msg\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tkeys = append(keys, key)\n\t}\n\n\tsort.Strings(keys)\n\n\tfor _, key := range keys {\n\t\tmsg += fmt.Sprintf(\" ◆ %s=%v\", key,\n\t\t\tfmt.Sprintf(\"%#v\", entry.Data[key]))\n\t}\n\n\tif errStr != \"\" {\n\t\tmsg += \"\\n\" + errStr\n\t}\n\n\tif string(msg[len(msg)-1]) != \"\\n\" {\n\t\tmsg += \"\\n\"\n\t}\n\n\toutput = []byte(msg)\n\n\treturn\n}\n\nfunc formatTime(timestamp time.Time) (str string) {\n\treturn colorize.ColorString(\n\t\ttimestamp.Format(\"[2006-01-02 15:04:05]\"),\n\t\tcolorize.Bold,\n\t\tcolorize.None,\n\t)\n}\n\nfunc formatLevel(lvl logrus.Level) (str string) {\n\tvar colorBg colorize.Color\n\n\tswitch lvl {\n\tcase logrus.InfoLevel:\n\t\tcolorBg = colorize.CyanBg\n\t\tstr = \"[INFO]\"\n\tcase logrus.WarnLevel:\n\t\tcolorBg = colorize.YellowBg\n\t\tstr = \"[WARN]\"\n\tcase logrus.ErrorLevel:\n\t\tcolorBg = colorize.RedBg\n\t\tstr = \"[ERRO]\"\n\tcase logrus.FatalLevel:\n\t\tcolorBg = colorize.RedBg\n\t\tstr = \"[FATL]\"\n\tcase logrus.PanicLevel:\n\t\tcolorBg = colorize.RedBg\n\t\tstr = \"[PANC]\"\n\tdefault:\n\t\tcolorBg = colorize.BlackBg\n\t}\n\n\tstr = colorize.ColorString(str, colorize.WhiteBold, colorBg)\n\n\treturn\n}\n\nfunc formatLevelPlain(lvl logrus.Level) string {\n\tswitch lvl {\n\tcase logrus.InfoLevel:\n\t\treturn \"[INFO]\"\n\tcase logrus.WarnLevel:\n\t\treturn \"[WARN]\"\n\tcase logrus.ErrorLevel:\n\t\treturn \"[ERRO]\"\n\tcase logrus.FatalLevel:\n\t\treturn \"[FATL]\"\n\tcase logrus.PanicLevel:\n\t\treturn \"[PANC]\"\n\tdefault:\n\t}\n\n\treturn \"\"\n}\n\ntype formatter struct{}\n\nfunc (f *formatter) Format(entry *logrus.Entry) ([]byte, error) {\n\treturn format(entry), nil\n}\n"
  },
  {
    "path": "logger/hook.go",
    "content": "package logger\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype logHook struct{}\n\nfunc (h *logHook) Fire(entry *logrus.Entry) (err error) {\n\tif strings.HasPrefix(entry.Message, \"logger:\") {\n\t\treturn\n\t}\n\n\tif len(buffer) <= 32 {\n\t\tbuffer <- entry\n\t}\n\n\treturn\n}\n\nfunc (h *logHook) Levels() []logrus.Level {\n\treturn []logrus.Level{\n\t\tlogrus.InfoLevel,\n\t\tlogrus.WarnLevel,\n\t\tlogrus.ErrorLevel,\n\t\tlogrus.FatalLevel,\n\t\tlogrus.PanicLevel,\n\t}\n}\n"
  },
  {
    "path": "logger/limiter.go",
    "content": "package logger\n\nimport (\n\t\"hash/fnv\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype limiter map[uint32]time.Time\n\nfunc (l limiter) Check(entry *logrus.Entry, limit time.Duration) bool {\n\thash := fnv.New32a()\n\thash.Write([]byte(entry.Message))\n\tkey := hash.Sum32()\n\n\tif timestamp, ok := l[key]; ok &&\n\t\ttime.Since(timestamp) < limit {\n\n\t\treturn false\n\t}\n\tl[key] = time.Now()\n\n\treturn true\n}\n"
  },
  {
    "path": "logger/logger.go",
    "content": "package logger\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tbuffer  = make(chan *logrus.Entry, 128)\n\tsenders = []sender{}\n)\n\nfunc initSender() {\n\tfor _, sndr := range senders {\n\t\tsndr.Init()\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\tentry := <-buffer\n\n\t\t\tif constants.Interrupt {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif strings.HasPrefix(entry.Message, \"logger:\") {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, sndr := range senders {\n\t\t\t\tsndr.Parse(entry)\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc Init() {\n\tlogrus.SetFormatter(&formatter{})\n\tlogrus.AddHook(&logHook{})\n\tlogrus.SetOutput(os.Stderr)\n\tlogrus.SetLevel(logrus.InfoLevel)\n}\n\nfunc InitStdout() {\n\tlogrus.SetFormatter(&formatter{})\n\tlogrus.SetOutput(os.Stdout)\n\tlogrus.SetLevel(logrus.InfoLevel)\n}\n\nfunc init() {\n\tmodule := requires.New(\"logger\")\n\tmodule.After(\"config\")\n\n\tmodule.Handler = func() (err error) {\n\t\tinitSender()\n\t\tinitDatabaseSender()\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "logger/sender.go",
    "content": "package logger\n\nimport (\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype sender interface {\n\tInit()\n\tParse(entry *logrus.Entry)\n}\n"
  },
  {
    "path": "logger/writer.go",
    "content": "package logger\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype ErrorWriter struct {\n\tMessage string\n\tFields  logrus.Fields\n\tFilters []string\n}\n\nfunc (w *ErrorWriter) Write(input []byte) (n int, err error) {\n\tn = len(input)\n\n\tinputStr := string(input)\n\n\tif w.Filters != nil {\n\t\tfor _, filter := range w.Filters {\n\t\t\tif strings.Contains(inputStr, filter) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tw.Fields[\"err\"] = inputStr\n\tlogrus.WithFields(w.Fields).Error(w.Message)\n\n\treturn\n}\n"
  },
  {
    "path": "lvm/lv.go",
    "content": "package lvm\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc CreateLv(vgName, lvName string, size int) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"lvcreate\", \"-an\", \"-L\", fmt.Sprintf(\"%d.1G\", size),\n\t\t\"-n\", lvName, vgName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveLv(vgName, lvName string) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged([]string{\n\t\t\"Failed to find\",\n\t}, \"lvremove\", \"-y\", fmt.Sprintf(\"%s/%s\", vgName, lvName))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ActivateLv(vgName, lvName string) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"lvchange\", \"-ay\", fmt.Sprintf(\"%s/%s\", vgName, lvName))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DeactivateLv(vgName, lvName string) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"lvchange\", \"-an\", fmt.Sprintf(\"%s/%s\", vgName, lvName))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc WriteLv(vgName, lvName, sourcePth string) (err error) {\n\tdstPth := filepath.Join(\"/dev/mapper\",\n\t\tfmt.Sprintf(\"%s-%s\", vgName, lvName))\n\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"qemu-img\", \"convert\", \"-f\", \"qcow2\",\n\t\t\"-O\", \"raw\", sourcePth, dstPth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExtendLv(vgName, lvName string, addSize int) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"lvextend\", \"-L\", fmt.Sprintf(\"+%dG\", addSize),\n\t\tfmt.Sprintf(\"%s/%s\", vgName, lvName))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetSizeLv(vgName, lvName string) (size int, err error) {\n\toutput, err := utils.ExecCombinedOutput(\"\",\n\t\t\"lvs\", fmt.Sprintf(\"%s/%s\", vgName, lvName),\n\t\t\"-o\", \"LV_SIZE\", \"--units\", \"g\", \"--noheadings\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\toutput = strings.Trim(strings.TrimSpace(strings.ToLower(output)), \"g\")\n\n\tnumber, err := strconv.ParseFloat(output, 64)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"lvm: Failed to parse lvm volume size\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsize = int(math.Round(number))\n\n\treturn\n}\n\nfunc HasLocking(vgName string) (hasLock bool, err error) {\n\toutput, err := utils.ExecCombinedOutput(\"\",\n\t\t\"vgs\", vgName, \"-o\", \"vg_lock_type\", \"--noheadings\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlockType := strings.TrimSpace(output)\n\thasLock = lockType != \"\" && lockType != \"none\"\n\n\treturn\n}\n\nfunc IsLockspaceActive(vgName string) (isLocked bool, err error) {\n\toutput, err := utils.ExecCombinedOutput(\"\",\n\t\t\"lvmlockctl\", \"-i\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlines := strings.Split(output, \"\\n\")\n\tfor _, line := range lines {\n\t\tif strings.Contains(line, vgName) &&\n\t\t\tstrings.Contains(line, \"sanlock\") {\n\n\t\t\tisLocked = true\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc InitLock(vgName string) (err error) {\n\thasLock, err := HasLocking(vgName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !hasLock {\n\t\treturn\n\t}\n\n\tisLocked, err := IsLockspaceActive(vgName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif isLocked {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"vgchange\", \"--lock-start\", vgName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "lvm/vgs.go",
    "content": "package lvm\n\nimport (\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tcachedNodePools          []*pool.Pool\n\tcachedNodePoolsTimestamp time.Time\n)\n\ntype report struct {\n\tReport []*vgReport `json:\"report\"`\n}\n\ntype vgReport struct {\n\tVg []*vgDetails `json:\"vg\"`\n}\n\ntype vgDetails struct {\n\tVgName    string `json:\"vg_name\"`\n\tPvCount   string `json:\"pv_count\"`\n\tLvCount   string `json:\"lv_count\"`\n\tSnapCount string `json:\"snap_count\"`\n\tVgAttr    string `json:\"vg_attr\"`\n\tVgSize    string `json:\"vg_size\"`\n\tVgFree    string `json:\"vg_free\"`\n}\n\nfunc GetAvailablePools(db *database.Database, zoneId bson.ObjectID) (\n\tavailablePools []*pool.Pool, err error) {\n\n\tif time.Since(cachedNodePoolsTimestamp) < 30*time.Second {\n\t\tavailablePools = cachedNodePools\n\t\treturn\n\t}\n\n\tavailablePools = []*pool.Pool{}\n\tvgNames := set.NewSet()\n\n\toutput, err := utils.ExecCombinedOutput(\"\",\n\t\t\"vgs\", \"--reportformat\", \"json\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\treprt := &report{}\n\terr = json.Unmarshal([]byte(output), reprt)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"deploy: Failed to unmarshal vgs report\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif reprt.Report != nil {\n\t\tfor _, reportGroup := range reprt.Report {\n\t\t\tif reportGroup.Vg != nil {\n\t\t\t\tfor _, reportVg := range reportGroup.Vg {\n\t\t\t\t\tvgNames.Add(reportVg.VgName)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif vgNames.Len() > 0 {\n\t\tpools, e := pool.GetAll(db, &bson.M{\n\t\t\t\"zone\": zoneId,\n\t\t})\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, pl := range pools {\n\t\t\tif vgNames.Contains(pl.VgName) {\n\t\t\t\tavailablePools = append(availablePools, pl)\n\t\t\t}\n\t\t}\n\t}\n\n\tcachedNodePools = availablePools\n\tcachedNodePoolsTimestamp = time.Now()\n\n\treturn\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/cmd\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/logger\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n)\n\nconst help = `\nUsage: pritunl-cloud COMMAND\n\nCommands:\n  version           Show version\n  mongo             Set MongoDB URI\n  set               Set a setting\n  unset             Unset a setting\n  start             Start node\n  clear-logs        Clear logs\n  reset-node-web    Reset node web server settings\n  optimize          Optimize system configuration\n  default-password  Get default administrator password\n  reset-password    Reset administrator password\n  disable-policies  Disable all policies\n  disable-firewall  Disable firewall on this node\n  start-instance    Start instance by name\n  stop-instance     Stop instance by name\n  mtu-check         Check and show instance MTUs\n  backup            Backup local data\n`\n\nfunc Init() {\n\tlogger.Init()\n\trequires.Init(nil)\n}\n\nfunc InitLimited() {\n\tlogger.Init()\n\trequires.Init([]string{\"ahandlers\", \"uhandlers\"})\n}\n\nfunc main() {\n\tdefer time.Sleep(500 * time.Millisecond)\n\n\tcommand := \"\"\n\tfor _, arg := range os.Args[1:] {\n\t\tif !strings.HasPrefix(arg, \"-\") {\n\t\t\tcommand = arg\n\t\t\tbreak\n\t\t}\n\t}\n\n\tswitch command {\n\tcase \"start\":\n\t\tflag.Parse()\n\n\t\tfor _, arg := range flag.Args() {\n\t\t\tswitch arg {\n\t\t\tcase \"--debug\":\n\t\t\t\tconstants.Production = false\n\t\t\t\tbreak\n\t\t\tcase \"--debug-web\":\n\t\t\t\tconstants.DebugWeb = true\n\t\t\t\tbreak\n\t\t\tcase \"--fast-exit\":\n\t\t\t\tconstants.FastExit = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tInit()\n\t\terr := cmd.Node()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"version\":\n\t\tfmt.Printf(\"pritunl-cloud v%s\\n\", constants.Version)\n\t\treturn\n\tcase \"mongo\":\n\t\tflag.Parse()\n\t\tlogger.Init()\n\t\terr := cmd.Mongo()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"optimize\":\n\t\tlogger.Init()\n\t\terr := cmd.Optimize()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"reset-node-web\":\n\t\tInitLimited()\n\t\terr := cmd.ResetNodeWeb()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"default-password\":\n\t\tInitLimited()\n\t\terr := cmd.DefaultPassword()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"reset-password\":\n\t\tInitLimited()\n\t\terr := cmd.ResetPassword()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"disable-policies\":\n\t\tInitLimited()\n\t\terr := cmd.DisablePolicies()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"disable-firewall\":\n\t\tInitLimited()\n\t\terr := cmd.DisableFirewall()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"mtu-check\":\n\t\tInitLimited()\n\t\terr := cmd.MtuCheck()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"set\":\n\t\tflag.Parse()\n\t\tInitLimited()\n\t\terr := cmd.SettingsSet()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"unset\":\n\t\tflag.Parse()\n\t\tInitLimited()\n\t\terr := cmd.SettingsUnset()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"clear-logs\":\n\t\tInitLimited()\n\t\terr := cmd.ClearLogs()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"backup\":\n\t\tflag.Parse()\n\t\tInitLimited()\n\t\terr := cmd.Backup()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"imds-server\":\n\t\terr := cmd.ImdsServer()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"dhcp4-server\":\n\t\terr := cmd.Dhcp4Server()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"dhcp6-server\":\n\t\terr := cmd.Dhcp6Server()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"dhcp-client\":\n\t\terr := cmd.DhcpClient()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"ndp-server\":\n\t\terr := cmd.NdpServer()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"start-instance\":\n\t\tflag.Parse()\n\t\tInitLimited()\n\t\terr := cmd.StartInstance(flag.Args()[1])\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tcase \"stop-instance\":\n\t\tflag.Parse()\n\t\tInitLimited()\n\t\terr := cmd.StopInstance(flag.Args()[1])\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\tdefault:\n\t\tfmt.Printf(help)\n\t}\n}\n"
  },
  {
    "path": "middlewear/gzip.go",
    "content": "package middlewear\n\nimport (\n\t\"compress/gzip\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype GzipWriter struct {\n\tgzipWriter *gzip.Writer\n\thttpWriter http.ResponseWriter\n}\n\nfunc (g *GzipWriter) Header() http.Header {\n\treturn g.httpWriter.Header()\n}\n\nfunc (g *GzipWriter) WriteHeader(statusCode int) {\n\tg.httpWriter.WriteHeader(statusCode)\n}\n\nfunc (g *GzipWriter) Write(b []byte) (int, error) {\n\tif g.gzipWriter != nil {\n\t\treturn g.gzipWriter.Write(b)\n\t}\n\treturn g.httpWriter.Write(b)\n}\n\nfunc (g *GzipWriter) Close() {\n\tif g.gzipWriter != nil {\n\t\tg.gzipWriter.Close()\n\t}\n}\n\nfunc NewGzipWriter(c *gin.Context) *GzipWriter {\n\tif !strings.Contains(c.GetHeader(\"Accept-Encoding\"), \"gzip\") {\n\t\treturn &GzipWriter{\n\t\t\thttpWriter: c.Writer,\n\t\t}\n\t}\n\n\tc.Writer.Header().Set(\"Content-Encoding\", \"gzip\")\n\tc.Writer.Header().Set(\"Vary\", \"Accept-Encoding\")\n\n\tgz, _ := gzip.NewWriterLevel(c.Writer, gzip.DefaultCompression)\n\n\treturn &GzipWriter{\n\t\tgzipWriter: gz,\n\t\thttpWriter: c.Writer,\n\t}\n}\n"
  },
  {
    "path": "middlewear/middlewear.go",
    "content": "package middlewear\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/auth\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/csrf\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/organization\"\n\t\"github.com/pritunl/pritunl-cloud/session\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/validator\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst robots = `User-agent: *\nDisallow: /\n`\n\nfunc Limiter(c *gin.Context) {\n\tc.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1000000)\n}\n\nfunc Counter(c *gin.Context) {\n\tnode.Self.AddRequest()\n}\n\nfunc Database(c *gin.Context) {\n\tdb := database.GetDatabaseCtx(c.Request.Context())\n\tc.Set(\"db\", db)\n\tc.Next()\n\tdb.Close()\n}\n\nfunc Headers(c *gin.Context) {\n\theaders := c.Writer.Header()\n\n\theaders.Add(\"Cache-Control\", \"no-store\")\n\theaders.Add(\"X-Frame-Options\", \"DENY\")\n\theaders.Add(\"X-XSS-Protection\", \"1; mode=block\")\n\theaders.Add(\"X-Content-Type-Options\", \"nosniff\")\n\theaders.Add(\"X-Robots-Tag\", \"noindex\")\n}\n\nfunc SessionAdmin(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tauthr, err := authorizer.AuthorizeAdmin(db, c.Writer, c.Request)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *errortypes.AuthenticationError:\n\t\t\tutils.AbortWithError(c, 401, err)\n\t\t\tbreak\n\t\tdefault:\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tif authr.IsValid() {\n\t\tusr, err := authr.GetUser(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif usr != nil {\n\t\t\tactive, err := auth.SyncUser(db, usr)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !active {\n\t\t\t\terr = authr.Clear(db, c.Writer, c.Request)\n\t\t\t\tif err != nil {\n\t\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\terr = session.RemoveAll(db, usr.Id)\n\t\t\t\tif err != nil {\n\t\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tc.Set(\"authorizer\", authr)\n}\n\nfunc SessionUser(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tauthr, err := authorizer.AuthorizeUser(db, c.Writer, c.Request)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *errortypes.AuthenticationError:\n\t\t\tutils.AbortWithError(c, 401, err)\n\t\t\tbreak\n\t\tdefault:\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tif authr.IsValid() {\n\t\tusr, err := authr.GetUser(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif usr != nil {\n\t\t\tactive, err := auth.SyncUser(db, usr)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !active {\n\t\t\t\terr = authr.Clear(db, c.Writer, c.Request)\n\t\t\t\tif err != nil {\n\t\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\terr = session.RemoveAll(db, usr.Id)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tc.Set(\"authorizer\", authr)\n}\n\nfunc AuthAdmin(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tif !authr.IsValid() {\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif usr == nil {\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n\n\t_, _, errAudit, errData, err := validator.ValidateAdmin(\n\t\tdb, usr, authr.IsApi(), c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = authr.Clear(db, c.Writer, c.Request)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"check\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.AdminAuthFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n}\n\nfunc AuthUser(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tif !authr.IsValid() {\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif usr == nil {\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n\n\t_, _, errAudit, errData, err := validator.ValidateUser(\n\t\tdb, usr, authr.IsApi(), c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = authr.Clear(db, c.Writer, c.Request)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"check\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserAuthFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n}\n\nfunc UserOrg(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tif !authr.IsValid() {\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\torgIdStr := \"\"\n\tif strings.ToLower(c.Request.Header.Get(\"Upgrade\")) == \"websocket\" {\n\t\torgIdStr = c.Query(\"organization\")\n\t} else {\n\t\torgIdStr = c.GetHeader(\"Organization\")\n\t}\n\tif orgIdStr == \"\" {\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n\n\torgId, ok := utils.ParseObjectId(orgIdStr)\n\tif orgId.IsZero() || !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\torg, err := organization.Get(db, orgId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tmatch := usr.RolesMatch(org.Roles)\n\tif !match {\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n\n\tc.Set(\"organization\", org.Id)\n}\n\nfunc CsrfToken(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tif !authr.IsValid() {\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n\n\tif authr.IsApi() {\n\t\treturn\n\t}\n\n\ttoken := \"\"\n\tif strings.ToLower(c.Request.Header.Get(\"Upgrade\")) == \"websocket\" {\n\t\ttoken = c.Query(\"csrf_token\")\n\t} else {\n\t\ttoken = c.Request.Header.Get(\"Csrf-Token\")\n\t}\n\n\tvalid, err := csrf.ValidateToken(db, authr.SessionId(), token)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\tutils.AbortWithStatus(c, 401)\n\t\t\tbreak\n\t\tdefault:\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tif !valid {\n\t\tutils.AbortWithStatus(c, 401)\n\t\treturn\n\t}\n}\n\nfunc Recovery(c *gin.Context) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"client\": node.Self.GetRemoteAddr(c.Request),\n\t\t\t\t\"error\":  errors.New(fmt.Sprintf(\"%s\", r)),\n\t\t\t}).Error(\"middlewear: Handler panic\")\n\t\t\tutils.AbortWithStatus(c, 500)\n\t\t\treturn\n\t\t}\n\t}()\n\tdefer func() {\n\t\tif c.Errors != nil && len(c.Errors) != 0 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"client\": node.Self.GetRemoteAddr(c.Request),\n\t\t\t\t\"error\":  c.Errors,\n\t\t\t}).Error(\"middlewear: Handler error\")\n\t\t}\n\t}()\n\n\tc.Next()\n}\n\nfunc RobotsGet(c *gin.Context) {\n\tc.String(200, robots)\n}\n\nfunc NotFound(c *gin.Context) {\n\tutils.AbortWithStatus(c, 404)\n}\n"
  },
  {
    "path": "mtu/mtu.go",
    "content": "package mtu\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/config\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/ip\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\ntype Check struct {\n\tnode        *node.Node\n\tmtuInternal int\n\tmtuExternal int\n\tmtuInstance int\n\tmtuHost     int\n\tInstances   []*instance.Instance\n}\n\nfunc (c *Check) host(db *database.Database) (err error) {\n\tifaces, e := ip.GetIfaces(\"\")\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tinternalIfaces := set.NewSet()\n\texternalIfaces := set.NewSet()\n\n\tfor _, iface := range c.node.InternalInterfaces {\n\t\tinternalIfaces.Add(iface)\n\t}\n\tfor _, iface := range c.node.ExternalInterfaces {\n\t\texternalIfaces.Add(iface)\n\t}\n\tfor _, iface := range c.node.ExternalInterfaces6 {\n\t\texternalIfaces.Add(iface)\n\t}\n\tfor _, blck := range c.node.Blocks {\n\t\texternalIfaces.Add(blck.Interface)\n\t}\n\tfor _, blck := range c.node.Blocks6 {\n\t\texternalIfaces.Add(blck.Interface)\n\t}\n\n\tfmt.Println(\"*******************************************\")\n\tfmt.Printf(\"host: %s\\n\", c.node.Name)\n\n\tfor _, iface := range ifaces {\n\t\tmtu := 0\n\n\t\tif iface.Ifname == settings.Hypervisor.HostNetworkName {\n\t\t\tmtu = c.mtuHost\n\t\t} else if iface.Ifname == settings.Hypervisor.NodePortNetworkName {\n\t\t\tmtu = c.mtuHost\n\t\t} else if internalIfaces.Contains(iface.Ifname) {\n\t\t\tmtu = c.mtuHost\n\t\t} else if externalIfaces.Contains(iface.Ifname) {\n\t\t\tmtu = c.mtuExternal\n\t\t} else if len(iface.Ifname) != 14 {\n\t\t\tcontinue\n\t\t} else if strings.HasPrefix(iface.Ifname, \"b\") {\n\t\t\tmtu = c.mtuInternal\n\t\t} else if strings.HasPrefix(iface.Ifname, \"k\") {\n\t\t\tmtu = c.mtuInternal\n\t\t} else if strings.HasPrefix(iface.Ifname, \"r\") {\n\t\t\tmtu = c.mtuExternal\n\t\t} else if strings.HasPrefix(iface.Ifname, \"j\") {\n\t\t\tmtu = c.mtuInternal\n\t\t} else if strings.HasPrefix(iface.Ifname, \"k\") {\n\t\t\tmtu = c.mtuInternal\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\n\t\tif iface.Mtu != mtu {\n\t\t\tfmt.Printf(\"◆◆◆ERROR◆◆◆\\n%s: %d (%d)\\n\",\n\t\t\t\tiface.Ifname, iface.Mtu, mtu)\n\t\t} else {\n\t\t\tfmt.Printf(\"%s: %d\\n\", iface.Ifname, iface.Mtu)\n\t\t}\n\t}\n\n\tfmt.Println(\"*******************************************\")\n\n\treturn\n}\n\nfunc (c *Check) instances(db *database.Database) (err error) {\n\tinsts, err := instance.GetAll(db, &bson.M{\n\t\t\"node\": c.node.Id,\n\t})\n\n\tfor _, inst := range insts {\n\t\tif inst.State != vm.Running {\n\t\t\tcontinue\n\t\t}\n\n\t\tnamespace := inst.NetworkNamespace\n\t\tif namespace == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tifaces, e := ip.GetIfaces(namespace)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Println(\"*******************************************\")\n\t\tfmt.Printf(\"instance: %s\\n\", inst.Name)\n\n\t\tfor _, iface := range ifaces {\n\t\t\tmtu := 0\n\n\t\t\tif iface.Ifname == settings.Hypervisor.BridgeIfaceName {\n\t\t\t\tmtu = c.mtuInstance\n\t\t\t} else if iface.Ifname == \"lo\" {\n\t\t\t\tcontinue\n\t\t\t} else if strings.HasPrefix(iface.Ifname, \"p\") {\n\t\t\t\tmtu = c.mtuInstance\n\t\t\t} else if strings.HasPrefix(iface.Ifname, \"e\") {\n\t\t\t\tmtu = c.mtuExternal\n\t\t\t} else if strings.HasPrefix(iface.Ifname, \"i\") {\n\t\t\t\tmtu = c.mtuInternal\n\t\t\t} else if strings.HasPrefix(iface.Ifname, \"x\") {\n\t\t\t\tmtu = c.mtuInternal\n\t\t\t} else if strings.HasPrefix(iface.Ifname, \"h\") {\n\t\t\t\tmtu = c.mtuHost\n\t\t\t} else if strings.HasPrefix(iface.Ifname, \"m\") {\n\t\t\t\tmtu = c.mtuHost\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"◆◆◆UNKNOWN IFACE◆◆◆\")\n\t\t\t}\n\n\t\t\tif iface.Mtu != mtu {\n\t\t\t\tfmt.Printf(\"◆◆◆ERROR◆◆◆\\n%s-%s: %d (%d)\\n\", namespace,\n\t\t\t\t\tiface.Ifname, iface.Mtu, mtu)\n\t\t\t} else {\n\t\t\t\tfmt.Printf(\"%s-%s: %d\\n\", namespace,\n\t\t\t\t\tiface.Ifname, iface.Mtu)\n\t\t\t}\n\t\t}\n\n\t\tfmt.Println(\"*******************************************\")\n\t}\n\n\treturn\n}\n\nfunc (c *Check) Run() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tndeId, err := bson.ObjectIDFromHex(config.Config.NodeId)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"backup: Failed to parse ObjectId\"),\n\t\t}\n\t\treturn\n\t}\n\n\tc.node, err = node.Get(db, ndeId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdc, err := datacenter.Get(db, c.node.Datacenter)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tc.mtuInternal -= dc.GetOverlayMtu()\n\tc.mtuInstance -= dc.GetInstanceMtu()\n\n\terr = c.host(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.instances(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc NewCheck() (chk *Check) {\n\treturn &Check{}\n}\n"
  },
  {
    "path": "netconf/address.go",
    "content": "package netconf\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/interfaces\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\nfunc (n *NetConf) Address(db *database.Database) (err error) {\n\tvc, err := vpc.Get(db, n.VmAdapter.Vpc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tn.VlanId = vc.VpcId\n\n\tvcNet, err := vc.GetNetwork()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcidr, _ := vcNet.Mask.Size()\n\taddr, gatewayAddr, err := vc.GetIp(db, n.VmAdapter.Subnet, n.Virt.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tn.InternalAddr = addr\n\tn.InternalGatewayAddr = gatewayAddr\n\tn.InternalGatewayAddrCidr = fmt.Sprintf(\n\t\t\"%s/%d\", gatewayAddr.String(), cidr)\n\n\tn.InternalAddr6 = vc.GetIp6(n.Virt.Id)\n\tn.InternalGatewayAddr6 = vc.GetGatewayIp6(n.Virt.Id)\n\n\tn.ExternalMacAddr = vm.GetMacAddrExternal(n.Virt.Id, vc.Id)\n\tn.InternalMacAddr = vm.GetMacAddrInternal(n.Virt.Id, vc.Id)\n\tn.HostMacAddr = vm.GetMacAddrHost(n.Virt.Id, vc.Id)\n\tn.NodePortMacAddr = vm.GetMacAddrNodePort(n.Virt.Id, vc.Id)\n\n\tif n.NetworkMode == node.Dhcp {\n\t\tn.PhysicalExternalIface = interfaces.GetExternal(\n\t\t\tn.SystemExternalIface)\n\t} else if n.NetworkMode == node.Static {\n\t\tblck, staticAddr, externalIface, e := node.Self.GetStaticAddr(\n\t\t\tdb, n.Virt.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tn.PhysicalExternalIface = externalIface\n\n\t\tstaticGateway := blck.GetGateway()\n\t\tstaticMask := blck.GetMask()\n\t\tif staticGateway == nil || staticMask == nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"qemu: Invalid block gateway cidr\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tstaticSize, _ := staticMask.Size()\n\t\tstaticCidr := fmt.Sprintf(\n\t\t\t\"%s/%d\", staticAddr.String(), staticSize)\n\n\t\tn.ExternalVlan = blck.Vlan\n\t\tif n.ExternalVlan != 0 {\n\t\t\tn.SpaceExternalIfaceMod = n.SpaceExternalIface + \"x\"\n\t\t}\n\n\t\tn.ExternalAddrCidr = staticCidr\n\t\tn.ExternalGatewayAddr = staticGateway\n\t} else if n.NetworkMode6 != node.Disabled &&\n\t\tn.NetworkMode6 != node.Cloud {\n\n\t\tn.PhysicalExternalIface = interfaces.GetExternal(\n\t\t\tn.SystemExternalIface)\n\t}\n\n\tif n.NetworkMode6 == node.Static {\n\t\tblck, staticAddr, prefix, iface, e := node.Self.GetStaticAddr6(\n\t\t\tdb, n.Virt.Id, vc.VpcId, n.PhysicalExternalIface)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif n.PhysicalExternalIface != \"\" && n.PhysicalExternalIface != iface {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\n\t\t\t\t\t\"netconf: Unsupported mismatched external \"+\n\t\t\t\t\t\t\"IPv4 and IPv6 iface %s - %s\",\n\t\t\t\t\tn.PhysicalExternalIface, iface,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tn.PhysicalExternalIface = iface\n\n\t\tstaticCidr6 := fmt.Sprintf(\"%s/%d\", staticAddr.String(), prefix)\n\t\tgateway6 := blck.GetGateway6()\n\n\t\tn.ExternalVlan6 = blck.Vlan\n\t\tif n.ExternalVlan6 != 0 {\n\t\t\tif n.ExternalVlan == n.ExternalVlan6 {\n\t\t\t\tn.SpaceExternalIfaceMod6 = n.SpaceExternalIfaceMod\n\t\t\t} else {\n\t\t\t\tn.SpaceExternalIfaceMod6 = n.SpaceExternalIface + \"y\"\n\t\t\t}\n\t\t}\n\n\t\tn.ExternalAddrCidr6 = staticCidr6\n\t\tn.ExternalGatewayAddr6 = gateway6\n\t}\n\n\tif n.HostNetwork {\n\t\tblck, staticAddr, e := node.Self.GetStaticHostAddr(db, n.Virt.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tn.HostAddr = staticAddr\n\n\t\thostStaticGateway := blck.GetGateway()\n\t\thostStaticMask := blck.GetMask()\n\t\tif hostStaticGateway == nil || hostStaticMask == nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"qemu: Invalid block gateway cidr\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\thostStaticSize, _ := hostStaticMask.Size()\n\t\thostStaticCidr := fmt.Sprintf(\n\t\t\t\"%s/%d\", staticAddr.String(), hostStaticSize)\n\n\t\tn.HostAddrCidr = hostStaticCidr\n\t\tn.HostGatewayAddr = hostStaticGateway\n\t}\n\n\tif n.NodePortNetwork {\n\t\tblck, staticAddr, e := node.Self.GetStaticNodePortAddr(db, n.Virt.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tn.NodePortAddr = staticAddr\n\n\t\tnodePortStaticMask := blck.GetMask()\n\t\tif nodePortStaticMask == nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"qemu: Invalid block gateway cidr\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tnodePortStaticSize, _ := nodePortStaticMask.Size()\n\t\tnodePortStaticCidr := fmt.Sprintf(\n\t\t\t\"%s/%d\", staticAddr.String(), nodePortStaticSize)\n\n\t\tn.NodePortAddrCidr = nodePortStaticCidr\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/base.go",
    "content": "package netconf\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc (n *NetConf) Base(db *database.Database) (err error) {\n\tif n.PhysicalExternalIface != \"\" {\n\t\tn.PhysicalExternalIfaceBridge, err = utils.IsInterfaceBridge(\n\t\t\tn.PhysicalExternalIface)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tn.PhysicalInternalIfaceBridge, err = utils.IsInterfaceBridge(\n\t\tn.PhysicalInternalIface)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/bridge.go",
    "content": "package netconf\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/iptables\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc (n *NetConf) bridgeNet(db *database.Database) (err error) {\n\terr = iproute.BridgeAdd(n.Namespace, n.SpaceBridgeIface)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\",\n\t\t\"net.ipv4.conf.br0.arp_accept=0\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\",\n\t\t\"net.ipv4.conf.br0.arp_ignore=2\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\",\n\t\t\"net.ipv4.conf.br0.arp_filter=1\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) bridgeMaster(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\", \"set\",\n\t\tn.BridgeInternalIface, \"master\", n.SpaceBridgeIface,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\", \"set\",\n\t\tn.VirtIface, \"master\", n.SpaceBridgeIface,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) bridgeRoute(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\"File exists\"},\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"addr\",\n\t\t\"add\", n.InternalGatewayAddrCidr,\n\t\t\"dev\", n.SpaceBridgeIface,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\"File exists\"},\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"-6\", \"addr\",\n\t\t\"add\", n.InternalGatewayAddr6.String()+\"/64\",\n\t\t\"dev\", n.SpaceBridgeIface,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) bridgeIptables(db *database.Database) (err error) {\n\tiptables.Lock()\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ebtables\",\n\t\t\"-A\", \"INPUT\",\n\t\t\"-p\", \"ARP\",\n\t\t\"-i\", \"!\", n.VirtIface,\n\t\t\"--arp-ip-dst\", n.InternalGatewayAddr.String(),\n\t\t\"-j\", \"DROP\",\n\t)\n\tiptables.Unlock()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tiptables.Lock()\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ebtables\",\n\t\t\"-A\", \"OUTPUT\",\n\t\t\"-p\", \"ARP\",\n\t\t\"-o\", \"!\", n.VirtIface,\n\t\t\"--arp-ip-dst\", n.InternalGatewayAddr.String(),\n\t\t\"-j\", \"DROP\",\n\t)\n\tiptables.Unlock()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tiptables.Lock()\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ebtables\",\n\t\t\"-A\", \"FORWARD\",\n\t\t\"-p\", \"ARP\",\n\t\t\"-o\", \"!\", n.VirtIface,\n\t\t\"--arp-ip-dst\", n.InternalGatewayAddr.String(),\n\t\t\"-j\", \"DROP\",\n\t)\n\tiptables.Unlock()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tiptables.Lock()\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ebtables\",\n\t\t\"-A\", \"INPUT\",\n\t\t\"-p\", \"ARP\",\n\t\t\"-i\", \"!\", n.VirtIface,\n\t\t\"--arp-ip-src\", n.InternalGatewayAddr.String(),\n\t\t\"-j\", \"DROP\",\n\t)\n\tiptables.Unlock()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tiptables.Lock()\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ebtables\",\n\t\t\"-A\", \"OUTPUT\",\n\t\t\"-p\", \"ARP\",\n\t\t\"-o\", \"!\", n.VirtIface,\n\t\t\"--arp-ip-src\", n.InternalGatewayAddr.String(),\n\t\t\"-j\", \"DROP\",\n\t)\n\tiptables.Unlock()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tiptables.Lock()\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ebtables\",\n\t\t\"-A\", \"FORWARD\",\n\t\t\"-p\", \"ARP\",\n\t\t\"-o\", \"!\", n.VirtIface,\n\t\t\"--arp-ip-src\", n.InternalGatewayAddr.String(),\n\t\t\"-j\", \"DROP\",\n\t)\n\tiptables.Unlock()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) bridgeUp(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.SpaceBridgeIface, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"bridge\", \"link\",\n\t\t\"set\", \"dev\", n.VirtIface, \"hairpin\", \"on\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) Bridge(db *database.Database) (err error) {\n\terr = n.bridgeNet(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.bridgeMaster(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.bridgeRoute(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.bridgeIptables(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.bridgeUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/clear.go",
    "content": "package netconf\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/interfaces\"\n\t\"github.com/pritunl/pritunl-cloud/store\"\n\t\"github.com/pritunl/tools/commander\"\n)\n\nfunc (n *NetConf) Clear(db *database.Database) (err error) {\n\tlockId := lock.Lock(\"clear\")\n\tdefer lock.Unlock(\"clear\", lockId)\n\n\tclearIface(\"\", n.SystemExternalIface) // bridged\n\tclearIface(\"\", n.SystemInternalIface) // bridged\n\tclearIface(\"\", n.SystemHostIface)     // bridged\n\tclearIface(\"\", n.SystemNodePortIface) // bridged\n\tclearIface(\"\", n.SpaceExternalIface)\n\tclearIface(\"\", n.SpaceExternalIfaceMod)\n\tclearIface(\"\", n.SpaceExternalIfaceMod6)\n\tclearIface(\"\", n.SpaceInternalIface)\n\tclearIface(\"\", n.SpaceHostIface)\n\tclearIface(\"\", n.SpaceNodePortIface)\n\n\tclearIface(n.Namespace, n.SpaceBridgeIface)\n\tclearIface(n.Namespace, n.SpaceImdsIface)\n\n\tinterfaces.RemoveVirtIface(n.SystemExternalIface)\n\tinterfaces.RemoveVirtIface(n.SystemInternalIface)\n\tinterfaces.RemoveVirtIface(n.SystemNodePortIface)\n\n\treturn\n}\n\nfunc (n *NetConf) ClearAll(db *database.Database) (err error) {\n\tif len(n.Virt.NetworkAdapters) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"qemu: Missing network interfaces\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = n.Clear(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstore.RemAddress(n.Virt.Id)\n\tstore.RemRoutes(n.Virt.Id)\n\tstore.RemArp(n.Virt.Id)\n\n\treturn\n}\n\nfunc clearIface(namespace, iface string) {\n\tif iface == \"\" {\n\t\treturn\n\t}\n\n\tif namespace != \"\" {\n\t\tcommander.Exec(&commander.Opt{\n\t\t\tName: \"ip\",\n\t\t\tArgs: []string{\n\t\t\t\t\"netns\", \"exec\", namespace,\n\t\t\t\t\"ip\", \"link\", \"set\", iface, \"nomaster\",\n\t\t\t},\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\tcommander.Exec(&commander.Opt{\n\t\t\tName: \"ip\",\n\t\t\tArgs: []string{\n\t\t\t\t\"netns\", \"exec\", namespace,\n\t\t\t\t\"ip\", \"link\", \"set\", iface, \"down\",\n\t\t\t},\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t\tcommander.Exec(&commander.Opt{\n\t\t\tName: \"ip\",\n\t\t\tArgs: []string{\n\t\t\t\t\"netns\", \"exec\", namespace,\n\t\t\t\t\"ip\", \"link\", \"del\", iface,\n\t\t\t},\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t} else {\n\t\tcommander.Exec(&commander.Opt{\n\t\t\tName: \"ip\",\n\t\t\tArgs: []string{\n\t\t\t\t\"link\", \"set\", iface, \"nomaster\",\n\t\t\t},\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\tcommander.Exec(&commander.Opt{\n\t\t\tName: \"ip\",\n\t\t\tArgs: []string{\n\t\t\t\t\"link\", \"set\", iface, \"down\",\n\t\t\t},\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t\tcommander.Exec(&commander.Opt{\n\t\t\tName: \"ip\",\n\t\t\tArgs: []string{\n\t\t\t\t\"link\", \"del\", iface,\n\t\t\t},\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "netconf/external.go",
    "content": "package netconf\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc (n *NetConf) externalNet(db *database.Database) (err error) {\n\tif (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) ||\n\t\t(n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud) {\n\n\t\tif n.PhysicalExternalIfaceBridge {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"add\", n.SystemExternalIface,\n\t\t\t\t\"type\", \"veth\",\n\t\t\t\t\"peer\", \"name\", n.SpaceExternalIface,\n\t\t\t\t\"addr\", n.ExternalMacAddr,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"tc\", \"qdisc\", \"replace\", \"dev\", n.SystemExternalIface,\n\t\t\t\t\"root\", \"fq_codel\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"add\", n.SpaceExternalIface,\n\t\t\t\t\"addr\", n.ExternalMacAddr,\n\t\t\t\t\"link\", n.PhysicalExternalIface,\n\t\t\t\t\"type\", \"macvlan\",\n\t\t\t\t\"mode\", \"bridge\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) externalMtu(db *database.Database) (err error) {\n\tif (n.PhysicalExternalIfaceBridge &&\n\t\tn.SystemExternalIfaceMtu != \"\") &&\n\t\t((n.NetworkMode != node.Disabled &&\n\t\t\tn.NetworkMode != node.Cloud) ||\n\t\t\t(n.NetworkMode6 != node.Disabled &&\n\t\t\t\tn.NetworkMode6 != node.Cloud)) {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SystemExternalIface,\n\t\t\t\"mtu\", n.SystemExternalIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif n.SpaceExternalIfaceMtu != \"\" &&\n\t\t((n.NetworkMode != node.Disabled &&\n\t\t\tn.NetworkMode != node.Cloud) ||\n\t\t\t(n.NetworkMode6 != node.Disabled &&\n\t\t\t\tn.NetworkMode6 != node.Cloud)) {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceExternalIface,\n\t\t\t\"mtu\", n.SpaceExternalIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) externalUp(db *database.Database) (err error) {\n\tif n.PhysicalExternalIfaceBridge &&\n\t\t((n.NetworkMode != node.Disabled &&\n\t\t\tn.NetworkMode != node.Cloud) ||\n\t\t\t(n.NetworkMode6 != node.Disabled &&\n\t\t\t\tn.NetworkMode6 != node.Cloud)) {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SystemExternalIface, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) externalSysctl(db *database.Database) (err error) {\n\tif n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil, \"sysctl\", \"-w\",\n\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.accept_ra=2\",\n\t\t\t\tstrings.ReplaceAll(n.PhysicalExternalIface, \".\", \"/\")),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif n.NetworkMode6 != node.Slaac && n.NetworkMode6 != node.DhcpSlaac {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil, \"sysctl\", \"-w\",\n\t\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.addr_gen_mode=1\",\n\t\t\t\t\tstrings.ReplaceAll(n.PhysicalExternalIface, \".\", \"/\")),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) externalMaster(db *database.Database) (err error) {\n\tif n.PhysicalExternalIfaceBridge &&\n\t\t((n.NetworkMode != node.Disabled &&\n\t\t\tn.NetworkMode != node.Cloud) ||\n\t\t\t(n.NetworkMode6 != node.Disabled &&\n\t\t\t\tn.NetworkMode6 != node.Cloud)) {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", n.SystemExternalIface,\n\t\t\t\"master\", n.PhysicalExternalIface,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) externalSpace(db *database.Database) (err error) {\n\tif (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) ||\n\t\t(n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud) {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"File exists\"},\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceExternalIface,\n\t\t\t\"netns\", n.Namespace,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif n.PhysicalExternalIfaceBridge {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"tc\", \"qdisc\", \"replace\", \"dev\", n.SpaceExternalIface,\n\t\t\t\t\"root\", \"fq_codel\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) externalSpaceMod(db *database.Database) (err error) {\n\tif n.SpaceExternalIfaceMod != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"File exists\"},\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"add\", \"link\", n.SpaceExternalIface,\n\t\t\t\"name\", n.SpaceExternalIfaceMod,\n\t\t\t\"type\", \"vlan\",\n\t\t\t\"id\", strconv.Itoa(n.ExternalVlan),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceExternalIfaceMod,\n\t\t\t\"mtu\", n.SpaceExternalIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif n.SpaceExternalIfaceMod6 != \"\" &&\n\t\tn.SpaceExternalIfaceMod6 != n.SpaceExternalIfaceMod {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"File exists\"},\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"add\", \"link\", n.SpaceExternalIface,\n\t\t\t\"name\", n.SpaceExternalIfaceMod6,\n\t\t\t\"type\", \"vlan\",\n\t\t\t\"id\", strconv.Itoa(n.ExternalVlan6),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceExternalIfaceMod6,\n\t\t\t\"mtu\", n.SpaceExternalIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) externalSpaceSysctl(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\", \"net.ipv6.conf.all.accept_ra=0\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\", \"net.ipv6.conf.default.accept_ra=0\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif n.NetworkMode6 == node.Static {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"sysctl\", \"-w\",\n\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.autoconf=0\",\n\t\t\t\tn.SpaceExternalIface),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif n.SpaceExternalIfaceMod6 != \"\" {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"sysctl\", \"-w\",\n\t\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.autoconf=0\",\n\t\t\t\t\tn.SpaceExternalIfaceMod6),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud {\n\t\tif n.SpaceExternalIfaceMod6 == \"\" {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"sysctl\", \"-w\",\n\t\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.accept_ra=2\",\n\t\t\t\t\tn.SpaceExternalIface),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"sysctl\", \"-w\",\n\t\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.accept_ra=2\",\n\t\t\t\t\tn.SpaceExternalIfaceMod6),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) &&\n\t\t(n.NetworkMode6 == node.Disabled || n.NetworkMode6 == node.Cloud) {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"sysctl\", \"-w\",\n\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.disable_ipv6=1\",\n\t\t\t\tn.SpaceExternalIface),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif n.SpaceExternalIfaceMod != n.SpaceExternalIfaceMod6 &&\n\t\tn.SpaceExternalIfaceMod != \"\" {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"sysctl\", \"-w\",\n\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.disable_ipv6=1\",\n\t\t\t\tn.SpaceExternalIfaceMod),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) externalSpaceUp(db *database.Database) (err error) {\n\tif (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) ||\n\t\t(n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud) {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceExternalIface, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif n.SpaceExternalIfaceMod != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceExternalIfaceMod, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif n.SpaceExternalIfaceMod6 != \"\" &&\n\t\tn.SpaceExternalIfaceMod6 != n.SpaceExternalIfaceMod {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceExternalIfaceMod6, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) External(db *database.Database) (err error) {\n\tdelay := time.Duration(settings.Hypervisor.ActionRate) * time.Second\n\tlockId := lock.Lock(\"external\")\n\tdefer lock.DelayUnlock(\"external\", lockId, delay)\n\n\terr = n.externalNet(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.externalMtu(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.externalUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.externalSysctl(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.externalMaster(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.externalSpace(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.externalSpaceMod(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.externalSpaceSysctl(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.externalSpaceUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/host.go",
    "content": "package netconf\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc (n *NetConf) hostNet(db *database.Database) (err error) {\n\tif n.HostNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"add\", n.SystemHostIface,\n\t\t\t\"type\", \"veth\",\n\t\t\t\"peer\", \"name\", n.SpaceHostIface,\n\t\t\t\"addr\", n.HostMacAddr,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"tc\", \"qdisc\", \"replace\", \"dev\", n.SystemHostIface,\n\t\t\t\"root\", \"fq_codel\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) hostMtu(db *database.Database) (err error) {\n\tif n.HostNetwork && n.SystemHostIfaceMtu != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SystemHostIface,\n\t\t\t\"mtu\", n.SystemHostIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tif n.HostNetwork && n.SpaceHostIfaceMtu != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceHostIface,\n\t\t\t\"mtu\", n.SpaceHostIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) hostUp(db *database.Database) (err error) {\n\tif n.HostNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SystemHostIface, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) hostMaster(db *database.Database) (err error) {\n\tif n.HostNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", n.SystemHostIface,\n\t\t\t\"master\", n.PhysicalHostIface,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) hostSpace(db *database.Database) (err error) {\n\tif n.HostNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"File exists\"},\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceHostIface,\n\t\t\t\"netns\", n.Namespace,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"tc\", \"qdisc\", \"replace\", \"dev\", n.SpaceHostIface,\n\t\t\t\"root\", \"fq_codel\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) hostSpaceUp(db *database.Database) (err error) {\n\tif n.HostNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceHostIface, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) Host(db *database.Database) (err error) {\n\tdelay := time.Duration(settings.Hypervisor.ActionRate) * time.Second\n\tlockId := lock.Lock(\"host\")\n\tdefer lock.DelayUnlock(\"host\", lockId, delay)\n\n\terr = n.hostNet(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.hostMtu(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.hostUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.hostMaster(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.hostSpace(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.hostSpaceUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/iface.go",
    "content": "package netconf\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/interfaces\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc (n *NetConf) Iface1(db *database.Database) (err error) {\n\tn.NetworkMode = node.Self.NetworkMode\n\tif n.NetworkMode == \"\" {\n\t\tn.NetworkMode = node.Dhcp\n\t}\n\tn.NetworkMode6 = node.Self.NetworkMode6\n\tif n.NetworkMode6 == \"\" {\n\t\tn.NetworkMode6 = node.Dhcp\n\t}\n\tif n.NetworkMode == node.Internal || n.Virt.NoPublicAddress {\n\t\tn.NetworkMode = node.Disabled\n\t}\n\tif n.Virt.NoPublicAddress6 {\n\t\tn.NetworkMode6 = node.Disabled\n\t}\n\n\tif !node.Self.NoHostNetwork && !n.Virt.NoHostAddress {\n\t\tn.HostNetwork = true\n\t\tif node.Self.HostNat {\n\t\t\tn.HostNat = true\n\t\t}\n\n\t\tblck, e := block.GetNodeBlock(node.Self.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\thostNetwork, e := blck.GetNetwork()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tn.HostSubnet = hostNetwork.String()\n\t}\n\n\tif !node.Self.NoNodePortNetwork {\n\t\tn.NodePortNetwork = true\n\n\t\tblck, e := block.GetNodePortBlock(node.Self.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tnodePortNetwork, e := blck.GetNetwork()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tn.NodePortSubnet = nodePortNetwork.String()\n\t}\n\n\tn.CloudSubnets = set.NewSet()\n\tif node.Self.CloudSubnets != nil {\n\t\tfor _, subnet := range node.Self.CloudSubnets {\n\t\t\tn.CloudSubnets.Add(subnet)\n\t\t}\n\t}\n\n\tn.Namespace = vm.GetNamespace(n.Virt.Id, 0)\n\n\tif n.Virt.NetworkAdapters == nil || len(n.Virt.NetworkAdapters) < 1 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"netconf: Missing virt network adapter\"),\n\t\t}\n\t\treturn\n\t}\n\tn.VmAdapter = n.Virt.NetworkAdapters[0]\n\n\tn.VirtIface = vm.GetIface(n.Virt.Id, 0)\n\tn.SystemExternalIface = vm.GetIfaceNodeExternal(n.Virt.Id, 0)\n\tn.SystemInternalIface = vm.GetIfaceNodeInternal(n.Virt.Id, 0)\n\tn.SystemHostIface = vm.GetIfaceHost(n.Virt.Id, 0)\n\tn.SystemNodePortIface = vm.GetIfaceNodePort(n.Virt.Id, 0)\n\tn.SpaceExternalIface = vm.GetIfaceExternal(n.Virt.Id, 0)\n\tn.SpaceInternalIface = vm.GetIfaceInternal(n.Virt.Id, 0)\n\tn.SpaceHostIface = vm.GetIfaceHost(n.Virt.Id, 1)\n\tn.SpaceNodePortIface = vm.GetIfaceNodePort(n.Virt.Id, 1)\n\tn.SpaceCloudIface = vm.GetIfaceCloud(n.Virt.Id, 0)\n\tn.SpaceCloudVirtIface = vm.GetIfaceCloudVirt(n.Virt.Id, 0)\n\tn.SpaceBridgeIface = settings.Hypervisor.BridgeIfaceName\n\tn.SpaceImdsIface = settings.Hypervisor.ImdsIfaceName\n\n\treturn\n}\n\nfunc (n *NetConf) Iface2(db *database.Database, clean bool) (err error) {\n\tdc, err := datacenter.Get(db, node.Self.Datacenter)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tn.Vxlan = dc.Vxlan()\n\n\tn.PhysicalHostIface = settings.Hypervisor.HostNetworkName\n\tn.PhysicalNodePortIface = settings.Hypervisor.NodePortNetworkName\n\n\tn.BridgeInternalIface = vm.GetIfaceVlan(n.Virt.Id, 0)\n\n\tn.PhysicalInternalIface = interfaces.GetInternal(\n\t\tn.SystemInternalIface, n.Vxlan)\n\n\tmtuSizeExternal := dc.GetBaseExternalMtu()\n\tmtuSizeInternal := dc.GetBaseInternalMtu()\n\tmtuSizeOverlay := dc.GetOverlayMtu()\n\tmtuSizeInstance := dc.GetInstanceMtu()\n\n\tn.SpaceExternalIfaceMtu = strconv.Itoa(mtuSizeExternal)\n\tn.SystemExternalIfaceMtu = strconv.Itoa(mtuSizeExternal)\n\n\tn.SpaceHostIfaceMtu = strconv.Itoa(mtuSizeInternal)\n\tn.SpaceNodePortIfaceMtu = strconv.Itoa(mtuSizeInternal)\n\tn.SystemHostIfaceMtu = strconv.Itoa(mtuSizeInternal)\n\tn.SystemNodePortIfaceMtu = strconv.Itoa(mtuSizeInternal)\n\tn.ImdsIfaceMtu = strconv.Itoa(mtuSizeInternal)\n\n\tn.SpaceInternalIfaceMtu = strconv.Itoa(mtuSizeOverlay)\n\tn.BridgeInternalIfaceMtu = strconv.Itoa(mtuSizeOverlay)\n\tn.SystemInternalIfaceMtu = strconv.Itoa(mtuSizeOverlay)\n\n\tn.VirtIfaceMtu = strconv.Itoa(mtuSizeInstance)\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/imds.go",
    "content": "package netconf\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/imds\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc (n *NetConf) imdsNet(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"File exists\",\n\t\t},\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"add\", n.SpaceImdsIface,\n\t\t\"type\", \"dummy\",\n\t\t//\"addr\", n.ImdsMacAddr,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) imdsMtu(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.SpaceImdsIface,\n\t\t\"mtu\", n.ImdsIfaceMtu,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) imdsAddr(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"addr\",\n\t\t\"add\", settings.Hypervisor.ImdsAddress,\n\t\t\"dev\", n.SpaceImdsIface,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) imdsUp(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.SpaceImdsIface, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) imdsStart(db *database.Database) (err error) {\n\terr = imds.Start(db, n.Virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) Imds(db *database.Database) (err error) {\n\terr = n.imdsNet(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.imdsMtu(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.imdsAddr(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.imdsUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.imdsStart(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/internal.go",
    "content": "package netconf\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc (n *NetConf) internalNet(db *database.Database) (err error) {\n\tif n.PhysicalInternalIfaceBridge {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"add\", n.SystemInternalIface,\n\t\t\t\"type\", \"veth\",\n\t\t\t\"peer\", \"name\", n.SpaceInternalIface,\n\t\t\t\"addr\", n.InternalMacAddr,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"tc\", \"qdisc\", \"replace\", \"dev\", n.SystemInternalIface,\n\t\t\t\"root\", \"fq_codel\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"add\", n.SpaceInternalIface,\n\t\t\t\"addr\", n.InternalMacAddr,\n\t\t\t\"link\", n.PhysicalInternalIface,\n\t\t\t\"type\", \"macvlan\",\n\t\t\t\"mode\", \"bridge\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) internalMtu(db *database.Database) (err error) {\n\tif n.SystemInternalIfaceMtu != \"\" && n.PhysicalInternalIfaceBridge {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SystemInternalIface,\n\t\t\t\"mtu\", n.SystemInternalIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tif n.SpaceInternalIfaceMtu != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceInternalIface,\n\t\t\t\"mtu\", n.SpaceInternalIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) internalUp(db *database.Database) (err error) {\n\tif n.PhysicalInternalIfaceBridge {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SystemInternalIface, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) internalMaster(db *database.Database) (err error) {\n\tif n.PhysicalInternalIfaceBridge {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", n.SystemInternalIface,\n\t\t\t\"master\", n.PhysicalInternalIface,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) internalSpace(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\"File exists\"},\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.SpaceInternalIface,\n\t\t\"netns\", n.Namespace,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif n.PhysicalInternalIfaceBridge {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"tc\", \"qdisc\", \"replace\", \"dev\", n.SpaceInternalIface,\n\t\t\t\"root\", \"fq_codel\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) internalSpaceUp(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.SpaceInternalIface, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) Internal(db *database.Database) (err error) {\n\tdelay := time.Duration(settings.Hypervisor.ActionRate) * time.Second\n\tlockId := lock.Lock(\"internal\")\n\tdefer lock.DelayUnlock(\"internal\", lockId, delay)\n\n\terr = n.internalNet(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.internalMtu(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.internalUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.internalMaster(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.internalSpace(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.internalSpaceUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/ip.go",
    "content": "package netconf\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/dhcpc\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/imds\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/iptables\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/store\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/tools/commander\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc (n *NetConf) ipExternal(db *database.Database) (err error) {\n\tif n.NetworkMode == node.Dhcp || n.NetworkMode6 == node.Dhcp ||\n\t\tn.NetworkMode6 == node.DhcpSlaac {\n\n\t\terr = dhcpc.Start(\n\t\t\tdb,\n\t\t\tn.Virt,\n\t\t\tn.SpaceExternalIface,\n\t\t\tn.SpaceExternalIface,\n\t\t\tn.NetworkMode == node.Dhcp,\n\t\t\tn.NetworkMode6 == node.Dhcp || n.NetworkMode6 == node.DhcpSlaac,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tvar imdsErr error\n\t\tip4 := false\n\t\tip6 := false\n\t\tipTimeout := settings.Hypervisor.IpTimeout * 4\n\t\tfor i := 0; i < ipTimeout; i++ {\n\t\t\tstat, e := imds.State(db, n.Virt.Id, n.Virt.ImdsHostSecret)\n\t\t\tif e != nil {\n\t\t\t\timdsErr = e\n\t\t\t\ttime.Sleep(250 * time.Millisecond)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif stat == nil {\n\t\t\t\ttime.Sleep(250 * time.Millisecond)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif stat.DhcpIp != nil && stat.DhcpGateway != nil {\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"File exists\", \"already assigned\"},\n\t\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\t\"add\", stat.DhcpIp.String(),\n\t\t\t\t\t\"dev\", n.SpaceExternalIface,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\t\"ip\", \"route\",\n\t\t\t\t\t\"add\", \"default\",\n\t\t\t\t\t\"via\", stat.DhcpGateway.String(),\n\t\t\t\t\t\"dev\", n.SpaceExternalIface,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tip4 = true\n\t\t\t}\n\n\t\t\tif stat.DhcpIp6 != nil {\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"File exists\", \"already assigned\"},\n\t\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\t\"add\", stat.DhcpIp6.String(),\n\t\t\t\t\t\"dev\", n.SpaceExternalIface,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tip6 = true\n\t\t\t}\n\n\t\t\tif (n.NetworkMode != node.Dhcp || ip4) &&\n\t\t\t\t((n.NetworkMode6 != node.Dhcp &&\n\t\t\t\t\tn.NetworkMode6 != node.DhcpSlaac) || ip6) {\n\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttime.Sleep(250 * time.Millisecond)\n\t\t}\n\n\t\tif !ip4 && n.NetworkMode == node.Dhcp {\n\t\t\tif imdsErr != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance\": n.Virt.Id.Hex(),\n\t\t\t\t\t\"dhcp4\":    ip4,\n\t\t\t\t\t\"dhcp6\":    ip6,\n\t\t\t\t\t\"error\":    imdsErr,\n\t\t\t\t}).Error(\"netconf: DHCP IPv4 timeout\")\n\t\t\t} else {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance\": n.Virt.Id.Hex(),\n\t\t\t\t}).Error(\"netconf: DHCP IPv4 timeout\")\n\t\t\t}\n\t\t}\n\n\t\tif !ip6 && (n.NetworkMode6 == node.Dhcp ||\n\t\t\tn.NetworkMode6 == node.DhcpSlaac) {\n\n\t\t\tif imdsErr != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance\": n.Virt.Id.Hex(),\n\t\t\t\t\t\"dhcp4\":    ip4,\n\t\t\t\t\t\"dhcp6\":    ip6,\n\t\t\t\t\t\"error\":    imdsErr,\n\t\t\t\t}).Error(\"netconf: DHCP IPv6 timeout\")\n\t\t\t} else {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance\": n.Virt.Id.Hex(),\n\t\t\t\t\t\"dhcp4\":    ip4,\n\t\t\t\t\t\"dhcp6\":    ip6,\n\t\t\t\t}).Error(\"netconf: DHCP IPv6 timeout\")\n\t\t\t}\n\t\t}\n\t}\n\n\tif n.NetworkMode == node.Static {\n\t\tif n.SpaceExternalIfaceMod != \"\" {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\"add\", n.ExternalAddrCidr,\n\t\t\t\t\"dev\", n.SpaceExternalIfaceMod,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"ip\", \"route\",\n\t\t\t\t\"add\", \"default\",\n\t\t\t\t\"via\", n.ExternalGatewayAddr.String(),\n\t\t\t\t\"dev\", n.SpaceExternalIfaceMod,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\"add\", n.ExternalAddrCidr,\n\t\t\t\t\"dev\", n.SpaceExternalIface,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"ip\", \"route\",\n\t\t\t\t\"add\", \"default\",\n\t\t\t\t\"via\", n.ExternalGatewayAddr.String(),\n\t\t\t\t\"dev\", n.SpaceExternalIface,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif n.NetworkMode6 == node.Static {\n\t\tif n.SpaceExternalIfaceMod6 != \"\" {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\"add\", n.ExternalAddrCidr6,\n\t\t\t\t\"dev\", n.SpaceExternalIfaceMod6,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif n.ExternalGatewayAddr6 != nil {\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\t\"ip\", \"-6\", \"route\",\n\t\t\t\t\t\"add\", \"default\",\n\t\t\t\t\t\"via\", n.ExternalGatewayAddr6.String(),\n\t\t\t\t\t\"dev\", n.SpaceExternalIfaceMod6,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"ip\", \"addr\",\n\t\t\t\t\"add\", n.ExternalAddrCidr6,\n\t\t\t\t\"dev\", n.SpaceExternalIface,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif n.ExternalGatewayAddr6 != nil {\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\t\"ip\", \"-6\", \"route\",\n\t\t\t\t\t\"add\", \"default\",\n\t\t\t\t\t\"via\", n.ExternalGatewayAddr6.String(),\n\t\t\t\t\t\"dev\", n.SpaceExternalIface,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) ipHost(db *database.Database) (err error) {\n\tif n.HostNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"File exists\"},\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"addr\",\n\t\t\t\"add\", n.HostAddrCidr,\n\t\t\t\"dev\", n.SpaceHostIface,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif n.NetworkMode == node.Disabled {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\"File exists\"},\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"ip\", \"route\",\n\t\t\t\t\"add\", \"default\",\n\t\t\t\t\"via\", n.HostGatewayAddr.String(),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) ipNodePort(db *database.Database) (err error) {\n\tif n.NodePortNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"File exists\"},\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"addr\",\n\t\t\t\"add\", n.NodePortAddrCidr,\n\t\t\t\"dev\", n.SpaceNodePortIface,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) ipDetect(db *database.Database) (err error) {\n\ttime.Sleep(250 * time.Millisecond)\n\n\tipTimeout := settings.Hypervisor.IpTimeout * 4\n\tipTimeout6 := settings.Hypervisor.IpTimeout6 * 4\n\n\tpubAddr := \"\"\n\tpubAddr6 := \"\"\n\tif n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud {\n\t\tfor i := 0; i < ipTimeout; i++ {\n\t\t\taddress, address6, e := iproute.AddressGetIfaceMod(\n\t\t\t\tn.Namespace, n.SpaceExternalIface)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif n.NetworkMode6 != node.Disabled &&\n\t\t\t\tn.NetworkMode6 != node.Cloud {\n\n\t\t\t\tif address != nil {\n\t\t\t\t\tpubAddr = address.Local\n\t\t\t\t}\n\n\t\t\t\tif address != nil && address6 != nil {\n\t\t\t\t\tif address6 != nil {\n\t\t\t\t\t\tpubAddr6 = address6.Local\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else if address != nil {\n\t\t\t\tpubAddr = address.Local\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttime.Sleep(250 * time.Millisecond)\n\t\t}\n\n\t\tif pubAddr == \"\" {\n\t\t\terr = &errortypes.NetworkError{\n\t\t\t\terrors.New(\"qemu: Instance missing IPv4 address\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else if n.NetworkMode6 != node.Disabled &&\n\t\tn.NetworkMode6 != node.Cloud {\n\n\t\tfor i := 0; i < ipTimeout6; i++ {\n\t\t\t_, address6, e := iproute.AddressGetIfaceMod(\n\t\t\t\tn.Namespace, n.SpaceExternalIface)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif address6 != nil {\n\t\t\t\tpubAddr6 = address6.Local\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttime.Sleep(250 * time.Millisecond)\n\t\t}\n\n\t\tif pubAddr6 == \"\" {\n\t\t\terr = &errortypes.NetworkError{\n\t\t\t\terrors.New(\"qemu: Instance missing IPv6 address\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tn.PublicAddress = pubAddr\n\tif n.ExternalAddrCidr6 != \"\" {\n\t\tn.PublicAddress6 = n.ExternalAddrCidr6\n\t} else {\n\t\tn.PublicAddress6 = pubAddr6\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) ipHostIptables(db *database.Database) (err error) {\n\tif n.HostNetwork {\n\t\tiptables.Lock()\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"iptables\", \"-t\", \"nat\",\n\t\t\t\"-A\", \"POSTROUTING\",\n\t\t\t\"-s\", n.InternalAddr.String()+\"/32\",\n\t\t\t\"-d\", n.InternalAddr.String()+\"/32\",\n\t\t\t\"-m\", \"comment\",\n\t\t\t\"--comment\", \"pritunl_cloud_host_nat\",\n\t\t\t\"-j\", \"SNAT\",\n\t\t\t\"--to\", n.HostAddr.String(),\n\t\t)\n\t\tiptables.Unlock()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif n.HostNat {\n\t\t\tiptables.Lock()\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"iptables\", \"-t\", \"nat\",\n\t\t\t\t\"-A\", \"POSTROUTING\",\n\t\t\t\t\"-s\", n.InternalAddr.String()+\"/32\",\n\t\t\t\t\"-o\", n.SpaceHostIface,\n\t\t\t\t\"-m\", \"comment\",\n\t\t\t\t\"--comment\", \"pritunl_cloud_host_nat\",\n\t\t\t\t\"-j\", \"MASQUERADE\",\n\t\t\t)\n\t\t\tiptables.Unlock()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tiptables.Lock()\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"iptables\", \"-t\", \"nat\",\n\t\t\t\t\"-A\", \"POSTROUTING\",\n\t\t\t\t\"-s\", n.InternalAddr.String()+\"/32\",\n\t\t\t\t\"-d\", n.HostSubnet,\n\t\t\t\t\"-o\", n.SpaceHostIface,\n\t\t\t\t\"-m\", \"comment\",\n\t\t\t\t\"--comment\", \"pritunl_cloud_host_nat\",\n\t\t\t\t\"-j\", \"MASQUERADE\",\n\t\t\t)\n\t\t\tiptables.Unlock()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tiptables.Lock()\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"iptables\", \"-t\", \"nat\",\n\t\t\t\"-A\", \"PREROUTING\",\n\t\t\t\"-d\", n.HostAddr.String()+\"/32\",\n\t\t\t\"-m\", \"comment\",\n\t\t\t\"--comment\", \"pritunl_cloud_host_nat\",\n\t\t\t\"-j\", \"DNAT\",\n\t\t\t\"--to-destination\", n.InternalAddr.String(),\n\t\t)\n\t\tiptables.Unlock()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) ipDatabase(db *database.Database) (err error) {\n\tstore.RemAddress(n.Virt.Id)\n\tstore.RemRoutes(n.Virt.Id)\n\tstore.RemArp(n.Virt.Id)\n\n\thostIps := []string{}\n\tif n.HostAddr != nil {\n\t\thostIps = append(hostIps, n.HostAddr.String())\n\t}\n\n\tnodePortIps := []string{}\n\tif n.NodePortAddr != nil {\n\t\tnodePortIps = append(nodePortIps, n.NodePortAddr.String())\n\t}\n\n\tcoll := db.Instances()\n\terr = coll.UpdateId(n.Virt.Id, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"private_ips\":  []string{n.InternalAddr.String()},\n\t\t\t\"private_ips6\": []string{n.InternalAddr6.String()},\n\t\t\t\"gateway_ips\":  []string{n.InternalGatewayAddrCidr},\n\t\t\t\"gateway_ips6\": []string{\n\t\t\t\tn.InternalGatewayAddr6.String() + \"/64\"},\n\t\t\t\"network_namespace\": n.Namespace,\n\t\t\t\"host_ips\":          hostIps,\n\t\t\t\"node_port_ips\":     nodePortIps,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif !n.Virt.Deployment.IsZero() {\n\t\tcoll = db.Deployments()\n\n\t\thostIps := []string{}\n\t\tif n.HostAddr != nil {\n\t\t\thostIps = append(hostIps, n.HostAddr.String())\n\t\t}\n\t\tprivateIps := []string{}\n\t\tif n.InternalAddr != nil {\n\t\t\tprivateIps = append(privateIps, n.InternalAddr.String())\n\t\t}\n\t\tprivateIps6 := []string{}\n\t\tif n.InternalAddr6 != nil {\n\t\t\tprivateIps6 = append(privateIps6, n.InternalAddr6.String())\n\t\t}\n\n\t\terr = coll.UpdateId(n.Virt.Deployment, &bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"instance_data.host_ips\":     hostIps,\n\t\t\t\t\"instance_data.private_ips\":  privateIps,\n\t\t\t\t\"instance_data.private_ips6\": privateIps6,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) ipInit6(db *database.Database) (err error) {\n\tif n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud &&\n\t\tn.PublicAddress6 != \"\" && !settings.Hypervisor.NoIpv6PingInit {\n\n\t\tfor i := 0; i < 3; i++ {\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\n\t\t\tresp, e := commander.Exec(&commander.Opt{\n\t\t\t\tName: \"ip\",\n\t\t\t\tArgs: []string{\n\t\t\t\t\t\"netns\", \"exec\", n.Namespace, \"dig\",\n\t\t\t\t\t\"@\" + settings.Hypervisor.DnsServerPrimary6,\n\t\t\t\t\t\"app6.pritunl.com\",\n\t\t\t\t\t\"AAAA\",\n\t\t\t\t},\n\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t\tPipeOut: true,\n\t\t\t\tPipeErr: true,\n\t\t\t})\n\t\t\tif e != nil {\n\t\t\t\toutput := \"\"\n\t\t\t\tif resp != nil {\n\t\t\t\t\toutput = string(resp.Output)\n\t\t\t\t}\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": n.Virt.Id.Hex(),\n\t\t\t\t\t\"namespace\":   n.Namespace,\n\t\t\t\t\t\"address6\":    n.PublicAddress6,\n\t\t\t\t\t\"output\":      output,\n\t\t\t\t}).Warn(\"netconf: IPv6 network DNS lookup test failed\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tresp, e = commander.Exec(&commander.Opt{\n\t\t\t\tName: \"ip\",\n\t\t\t\tArgs: []string{\n\t\t\t\t\t\"netns\", \"exec\", n.Namespace, \"ping6\",\n\t\t\t\t\t\"-c\", \"3\", \"-i\", \"0.5\", \"-w\", \"6\",\n\t\t\t\t\tsettings.Hypervisor.Ipv6PingHost,\n\t\t\t\t},\n\t\t\t\tTimeout: 6 * time.Second,\n\t\t\t\tPipeOut: true,\n\t\t\t\tPipeErr: true,\n\t\t\t})\n\t\t\tif e != nil {\n\t\t\t\toutput := \"\"\n\t\t\t\tif resp != nil {\n\t\t\t\t\toutput = string(resp.Output)\n\t\t\t\t}\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": n.Virt.Id.Hex(),\n\t\t\t\t\t\"namespace\":   n.Namespace,\n\t\t\t\t\t\"address6\":    n.PublicAddress6,\n\t\t\t\t\t\"output\":      output,\n\t\t\t\t}).Warn(\"netconf: IPv6 network DNS lookup test failed\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) ipArp(db *database.Database) (err error) {\n\tif n.NetworkMode == node.Static {\n\t\taddr := strings.Split(n.ExternalAddrCidr, \"/\")[0]\n\t\tiface := n.SpaceExternalIfaceMod\n\t\tif iface == \"\" {\n\t\t\tiface = n.SpaceExternalIface\n\t\t}\n\n\t\t_, _ = commander.Exec(&commander.Opt{\n\t\t\tName: \"ip\",\n\t\t\tArgs: []string{\n\t\t\t\t\"netns\", \"exec\", n.Namespace, \"arping\",\n\t\t\t\t\"-U\", \"-I\", iface, \"-c\", \"3\", addr,\n\t\t\t},\n\t\t\tTimeout: 6 * time.Second,\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\n\t\t_, _ = commander.Exec(&commander.Opt{\n\t\t\tName: \"ip\",\n\t\t\tArgs: []string{\n\t\t\t\t\"netns\", \"exec\", n.Namespace, \"arping\",\n\t\t\t\t\"-I\", iface, \"-c\", \"3\",\n\t\t\t\tn.ExternalGatewayAddr.String(),\n\t\t\t},\n\t\t\tTimeout: 6 * time.Second,\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t}\n\n\tif n.NetworkMode6 == node.Static {\n\t\taddr := strings.Split(n.ExternalAddrCidr6, \"/\")[0]\n\t\tiface := n.SpaceExternalIfaceMod6\n\t\tif iface == \"\" {\n\t\t\tiface = n.SpaceExternalIface\n\t\t}\n\n\t\t_, _ = commander.Exec(&commander.Opt{\n\t\t\tName: \"ip\",\n\t\t\tArgs: []string{\n\t\t\t\t\"netns\", \"exec\", n.Namespace, \"ndisc6\",\n\t\t\t\t\"-r\", \"3\", addr, iface,\n\t\t\t},\n\t\t\tTimeout: 6 * time.Second,\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\n\t\tif n.ExternalGatewayAddr6 != nil {\n\t\t\t_, _ = commander.Exec(&commander.Opt{\n\t\t\t\tName: \"ip\",\n\t\t\t\tArgs: []string{\n\t\t\t\t\t\"netns\", \"exec\", n.Namespace, \"ping6\",\n\t\t\t\t\t\"-c\", \"3\", \"-i\", \"0.5\", \"-w\", \"6\", \"-I\", iface,\n\t\t\t\t\tn.ExternalGatewayAddr6.String(),\n\t\t\t\t},\n\t\t\t\tTimeout: 8 * time.Second,\n\t\t\t\tPipeOut: true,\n\t\t\t\tPipeErr: true,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) ipInit6Alt(db *database.Database) (err error) {\n\tif n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud &&\n\t\tn.PublicAddress6 != \"\" && !settings.Hypervisor.NoIpv6PingInit {\n\n\t\taddrs, e := utils.DnsLookup(\n\t\t\tsettings.Hypervisor.DnsServerPrimary6,\n\t\t\t\"app6.pritunl.com\",\n\t\t)\n\t\tif e != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": n.Virt.Id.Hex(),\n\t\t\t\t\"namespace\":   n.Namespace,\n\t\t\t\t\"address6\":    n.PublicAddress6,\n\t\t\t\t\"error\":       e,\n\t\t\t}).Warn(\"netconf: Failed to initialize IPv6 network DNS lookup\")\n\t\t} else if addrs != nil && len(addrs) > 0 {\n\t\t\toutput, e := utils.ExecCombinedOutput(\n\t\t\t\t\"\",\n\t\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\t\"ping6\", \"-c\", \"3\", \"-i\", \"0.5\", \"-w\", \"6\", addrs[0],\n\t\t\t)\n\t\t\tif e != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": n.Virt.Id.Hex(),\n\t\t\t\t\t\"namespace\":   n.Namespace,\n\t\t\t\t\t\"address6\":    n.PublicAddress6,\n\t\t\t\t\t\"output\":      output,\n\t\t\t\t}).Warn(\"netconf: Failed to initialize IPv6 network ping\")\n\t\t\t}\n\t\t} else {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": n.Virt.Id.Hex(),\n\t\t\t\t\"namespace\":   n.Namespace,\n\t\t\t\t\"address6\":    n.PublicAddress6,\n\t\t\t\t\"lookup\":      addrs,\n\t\t\t}).Warn(\"netconf: Failed to initialize IPv6 network DNS lookup\")\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) Ip(db *database.Database) (err error) {\n\terr = n.ipExternal(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.ipHost(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.ipNodePort(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.ipDetect(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.ipHostIptables(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.ipDatabase(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.ipArp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.ipInit6(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/netconf.go",
    "content": "package netconf\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar (\n\tlock = utils.NewMultiTimeoutLock(2 * time.Minute)\n)\n\ntype NetConf struct {\n\tVirt            *vm.VirtualMachine\n\tVxlan           bool\n\tVlanId          int\n\tNetworkMode     string\n\tNetworkMode6    string\n\tHostNetwork     bool\n\tHostNat         bool\n\tHostSubnet      string\n\tNodePortNetwork bool\n\tNodePortSubnet  string\n\tCloudSubnets    set.Set\n\tNamespace       string\n\tVmAdapter       *vm.NetworkAdapter\n\n\tPublicAddress  string\n\tPublicAddress6 string\n\n\tCloudVlan           int\n\tCloudAddress        string\n\tCloudAddressSubnet  string\n\tCloudRouterAddress  string\n\tCloudAddress6       string\n\tCloudAddressSubnet6 string\n\tCloudRouterAddress6 string\n\tCloudMetal          bool\n\n\tSpaceBridgeIface       string\n\tVirtIface              string\n\tSpaceExternalIface     string\n\tSpaceExternalIfaceMod  string\n\tSpaceExternalIfaceMod6 string\n\tSpaceInternalIface     string\n\tSpaceHostIface         string\n\tSpaceNodePortIface     string\n\tSpaceCloudIface        string\n\tSpaceCloudVirtIface    string\n\tSpaceImdsIface         string\n\n\tBridgeInternalIface string\n\n\tSystemExternalIface string\n\tSystemInternalIface string\n\tSystemHostIface     string\n\tSystemNodePortIface string\n\n\tPhysicalExternalIface       string\n\tPhysicalExternalIfaceBridge bool\n\tPhysicalInternalIface       string\n\tPhysicalInternalIfaceBridge bool\n\tPhysicalHostIface           string\n\tPhysicalNodePortIface       string\n\n\tSpaceExternalIfaceMtu  string\n\tSystemExternalIfaceMtu string\n\n\tSpaceInternalIfaceMtu  string\n\tBridgeInternalIfaceMtu string\n\tSystemInternalIfaceMtu string\n\n\tSpaceHostIfaceMtu  string\n\tSystemHostIfaceMtu string\n\tImdsIfaceMtu       string\n\n\tSpaceNodePortIfaceMtu  string\n\tSystemNodePortIfaceMtu string\n\n\tVirtIfaceMtu string\n\n\tInternalAddr            net.IP\n\tInternalGatewayAddr     net.IP\n\tInternalGatewayAddrCidr string\n\tInternalAddr6           net.IP\n\tInternalGatewayAddr6    net.IP\n\n\tExternalVlan         int\n\tExternalAddrCidr     string\n\tExternalGatewayAddr  net.IP\n\tExternalVlan6        int\n\tExternalAddrCidr6    string\n\tExternalGatewayAddr6 net.IP\n\n\tHostAddr        net.IP\n\tHostAddrCidr    string\n\tHostGatewayAddr net.IP\n\n\tNodePortAddr     net.IP\n\tNodePortAddrCidr string\n\n\tExternalMacAddr string\n\tInternalMacAddr string\n\tHostMacAddr     string\n\tNodePortMacAddr string\n}\n\nfunc (n *NetConf) Init(db *database.Database) (err error) {\n\terr = n.Validate()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Iface1(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Address(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Iface2(db, false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Clear(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Base(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Oracle(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.External(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Internal(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Host(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.NodePort(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Space(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Vlan(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Bridge(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Imds(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Ip(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) Clean(db *database.Database) (err error) {\n\terr = n.Iface1(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.Iface2(db, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.ClearAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/nodeport.go",
    "content": "package netconf\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc (n *NetConf) nodePortNet(db *database.Database) (err error) {\n\tif n.NodePortNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"add\", n.SystemNodePortIface,\n\t\t\t\"type\", \"veth\",\n\t\t\t\"peer\", \"name\", n.SpaceNodePortIface,\n\t\t\t\"addr\", n.NodePortMacAddr,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"tc\", \"qdisc\", \"replace\", \"dev\", n.SystemNodePortIface,\n\t\t\t\"root\", \"fq_codel\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) nodePortMtu(db *database.Database) (err error) {\n\tif n.NodePortNetwork && n.SystemNodePortIfaceMtu != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SystemNodePortIface,\n\t\t\t\"mtu\", n.SystemNodePortIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tif n.NodePortNetwork && n.SpaceNodePortIfaceMtu != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceNodePortIface,\n\t\t\t\"mtu\", n.SpaceNodePortIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) nodePortUp(db *database.Database) (err error) {\n\tif n.NodePortNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SystemNodePortIface, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) nodePortMaster(db *database.Database) (err error) {\n\tif n.NodePortNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", n.SystemNodePortIface,\n\t\t\t\"master\", n.PhysicalNodePortIface,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) nodePortSpace(db *database.Database) (err error) {\n\tif n.NodePortNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"File exists\"},\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceNodePortIface,\n\t\t\t\"netns\", n.Namespace,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"tc\", \"qdisc\", \"replace\", \"dev\", n.SpaceNodePortIface,\n\t\t\t\"root\", \"fq_codel\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) nodePortSpaceUp(db *database.Database) (err error) {\n\tif n.NodePortNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceNodePortIface, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) NodePort(db *database.Database) (err error) {\n\tdelay := time.Duration(settings.Hypervisor.ActionRate) * time.Second\n\tlockId := lock.Lock(\"nodeport\")\n\tdefer lock.DelayUnlock(\"nodeport\", lockId, delay)\n\n\terr = n.nodePortNet(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.nodePortMtu(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.nodePortUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.nodePortMaster(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.nodePortSpace(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.nodePortSpaceUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/oracle.go",
    "content": "package netconf\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/oracle\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc (n *NetConf) oracleInitVnic(db *database.Database) (err error) {\n\tpv, err := oracle.NewProvider(node.Self.GetOracleAuthProvider())\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvar vnic *oracle.Vnic\n\n\tfound := false\n\tif n.Virt.CloudVnic != \"\" {\n\t\tvnic, err = oracle.GetVnic(pv, n.Virt.CloudVnic)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*errortypes.NotFoundError); ok {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"vnic_id\": n.Virt.CloudVnic,\n\t\t\t\t\t\"error\":   err,\n\t\t\t\t}).Warn(\"netconf: Cloud vnic not found, creating new vnic\")\n\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif vnic == nil {\n\t\t\tfound = false\n\t\t} else if vnic.SubnetId != n.Virt.CloudSubnet {\n\t\t\terr = oracle.RemoveVnic(pv, n.Virt.CloudVnicAttach)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvnic = nil\n\t\t} else if !n.CloudSubnets.Contains(vnic.SubnetId) {\n\t\t\terr = oracle.RemoveVnic(pv, n.Virt.CloudVnicAttach)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvnic = nil\n\t\t} else {\n\t\t\tfound = true\n\t\t}\n\t}\n\n\tif !n.CloudSubnets.Contains(n.Virt.CloudSubnet) {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"netconf: Invalid cloud subnet\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif !found {\n\t\tvnicId, vnicAttachId, e := oracle.CreateVnic(\n\t\t\tpv, n.Virt.Id.Hex(), n.Virt.CloudSubnet, !n.Virt.NoPublicAddress,\n\t\t\t!n.Virt.NoPublicAddress6)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tn.Virt.CloudVnic = vnicId\n\t\tn.Virt.CloudVnicAttach = vnicAttachId\n\t\terr = n.Virt.CommitCloudVnic(db)\n\t\tif err != nil {\n\t\t\t_ = oracle.RemoveVnic(pv, vnicAttachId)\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) oracleConfVnic(db *database.Database) (err error) {\n\tmdata, e := oracle.GetOciMetadata()\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tn.CloudMetal = mdata.IsBareMetal()\n\n\tif n.CloudMetal {\n\t\terr = n.oracleConfVnicMetal(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = n.oracleConfVnicVirt(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) oracleConfVnicMetal(db *database.Database) (err error) {\n\tfound := false\n\tnicIndex := 0\n\tmacAddr := \"\"\n\tphysicalMacAddr := \"\"\n\n\tpv, err := oracle.NewProvider(node.Self.GetOracleAuthProvider())\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor i := 0; i < 120; i++ {\n\t\ttime.Sleep(2 * time.Second)\n\n\t\tmdata, e := oracle.GetOciMetadata()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, vnic := range mdata.Vnics {\n\t\t\tif vnic.Id == n.Virt.CloudVnic {\n\t\t\t\tn.Virt.CloudPrivateIp = vnic.PrivateIp\n\t\t\t\tn.CloudVlan = vnic.VlanTag\n\t\t\t\tn.CloudAddress = vnic.PrivateIp\n\t\t\t\tn.CloudAddressSubnet = vnic.SubnetCidrBlock\n\t\t\t\tn.CloudRouterAddress = vnic.VirtualRouterIp\n\n\t\t\t\tif len(vnic.Ipv6Addresses) > 0 {\n\t\t\t\t\tn.CloudAddress6 = vnic.Ipv6Addresses[0]\n\t\t\t\t\tn.CloudAddressSubnet6 = vnic.Ipv6SubnetCidrBlock\n\t\t\t\t\tn.CloudRouterAddress6 = vnic.Ipv6VirtualRouterIp\n\t\t\t\t}\n\n\t\t\t\tnicIndex = vnic.NicIndex\n\t\t\t\tmacAddr = strings.ToLower(vnic.MacAddr)\n\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif found {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"netconf: Failed to find vnic\"),\n\t\t}\n\t\treturn\n\t}\n\n\tmdata, err := oracle.GetOciMetadata()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfound = false\n\tfor _, vnic := range mdata.Vnics {\n\t\tif vnic.NicIndex == nicIndex && vnic.VlanTag == 0 {\n\t\t\tphysicalMacAddr = strings.ToLower(vnic.MacAddr)\n\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"netconf: Failed to find physical nic\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvnic, err := oracle.GetVnic(pv, n.Virt.CloudVnic)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tn.Virt.CloudPublicIp = vnic.PublicIp\n\tn.Virt.CloudPublicIp6 = vnic.PublicIp6\n\n\terr = n.Virt.CommitCloudIps(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"netconf: Failed get network interfaces\"),\n\t\t}\n\t\treturn\n\t}\n\n\tphysicalIface := \"\"\n\tfor _, iface := range ifaces {\n\t\tif strings.ToLower(iface.HardwareAddr.String()) == physicalMacAddr {\n\t\t\tphysicalIface = iface.Name\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif physicalIface == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"netconf: Failed to find cloud physical interface\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"link\",\n\t\t\"add\", \"link\", physicalIface,\n\t\t\"name\", n.SpaceCloudVirtIface,\n\t\t\"address\", macAddr,\n\t\t\"type\", \"macvlan\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\"File exists\"},\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.SpaceCloudVirtIface,\n\t\t\"netns\", n.Namespace,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\"File exists\"},\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"add\", \"link\", n.SpaceCloudVirtIface,\n\t\t\"name\", n.SpaceCloudIface,\n\t\t\"type\", \"vlan\",\n\t\t\"id\", strconv.Itoa(n.CloudVlan),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) oracleConfVnicVirt(db *database.Database) (err error) {\n\tfound := false\n\tcloudMacAddr := \"\"\n\n\tpv, err := oracle.NewProvider(node.Self.GetOracleAuthProvider())\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor i := 0; i < 120; i++ {\n\t\ttime.Sleep(2 * time.Second)\n\n\t\tmdata, e := oracle.GetOciMetadata()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, vnic := range mdata.Vnics {\n\t\t\tif vnic.Id == n.Virt.CloudVnic {\n\t\t\t\tn.Virt.CloudPrivateIp = vnic.PrivateIp\n\t\t\t\tn.CloudAddress = vnic.PrivateIp\n\t\t\t\tn.CloudAddressSubnet = vnic.SubnetCidrBlock\n\t\t\t\tn.CloudRouterAddress = vnic.VirtualRouterIp\n\n\t\t\t\tif len(vnic.Ipv6Addresses) > 0 {\n\t\t\t\t\tn.CloudAddress6 = vnic.Ipv6Addresses[0]\n\t\t\t\t\tn.CloudAddressSubnet6 = vnic.Ipv6SubnetCidrBlock\n\t\t\t\t\tn.CloudRouterAddress6 = vnic.Ipv6VirtualRouterIp\n\t\t\t\t}\n\n\t\t\t\tcloudMacAddr = strings.ToLower(vnic.MacAddr)\n\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif found {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"netconf: Failed to find vnic\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvnic, err := oracle.GetVnic(pv, n.Virt.CloudVnic)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tn.Virt.CloudPublicIp = vnic.PublicIp\n\tn.Virt.CloudPublicIp6 = vnic.PublicIp6\n\n\terr = n.Virt.CommitCloudIps(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"netconf: Failed get network interfaces\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcloudIface := \"\"\n\tfor _, iface := range ifaces {\n\t\tif strings.ToLower(iface.HardwareAddr.String()) == cloudMacAddr {\n\t\t\tcloudIface = iface.Name\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif cloudIface == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"netconf: Failed to find cloud interface\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", cloudIface, \"down\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif cloudIface != n.SpaceCloudIface {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", cloudIface,\n\t\t\t\"name\", n.SpaceCloudIface,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\"File exists\"},\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.SpaceCloudIface,\n\t\t\"netns\", n.Namespace,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) oracleMtu(db *database.Database) (err error) {\n\tif n.CloudMetal {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceCloudVirtIface,\n\t\t\t\"mtu\", \"9000\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.SpaceCloudIface,\n\t\t\"mtu\", \"9000\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) oracleIp(db *database.Database) (err error) {\n\tsubnetSplit := strings.Split(n.CloudAddressSubnet, \"/\")\n\tif len(subnetSplit) != 2 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"netconf: Failed to get cloud cidr %s\",\n\t\t\t\tn.CloudAddressSubnet),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\"File exists\"},\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"addr\",\n\t\t\"add\", n.CloudAddress+\"/\"+subnetSplit[1],\n\t\t\"dev\", n.SpaceCloudIface,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif n.CloudAddress6 != \"\" {\n\t\tsubnetSplit6 := strings.Split(n.CloudAddressSubnet6, \"/\")\n\t\tif len(subnetSplit6) != 2 {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"netconf: Failed to get cloud cidr6 %s\",\n\t\t\t\t\tn.CloudAddressSubnet6),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"File exists\"},\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"addr\",\n\t\t\t\"add\", n.CloudAddress6+\"/\"+subnetSplit6[1],\n\t\t\t\"dev\", n.SpaceCloudIface,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) oracleUp(db *database.Database) (err error) {\n\tif n.CloudMetal {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.SpaceCloudVirtIface, \"up\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.SpaceCloudIface, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) oracleRoute(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\"File exists\"},\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"route\",\n\t\t\"add\", \"default\",\n\t\t\"via\", n.CloudRouterAddress,\n\t\t\"dev\", n.SpaceCloudIface,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif n.CloudAddress6 != \"\" && n.CloudRouterAddress6 != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"File exists\"},\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"-6\", \"route\",\n\t\t\t\"add\", \"default\",\n\t\t\t\"via\", n.CloudRouterAddress6,\n\t\t\t\"dev\", n.SpaceCloudIface,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) Oracle(db *database.Database) (err error) {\n\tif n.NetworkMode != node.Cloud || n.Virt.CloudSubnet == \"\" {\n\t\treturn\n\t}\n\n\tdelay := time.Duration(settings.Hypervisor.ActionRate) * time.Second\n\tlockId := lock.Lock(\"oracle\")\n\tdefer lock.DelayUnlock(\"oracle\", lockId, delay)\n\n\terr = n.oracleInitVnic(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.oracleConfVnic(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.oracleMtu(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.oracleIp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.oracleUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.oracleRoute(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/space.go",
    "content": "package netconf\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/iptables\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc (n *NetConf) spaceSysctl(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\", \"net.ipv4.conf.all.accept_redirects=0\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\", \"net.ipv4.conf.default.accept_redirects=0\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\", \"net.ipv4.conf.all.rp_filter=1\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\", \"net.ipv4.conf.default.rp_filter=1\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif n.HostNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"sysctl\", \"-w\",\n\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.disable_ipv6=1\",\n\t\t\t\tn.SpaceHostIface),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif n.NodePortNetwork {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"sysctl\", \"-w\",\n\t\t\tfmt.Sprintf(\"net.ipv6.conf.%s.disable_ipv6=1\",\n\t\t\t\tn.SpaceNodePortIface),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) spaceForward(db *database.Database) (err error) {\n\tif (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) ||\n\t\t(n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud) {\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"already exists\"},\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ipset\",\n\t\t\t\"create\", \"prx_inst6\", \"hash:net\",\n\t\t\t\"family\", \"inet6\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"already added\"},\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ipset\",\n\t\t\t\"add\", \"prx_inst6\", \"fe80::/64\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t[]string{\"already added\"},\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ipset\",\n\t\t\t\"add\", \"prx_inst6\", n.InternalAddr6.String()+\"/128\",\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tiptables.Lock()\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"iptables\",\n\t\t\t\"-I\", \"FORWARD\", \"1\",\n\t\t\t\"!\", \"-d\", n.InternalAddr.String()+\"/32\",\n\t\t\t\"-i\", n.SpaceExternalIface+\"+\",\n\t\t\t\"-m\", \"comment\",\n\t\t\t\"--comment\", \"pritunl_cloud_base\",\n\t\t\t\"-j\", \"DROP\",\n\t\t)\n\t\tiptables.Unlock()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tiptables.Lock()\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip6tables\",\n\t\t\t\"-I\", \"FORWARD\", \"1\",\n\t\t\t\"-m\", \"set\",\n\t\t\t\"!\", \"--match-set\", \"prx_inst6\", \"dst\",\n\t\t\t\"-i\", n.SpaceExternalIface+\"+\",\n\t\t\t\"-m\", \"comment\",\n\t\t\t\"--comment\", \"pritunl_cloud_base\",\n\t\t\t\"-j\", \"DROP\",\n\t\t)\n\t\tiptables.Unlock()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif n.HostNetwork {\n\t\tiptables.Lock()\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"iptables\",\n\t\t\t\"-I\", \"FORWARD\", \"1\",\n\t\t\t\"!\", \"-d\", n.InternalAddr.String()+\"/32\",\n\t\t\t\"-i\", n.SpaceHostIface,\n\t\t\t\"-m\", \"comment\",\n\t\t\t\"--comment\", \"pritunl_cloud_base\",\n\t\t\t\"-j\", \"DROP\",\n\t\t)\n\t\tiptables.Unlock()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif n.NodePortNetwork {\n\t\tiptables.Lock()\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"iptables\",\n\t\t\t\"-I\", \"FORWARD\", \"1\",\n\t\t\t\"!\", \"-d\", n.InternalAddr.String()+\"/32\",\n\t\t\t\"-i\", n.SpaceNodePortIface,\n\t\t\t\"-m\", \"comment\",\n\t\t\t\"--comment\", \"pritunl_cloud_base\",\n\t\t\t\"-j\", \"DROP\",\n\t\t)\n\t\tiptables.Unlock()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\", \"net.ipv4.ip_forward=1\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"sysctl\", \"-w\", \"net.ipv6.conf.all.forwarding=1\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) spaceVirt(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"Cannot find device\",\n\t\t\t\"File exists\",\n\t\t},\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.VirtIface,\n\t\t\"netns\", n.Namespace,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) spaceLoopback(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", \"lo\", \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) spaceMtu(db *database.Database) (err error) {\n\tif n.VirtIfaceMtu != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.VirtIface,\n\t\t\t\"mtu\", n.VirtIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) spaceUp(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.VirtIface, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) Space(db *database.Database) (err error) {\n\terr = n.spaceSysctl(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.spaceForward(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.spaceVirt(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.spaceLoopback(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.spaceMtu(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.spaceUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/utils.go",
    "content": "package netconf\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/oracle\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc New(virt *vm.VirtualMachine) *NetConf {\n\treturn &NetConf{\n\t\tVirt: virt,\n\t}\n}\n\nfunc Destroy(db *database.Database, virt *vm.VirtualMachine) (err error) {\n\tif virt.CloudVnicAttach == \"\" {\n\t\treturn\n\t}\n\n\tpv, err := oracle.NewProvider(node.Self.GetOracleAuthProvider())\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = oracle.RemoveVnic(pv, virt.CloudVnicAttach)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/validate.go",
    "content": "package netconf\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc (n *NetConf) Validate() (err error) {\n\tnamespace := vm.GetNamespace(n.Virt.Id, 0)\n\n\tif len(n.Virt.NetworkAdapters) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"netconf: Missing network interfaces\"),\n\t\t}\n\t\treturn\n\t}\n\n\tifaceNames := set.NewSet()\n\n\tfor i := range n.Virt.NetworkAdapters {\n\t\tifaceNames.Add(vm.GetIface(n.Virt.Id, i))\n\t}\n\n\tfor i := range n.Virt.NetworkAdapters {\n\t\tifaceNames.Add(vm.GetIface(n.Virt.Id, i))\n\t}\n\n\tfor i := 0; i < 100; i++ {\n\t\tifaces, e := net.Interfaces()\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"qemu: Failed to get network interfaces\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfor _, iface := range ifaces {\n\t\t\tif ifaceNames.Contains(iface.Name) {\n\t\t\t\tifaceNames.Remove(iface.Name)\n\t\t\t}\n\t\t}\n\n\t\tif ifaceNames.Len() == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tifaces2, e := iproute.IfaceGetAll(namespace)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"qemu: Failed to get network interfaces\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfor _, iface := range ifaces2 {\n\t\t\tif ifaceNames.Contains(iface.Name) {\n\t\t\t\tifaceNames.Remove(iface.Name)\n\t\t\t}\n\t\t}\n\n\t\tif ifaceNames.Len() == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(250 * time.Millisecond)\n\t}\n\n\tif ifaceNames.Len() != 0 {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.New(\"qemu: Failed to find network interfaces\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "netconf/vlan.go",
    "content": "package netconf\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc (n *NetConf) vlanNet(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\"File exists\"},\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"add\", \"link\", n.SpaceInternalIface,\n\t\t\"name\", n.BridgeInternalIface,\n\t\t\"type\", \"vlan\",\n\t\t\"id\", strconv.Itoa(n.VlanId),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) vlanMtu(db *database.Database) (err error) {\n\tif n.SpaceInternalIfaceMtu != \"\" {\n\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\tnil,\n\t\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\t\"ip\", \"link\",\n\t\t\t\"set\", \"dev\", n.BridgeInternalIface,\n\t\t\t\"mtu\", n.SpaceInternalIfaceMtu,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) vlanUp(db *database.Database) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", n.Namespace,\n\t\t\"ip\", \"link\",\n\t\t\"set\", \"dev\", n.BridgeInternalIface, \"up\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NetConf) Vlan(db *database.Database) (err error) {\n\terr = n.vlanNet(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.vlanMtu(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = n.vlanUp(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "node/block.go",
    "content": "package node\n\nimport \"github.com/pritunl/mongo-go-driver/v2/bson\"\n\ntype BlockAttachment struct {\n\tInterface string        `bson:\"interface\" json:\"interface\"`\n\tBlock     bson.ObjectID `bson:\"block\" json:\"block\"`\n}\n"
  },
  {
    "path": "node/certificate.go",
    "content": "package node\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"math/big\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc selfCert(parent *x509.Certificate, parentKey *ecdsa.PrivateKey) (\n\tcert *x509.Certificate, certByt []byte, certKey *ecdsa.PrivateKey,\n\terr error) {\n\n\tcertKey, err = ecdsa.GenerateKey(\n\t\telliptic.P384(),\n\t\trand.Reader,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"certificate: Failed to generate private key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tserialLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserial, err := rand.Int(rand.Reader, serialLimit)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(\n\t\t\t\terr,\n\t\t\t\t\"certificate: Failed to generate certificate serial\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tcertTempl := &x509.Certificate{\n\t\tSerialNumber: serial,\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"Pritunl Cloud\"},\n\t\t},\n\t\tNotBefore: time.Now().Add(-24 * time.Hour),\n\t\tNotAfter:  time.Now().Add(26280 * time.Hour),\n\t\tKeyUsage: x509.KeyUsageKeyEncipherment |\n\t\t\tx509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t\tSignatureAlgorithm:    x509.ECDSAWithSHA256,\n\t}\n\n\tif parent == nil {\n\t\tparent = certTempl\n\t\tparentKey = certKey\n\t}\n\n\tcertByt, err = x509.CreateCertificate(rand.Reader, certTempl, parent,\n\t\tcertKey.Public(), parentKey)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"certificate: Failed to create certificate\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcert, err = x509.ParseCertificate(certByt)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"certificate: Failed to parse certificate\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc SelfCert() (certPem, keyPem []byte, err error) {\n\tif Self.SelfCertificate != \"\" && Self.SelfCertificateKey != \"\" {\n\t\tcertPem = []byte(Self.SelfCertificate)\n\t\tkeyPem = []byte(Self.SelfCertificateKey)\n\t\treturn\n\t}\n\n\tlogrus.Info(\"certificate: Generating self signed certificate\")\n\n\tcaCert, _, caKey, err := selfCert(nil, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, certByt, certKey, err := selfCert(caCert, caKey)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcertKeyByte, err := x509.MarshalECPrivateKey(certKey)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"certificate: Failed to parse private key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcertKeyBlock := &pem.Block{\n\t\tType:  \"EC PRIVATE KEY\",\n\t\tBytes: certKeyByte,\n\t}\n\tkeyPem = pem.EncodeToMemory(certKeyBlock)\n\n\tcertBlock := &pem.Block{\n\t\tType:  \"CERTIFICATE\",\n\t\tBytes: certByt,\n\t}\n\tcertPem = pem.EncodeToMemory(certBlock)\n\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tSelf.SelfCertificate = string(certPem)\n\tSelf.SelfCertificateKey = string(keyPem)\n\terr = Self.CommitFields(db, set.NewSet(\n\t\t\"self_certificate\", \"self_certificate_key\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "node/constants.go",
    "content": "package node\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\nconst (\n\tAdmin      = \"admin\"\n\tUser       = \"user\"\n\tBalancer   = \"balancer\"\n\tHypervisor = \"hypervisor\"\n\n\tQemu = \"qemu\"\n\tKvm  = \"kvm\"\n\n\t// std\n\tStd = \"std\"\n\t// vmware\n\tVmware = \"vmware\"\n\t// virtio-vga\n\tVirtio = \"virtio\"\n\t// virtio-gpu-pci\n\tVirtioPci = \"virtio_pci\"\n\t// virtio-vga-gl\n\tVirtioVgaGl = \"virtio_vga_gl\"\n\t// virtio-vga-gl,venus=true\n\tVirtioVgaGlVulkan = \"virtio_vga_gl_vulkan\"\n\t// virtio-gpu-gl\n\tVirtioGl = \"virtio_gl\"\n\t// virtio-gpu-gl,venus=true\n\tVirtioGlVulkan = \"virtio_gl_vulkan\"\n\t// virtio-gpu-gl-pci\n\tVirtioPciGl = \"virtio_pci_gl\"\n\t// virtio-gpu-gl-pci,venus=true\n\tVirtioPciGlVulkan = \"virtio_pci_gl_vulkan\"\n\t// virtio-vga prime=1\n\tVirtioPrime = \"virtio_prime\"\n\t// virtio-gpu-pci prime=1\n\tVirtioPciPrime = \"virtio_pci_prime\"\n\t// virtio-vga-gl prime=1\n\tVirtioVgaGlPrime = \"virtio_vga_gl_prime\"\n\t// virtio-vga-gl,venus=true prime=1\n\tVirtioVgaGlVulkanPrime = \"virtio_vga_gl_vulkan_prime\"\n\t// virtio-gpu-gl prime=1\n\tVirtioGlPrime = \"virtio_gl_prime\"\n\t// virtio-gpu-gl,venus=true prime=1\n\tVirtioGlVulkanPrime = \"virtio_gl_vulkan_prime\"\n\t// virtio-gpu-gl-pci prime=1\n\tVirtioPciGlPrime = \"virtio_pci_gl_prime\"\n\t// virtio-gpu-gl-pci,venus=true prime=1\n\tVirtioPciGlVulkanPrime = \"virtio_pci_gl_vulkan_prime\"\n\n\tSdl = \"sdl\"\n\tGtk = \"gtk\"\n\n\tDisabled  = \"disabled\"\n\tDhcp      = \"dhcp\"\n\tDhcpSlaac = \"dhcp_slaac\"\n\tSlaac     = \"slaac\"\n\tStatic    = \"static\"\n\tInternal  = \"internal\"\n\tCloud     = \"cloud\"\n\n\tRestart = \"restart\"\n\n\tHostPath = \"host_path\"\n)\n\nvar (\n\tVgaModes = set.NewSet(\n\t\tStd,\n\t\tVmware,\n\t\tVirtio,\n\t\tVirtioPci,\n\t\tVirtioVgaGl,\n\t\tVirtioVgaGlVulkan,\n\t\tVirtioGl,\n\t\tVirtioGlVulkan,\n\t\tVirtioPciGl,\n\t\tVirtioPciGlVulkan,\n\t\tVirtioPrime,\n\t\tVirtioPciPrime,\n\t\tVirtioVgaGlPrime,\n\t\tVirtioVgaGlVulkanPrime,\n\t\tVirtioGlPrime,\n\t\tVirtioGlVulkanPrime,\n\t\tVirtioPciGlPrime,\n\t\tVirtioPciGlVulkanPrime,\n\t)\n\tVgaRenderModes = set.NewSet(\n\t\tVirtioPci,\n\t\tVirtioVgaGl,\n\t\tVirtioVgaGlVulkan,\n\t\tVirtioGl,\n\t\tVirtioGlVulkan,\n\t\tVirtioPciGl,\n\t\tVirtioPciGlVulkan,\n\t\tVirtioPciPrime,\n\t\tVirtioVgaGlPrime,\n\t\tVirtioVgaGlVulkanPrime,\n\t\tVirtioGlPrime,\n\t\tVirtioGlVulkanPrime,\n\t\tVirtioPciGlPrime,\n\t\tVirtioPciGlVulkanPrime,\n\t)\n)\n"
  },
  {
    "path": "node/interfaces.go",
    "content": "package node\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/ip\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tnetLock           = sync.Mutex{}\n\tnetIfaces         = []ip.Interface{}\n\tnetLastIfacesSync time.Time\n\tdefaultIface      = \"\"\n\tdefaultIfaceSync  time.Time\n)\n\nfunc ClearIfaceCache() {\n\tnetLastIfacesSync = time.Time{}\n\tnetIfaces = []ip.Interface{}\n\tdefaultIfaceSync = time.Time{}\n\tdefaultIface = \"\"\n}\n\nfunc GetInterfaces() (ifaces []ip.Interface, err error) {\n\tif time.Since(netLastIfacesSync) < 15*time.Second {\n\t\tifaces = netIfaces\n\t\treturn\n\t}\n\n\tifacesNew := []ip.Interface{}\n\tallIfaces, err := utils.GetInterfaces()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tifacesData, err := ip.GetIfacesCached(\"\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, iface := range allIfaces {\n\t\tif len(iface) == 14 || iface == \"lo\" ||\n\t\t\tstrings.Contains(iface, \"br\") ||\n\t\t\tiface == settings.Hypervisor.HostNetworkName ||\n\t\t\tiface == settings.Hypervisor.NodePortNetworkName ||\n\t\t\tiface == \"\" {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tifaceData := ifacesData[iface]\n\t\tif ifaceData != nil {\n\t\t\tifacesNew = append(ifacesNew, ip.Interface{\n\t\t\t\tName:    iface,\n\t\t\t\tAddress: ifaceData.GetAddress(),\n\t\t\t})\n\t\t} else {\n\t\t\tifacesNew = append(ifacesNew, ip.Interface{\n\t\t\t\tName: iface,\n\t\t\t})\n\t\t}\n\t}\n\n\tifaces = ifacesNew\n\tnetLastIfacesSync = time.Now()\n\tnetIfaces = ifacesNew\n\n\treturn\n}\n\nfunc getDefaultIface() (iface string, err error) {\n\tif time.Since(defaultIfaceSync) < 900*time.Second {\n\t\tiface = defaultIface\n\t\treturn\n\t}\n\n\toutput, err := utils.ExecCombinedOutput(\"\", \"route\", \"-n\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\toutputLines := strings.Split(output, \"\\n\")\n\tfor _, line := range outputLines {\n\t\tfields := strings.Fields(line)\n\t\tif len(fields) < 2 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif fields[0] == \"0.0.0.0\" {\n\t\t\tiface = strings.TrimSpace(fields[len(fields)-1])\n\t\t\t_ = strings.TrimSpace(fields[1])\n\t\t}\n\t}\n\n\tdefaultIface = iface\n\tdefaultIfaceSync = time.Now()\n\n\treturn\n}\n"
  },
  {
    "path": "node/node.go",
    "content": "package node\n\nimport (\n\t\"container/list\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"runtime/debug\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/go-webauthn/webauthn/webauthn\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/advisory\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/bridges\"\n\t\"github.com/pritunl/pritunl-cloud/cloud\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/drive\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/ip\"\n\t\"github.com/pritunl/pritunl-cloud/iso\"\n\t\"github.com/pritunl/pritunl-cloud/lvm\"\n\t\"github.com/pritunl/pritunl-cloud/pci\"\n\t\"github.com/pritunl/pritunl-cloud/render\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/telemetry\"\n\t\"github.com/pritunl/pritunl-cloud/usb\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tSelf *Node\n)\n\ntype Node struct {\n\tId                      bson.ObjectID       `bson:\"_id\" json:\"id\"`\n\tDatacenter              bson.ObjectID       `bson:\"datacenter,omitempty\" json:\"datacenter\"`\n\tZone                    bson.ObjectID       `bson:\"zone,omitempty\" json:\"zone\"`\n\tName                    string              `bson:\"name\" json:\"name\"`\n\tComment                 string              `bson:\"comment\" json:\"comment\"`\n\tTypes                   []string            `bson:\"types\" json:\"types\"`\n\tTimestamp               time.Time           `bson:\"timestamp\" json:\"timestamp\"`\n\tPort                    int                 `bson:\"port\" json:\"port\"`\n\tHttp2                   bool                `bson:\"http2\" json:\"http2\"`\n\tNoRedirectServer        bool                `bson:\"no_redirect_server\" json:\"no_redirect_server\"`\n\tProtocol                string              `bson:\"protocol\" json:\"protocol\"`\n\tHypervisor              string              `bson:\"hypervisor\" json:\"hypervisor\"`\n\tVga                     string              `bson:\"vga\" json:\"vga\"`\n\tVgaRender               string              `bson:\"vga_render\" json:\"vga_render\"`\n\tAvailableRenders        []string            `bson:\"available_renders\" json:\"available_renders\"`\n\tGui                     bool                `bson:\"gui\" json:\"gui\"`\n\tGuiUser                 string              `bson:\"gui_user\" json:\"gui_user\"`\n\tGuiMode                 string              `bson:\"gui_mode\" json:\"gui_mode\"`\n\tCertificates            []bson.ObjectID     `bson:\"certificates\" json:\"certificates\"`\n\tSelfCertificate         string              `bson:\"self_certificate_key\" json:\"-\"`\n\tSelfCertificateKey      string              `bson:\"self_certificate\" json:\"-\"`\n\tAdminDomain             string              `bson:\"admin_domain\" json:\"admin_domain\"`\n\tUserDomain              string              `bson:\"user_domain\" json:\"user_domain\"`\n\tWebauthnDomain          string              `bson:\"webauthn_domain\" json:\"webauthn_domain\"`\n\tRequestsMin             int64               `bson:\"requests_min\" json:\"requests_min\"`\n\tForwardedForHeader      string              `bson:\"forwarded_for_header\" json:\"forwarded_for_header\"`\n\tForwardedProtoHeader    string              `bson:\"forwarded_proto_header\" json:\"forwarded_proto_header\"`\n\tExternalInterfaces      []string            `bson:\"external_interfaces\" json:\"external_interfaces\"`\n\tExternalInterfaces6     []string            `bson:\"external_interfaces6\" json:\"external_interfaces6\"`\n\tInternalInterfaces      []string            `bson:\"internal_interfaces\" json:\"internal_interfaces\"`\n\tAvailableInterfaces     []ip.Interface      `bson:\"available_interfaces\" json:\"available_interfaces\"`\n\tAvailableBridges        []ip.Interface      `bson:\"available_bridges\" json:\"available_bridges\"`\n\tAvailableVpcs           []*cloud.Vpc        `bson:\"available_vpcs\" json:\"available_vpcs\"`\n\tCloudSubnets            []string            `bson:\"cloud_subnets\" json:\"cloud_subnets\"`\n\tDefaultInterface        string              `bson:\"default_interface\" json:\"default_interface\"`\n\tNetworkMode             string              `bson:\"network_mode\" json:\"network_mode\"`\n\tNetworkMode6            string              `bson:\"network_mode6\" json:\"network_mode6\"`\n\tBlocks                  []*BlockAttachment  `bson:\"blocks\" json:\"blocks\"`\n\tBlocks6                 []*BlockAttachment  `bson:\"blocks6\" json:\"blocks6\"`\n\tPools                   []bson.ObjectID     `bson:\"pools\" json:\"pools\"`\n\tShares                  []*Share            `bson:\"shares\" json:\"shares\"`\n\tAvailableDrives         []*drive.Device     `bson:\"available_drives\" json:\"available_drives\"`\n\tInstanceDrives          []*drive.Device     `bson:\"instance_drives\" json:\"instance_drives\"`\n\tNoHostNetwork           bool                `bson:\"no_host_network\" json:\"no_host_network\"`\n\tNoNodePortNetwork       bool                `bson:\"no_node_port_network\" json:\"no_node_port_network\"`\n\tHostNat                 bool                `bson:\"host_nat\" json:\"host_nat\"`\n\tDefaultNoPublicAddress  bool                `bson:\"default_no_public_address\" json:\"default_no_public_address\"`\n\tDefaultNoPublicAddress6 bool                `bson:\"default_no_public_address6\" json:\"default_no_public_address6\"`\n\tJumboFrames             bool                `bson:\"jumbo_frames\" json:\"jumbo_frames\"`\n\tJumboFramesInternal     bool                `bson:\"jumbo_frames_internal\" json:\"jumbo_frames_internal\"`\n\tIscsi                   bool                `bson:\"iscsi\" json:\"iscsi\"`\n\tLocalIsos               []*iso.Iso          `bson:\"local_isos\" json:\"local_isos\"`\n\tUsbPassthrough          bool                `bson:\"usb_passthrough\" json:\"usb_passthrough\"`\n\tUsbDevices              []*usb.Device       `bson:\"usb_devices\" json:\"usb_devices\"`\n\tPciPassthrough          bool                `bson:\"pci_passthrough\" json:\"pci_passthrough\"`\n\tPciDevices              []*pci.Device       `bson:\"pci_devices\" json:\"pci_devices\"`\n\tHugepages               bool                `bson:\"hugepages\" json:\"hugepages\"`\n\tHugepagesSize           int                 `bson:\"hugepages_size\" json:\"hugepages_size\"`\n\tFirewall                bool                `bson:\"firewall\" json:\"firewall\"`\n\tRoles                   []string            `bson:\"roles\" json:\"roles\"`\n\tMemory                  float64             `bson:\"memory\" json:\"memory\"`\n\tHugePagesUsed           float64             `bson:\"hugepages_used\" json:\"hugepages_used\"`\n\tLoad1                   float64             `bson:\"load1\" json:\"load1\"`\n\tLoad5                   float64             `bson:\"load5\" json:\"load5\"`\n\tLoad15                  float64             `bson:\"load15\" json:\"load15\"`\n\tCpuUnits                int                 `bson:\"cpu_units\" json:\"cpu_units\"`\n\tMemoryUnits             float64             `bson:\"memory_units\" json:\"memory_units\"`\n\tCpuUnitsRes             int                 `bson:\"cpu_units_res\" json:\"cpu_units_res\"`\n\tMemoryUnitsRes          float64             `bson:\"memory_units_res\" json:\"memory_units_res\"`\n\tUpdates                 []*telemetry.Update `bson:\"updates\" json:\"updates\"`\n\tPublicIps               []string            `bson:\"public_ips\" json:\"public_ips\"`\n\tPublicIps6              []string            `bson:\"public_ips6\" json:\"public_ips6\"`\n\tPrivateIps              map[string]string   `bson:\"private_ips\" json:\"private_ips\"`\n\tSoftwareVersion         string              `bson:\"software_version\" json:\"software_version\"`\n\tHostname                string              `bson:\"hostname\" json:\"hostname\"`\n\tVersion                 int                 `bson:\"version\" json:\"-\"`\n\tVirtPath                string              `bson:\"virt_path\" json:\"virt_path\"`\n\tCachePath               string              `bson:\"cache_path\" json:\"cache_path\"`\n\tTempPath                string              `bson:\"temp_path\" json:\"temp_path\"`\n\tOracleUser              string              `bson:\"oracle_user\" json:\"oracle_user\"`\n\tOracleTenancy           string              `bson:\"oracle_tenancy\" json:\"oracle_tenancy\"`\n\tOraclePrivateKey        string              `bson:\"oracle_private_key\" json:\"-\"`\n\tOraclePublicKey         string              `bson:\"oracle_public_key\" json:\"oracle_public_key\"`\n\tOperation               string              `bson:\"operation\" json:\"operation\"`\n\tcloudSubnetsNamed       []*CloudSubnet      `bson:\"-\" json:\"-\"`\n\treqCount                *list.List          `bson:\"-\" json:\"-\"`\n\tdcId                    bson.ObjectID       `bson:\"-\" json:\"-\"`\n\tdcZoneId                bson.ObjectID       `bson:\"-\" json:\"-\"`\n\tlock                    sync.Mutex          `bson:\"-\" json:\"-\"`\n}\n\ntype Completion struct {\n\tId    bson.ObjectID `bson:\"_id\" json:\"id\"`\n\tName  string        `bson:\"name\" json:\"name\"`\n\tZone  bson.ObjectID `bson:\"zone,omitempty\" json:\"zone\"`\n\tTypes []string      `bson:\"types\" json:\"types\"`\n}\n\nfunc (n *Completion) IsHypervisor() bool {\n\tfor _, typ := range n.Types {\n\t\tif typ == Hypervisor {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype Share struct {\n\tType  string   `bson:\"type\" json:\"type\"`\n\tPath  string   `bson:\"path\" json:\"path\"`\n\tRoles []string `bson:\"roles\" json:\"roles\"`\n}\n\nfunc (s *Share) MatchPath(pth string) bool {\n\tsharePath := utils.FilterPath(s.Path) + string(filepath.Separator)\n\tpth = utils.FilterPath(pth) + string(filepath.Separator)\n\n\tif sharePath == \"\" || pth == \"\" {\n\t\treturn false\n\t}\n\n\tif sharePath == pth {\n\t\treturn true\n\t}\n\n\tif !strings.HasPrefix(pth, sharePath) {\n\t\treturn false\n\t}\n\n\trelPath, err := filepath.Rel(sharePath, pth)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn !strings.HasPrefix(relPath, \"..\")\n}\n\ntype CloudSubnet struct {\n\tId   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\nfunc (n *Node) Copy() *Node {\n\tn.lock.Lock()\n\tdefer n.lock.Unlock()\n\n\tnde := &Node{\n\t\tId:                      n.Id,\n\t\tDatacenter:              n.Datacenter,\n\t\tZone:                    n.Zone,\n\t\tName:                    n.Name,\n\t\tComment:                 n.Comment,\n\t\tTypes:                   n.Types,\n\t\tTimestamp:               n.Timestamp,\n\t\tPort:                    n.Port,\n\t\tHttp2:                   n.Http2,\n\t\tNoRedirectServer:        n.NoRedirectServer,\n\t\tProtocol:                n.Protocol,\n\t\tHypervisor:              n.Hypervisor,\n\t\tVga:                     n.Vga,\n\t\tVgaRender:               n.VgaRender,\n\t\tGui:                     n.Gui,\n\t\tGuiUser:                 n.GuiUser,\n\t\tGuiMode:                 n.GuiMode,\n\t\tAvailableRenders:        n.AvailableRenders,\n\t\tCertificates:            n.Certificates,\n\t\tSelfCertificate:         n.SelfCertificate,\n\t\tSelfCertificateKey:      n.SelfCertificateKey,\n\t\tAdminDomain:             n.AdminDomain,\n\t\tUserDomain:              n.UserDomain,\n\t\tWebauthnDomain:          n.WebauthnDomain,\n\t\tRequestsMin:             n.RequestsMin,\n\t\tForwardedForHeader:      n.ForwardedForHeader,\n\t\tForwardedProtoHeader:    n.ForwardedProtoHeader,\n\t\tExternalInterfaces:      n.ExternalInterfaces,\n\t\tExternalInterfaces6:     n.ExternalInterfaces6,\n\t\tInternalInterfaces:      n.InternalInterfaces,\n\t\tAvailableInterfaces:     n.AvailableInterfaces,\n\t\tAvailableBridges:        n.AvailableBridges,\n\t\tAvailableVpcs:           n.AvailableVpcs,\n\t\tCloudSubnets:            n.CloudSubnets,\n\t\tDefaultInterface:        n.DefaultInterface,\n\t\tNetworkMode:             n.NetworkMode,\n\t\tNetworkMode6:            n.NetworkMode6,\n\t\tBlocks:                  n.Blocks,\n\t\tBlocks6:                 n.Blocks6,\n\t\tShares:                  n.Shares,\n\t\tPools:                   n.Pools,\n\t\tAvailableDrives:         n.AvailableDrives,\n\t\tInstanceDrives:          n.InstanceDrives,\n\t\tNoHostNetwork:           n.NoHostNetwork,\n\t\tNoNodePortNetwork:       n.NoNodePortNetwork,\n\t\tHostNat:                 n.HostNat,\n\t\tDefaultNoPublicAddress:  n.DefaultNoPublicAddress,\n\t\tDefaultNoPublicAddress6: n.DefaultNoPublicAddress6,\n\t\tJumboFrames:             n.JumboFrames,\n\t\tJumboFramesInternal:     n.JumboFramesInternal,\n\t\tIscsi:                   n.Iscsi,\n\t\tLocalIsos:               n.LocalIsos,\n\t\tUsbPassthrough:          n.UsbPassthrough,\n\t\tUsbDevices:              n.UsbDevices,\n\t\tPciPassthrough:          n.PciPassthrough,\n\t\tPciDevices:              n.PciDevices,\n\t\tHugepages:               n.Hugepages,\n\t\tHugepagesSize:           n.HugepagesSize,\n\t\tFirewall:                n.Firewall,\n\t\tRoles:                   n.Roles,\n\t\tMemory:                  n.Memory,\n\t\tHugePagesUsed:           n.HugePagesUsed,\n\t\tLoad1:                   n.Load1,\n\t\tLoad5:                   n.Load5,\n\t\tLoad15:                  n.Load15,\n\t\tCpuUnits:                n.CpuUnits,\n\t\tMemoryUnits:             n.MemoryUnits,\n\t\tCpuUnitsRes:             n.CpuUnitsRes,\n\t\tMemoryUnitsRes:          n.MemoryUnitsRes,\n\t\tUpdates:                 n.Updates,\n\t\tPublicIps:               n.PublicIps,\n\t\tPublicIps6:              n.PublicIps6,\n\t\tPrivateIps:              n.PrivateIps,\n\t\tSoftwareVersion:         n.SoftwareVersion,\n\t\tHostname:                n.Hostname,\n\t\tVersion:                 n.Version,\n\t\tVirtPath:                n.VirtPath,\n\t\tCachePath:               n.CachePath,\n\t\tTempPath:                n.TempPath,\n\t\tOracleUser:              n.OracleUser,\n\t\tOracleTenancy:           n.OracleTenancy,\n\t\tOraclePrivateKey:        n.OraclePrivateKey,\n\t\tOraclePublicKey:         n.OraclePublicKey,\n\t\tOperation:               n.Operation,\n\t\tcloudSubnetsNamed:       n.cloudSubnetsNamed,\n\t\tdcId:                    n.dcId,\n\t\tdcZoneId:                n.dcZoneId,\n\t}\n\n\treturn nde\n}\n\nfunc (n *Node) AddRequest() {\n\tn.lock.Lock()\n\tback := n.reqCount.Back()\n\tback.Value = back.Value.(int) + 1\n\tn.lock.Unlock()\n}\n\nfunc (n *Node) GetVirtPath() string {\n\tif n.VirtPath == \"\" {\n\t\treturn constants.DefaultRoot\n\t}\n\treturn n.VirtPath\n}\n\nfunc (n *Node) GetCachePath() string {\n\tif n.CachePath == \"\" {\n\t\treturn constants.DefaultCache\n\t}\n\treturn n.CachePath\n}\n\nfunc (n *Node) GetTempPath() string {\n\tif n.TempPath == \"\" {\n\t\treturn constants.DefaultTemp\n\t}\n\treturn n.TempPath\n}\n\nfunc (n *Node) GetDatacenter(db *database.Database) (\n\tdcId bson.ObjectID, err error) {\n\n\tn.lock.Lock()\n\tif n.Zone == n.dcZoneId {\n\t\tdcId = n.dcId\n\t\tn.lock.Unlock()\n\t\treturn\n\t}\n\tn.lock.Unlock()\n\n\tzne, err := zone.Get(db, n.Zone)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdcId = zne.Datacenter\n\tn.lock.Lock()\n\tn.dcId = zne.Datacenter\n\tn.dcZoneId = n.Zone\n\tn.lock.Unlock()\n\n\treturn\n}\n\nfunc (n *Node) GetCloudSubnetsName() (subnets []*CloudSubnet) {\n\tn.lock.Lock()\n\tsubnets = n.cloudSubnetsNamed\n\tn.lock.Unlock()\n\tif subnets != nil {\n\t\treturn\n\t}\n\n\tnames := map[string]string{}\n\n\tif n.AvailableVpcs != nil {\n\t\tfor _, vpc := range n.AvailableVpcs {\n\t\t\tfor _, subnet := range vpc.Subnets {\n\t\t\t\tnames[subnet.Id] = fmt.Sprintf(\n\t\t\t\t\t\"%s - %s\", vpc.Name, subnet.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\tsubnets = []*CloudSubnet{}\n\n\tif n.CloudSubnets != nil {\n\t\tfor _, subnetId := range n.CloudSubnets {\n\t\t\tname := names[subnetId]\n\t\t\tif name == \"\" {\n\t\t\t\tname = subnetId\n\t\t\t}\n\n\t\t\tsubnets = append(subnets, &CloudSubnet{\n\t\t\t\tId:   subnetId,\n\t\t\t\tName: name,\n\t\t\t})\n\t\t}\n\t}\n\n\tn.lock.Lock()\n\tn.cloudSubnetsNamed = subnets\n\tn.lock.Unlock()\n\n\treturn\n}\n\nfunc (n *Node) IsAdmin() bool {\n\tfor _, typ := range n.Types {\n\t\tif typ == Admin {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (n *Node) IsUser() bool {\n\tfor _, typ := range n.Types {\n\t\tif typ == User {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (n *Node) IsBalancer() bool {\n\tfor _, typ := range n.Types {\n\t\tif typ == Balancer {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (n *Node) IsHypervisor() bool {\n\tfor _, typ := range n.Types {\n\t\tif typ == Hypervisor {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (n *Node) IsOnline() bool {\n\tif time.Since(n.Timestamp) > time.Duration(\n\t\tsettings.System.NodeTimestampTtl)*time.Second {\n\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (n *Node) IsDhcp() bool {\n\treturn n.NetworkMode == Dhcp ||\n\t\tn.NetworkMode == DhcpSlaac\n}\n\nfunc (n *Node) IsDhcp6() bool {\n\treturn n.NetworkMode6 == Dhcp ||\n\t\tn.NetworkMode6 == DhcpSlaac\n}\n\nfunc (n *Node) Usage() int {\n\tmemoryUsage := float64(n.MemoryUnitsRes) / float64(n.MemoryUnits)\n\tif memoryUsage > 1.0 {\n\t\tmemoryUsage = 1.0\n\t}\n\n\tcpuUsage := float64(n.CpuUnitsRes) / float64(n.CpuUnits)\n\tif cpuUsage > 1.0 {\n\t\tcpuUsage = 1.0\n\t}\n\n\ttotalUsage := (memoryUsage * 0.75) + (cpuUsage * 0.25)\n\tif totalUsage > 1.0 {\n\t\ttotalUsage = 1.0\n\t}\n\n\treturn int(totalUsage * 100)\n}\n\nfunc (n *Node) SizeResource(memory, processors int) bool {\n\tmemoryUnits := float64(memory) / float64(1024)\n\n\tif memoryUnits+n.MemoryUnitsRes > n.MemoryUnits {\n\t\treturn false\n\t}\n\n\tif processors+n.CpuUnitsRes > n.CpuUnits*2 {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (n *Node) GetOracleAuthProvider() (pv *NodeOracleAuthProvider) {\n\tpv = &NodeOracleAuthProvider{\n\t\tnde: n,\n\t}\n\treturn\n}\n\nfunc (n *Node) GetWebauthn(origin string, strict bool) (\n\tweb *webauthn.WebAuthn, err error) {\n\n\twebauthnDomain := n.WebauthnDomain\n\tif webauthnDomain == \"\" {\n\t\tif strict {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.New(\"node: Webauthn domain not configured\"),\n\t\t\t}\n\t\t\treturn\n\t\t} else {\n\t\t\tuserN := strings.Count(n.UserDomain, \".\")\n\t\t\tadminN := strings.Count(n.AdminDomain, \".\")\n\n\t\t\tif userN <= adminN {\n\t\t\t\twebauthnDomain = n.UserDomain\n\t\t\t} else {\n\t\t\t\twebauthnDomain = n.AdminDomain\n\t\t\t}\n\t\t}\n\t}\n\n\tweb, err = webauthn.New(&webauthn.Config{\n\t\tRPDisplayName: \"Pritunl Cloud\",\n\t\tRPID:          webauthnDomain,\n\t\tRPOrigins:     []string{origin},\n\t})\n\tif err != nil {\n\t\terr = utils.ParseWebauthnError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *Node) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tn.Name = utils.FilterName(n.Name)\n\n\tif n.Hypervisor == \"\" {\n\t\tn.Hypervisor = Kvm\n\t}\n\n\tif n.Vga == \"\" {\n\t\tn.Vga = Virtio\n\t\tn.VgaRender = \"\"\n\t}\n\n\tif !VgaModes.Contains(n.Vga) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"node_vga_invalid\",\n\t\t\tMessage: \"Invalid VGA type\",\n\t\t}\n\t\treturn\n\t}\n\n\tif VgaRenderModes.Contains(n.Vga) && n.VgaRender != \"\" {\n\t\tfound := false\n\t\tfor _, rendr := range n.AvailableRenders {\n\t\t\tif n.VgaRender == rendr {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !found {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"node_vga_render_invalid\",\n\t\t\t\tMessage: \"Invalid EGL render\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tn.VgaRender = \"\"\n\t}\n\n\tif n.Gui {\n\t\tif n.GuiUser == \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"gui_user_missing\",\n\t\t\t\tMessage: \"Desktop GUI user must be set\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tswitch n.GuiMode {\n\t\tcase Sdl, \"\":\n\t\t\tn.GuiMode = Sdl\n\t\t\tbreak\n\t\tcase Gtk:\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"gui_mode_invalid\",\n\t\t\t\tMessage: \"Invalid desktop GUI mode\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tn.GuiUser = \"\"\n\t\tn.GuiMode = \"\"\n\t}\n\n\tif n.Protocol != \"http\" && n.Protocol != \"https\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"node_protocol_invalid\",\n\t\t\tMessage: \"Invalid node server protocol\",\n\t\t}\n\t\treturn\n\t}\n\n\tif n.Port < 1 || n.Port > 65535 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"node_port_invalid\",\n\t\t\tMessage: \"Invalid node server port\",\n\t\t}\n\t\treturn\n\t}\n\n\tif n.Certificates == nil || n.Protocol != \"https\" {\n\t\tn.Certificates = []bson.ObjectID{}\n\t}\n\n\tif n.Types == nil {\n\t\tn.Types = []string{}\n\t}\n\n\tfor _, typ := range n.Types {\n\t\tswitch typ {\n\t\tcase Admin, User, Balancer, Hypervisor:\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"type_invalid\",\n\t\t\t\tMessage: \"Invalid node type\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tif !n.IsBalancer() && ((n.IsAdmin() && !n.IsUser()) ||\n\t\t(n.IsUser() && !n.IsAdmin())) {\n\n\t\tn.AdminDomain = \"\"\n\t\tn.UserDomain = \"\"\n\t} else {\n\t\tif !n.IsAdmin() {\n\t\t\tn.AdminDomain = \"\"\n\t\t}\n\t\tif !n.IsUser() {\n\t\t\tn.UserDomain = \"\"\n\t\t}\n\t}\n\n\tif !n.Zone.IsZero() {\n\t\tzne, e := zone.Get(db, n.Zone)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif zne == nil {\n\t\t\tn.Zone = bson.NilObjectID\n\t\t} else {\n\t\t\tn.Datacenter = zne.Datacenter\n\t\t\tn.Zone = zne.Id\n\t\t}\n\t}\n\n\tif n.VirtPath == \"\" {\n\t\tn.VirtPath = constants.DefaultRoot\n\t}\n\tif n.CachePath == \"\" {\n\t\tn.CachePath = constants.DefaultCache\n\t}\n\n\tif n.Roles == nil {\n\t\tn.Roles = []string{}\n\t}\n\n\tif n.Firewall && len(n.Roles) == 0 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"firewall_empty_roles\",\n\t\t\tMessage: \"Cannot enable firewall without network roles\",\n\t\t}\n\t\treturn\n\t}\n\n\tif n.ExternalInterfaces == nil {\n\t\tn.ExternalInterfaces = []string{}\n\t}\n\tif n.InternalInterfaces == nil {\n\t\tn.InternalInterfaces = []string{}\n\t}\n\tif n.Blocks == nil {\n\t\tn.Blocks = []*BlockAttachment{}\n\t}\n\tif n.ExternalInterfaces6 == nil {\n\t\tn.ExternalInterfaces6 = []string{}\n\t}\n\tif n.Blocks6 == nil {\n\t\tn.Blocks6 = []*BlockAttachment{}\n\t}\n\n\tif len(n.InternalInterfaces) == 0 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"internal_interface_invalid\",\n\t\t\tMessage: \"Missing required internal interface\",\n\t\t}\n\t\treturn\n\t}\n\n\tif n.CloudSubnets == nil {\n\t\tn.CloudSubnets = []string{}\n\t}\n\n\tinstanceDrives := []*drive.Device{}\n\tif n.InstanceDrives != nil {\n\t\tfor _, device := range n.InstanceDrives {\n\t\t\tdevice.Id = utils.FilterRelPath(device.Id)\n\t\t\tinstanceDrives = append(instanceDrives, device)\n\t\t}\n\t}\n\tn.InstanceDrives = instanceDrives\n\n\tswitch n.NetworkMode {\n\tcase Static:\n\t\tfor _, blckAttch := range n.Blocks {\n\t\t\tblck, e := block.Get(db, blckAttch.Block)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif blck == nil || blck.Type != block.IPv4 {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_block\",\n\t\t\t\t\tMessage: \"External IPv4 block invalid\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tbreak\n\tcase Cloud:\n\t\tn.Blocks = []*BlockAttachment{}\n\t\tbreak\n\tcase Dhcp:\n\t\tn.Blocks = []*BlockAttachment{}\n\t\tbreak\n\tcase Disabled, \"\":\n\t\tn.NetworkMode = Disabled\n\t\tn.Blocks = []*BlockAttachment{}\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_network_mode\",\n\t\t\tMessage: \"Network mode invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch n.NetworkMode6 {\n\tcase Static:\n\t\tfor _, blckAttch := range n.Blocks6 {\n\t\t\tblck, e := block.Get(db, blckAttch.Block)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif blck == nil || blck.Type != block.IPv6 {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_block6\",\n\t\t\t\t\tMessage: \"External IPv6 block invalid\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tbreak\n\tcase Cloud:\n\t\tn.Blocks6 = []*BlockAttachment{}\n\t\tbreak\n\tcase Dhcp:\n\t\tn.Blocks6 = []*BlockAttachment{}\n\t\tbreak\n\tcase Disabled, \"\":\n\t\tn.NetworkMode6 = Disabled\n\t\tn.Blocks6 = []*BlockAttachment{}\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_network_mode6\",\n\t\t\tMessage: \"Network mode6 invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif n.NetworkMode == Static && n.NetworkMode6 == Static ||\n\t\tn.NetworkMode == Disabled && n.NetworkMode6 == Disabled {\n\n\t\tn.ExternalInterfaces = []string{}\n\t}\n\n\tif n.NetworkMode == Cloud || n.NetworkMode6 == Cloud {\n\t\tif n.OracleUser == \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"missing_oracle_user\",\n\t\t\t\tMessage: \"Oracle user OCID required for host routing\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif n.OracleTenancy == \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"missing_oracle_tenancy\",\n\t\t\t\tMessage: \"Oracle tenancy OCID required for host routing\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tn.OracleUser = \"\"\n\t\tn.OracleTenancy = \"\"\n\t\tn.CloudSubnets = []string{}\n\t\tn.AvailableVpcs = []*cloud.Vpc{}\n\t}\n\n\tnewShares := []*Share{}\n\tfor _, share := range n.Shares {\n\t\tif share.Type == \"\" && share.Path == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tshare.Type = HostPath\n\t\tshare.Path = utils.FilterPath(share.Path)\n\n\t\tif share.Path == \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"share_path_invalid\",\n\t\t\t\tMessage: \"Invalid share path\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif len(share.Roles) == 0 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"missing_share_path_roles\",\n\t\t\t\tMessage: \"Share path missing required roles\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tnewShares = append(newShares, share)\n\t}\n\tn.Shares = newShares\n\n\tn.Format()\n\n\treturn\n}\n\nfunc (n *Node) Format() {\n\tsort.Strings(n.Types)\n\tutils.SortObjectIds(n.Certificates)\n}\n\nfunc (n *Node) JsonHypervisor() {\n\tvpcs := []*cloud.Vpc{}\n\n\toracleSubnets := set.NewSet()\n\tfor _, subnet := range n.CloudSubnets {\n\t\toracleSubnets.Add(subnet)\n\t}\n\n\tfor _, vpc := range n.AvailableVpcs {\n\t\tsubnets := []*cloud.Subnet{}\n\t\tfor _, subnet := range vpc.Subnets {\n\t\t\tif oracleSubnets.Contains(subnet.Id) {\n\t\t\t\tsubnets = append(subnets, subnet)\n\t\t\t}\n\t\t}\n\n\t\tif len(subnets) > 0 {\n\t\t\tvpcs = append(vpcs, vpc)\n\t\t}\n\t}\n\n\tn.AvailableVpcs = vpcs\n\n\treturn\n}\n\nfunc (n *Node) SetActive() {\n\tif time.Since(n.Timestamp) > 30*time.Second {\n\t\tn.RequestsMin = 0\n\t\tn.Memory = 0\n\t\tn.Load1 = 0\n\t\tn.Load5 = 0\n\t\tn.Load15 = 0\n\t\tn.CpuUnits = 0\n\t\tn.CpuUnitsRes = 0\n\t\tn.MemoryUnits = 0\n\t\tn.MemoryUnitsRes = 0\n\t}\n}\n\nfunc (n *Node) Commit(db *database.Database) (err error) {\n\tcoll := db.Nodes()\n\n\terr = coll.Commit(n.Id, n)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *Node) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Nodes()\n\n\terr = coll.CommitFields(n.Id, n, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *Node) GetStaticAddr(db *database.Database,\n\tinstId bson.ObjectID) (blck *block.Block, ip net.IP, iface string,\n\terr error) {\n\n\tblck, blckIp, err := block.GetInstanceIp(db, instId, block.External)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif blckIp != nil {\n\t\tfor _, blckAttch := range n.Blocks {\n\t\t\tif blckAttch.Block == blck.Id {\n\t\t\t\tip = blckIp.GetIp()\n\t\t\t\tiface = blckAttch.Interface\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr = block.RemoveIp(db, blckIp.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, blckAttch := range n.Blocks {\n\t\tblck, err = block.Get(db, blckAttch.Block)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tiface = blckAttch.Interface\n\n\t\tip, err = blck.GetIp(db, instId, block.External)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*block.BlockFull); ok {\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tbreak\n\t}\n\n\tif ip == nil {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"node: No external block addresses available\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *Node) GetStaticAddr6(db *database.Database,\n\tinstId bson.ObjectID, vlan int, matchIface string) (\n\tblck *block.Block, ip net.IP, cidr int, iface string, err error) {\n\n\tmismatch := false\n\n\tif matchIface != \"\" {\n\t\tfor _, blckAttch := range n.Blocks6 {\n\t\t\tif blckAttch.Interface != matchIface {\n\t\t\t\tmismatch = true\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tblck, err = block.Get(db, blckAttch.Block)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tiface = blckAttch.Interface\n\n\t\t\tip, cidr, err = blck.GetIp6(db, instId, vlan)\n\t\t\tif err != nil {\n\t\t\t\tif _, ok := err.(*block.BlockFull); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t\tcontinue\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\t} else {\n\t\tfor _, blckAttch := range n.Blocks6 {\n\t\t\tblck, err = block.Get(db, blckAttch.Block)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tiface = blckAttch.Interface\n\n\t\t\tip, cidr, err = blck.GetIp6(db, instId, vlan)\n\t\t\tif err != nil {\n\t\t\t\tif _, ok := err.(*block.BlockFull); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t\tcontinue\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif ip == nil {\n\t\tif mismatch {\n\t\t\terr = &errortypes.NotFoundError{\n\t\t\t\terrors.New(\"node: No external block6 with matching \" +\n\t\t\t\t\t\"block interface available\"),\n\t\t\t}\n\t\t} else {\n\t\t\terr = &errortypes.NotFoundError{\n\t\t\t\terrors.New(\"node: No external block6 addresses available\"),\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *Node) GetStaticHostAddr(db *database.Database,\n\tinstId bson.ObjectID) (blck *block.Block, ip net.IP, err error) {\n\n\tblck, err = block.GetNodeBlock(n.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tblckIp, err := block.GetInstanceHostIp(db, instId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif blckIp != nil {\n\t\tcontains, e := blck.Contains(blckIp)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif contains {\n\t\t\tip = blckIp.GetIp()\n\t\t\treturn\n\t\t}\n\n\t\terr = block.RemoveIp(db, blckIp.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tip, err = blck.GetIp(db, instId, block.Host)\n\tif err != nil {\n\t\tif _, ok := err.(*block.BlockFull); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif ip == nil {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"node: No host addresses available\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *Node) GetStaticNodePortAddr(db *database.Database,\n\tinstId bson.ObjectID) (blck *block.Block, ip net.IP, err error) {\n\n\tblck, err = block.GetNodePortBlock(n.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tblckIp, err := block.GetInstanceNodePortIp(db, instId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif blckIp != nil {\n\t\tcontains, e := blck.Contains(blckIp)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif contains {\n\t\t\tip = blckIp.GetIp()\n\t\t\treturn\n\t\t}\n\n\t\terr = block.RemoveIp(db, blckIp.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tip, err = blck.GetIp(db, instId, block.NodePort)\n\tif err != nil {\n\t\tif _, ok := err.(*block.BlockFull); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif ip == nil {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"node: No node port addresses available\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *Node) GetRemoteAddr(r *http.Request) (addr string) {\n\tif n.ForwardedForHeader != \"\" {\n\t\taddr = strings.TrimSpace(\n\t\t\tstrings.SplitN(r.Header.Get(n.ForwardedForHeader), \",\", 2)[0])\n\t\tif addr != \"\" {\n\t\t\treturn\n\t\t}\n\t}\n\n\taddr = utils.StripPort(r.RemoteAddr)\n\treturn\n}\n\nfunc (n *Node) SyncNetwork(clearCache bool) {\n\tnetLock.Lock()\n\tdefer netLock.Unlock()\n\n\tif clearCache {\n\t\tClearIfaceCache()\n\t\tbridges.ClearCache()\n\t}\n\n\tdefaultIface, err := getDefaultIface()\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"default_interface\": defaultIface,\n\t\t\t\"error\":             err,\n\t\t}).Error(\"node: Failed to get public address\")\n\t}\n\n\tif defaultIface != \"\" {\n\t\tn.DefaultInterface = defaultIface\n\n\t\tpubAddr, pubAddr6, err := bridges.GetIpAddrs(defaultIface)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"default_interface\": defaultIface,\n\t\t\t\t\"error\":             err,\n\t\t\t}).Error(\"node: Failed to get public address\")\n\t\t}\n\n\t\tif pubAddr != \"\" {\n\t\t\tn.PublicIps = []string{\n\t\t\t\tpubAddr,\n\t\t\t}\n\t\t}\n\n\t\tif pubAddr6 != \"\" {\n\t\t\tn.PublicIps6 = []string{\n\t\t\t\tpubAddr6,\n\t\t\t}\n\t\t}\n\t}\n\n\tprivateIps := map[string]string{}\n\tinternalInterfaces := n.InternalInterfaces\n\tfor _, iface := range internalInterfaces {\n\t\taddr, _, err := bridges.GetIpAddrs(iface)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"internal_interface\": iface,\n\t\t\t\t\"error\":              err,\n\t\t\t}).Error(\"node: Failed to get private address\")\n\t\t}\n\n\t\tif addr != \"\" {\n\t\t\tprivateIps[iface] = addr\n\t\t}\n\t}\n\tn.PrivateIps = privateIps\n\n\tifaces, err := GetInterfaces()\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to get interfaces\")\n\t}\n\n\tif ifaces != nil {\n\t\tn.AvailableInterfaces = ifaces\n\t} else {\n\t\tn.AvailableInterfaces = []ip.Interface{}\n\t}\n\n\tbrdgs, err := bridges.GetBridges()\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to get bridge interfaces\")\n\t}\n\n\tif brdgs != nil {\n\t\tn.AvailableBridges = brdgs\n\t} else {\n\t\tn.AvailableBridges = []ip.Interface{}\n\t}\n\n\tif n.JumboFrames {\n\t\tn.JumboFramesInternal = true\n\t}\n\n\tif n.NetworkMode == Cloud || n.NetworkMode6 == Cloud {\n\t\toracleVpcs, e := cloud.GetOracleVpcs(n.GetOracleAuthProvider())\n\t\tif e != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": e,\n\t\t\t}).Error(\"node: Failed to get oracle vpcs\")\n\t\t}\n\n\t\tif oracleVpcs != nil {\n\t\t\tn.AvailableVpcs = oracleVpcs\n\t\t} else {\n\t\t\tn.AvailableVpcs = []*cloud.Vpc{}\n\t\t}\n\t} else {\n\t\tn.AvailableVpcs = []*cloud.Vpc{}\n\t}\n}\n\nfunc (n *Node) getUpdateDetails(db *database.Database) (\n\tupdates []*telemetry.Update, ok bool) {\n\n\tupdates, ok = telemetry.Updates.Get()\n\tif !ok {\n\t\treturn\n\t}\n\n\tif len(updates) == 0 {\n\t\treturn\n\t}\n\n\tdetailsMap := map[string][]*advisory.Advisory{}\n\tfor _, upd := range n.Updates {\n\t\tif upd.Advisory != \"\" && len(upd.Details) > 0 {\n\t\t\tdetailsMap[upd.Advisory] = upd.Details\n\t\t}\n\t}\n\n\tfor _, upd := range updates {\n\t\tif details, ok := detailsMap[upd.Advisory]; ok {\n\t\t\tupd.Details = details\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *Node) update(db *database.Database) (err error) {\n\tcoll := db.Nodes()\n\n\tfields := bson.M{\n\t\t\"timestamp\":            n.Timestamp,\n\t\t\"requests_min\":         n.RequestsMin,\n\t\t\"memory\":               n.Memory,\n\t\t\"hugepages_used\":       n.HugePagesUsed,\n\t\t\"load1\":                n.Load1,\n\t\t\"load5\":                n.Load5,\n\t\t\"load15\":               n.Load15,\n\t\t\"cpu_units\":            n.CpuUnits,\n\t\t\"memory_units\":         n.MemoryUnits,\n\t\t\"cpu_units_res\":        n.CpuUnitsRes,\n\t\t\"memory_units_res\":     n.MemoryUnitsRes,\n\t\t\"public_ips\":           n.PublicIps,\n\t\t\"public_ips6\":          n.PublicIps6,\n\t\t\"private_ips\":          n.PrivateIps,\n\t\t\"hostname\":             n.Hostname,\n\t\t\"local_isos\":           n.LocalIsos,\n\t\t\"usb_devices\":          n.UsbDevices,\n\t\t\"pci_devices\":          n.PciDevices,\n\t\t\"available_renders\":    n.AvailableRenders,\n\t\t\"available_interfaces\": n.AvailableInterfaces,\n\t\t\"available_bridges\":    n.AvailableBridges,\n\t\t\"available_vpcs\":       n.AvailableVpcs,\n\t\t\"default_interface\":    n.DefaultInterface,\n\t\t\"pools\":                n.Pools,\n\t\t\"available_drives\":     n.AvailableDrives,\n\t}\n\n\tupdates, ok := n.getUpdateDetails(db)\n\tif ok {\n\t\tfields[\"updates\"] = updates\n\t}\n\n\tnde := &Node{}\n\terr = coll.FindOneAndUpdate(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": n.Id,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$set\": fields,\n\t\t},\n\t\toptions.FindOneAndUpdate().SetReturnDocument(options.After),\n\t).Decode(nde)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tn.Id = nde.Id\n\tn.Datacenter = nde.Datacenter\n\tn.Zone = nde.Zone\n\tn.Name = nde.Name\n\tn.Comment = nde.Comment\n\tn.Types = nde.Types\n\tn.Port = nde.Port\n\tn.Http2 = nde.Http2\n\tn.NoRedirectServer = nde.NoRedirectServer\n\tn.Protocol = nde.Protocol\n\tn.Hypervisor = nde.Hypervisor\n\tn.Vga = nde.Vga\n\tn.VgaRender = nde.VgaRender\n\tn.Gui = nde.Gui\n\tn.GuiUser = nde.GuiUser\n\tn.GuiMode = nde.GuiMode\n\tn.Certificates = nde.Certificates\n\tn.SelfCertificate = nde.SelfCertificate\n\tn.SelfCertificateKey = nde.SelfCertificateKey\n\tn.AdminDomain = nde.AdminDomain\n\tn.UserDomain = nde.UserDomain\n\tn.WebauthnDomain = nde.WebauthnDomain\n\tn.ForwardedForHeader = nde.ForwardedForHeader\n\tn.ForwardedProtoHeader = nde.ForwardedProtoHeader\n\tn.ExternalInterfaces = nde.ExternalInterfaces\n\tn.ExternalInterfaces6 = nde.ExternalInterfaces6\n\tn.InternalInterfaces = nde.InternalInterfaces\n\tn.CloudSubnets = nde.CloudSubnets\n\tn.NetworkMode = nde.NetworkMode\n\tn.NetworkMode6 = nde.NetworkMode6\n\tn.Blocks = nde.Blocks\n\tn.Blocks6 = nde.Blocks6\n\tn.Shares = nde.Shares\n\tn.InstanceDrives = nde.InstanceDrives\n\tn.NoHostNetwork = nde.NoHostNetwork\n\tn.NoNodePortNetwork = nde.NoNodePortNetwork\n\tn.HostNat = nde.HostNat\n\tn.DefaultNoPublicAddress = nde.DefaultNoPublicAddress\n\tn.DefaultNoPublicAddress6 = nde.DefaultNoPublicAddress6\n\tn.JumboFrames = nde.JumboFrames\n\tn.JumboFramesInternal = nde.JumboFramesInternal\n\tn.Iscsi = nde.Iscsi\n\tn.UsbPassthrough = nde.UsbPassthrough\n\tn.PciPassthrough = nde.PciPassthrough\n\tn.Hugepages = nde.Hugepages\n\tn.HugepagesSize = nde.HugepagesSize\n\tn.Firewall = nde.Firewall\n\tn.Roles = nde.Roles\n\tn.Updates = nde.Updates\n\tn.VirtPath = nde.VirtPath\n\tn.CachePath = nde.CachePath\n\tn.TempPath = nde.TempPath\n\tn.OracleUser = nde.OracleUser\n\tn.OracleTenancy = nde.OracleTenancy\n\tn.OraclePrivateKey = nde.OraclePrivateKey\n\tn.OraclePublicKey = nde.OraclePublicKey\n\tn.Operation = nde.Operation\n\n\treturn\n}\n\nfunc (n *Node) sync() (nde *Node) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tnde = n.Copy()\n\tn = nde\n\n\tn.Timestamp = time.Now()\n\n\tmem, err := utils.GetMemInfo()\n\tif err != nil {\n\t\tn.Memory = 0\n\t\tn.HugePagesUsed = 0\n\t\tn.MemoryUnits = 0\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to get memory\")\n\t} else {\n\t\tn.Memory = utils.ToFixed(mem.UsedPercent, 2)\n\t\tn.HugePagesUsed = utils.ToFixed(mem.HugePagesUsedPercent, 2)\n\t\tn.MemoryUnits = utils.ToFixed(\n\t\t\tfloat64(mem.Total)/float64(1048576), 2)\n\t}\n\n\tload, err := utils.LoadAverage()\n\tif err != nil {\n\t\tn.CpuUnits = 0\n\t\tn.Load1 = 0\n\t\tn.Load5 = 0\n\t\tn.Load15 = 0\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to get load\")\n\t} else {\n\t\tn.CpuUnits = load.CpuUnits\n\t\tn.Load1 = load.Load1\n\t\tn.Load5 = load.Load5\n\t\tn.Load15 = load.Load15\n\t}\n\n\tn.SyncNetwork(false)\n\n\tpools, err := lvm.GetAvailablePools(db, n.Zone)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to get pools\")\n\t}\n\n\tpoolIds := []bson.ObjectID{}\n\tfor _, pl := range pools {\n\t\tpoolIds = append(poolIds, pl.Id)\n\t}\n\tn.Pools = poolIds\n\n\tdrives, err := drive.GetDevices()\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to get drive devices\")\n\t\tn.AvailableDrives = []*drive.Device{}\n\t} else {\n\t\tn.AvailableDrives = drives\n\t}\n\n\trenders, err := render.GetRenders()\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to get renders\")\n\t\tn.AvailableRenders = []string{}\n\t} else {\n\t\tn.AvailableRenders = renders\n\t}\n\n\thostname, err := os.Hostname()\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"node: Failed to get hostname\"),\n\t\t}\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to get hostname\")\n\t}\n\tn.Hostname = hostname\n\n\tisos, err := iso.GetIsos(path.Join(n.GetVirtPath(), \"isos\"))\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to get isos\")\n\t\tn.LocalIsos = []*iso.Iso{}\n\t} else {\n\t\tn.LocalIsos = isos\n\t}\n\n\tif n.UsbPassthrough {\n\t\tdevices, err := usb.GetDevices()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"node: Failed to get usb devices\")\n\t\t\tn.UsbDevices = []*usb.Device{}\n\t\t} else {\n\t\t\tn.UsbDevices = devices\n\t\t}\n\t} else {\n\t\tn.UsbDevices = []*usb.Device{}\n\t}\n\n\tif n.PciPassthrough {\n\t\tpciDevices, err := pci.GetVfioAll()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"node: Failed to get vfio devices\")\n\t\t\tn.PciDevices = []*pci.Device{}\n\t\t} else {\n\t\t\tn.PciDevices = pciDevices\n\t\t}\n\t} else {\n\t\tn.PciDevices = []*pci.Device{}\n\t}\n\n\terr = n.update(db)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"node: Failed to update node\")\n\t}\n\n\tif n.Operation == Restart {\n\t\tlogrus.Info(\"node: Restarting node\")\n\n\t\tn.Operation = \"\"\n\t\terr = n.CommitFields(db, set.NewSet(\"operation\"))\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"node: Failed to commit node operation\")\n\t\t} else {\n\t\t\tcmd := exec.Command(\"systemctl\", \"restart\", \"pritunl-cloud\")\n\t\t\terr = cmd.Start()\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"node: Failed to start node restart\")\n\t\t\t}\n\t\t}\n\t}\n\n\tvar reqCount *list.List\n\tif Self != nil {\n\t\tSelf.lock.Lock()\n\t\treqCount = utils.CopyList(Self.reqCount)\n\t\tSelf.lock.Unlock()\n\t} else {\n\t\treqCount = list.New()\n\t\tfor i := 0; i < 60; i++ {\n\t\t\treqCount.PushBack(0)\n\t\t}\n\t}\n\n\tvar count int64\n\tfor elm := reqCount.Front(); elm != nil; elm = elm.Next() {\n\t\tcount += int64(elm.Value.(int))\n\t}\n\tn.RequestsMin = count\n\n\treqCount.Remove(reqCount.Front())\n\treqCount.PushBack(0)\n\n\tn.reqCount = reqCount\n\n\tSelf = n\n\n\treturn\n}\n\nfunc (n *Node) Init() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tcoll := db.Nodes()\n\n\terr = coll.FindOneId(n.Id, n)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\tn.SoftwareVersion = constants.Version\n\n\tif n.Name == \"\" {\n\t\tn.Name = utils.RandName()\n\t}\n\n\tif n.Types == nil {\n\t\tn.Types = []string{Admin, Hypervisor}\n\t}\n\n\tif n.Protocol == \"\" {\n\t\tn.Protocol = \"https\"\n\t}\n\n\tif n.Port == 0 {\n\t\tn.Port = 443\n\t}\n\n\tif n.Hypervisor == \"\" {\n\t\tn.Hypervisor = Kvm\n\t}\n\n\tbsonSet := bson.M{\n\t\t\"_id\":              n.Id,\n\t\t\"name\":             n.Name,\n\t\t\"types\":            n.Types,\n\t\t\"timestamp\":        time.Now(),\n\t\t\"protocol\":         n.Protocol,\n\t\t\"port\":             n.Port,\n\t\t\"hypervisor\":       n.Hypervisor,\n\t\t\"vga\":              n.Vga,\n\t\t\"software_version\": n.SoftwareVersion,\n\t}\n\n\tif n.OraclePublicKey == \"\" || n.OraclePrivateKey == \"\" {\n\t\tprivKey, pubKey, e := utils.GenerateRsaKey()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tbsonSet[\"oracle_public_key\"] = strings.TrimSpace(string(pubKey))\n\t\tbsonSet[\"oracle_private_key\"] = strings.TrimSpace(string(privKey))\n\t}\n\n\t_, err = coll.UpdateOne(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": n.Id,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$set\": bsonSet,\n\t\t},\n\t\toptions.UpdateOne().SetUpsert(true),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tn.sync()\n\n\tevent.PublishDispatch(db, \"node.change\")\n\n\tgo func() {\n\t\tnde := n\n\t\tfor {\n\t\t\tif constants.Shutdown {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnde = nde.sync()\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tpanc := recover()\n\t\t\tif panc != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\t\"panic\": panc,\n\t\t\t\t}).Error(\"node: Panic in telemetry\")\n\t\t\t}\n\t\t}()\n\n\t\tfor {\n\t\t\ttelemetry.Refresh()\n\t\t\ttime.Sleep(1 * time.Minute)\n\t\t}\n\t}()\n\n\treturn\n}\n"
  },
  {
    "path": "node/oracle.go",
    "content": "package node\n\ntype NodeOracleAuthProvider struct {\n\tnde *Node\n}\n\nfunc (n *NodeOracleAuthProvider) OracleUser() string {\n\treturn n.nde.OracleUser\n}\n\nfunc (n *NodeOracleAuthProvider) OracleTenancy() string {\n\treturn n.nde.OracleTenancy\n}\n\nfunc (n *NodeOracleAuthProvider) OraclePrivateKey() string {\n\treturn n.nde.OraclePrivateKey\n}\n"
  },
  {
    "path": "node/utils.go",
    "content": "package node\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, nodeId bson.ObjectID) (\n\tnde *Node, err error) {\n\n\tcoll := db.Nodes()\n\tnde = &Node{}\n\n\terr = coll.FindOneId(nodeId, nde)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database) (nodes []*Node, err error) {\n\tcoll := db.Nodes()\n\tnodes = []*Node{}\n\n\tcursor, err := coll.Find(db, bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Node{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tnde.SetActive()\n\t\tnodes = append(nodes, nde)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (nde *Node, err error) {\n\tcoll := db.Nodes()\n\tnde = &Node{}\n\n\terr = coll.FindOne(db, query).Decode(nde)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNamesMap(db *database.Database, query *bson.M) (\n\tnodeNames map[bson.ObjectID]string, err error) {\n\n\tcoll := db.Nodes()\n\tnodeNames = map[bson.ObjectID]string{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().SetProjection(&bson.D{\n\t\t\t{\"name\", 1},\n\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Node{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tnodeNames[nde.Id] = nde.Name\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllHypervisors(db *database.Database, query *bson.M) (\n\tnodes []*Node, err error) {\n\n\tcoll := db.Nodes()\n\tnodes = []*Node{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetProjection(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"types\", 1},\n\t\t\t\t{\"gui\", 1},\n\t\t\t\t{\"pools\", 1},\n\t\t\t\t{\"available_vpcs\", 1},\n\t\t\t\t{\"cloud_subnets\", 1},\n\t\t\t\t{\"default_no_public_address\", 1},\n\t\t\t\t{\"default_no_public_address6\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Node{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif !nde.IsHypervisor() {\n\t\t\tcontinue\n\t\t}\n\t\tnde.JsonHypervisor()\n\n\t\tnodes = append(nodes, nde)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPool(db *database.Database, poolId bson.ObjectID) (\n\tnodes []*Node, err error) {\n\n\tcoll := db.Nodes()\n\tnodes = []*Node{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"pools\": poolId,\n\t\t},\n\t\toptions.Find().SetProjection(&bson.D{\n\t\t\t{\"name\", 1},\n\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Node{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tnodes = append(nodes, nde)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (nodes []*Node, count int64, err error) {\n\n\tcoll := db.Nodes()\n\tnodes = []*Node{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Node{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tnde.SetActive()\n\t\tnodes = append(nodes, nde)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllShape(db *database.Database, zones []bson.ObjectID,\n\troles []string) (nodes []*Node, err error) {\n\n\tcoll := db.Nodes()\n\tnodes = []*Node{}\n\n\tquery := &bson.M{\n\t\t\"zone\": &bson.M{\n\t\t\t\"$in\": zones,\n\t\t},\n\t\t\"roles\": &bson.M{\n\t\t\t\"$in\": roles,\n\t\t},\n\t}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find(),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Node{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif !nde.IsHypervisor() || !nde.IsOnline() {\n\t\t\tcontinue\n\t\t}\n\n\t\tnodes = append(nodes, nde)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNet(db *database.Database) (nodes []*Node, err error) {\n\tcoll := db.Nodes()\n\tnodes = []*Node{}\n\n\tcursor, err := coll.Find(db, bson.M{}, options.Find().SetProjection(&bson.D{\n\t\t{\"datacenter\", 1},\n\t\t{\"zone\", 1},\n\t\t{\"private_ips\", 1},\n\t}))\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Node{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tnodes = append(nodes, nde)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, nodeId bson.ObjectID) (err error) {\n\tcoll := db.Nodes()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": nodeId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "nodeport/constants.go",
    "content": "package nodeport\n\nconst (\n\tTcp = \"tcp\"\n\tUdp = \"udp\"\n)\n"
  },
  {
    "path": "nodeport/mapping.go",
    "content": "package nodeport\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype Mapping struct {\n\tNodePort     bson.ObjectID `bson:\"node_port\" json:\"node_port\"`\n\tProtocol     string        `bson:\"protocol\" json:\"protocol\"`\n\tExternalPort int           `bson:\"external_port\" json:\"external_port\"`\n\tInternalPort int           `bson:\"internal_port\" json:\"internal_port\"`\n\tDelete       bool          `bson:\"-\" json:\"delete\"`\n}\n\nfunc (m *Mapping) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tswitch m.Protocol {\n\tcase Tcp, Udp:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_protocol\",\n\t\t\tMessage: \"Invalid node port protocol\",\n\t\t}\n\t\treturn\n\t}\n\n\tif m.ExternalPort != 0 {\n\t\tportRanges, e := GetPortRanges()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tmatched := false\n\t\tfor _, ports := range portRanges {\n\t\t\tif ports.Contains(m.ExternalPort) {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !matched {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_external_port\",\n\t\t\t\tMessage: \"Invalid external node port\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tif m.InternalPort <= 0 || m.InternalPort > 65535 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_internal_port\",\n\t\t\tMessage: \"Invalid internal node port\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (m *Mapping) Diff(mapping *Mapping) bool {\n\tif m.Protocol != mapping.Protocol {\n\t\treturn true\n\t}\n\n\tif m.ExternalPort != mapping.ExternalPort {\n\t\treturn true\n\t}\n\n\tif m.InternalPort != mapping.InternalPort {\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "nodeport/network.go",
    "content": "package nodeport\n\nimport (\n\t\"net\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nvar (\n\tnetwork *net.IPNet\n)\n\nfunc init() {\n\tmodule := requires.New(\"nodeport\")\n\tmodule.After(\"settings\")\n\n\tmodule.Handler = func() (err error) {\n\t\t_, network, err = net.ParseCIDR(settings.Hypervisor.NodePortNetwork)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\"nodeport: Failed to parse node port network\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "nodeport/nodeport.go",
    "content": "package nodeport\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype NodePort struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tDatacenter   bson.ObjectID `bson:\"datacenter\" json:\"datacenter\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tProtocol     string        `bson:\"protocol\" json:\"protocol\"`\n\tPort         int           `bson:\"port\" json:\"port\"`\n}\n\nfunc (n *NodePort) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tswitch n.Protocol {\n\tcase Tcp, Udp:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_protocol\",\n\t\t\tMessage: \"Invalid node port protocol\",\n\t\t}\n\t\treturn\n\t}\n\n\tif n.Port != 0 {\n\t\tportRanges, e := GetPortRanges()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tmatched := false\n\t\tfor _, ports := range portRanges {\n\t\t\tif ports.Contains(n.Port) {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !matched {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_port\",\n\t\t\t\tMessage: \"Invalid node port\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NodePort) Sync(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\n\tcount, err := coll.CountDocuments(db, &bson.M{\n\t\t\"node_ports.node_port\": n.Id,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif count == 0 {\n\t\terr = Remove(db, n.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (n *NodePort) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.NodePorts()\n\n\terr = coll.CommitFields(n.Id, n, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (n *NodePort) Insert(db *database.Database) (err error) {\n\tcoll := db.NodePorts()\n\n\tresp, err := coll.InsertOne(db, n)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tn.Id = resp.InsertedID.(bson.ObjectID)\n\treturn\n}\n"
  },
  {
    "path": "nodeport/utils.go",
    "content": "package nodeport\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/tools/set\"\n)\n\ntype PortRange struct {\n\tStart int\n\tEnd   int\n}\n\nfunc (r *PortRange) Contains(port int) bool {\n\tif port >= r.Start && port <= r.End {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc Get(db *database.Database, ndePrtId bson.ObjectID) (\n\tndePrt *NodePort, err error) {\n\n\tcoll := db.NodePorts()\n\tndePrt = &NodePort{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\": ndePrtId,\n\t}).Decode(ndePrt)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, ndePrtId bson.ObjectID) (\n\tndePrt *NodePort, err error) {\n\n\tcoll := db.NodePorts()\n\tndePrt = &NodePort{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          ndePrtId,\n\t\t\"organization\": orgId,\n\t}).Decode(ndePrt)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetPort(db *database.Database, dcId, orgId bson.ObjectID,\n\tprotocol string, port int) (ndePrt *NodePort, err error) {\n\n\tcoll := db.NodePorts()\n\tndePrt = &NodePort{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"datacenter\": dcId,\n\t\t\"port\":       port,\n\t}).Decode(ndePrt)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Available(db *database.Database, datacenterId, orgId bson.ObjectID,\n\tprotocol string, port int) (available bool, err error) {\n\n\tndePrt, err := GetPort(db, datacenterId, orgId, protocol, port)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tavailable = true\n\t\t\terr = nil\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\tif ndePrt.Organization == orgId {\n\t\tavailable = true\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetPortRanges() (ranges []*PortRange, err error) {\n\tranges = []*PortRange{}\n\tparts := strings.Split(settings.Hypervisor.NodePortRanges, \",\")\n\n\tfor _, part := range parts {\n\t\tpart = strings.TrimSpace(part)\n\t\tif part == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tbounds := strings.Split(part, \"-\")\n\t\tif len(bounds) != 2 {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"nodeport: Invalid port range format\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tstart, e := strconv.Atoi(strings.TrimSpace(bounds[0]))\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"nodeport: Invalid start port\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tend, e := strconv.Atoi(strings.TrimSpace(bounds[1]))\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"nodeport: Invalid end port\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif start >= end {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"nodeport: Start port larger than end port\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tranges = append(ranges, &PortRange{\n\t\t\tStart: start,\n\t\t\tEnd:   end,\n\t\t})\n\t}\n\n\tif len(ranges) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"nodeport: No node ports configured\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc New(db *database.Database, dcId, orgId bson.ObjectID,\n\tprotocol string, requestPort int) (\n\tndePrt *NodePort, errData *errortypes.ErrorData, err error) {\n\n\tmaxAttempts := settings.Hypervisor.NodePortMaxAttempts\n\n\tranges, err := GetPortRanges()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tndPt := &NodePort{\n\t\tDatacenter:   dcId,\n\t\tOrganization: orgId,\n\t\tProtocol:     protocol,\n\t\tPort:         requestPort,\n\t}\n\n\terrData, err = ndPt.Validate(db)\n\tif err != nil || errData != nil {\n\t\treturn\n\t}\n\n\tif ndPt.Port != 0 {\n\t\terr = ndPt.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tndePrt = ndPt\n\t\treturn\n\t}\n\n\tattempted := set.NewSet()\n\tfor i := 0; i < maxAttempts; i++ {\n\t\tselectedRange := ranges[utils.RandInt(0, len(ranges)-1)]\n\t\tndPt.Port = utils.RandInt(selectedRange.Start, selectedRange.End)\n\n\t\tif attempted.Contains(ndPt.Port) {\n\t\t\ti--\n\t\t\tcontinue\n\t\t}\n\t\tattempted.Add(ndPt.Port)\n\n\t\terr = ndPt.Insert(db)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tndePrt = ndPt\n\t\tbreak\n\t}\n\n\tif ndePrt == nil {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"nodeport: No available node ports found\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, ndePrtId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.NodePorts()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": ndePrtId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "nonce/nonce.go",
    "content": "package nonce\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype nonce struct {\n\tId        string    `bson:\"_id\"`\n\tTimestamp time.Time `bson:\"timestamp\"`\n}\n\nfunc Validate(db *database.Database, nce string) (err error) {\n\tdoc := &nonce{\n\t\tId:        nce,\n\t\tTimestamp: time.Now(),\n\t}\n\n\tcoll := db.Nonces()\n\n\t_, err = coll.InsertOne(db, doc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.DuplicateKeyError:\n\t\t\terr = &errortypes.AuthenticationError{\n\t\t\t\terrors.New(\"nonce: Duplicate authentication nonce\"),\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "notification/notification.go",
    "content": "package notification\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tclientTransport = &http.Transport{\n\t\tTLSHandshakeTimeout: 10 * time.Second,\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tMinVersion: tls.VersionTLS12,\n\t\t\tMaxVersion: tls.VersionTLS13,\n\t\t},\n\t}\n\tclient = &http.Client{\n\t\tTransport: clientTransport,\n\t\tTimeout:   15 * time.Second,\n\t}\n)\n\ntype notificationResp struct {\n\tWeb     bool   `json:\"web\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc Check() (err error) {\n\tu := &url.URL{\n\t\tScheme: \"https\",\n\t\tHost:   \"app.pritunl.com\",\n\t\tPath: fmt.Sprintf(\n\t\t\t\"/notification/cloud/%d\",\n\t\t\tutils.GetIntVer(constants.Version),\n\t\t),\n\t}\n\n\treq, err := http.NewRequestWithContext(\n\t\tcontext.Background(),\n\t\t\"GET\",\n\t\tu.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"notification: Request init error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"pritunl-cloud\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"notification: Request get error\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\t_ = res.Body.Close()\n\t}()\n\n\tif res.StatusCode != 200 {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\"notification: Bad status %d\", res.StatusCode),\n\t\t}\n\t\treturn\n\t}\n\n\tdata := &notificationResp{}\n\terr = json.NewDecoder(res.Body).Decode(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"notification: Failed to parse response body\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif data.Web {\n\t\tsettings.Local.DisableMsg = utils.FilterStr(data.Message, 256)\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"message\": settings.Local.DisableMsg,\n\t\t}).Error(\"notification: Disabling web server from vulnerability report\")\n\t\tsettings.Local.DisableWeb = true\n\t} else {\n\t\tsettings.Local.DisableWeb = false\n\t\tsettings.Local.DisableMsg = \"\"\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "oracle/iface.go",
    "content": "package oracle\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Iface struct {\n\tName       string\n\tAddress    string\n\tNamespace  string\n\tMacAddress string\n\tVnicId     string\n}\n\nvar (\n\tifaceLock = sync.Mutex{}\n)\n\nfunc GetIfaces(logOutput bool) (ifaces []*Iface, err error) {\n\tifaceLock.Lock()\n\tdefer ifaceLock.Unlock()\n\n\toutput, err := utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"does not have\",\n\t\t\t\"invalid metadata\",\n\t\t\t\"cannot locate\",\n\t\t},\n\t\t\"/usr/bin/bash\",\n\t\t\"/home/opc/secondary_vnic_all_configure.sh\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif logOutput {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"output\": output,\n\t\t}).Warn(\"oracle: Oracle iface output\")\n\t}\n\n\tfound := false\n\tfor _, line := range strings.Split(output, \"\\n\") {\n\t\tfields := strings.Fields(line)\n\t\tif len(fields) < 13 {\n\t\t\tif found {\n\t\t\t\tbreak\n\t\t\t} else {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif found && fields[0] == \"-\" && fields[5] != \"-\" {\n\t\t\tiface := &Iface{\n\t\t\t\tName:       fields[7],\n\t\t\t\tAddress:    fields[1],\n\t\t\t\tNamespace:  fields[5],\n\t\t\t\tMacAddress: fields[11],\n\t\t\t\tVnicId:     fields[12],\n\t\t\t}\n\n\t\t\tifaces = append(ifaces, iface)\n\t\t} else if fields[0] == \"CONFIG\" && fields[1] == \"ADDR\" &&\n\t\t\tfields[5] == \"NS\" && fields[7] == \"IFACE\" &&\n\t\t\tfields[11] == \"MAC\" && fields[12] == \"VNIC\" {\n\n\t\t\tfound = true\n\t\t\tcontinue\n\t\t}\n\n\t}\n\n\treturn\n}\n\nfunc ConfIfaces(logOutput bool) (err error) {\n\tifaceLock.Lock()\n\tdefer ifaceLock.Unlock()\n\n\toutput, err := utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"does not have\",\n\t\t\t\"invalid metadata\",\n\t\t\t\"cannot locate\",\n\t\t},\n\t\t\"/usr/bin/bash\",\n\t\t\"/home/opc/secondary_vnic_all_configure.sh\",\n\t\t\"-c\",\n\t\t\"-n\",\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif logOutput {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"output\": output,\n\t\t}).Warn(\"oracle: Oracle iface config output\")\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "oracle/metadata.go",
    "content": "package oracle\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tociLock sync.Mutex\n)\n\ntype Metadata struct {\n\tUserOcid        string\n\tPrivateKey      string\n\tRegionName      string\n\tTenancyOcid     string\n\tCompartmentOcid string\n\tInstanceOcid    string\n\tVnicOcid        string\n}\n\ntype OciMetaVnic struct {\n\tId                  string   `json:\"vnicId\"`\n\tVlanTag             int      `json:\"vlanTag\"`\n\tMacAddr             string   `json:\"macAddr\"`\n\tPrivateIp           string   `json:\"privateIp\"`\n\tVirtualRouterIp     string   `json:\"virtualRouterIp\"`\n\tSubnetCidrBlock     string   `json:\"subnetCidrBlock\"`\n\tIpv6Addresses       []string `json:\"ipv6Addresses\"`\n\tIpv6SubnetCidrBlock string   `json:\"ipv6SubnetCidrBlock\"`\n\tIpv6VirtualRouterIp string   `json:\"ipv6VirtualRouterIp\"`\n\tNicIndex            int      `json:\"nicIndex\"`\n}\n\ntype OciMetaInstance struct {\n\tId            string `json:\"id\"`\n\tDisplayName   string `json:\"displayName\"`\n\tCompartmentId string `json:\"compartmentId\"`\n\tRegionName    string `json:\"canonicalRegionName\"`\n\tShape         string `json:\"shape\"`\n}\n\ntype OciMeta struct {\n\tInstance OciMetaInstance `json:\"instance\"`\n\tVnics    []OciMetaVnic   `json:\"vnics\"`\n}\n\nfunc (o *OciMeta) IsBareMetal() bool {\n\tif strings.Contains(o.Instance.Shape, \"BM.\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc GetMetadata(authPv AuthProvider) (mdata *Metadata, err error) {\n\tuserOcid := authPv.OracleUser()\n\ttenancyOcid := authPv.OracleTenancy()\n\tprivateKey := authPv.OraclePrivateKey()\n\n\toutput, err := utils.ExecOutput(\"\", \"oci-metadata\", \"--json\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata := &OciMeta{}\n\n\terr = json.Unmarshal([]byte(output), data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to parse metadata\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvnicOcid := \"\"\n\tif data.Vnics != nil {\n\t\tfor _, vnic := range data.Vnics {\n\t\t\tvnicOcid = vnic.Id\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif vnicOcid == \"\" {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to get vnic in metadata\"),\n\t\t}\n\t\treturn\n\t}\n\n\tmdata = &Metadata{\n\t\tUserOcid:        userOcid,\n\t\tPrivateKey:      privateKey,\n\t\tRegionName:      data.Instance.RegionName,\n\t\tTenancyOcid:     tenancyOcid,\n\t\tCompartmentOcid: data.Instance.CompartmentId,\n\t\tInstanceOcid:    data.Instance.Id,\n\t\tVnicOcid:        vnicOcid,\n\t}\n\n\treturn\n}\n\nfunc GetOciMetadata() (mdata *OciMeta, err error) {\n\tociLock.Lock()\n\tdefer ociLock.Unlock()\n\n\toutput, err := utils.ExecOutput(\"\", \"oci-metadata\", \"--json\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tmdata = &OciMeta{}\n\n\terr = json.Unmarshal([]byte(output), mdata)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to parse metadata\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "oracle/oracle.go",
    "content": "package oracle\n\ntype AuthProvider interface {\n\tOracleUser() string\n\tOracleTenancy() string\n\tOraclePrivateKey() string\n}\n"
  },
  {
    "path": "oracle/provider.go",
    "content": "package oracle\n\nimport (\n\t\"crypto/rsa\"\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/oracle/oci-go-sdk/v65/common\"\n\t\"github.com/oracle/oci-go-sdk/v65/core\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Provider struct {\n\tMetadata      *Metadata\n\tprivateKey    *rsa.PrivateKey\n\ttenancy       string\n\tuser          string\n\tfingerprint   string\n\tregion        string\n\tcompartment   string\n\tnetClient     *core.VirtualNetworkClient\n\tcomputeClient *core.ComputeClient\n}\n\nfunc (p *Provider) LogInfo() {\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"region\":        p.Metadata.RegionName,\n\t\t\"tenancy\":       p.Metadata.TenancyOcid,\n\t\t\"compartment\":   p.Metadata.CompartmentOcid,\n\t\t\"instance\":      p.Metadata.InstanceOcid,\n\t\t\"instance_vnic\": p.Metadata.VnicOcid,\n\t\t\"user\":          p.Metadata.UserOcid,\n\t\t\"fingerprint\":   p.fingerprint,\n\t}).Info(\"oracle: Oracle provider data\")\n}\n\nfunc (p *Provider) AuthType() (common.AuthConfig, error) {\n\treturn common.AuthConfig{\n\t\tAuthType:         common.UserPrincipal,\n\t\tIsFromConfigFile: false,\n\t\tOboToken:         nil,\n\t}, nil\n}\n\nfunc (p *Provider) PrivateRSAKey() (*rsa.PrivateKey, error) {\n\treturn p.privateKey, nil\n}\n\nfunc (p *Provider) KeyID() (string, error) {\n\treturn fmt.Sprintf(\"%s/%s/%s\", p.tenancy, p.user, p.fingerprint), nil\n}\n\nfunc (p *Provider) TenancyOCID() (string, error) {\n\treturn p.tenancy, nil\n}\n\nfunc (p *Provider) UserOCID() (string, error) {\n\treturn p.user, nil\n}\n\nfunc (p *Provider) KeyFingerprint() (string, error) {\n\treturn p.fingerprint, nil\n}\n\nfunc (p *Provider) Region() (string, error) {\n\treturn p.region, nil\n}\n\nfunc (p *Provider) CompartmentOCID() (string, error) {\n\treturn p.compartment, nil\n}\n\nfunc (p *Provider) GetNetworkClient() (\n\tnetClient *core.VirtualNetworkClient, err error) {\n\n\tif p.netClient != nil {\n\t\tnetClient = p.netClient\n\t\treturn\n\t}\n\n\tclient, err := core.NewVirtualNetworkClientWithConfigurationProvider(p)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to create oracle client\"),\n\t\t}\n\t\treturn\n\t}\n\n\tp.netClient = &client\n\tnetClient = p.netClient\n\n\treturn\n}\n\nfunc (p *Provider) GetComputeClient() (\n\tcomputeClient *core.ComputeClient, err error) {\n\n\tif p.computeClient != nil {\n\t\tcomputeClient = p.computeClient\n\t\treturn\n\t}\n\n\tclient, err := core.NewComputeClientWithConfigurationProvider(p)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to create oracle client\"),\n\t\t}\n\t\treturn\n\t}\n\n\tp.computeClient = &client\n\tcomputeClient = p.computeClient\n\n\treturn\n}\n\nfunc NewProvider(authPv AuthProvider) (prov *Provider, err error) {\n\tmdata, err := GetMetadata(authPv)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tprivateKey, fingerprint, err := loadPrivateKey(mdata)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tprov = &Provider{\n\t\tMetadata:    mdata,\n\t\tprivateKey:  privateKey,\n\t\ttenancy:     mdata.TenancyOcid,\n\t\tuser:        mdata.UserOcid,\n\t\tfingerprint: fingerprint,\n\t\tregion:      mdata.RegionName,\n\t\tcompartment: mdata.CompartmentOcid,\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "oracle/routetable.go",
    "content": "package oracle\n\nimport (\n\t\"context\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/oracle/oci-go-sdk/v65/core\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype RouteTable struct {\n\tId         string\n\tVcnId      string\n\tRoutes     map[string]string\n\trouteRules []core.RouteRule\n}\n\nfunc (r *RouteTable) RouteExists(dest string, nextHopId string) bool {\n\tif r.Routes[dest] == nextHopId {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (r *RouteTable) RouteUpsert(dest string, nextHopId string) bool {\n\tfor i, routeRule := range r.routeRules {\n\t\tif routeRule.Destination != nil &&\n\t\t\t*routeRule.Destination == dest {\n\n\t\t\tif routeRule.NetworkEntityId != nil &&\n\t\t\t\t*routeRule.NetworkEntityId != nextHopId {\n\n\t\t\t\trouteRule.NetworkEntityId = &nextHopId\n\t\t\t\tr.routeRules[i] = routeRule\n\t\t\t\treturn true\n\t\t\t} else {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\trouteRule := core.RouteRule{\n\t\tDestination:     &dest,\n\t\tNetworkEntityId: &nextHopId,\n\t}\n\tr.routeRules = append(r.routeRules, routeRule)\n\treturn true\n}\n\nfunc (r *RouteTable) CommitRouteRules(pv *Provider) (err error) {\n\tclient, err := pv.GetNetworkClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq := core.UpdateRouteTableRequest{\n\t\tRtId: &r.Id,\n\t\tUpdateRouteTableDetails: core.UpdateRouteTableDetails{\n\t\t\tRouteRules: r.routeRules,\n\t\t},\n\t}\n\n\t_, err = client.UpdateRouteTable(context.Background(), req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to update route table\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetRouteTables(pv *Provider, vcnId string) (\n\ttables []*RouteTable, err error) {\n\n\tlimit := 100\n\tcompartmentId, err := pv.CompartmentOCID()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tclient, err := pv.GetNetworkClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvnicReq := core.ListRouteTablesRequest{\n\t\tCompartmentId: &compartmentId,\n\t\tVcnId:         &vcnId,\n\t\tLimit:         &limit,\n\t}\n\n\torcTables, err := client.ListRouteTables(context.Background(), vnicReq)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to list route tables\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttables = []*RouteTable{}\n\tif orcTables.Items != nil {\n\t\tfor _, orcTable := range orcTables.Items {\n\t\t\ttable := &RouteTable{}\n\n\t\t\tif orcTable.Id != nil {\n\t\t\t\ttable.Id = *orcTable.Id\n\t\t\t}\n\t\t\tif orcTable.VcnId != nil {\n\t\t\t\ttable.VcnId = *orcTable.VcnId\n\t\t\t}\n\t\t\tif orcTable.RouteRules != nil {\n\t\t\t\ttable.routeRules = orcTable.RouteRules\n\t\t\t} else {\n\t\t\t\ttable.routeRules = []core.RouteRule{}\n\t\t\t}\n\n\t\t\troutes := map[string]string{}\n\t\t\tfor _, rule := range table.routeRules {\n\t\t\t\tif rule.Destination == nil || rule.NetworkEntityId == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\troutes[*rule.Destination] = *rule.NetworkEntityId\n\t\t\t}\n\t\t\ttable.Routes = routes\n\n\t\t\ttables = append(tables, table)\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "oracle/subnet.go",
    "content": "package oracle\n\nimport (\n\t\"context\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/oracle/oci-go-sdk/v65/core\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Vcn struct {\n\tId      string\n\tName    string\n\tNetwork string\n\tSubnets []*Subnet\n}\n\ntype Subnet struct {\n\tId      string\n\tVcnId   string\n\tName    string\n\tNetwork string\n}\n\nfunc GetSubnet(pv *Provider, subnetId string) (subnet *Subnet, err error) {\n\tclient, err := pv.GetNetworkClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsubReq := core.GetSubnetRequest{\n\t\tSubnetId: &subnetId,\n\t}\n\n\torcSubnet, err := client.GetSubnet(context.Background(), subReq)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to get subnet\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsubnet = &Subnet{}\n\tif orcSubnet.Id != nil {\n\t\tsubnet.Id = *orcSubnet.Id\n\t}\n\tif orcSubnet.VcnId != nil {\n\t\tsubnet.VcnId = *orcSubnet.VcnId\n\t}\n\tif orcSubnet.DisplayName != nil {\n\t\tsubnet.Name = *orcSubnet.DisplayName\n\t}\n\tif orcSubnet.CidrBlock != nil {\n\t\tsubnet.Network = *orcSubnet.CidrBlock\n\t}\n\n\treturn\n}\n\nfunc GetVcns(pv *Provider) (vcns []*Vcn, err error) {\n\tclient, err := pv.GetNetworkClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcompartmentId, err := pv.CompartmentOCID()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq := core.ListVcnsRequest{\n\t\tCompartmentId: &compartmentId,\n\t\tLimit:         utils.PointerInt(100),\n\t}\n\n\torcVcns, err := client.ListVcns(context.Background(), req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to get VCNs\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvcns = []*Vcn{}\n\n\tfor _, orcVcn := range orcVcns.Items {\n\t\tvcn := &Vcn{}\n\n\t\tif orcVcn.Id != nil {\n\t\t\tvcn.Id = *orcVcn.Id\n\t\t}\n\t\tif orcVcn.DisplayName != nil {\n\t\t\tvcn.Name = *orcVcn.DisplayName\n\t\t}\n\t\tif orcVcn.CidrBlock != nil {\n\t\t\tvcn.Network = *orcVcn.CidrBlock\n\t\t}\n\n\t\tsubnets, e := GetSubnets(pv, vcn.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tvcn.Subnets = subnets\n\n\t\tvcns = append(vcns, vcn)\n\t}\n\n\treturn\n}\n\nfunc GetSubnets(pv *Provider, vcnId string) (subnets []*Subnet, err error) {\n\tclient, err := pv.GetNetworkClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcompartmentId, err := pv.CompartmentOCID()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq := core.ListSubnetsRequest{\n\t\tCompartmentId: &compartmentId,\n\t\tVcnId:         utils.PointerString(vcnId),\n\t\tLimit:         utils.PointerInt(256),\n\t}\n\n\torcSubnets, err := client.ListSubnets(context.Background(), req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to get subnets\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsubnets = []*Subnet{}\n\tfor _, orcSubnet := range orcSubnets.Items {\n\t\tsubnet := &Subnet{}\n\n\t\tif orcSubnet.Id != nil {\n\t\t\tsubnet.Id = *orcSubnet.Id\n\t\t}\n\t\tif orcSubnet.VcnId != nil {\n\t\t\tsubnet.VcnId = *orcSubnet.VcnId\n\t\t}\n\t\tif orcSubnet.DisplayName != nil {\n\t\t\tsubnet.Name = *orcSubnet.DisplayName\n\t\t}\n\t\tif orcSubnet.CidrBlock != nil {\n\t\t\tsubnet.Network = *orcSubnet.CidrBlock\n\t\t}\n\n\t\tsubnets = append(subnets, subnet)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "oracle/utils.go",
    "content": "package oracle\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc loadPrivateKey(mdata *Metadata) (\n\tkey *rsa.PrivateKey, fingerprint string, err error) {\n\n\tblock, _ := pem.Decode([]byte(mdata.PrivateKey))\n\tif block == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"oracle: Failed to decode private key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif block.Type != \"RSA PRIVATE KEY\" {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"oracle: Invalid private key type\"),\n\t\t}\n\t\treturn\n\t}\n\n\tkey, err = x509.ParsePKCS1PrivateKey(block.Bytes)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to parse rsa key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tpubKey, err := x509.MarshalPKIXPublicKey(key.Public())\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to marshal public key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tkeyHash := md5.New()\n\tkeyHash.Write(pubKey)\n\tfingerprint = fmt.Sprintf(\"%x\", keyHash.Sum(nil))\n\tfingerprintBuf := bytes.Buffer{}\n\n\tfor i, run := range fingerprint {\n\t\tfingerprintBuf.WriteRune(run)\n\t\tif i%2 == 1 && i != len(fingerprint)-1 {\n\t\t\tfingerprintBuf.WriteRune(':')\n\t\t}\n\t}\n\tfingerprint = fingerprintBuf.String()\n\n\treturn\n}\n"
  },
  {
    "path": "oracle/vnic.go",
    "content": "package oracle\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/oracle/oci-go-sdk/v65/core\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Vnic struct {\n\tId                  string\n\tSubnetId            string\n\tIsPrimary           bool\n\tMacAddress          string\n\tPrivateIp           string\n\tPrivateIpId         string\n\tPublicIp            string\n\tPublicIp6           string\n\tSkipSourceDestCheck bool\n}\n\nfunc (v *Vnic) SetSkipSourceDestCheck(pv *Provider, val bool) (err error) {\n\tclient, err := pv.GetNetworkClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq := core.UpdateVnicRequest{\n\t\tVnicId: &v.Id,\n\t\tUpdateVnicDetails: core.UpdateVnicDetails{\n\t\t\tSkipSourceDestCheck: &val,\n\t\t},\n\t}\n\n\t_, err = client.UpdateVnic(context.Background(), req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to update vnic\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetVnic(pv *Provider, vnicId string) (vnic *Vnic, err error) {\n\tclient, err := pv.GetNetworkClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq := core.GetVnicRequest{\n\t\tVnicId: utils.PointerString(vnicId),\n\t}\n\n\torcVnic, err := client.GetVnic(context.Background(), req)\n\tif err != nil {\n\t\tif orcVnic.RawResponse != nil &&\n\t\t\torcVnic.RawResponse.StatusCode == 404 {\n\n\t\t\terr = &errortypes.NotFoundError{\n\t\t\t\terrors.Wrap(err, \"oracle: Failed to find vnic\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to get vnic\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvnic = &Vnic{}\n\tif orcVnic.Id != nil {\n\t\tvnic.Id = *orcVnic.Id\n\t}\n\tif orcVnic.SubnetId != nil {\n\t\tvnic.SubnetId = *orcVnic.SubnetId\n\t}\n\tif orcVnic.IsPrimary != nil {\n\t\tvnic.IsPrimary = *orcVnic.IsPrimary\n\t}\n\tif orcVnic.MacAddress != nil {\n\t\tvnic.MacAddress = *orcVnic.MacAddress\n\t}\n\tif orcVnic.PrivateIp != nil {\n\t\tvnic.PrivateIp = *orcVnic.PrivateIp\n\t}\n\tif orcVnic.PublicIp != nil {\n\t\tvnic.PublicIp = *orcVnic.PublicIp\n\t}\n\tif len(orcVnic.Ipv6Addresses) > 0 {\n\t\tvnic.PublicIp6 = orcVnic.Ipv6Addresses[0]\n\t}\n\tif orcVnic.SkipSourceDestCheck != nil {\n\t\tvnic.SkipSourceDestCheck = *orcVnic.SkipSourceDestCheck\n\t}\n\n\tlimit := 10\n\tipReq := core.ListPrivateIpsRequest{\n\t\tVnicId: &vnic.Id,\n\t\tLimit:  &limit,\n\t}\n\n\torcIps, err := client.ListPrivateIps(context.Background(), ipReq)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to get vnic ips\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif orcIps.Items != nil {\n\t\tfor _, orcIp := range orcIps.Items {\n\t\t\tif orcIp.IsPrimary != nil && *orcIp.IsPrimary &&\n\t\t\t\torcIp.Id != nil {\n\n\t\t\t\tvnic.PrivateIpId = *orcIp.Id\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc getVnicAttachment(pv *Provider, attachmentId string) (\n\tvnicId string, err error) {\n\n\tclient, err := pv.GetComputeClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq := core.GetVnicAttachmentRequest{\n\t\tVnicAttachmentId: utils.PointerString(attachmentId),\n\t}\n\n\tresp, err := client.GetVnicAttachment(context.Background(), req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"oracle: Failed to create vnic\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif resp.VnicId != nil {\n\t\tvnicId = *resp.VnicId\n\t}\n\n\treturn\n}\n\nfunc CreateVnic(pv *Provider, name, subnetId string,\n\tpublicIp, publicIp6 bool) (vnicId, vnicAttachId string, err error) {\n\n\tclient, err := pv.GetComputeClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq := core.AttachVnicRequest{\n\t\tAttachVnicDetails: core.AttachVnicDetails{\n\t\t\tInstanceId:  utils.PointerString(pv.Metadata.InstanceOcid),\n\t\t\tDisplayName: utils.PointerString(name),\n\t\t\tCreateVnicDetails: &core.CreateVnicDetails{\n\t\t\t\tAssignPublicIp: utils.PointerBool(publicIp),\n\t\t\t\tAssignIpv6Ip:   utils.PointerBool(publicIp6),\n\t\t\t\tDisplayName:    utils.PointerString(name),\n\t\t\t\tSubnetId:       utils.PointerString(subnetId),\n\t\t\t},\n\t\t},\n\t}\n\n\tvar resp core.AttachVnicResponse\n\n\tretryCount := settings.System.OracleApiRetryCount\n\tretryRate := time.Duration(\n\t\tsettings.System.OracleApiRetryRate) * time.Second\n\n\tfor i := 0; i < retryCount; i++ {\n\t\tresp, err = client.AttachVnic(context.Background(), req)\n\t\tif err != nil {\n\t\t\tif i != retryCount-1 && resp.RawResponse != nil &&\n\t\t\t\tresp.RawResponse.StatusCode == 409 {\n\n\t\t\t\ttime.Sleep(retryRate)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"oracle: Failed to create vnic\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tbreak\n\t}\n\n\tif resp.Id == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"oracle: Nil vnic attachment id\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvnicAttachId = *resp.Id\n\n\tfor i := 0; i < 60; i++ {\n\t\tvnicId, err = getVnicAttachment(pv, vnicAttachId)\n\t\tif err != nil {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\tif i == 59 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = nil\n\t\t\tcontinue\n\t\t}\n\n\t\tif vnicId == \"\" {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\tcontinue\n\t\t}\n\n\t\tbreak\n\t}\n\n\tif vnicId == \"\" {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"oracle: Nil vnic id\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveVnic(pv *Provider, vnicAttachId string) (err error) {\n\tclient, err := pv.GetComputeClient()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq := core.DetachVnicRequest{\n\t\tVnicAttachmentId: utils.PointerString(vnicAttachId),\n\t}\n\n\tretryCount := settings.System.OracleApiRetryCount\n\tretryRate := time.Duration(\n\t\tsettings.System.OracleApiRetryRate) * time.Second\n\n\tfor i := 0; i < retryCount; i++ {\n\t\tresp, e := client.DetachVnic(context.Background(), req)\n\t\tif e != nil {\n\t\t\tif i != retryCount-1 && resp.RawResponse != nil &&\n\t\t\t\tresp.RawResponse.StatusCode == 409 {\n\n\t\t\t\ttime.Sleep(retryRate)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(e, \"oracle: Failed to remove vnic\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tbreak\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "organization/organization.go",
    "content": "package organization\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Organization struct {\n\tId      bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tRoles   []string      `bson:\"roles\" json:\"roles\"`\n\tName    string        `bson:\"name\" json:\"name\"`\n\tComment string        `bson:\"comment\" json:\"comment\"`\n}\n\nfunc (d *Organization) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\td.Name = utils.FilterName(d.Name)\n\n\tif d.Roles == nil {\n\t\td.Roles = []string{}\n\t}\n\n\treturn\n}\n\nfunc (d *Organization) Commit(db *database.Database) (err error) {\n\tcoll := db.Organizations()\n\n\terr = coll.Commit(d.Id, d)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (d *Organization) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Organizations()\n\n\terr = coll.CommitFields(d.Id, d, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Organization) Insert(db *database.Database) (err error) {\n\tcoll := db.Organizations()\n\n\tif !c.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"organization: Organization already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := coll.InsertOne(db, c)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tc.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  },
  {
    "path": "organization/utils.go",
    "content": "package organization\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, dcId bson.ObjectID) (\n\tdc *Organization, err error) {\n\n\tcoll := db.Organizations()\n\tdc = &Organization{}\n\n\terr = coll.FindOneId(dcId, dc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\torgs []*Organization, err error) {\n\n\tcoll := db.Organizations()\n\torgs = []*Organization{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\torg := &Organization{}\n\t\terr = cursor.Decode(org)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\torgs = append(orgs, org)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllName(db *database.Database) (orgs []*Organization, err error) {\n\tcoll := db.Organizations()\n\torgs = []*Organization{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{},\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetProjection(bson.D{{\"name\", 1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\torg := &Organization{}\n\t\terr = cursor.Decode(org)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\torgs = append(orgs, org)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNameRoles(db *database.Database, roles []string) (\n\torgs []*Organization, err error) {\n\n\tcoll := db.Organizations()\n\torgs = []*Organization{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"roles\": &bson.M{\n\t\t\t\t\"$in\": roles,\n\t\t\t},\n\t\t},\n\t\toptions.Find().\n\t\t\tSetProjection(bson.D{{\"name\", 1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\torg := &Organization{}\n\t\terr = cursor.Decode(org)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\torgs = append(orgs, org)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (orgs []*Organization, count int64, err error) {\n\n\tcoll := db.Organizations()\n\torgs = []*Organization{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\torg := &Organization{}\n\t\terr = cursor.Decode(org)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\torgs = append(orgs, org)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, dcId bson.ObjectID) (err error) {\n\tcoll := db.Organizations()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": dcId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Count(db *database.Database) (count int64, err error) {\n\tcoll := db.Organizations()\n\n\tcount, err = coll.CountDocuments(db, &bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "paths/paths.go",
    "content": "package paths\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/base32\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nfunc GetVmUuid(instId bson.ObjectID) string {\n\tidHash := md5.New()\n\tidHash.Write(instId[:])\n\tuuid := idHash.Sum(nil)\n\n\tuuid[6] = (uuid[6] & 0x0f) | uint8((3&0xf)<<4)\n\tuuid[8] = (uuid[8] & 0x3f) | 0x80\n\n\tbuffer := [36]byte{}\n\thex.Encode(buffer[:], uuid[:4])\n\tbuffer[8] = '-'\n\thex.Encode(buffer[9:13], uuid[4:6])\n\tbuffer[13] = '-'\n\thex.Encode(buffer[14:18], uuid[6:8])\n\tbuffer[18] = '-'\n\thex.Encode(buffer[19:23], uuid[8:10])\n\tbuffer[23] = '-'\n\thex.Encode(buffer[24:], uuid[10:])\n\treturn string(buffer[:])\n}\n\nfunc GetVmPath(instId bson.ObjectID) string {\n\treturn path.Join(node.Self.GetVirtPath(),\n\t\t\"instances\", instId.Hex())\n}\n\nfunc GetDisksPath() string {\n\treturn path.Join(node.Self.GetVirtPath(), \"disks\")\n}\n\nfunc GetLocalIsosPath() string {\n\treturn path.Join(node.Self.GetVirtPath(), \"isos\")\n}\n\nfunc GetBackingPath() string {\n\treturn path.Join(node.Self.GetVirtPath(), \"backing\")\n}\n\nfunc GetTpmsPath() string {\n\treturn path.Join(node.Self.GetVirtPath(), \"tpms\")\n}\n\nfunc GetTpmPath(virtId bson.ObjectID) string {\n\treturn path.Join(GetTpmsPath(), virtId.Hex())\n}\n\nfunc GetTpmSockPath(virtId bson.ObjectID) string {\n\treturn path.Join(GetTpmsPath(), virtId.Hex(), \"sock\")\n}\n\nfunc GetTpmPwdPath(virtId bson.ObjectID) string {\n\treturn path.Join(GetTpmsPath(), virtId.Hex(), \"pwd\")\n}\n\nfunc GetTempPath() string {\n\treturn node.Self.GetTempPath()\n}\n\nfunc GetTempDir() string {\n\treturn path.Join(GetTempPath(), bson.NewObjectID().Hex())\n}\n\nfunc GetDrivePath(driveId string) string {\n\treturn path.Join(\"/dev/disk/by-id\", driveId)\n}\n\nfunc GetCachesDir() string {\n\treturn path.Join(node.Self.GetVirtPath(), \"caches\")\n}\n\nfunc GetCacheDir(virtId bson.ObjectID) string {\n\treturn path.Join(GetCachesDir(), virtId.Hex())\n}\n\nfunc GetOvmfDir() string {\n\treturn path.Join(node.Self.GetVirtPath(), \"ovmf\")\n}\n\nfunc GetDiskPath(diskId bson.ObjectID) string {\n\treturn path.Join(GetDisksPath(),\n\t\tfmt.Sprintf(\"%s.qcow2\", diskId.Hex()))\n}\n\nfunc GetOvmfVarsPath(virtId bson.ObjectID) string {\n\treturn path.Join(GetOvmfDir(),\n\t\tfmt.Sprintf(\"%s_vars.fd\", virtId.Hex()))\n}\n\nfunc GetDiskTempPath() string {\n\treturn path.Join(GetTempPath(),\n\t\tfmt.Sprintf(\"disk-%s\", bson.NewObjectID().Hex()))\n}\n\nfunc GetImageTempPath() string {\n\treturn path.Join(GetTempPath(),\n\t\tfmt.Sprintf(\"image-%s\", bson.NewObjectID().Hex()))\n}\n\nfunc GetImdsPath() string {\n\treturn path.Join(node.Self.GetVirtPath(), \"imds\")\n}\n\nfunc GetImdsConfPath(instId bson.ObjectID) string {\n\treturn path.Join(GetImdsPath(),\n\t\tfmt.Sprintf(\"%s-conf.json\", instId.Hex()))\n}\n\nfunc GetInstRunPath(instId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.RunPath, instId.Hex())\n}\n\nfunc GetImdsSockPath(instId bson.ObjectID) string {\n\treturn path.Join(GetInstRunPath(instId), \"imds.sock\")\n}\n\nfunc GetDiskMountPath() string {\n\treturn path.Join(GetTempPath(), bson.NewObjectID().Hex())\n}\n\nfunc GetInitsPath() string {\n\treturn path.Join(node.Self.GetVirtPath(), \"inits\")\n}\n\nfunc GetInitPath(instId bson.ObjectID) string {\n\treturn path.Join(GetInitsPath(),\n\t\tfmt.Sprintf(\"%s.iso\", instId.Hex()))\n}\n\nfunc GetUnitName(virtId bson.ObjectID) string {\n\treturn fmt.Sprintf(\"pritunl_cloud_%s.service\", virtId.Hex())\n}\n\nfunc GetUnitPath(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.SystemdPath, GetUnitName(virtId))\n}\n\nfunc GetUnitNameDhcp4(virtId bson.ObjectID, n int) string {\n\treturn fmt.Sprintf(\"pritunl_dhcp4_%s_%d.service\", virtId.Hex(), n)\n}\n\nfunc GetUnitPathDhcp4(virtId bson.ObjectID, n int) string {\n\treturn path.Join(settings.Hypervisor.SystemdPath, GetUnitNameDhcp4(virtId, n))\n}\n\nfunc GetUnitNameDhcp6(virtId bson.ObjectID, n int) string {\n\treturn fmt.Sprintf(\"pritunl_dhcp6_%s_%d.service\", virtId.Hex(), n)\n}\n\nfunc GetUnitPathDhcp6(virtId bson.ObjectID, n int) string {\n\treturn path.Join(settings.Hypervisor.SystemdPath, GetUnitNameDhcp6(virtId, n))\n}\n\nfunc GetUnitNameNdp(virtId bson.ObjectID, n int) string {\n\treturn fmt.Sprintf(\"pritunl_ndp_%s_%d.service\", virtId.Hex(), n)\n}\n\nfunc GetUnitPathNdp(virtId bson.ObjectID, n int) string {\n\treturn path.Join(settings.Hypervisor.SystemdPath,\n\t\tGetUnitNameNdp(virtId, n))\n}\n\nfunc GetUnitNameTpm(virtId bson.ObjectID) string {\n\treturn fmt.Sprintf(\"pritunl_tpm_%s.service\", virtId.Hex())\n}\n\nfunc GetUnitPathTpm(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.SystemdPath,\n\t\tGetUnitNameTpm(virtId))\n}\n\nfunc GetUnitNameImds(virtId bson.ObjectID) string {\n\treturn fmt.Sprintf(\"pritunl_imds_%s.service\", virtId.Hex())\n}\n\nfunc GetUnitNameDhcpc(virtId bson.ObjectID) string {\n\treturn fmt.Sprintf(\"pritunl_dhcpc_%s.service\", virtId.Hex())\n}\n\nfunc GetShareId(virtId bson.ObjectID, shareName string) string {\n\thash := md5.New()\n\thash.Write([]byte(virtId.Hex()))\n\thash.Write([]byte(shareName))\n\treturn strings.ToLower(base32.StdEncoding.EncodeToString(\n\t\thash.Sum(nil))[:12])\n}\n\nfunc GetUnitNameShare(virtId bson.ObjectID, shareId string) string {\n\treturn fmt.Sprintf(\"pritunl_share_%s_%s.service\", virtId.Hex(), shareId)\n}\n\nfunc GetUnitNameShares(virtId bson.ObjectID) string {\n\treturn fmt.Sprintf(\"pritunl_share_%s_*.service\", virtId.Hex())\n}\n\nfunc GetUnitPathImds(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.SystemdPath,\n\t\tGetUnitNameImds(virtId))\n}\n\nfunc GetUnitPathDhcpc(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.SystemdPath,\n\t\tGetUnitNameDhcpc(virtId))\n}\n\nfunc GetUnitPathShare(virtId bson.ObjectID, shareId string) string {\n\treturn path.Join(settings.Hypervisor.SystemdPath,\n\t\tGetUnitNameShare(virtId, shareId))\n}\n\nfunc GetUnitPathShares(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.SystemdPath,\n\t\tGetUnitNameShares(virtId))\n}\n\nfunc GetPidPath(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.RunPath,\n\t\tfmt.Sprintf(\"%s.pid\", virtId.Hex()))\n}\n\nfunc GetShareSockPath(virtId bson.ObjectID, shareId string) string {\n\treturn path.Join(GetInstRunPath(virtId),\n\t\tfmt.Sprintf(\"virtiofs_%s.sock\", shareId))\n}\n\nfunc GetHugepagePath(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.HugepagesPath, virtId.Hex())\n}\n\nfunc GetSockPath(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.RunPath,\n\t\tfmt.Sprintf(\"%s.sock\", virtId.Hex()))\n}\n\nfunc GetQmpSockPath(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.RunPath,\n\t\tfmt.Sprintf(\"%s.qmp.sock\", virtId.Hex()))\n}\n\nfunc GetGuestPath(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.RunPath,\n\t\tfmt.Sprintf(\"%s.guest\", virtId.Hex()))\n}\n\n// TODO Backward compatibility\nfunc GetPidPathOld(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.LibPath,\n\t\tfmt.Sprintf(\"%s.pid\", virtId.Hex()))\n}\n\n// TODO Backward compatibility\nfunc GetSockPathOld(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.LibPath,\n\t\tfmt.Sprintf(\"%s.sock\", virtId.Hex()))\n}\n\n// TODO Backward compatibility\nfunc GetQmpSockPathOld(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.LibPath,\n\t\tfmt.Sprintf(\"%s.qmp.sock\", virtId.Hex()))\n}\n\n// TODO Backward compatibility\nfunc GetGuestPathOld(virtId bson.ObjectID) string {\n\treturn path.Join(settings.Hypervisor.LibPath,\n\t\tfmt.Sprintf(\"%s.guest\", virtId.Hex()))\n}\n\nfunc GetNamespacesPath() string {\n\treturn \"/etc/netns\"\n}\n\nfunc GetNamespacePath(namespace string) string {\n\treturn path.Join(GetNamespacesPath(), namespace)\n}\n"
  },
  {
    "path": "paths/utils.go",
    "content": "package paths\n\nimport (\n\t\"os\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nvar (\n\tovmfCodePaths = []string{\n\t\t\"/usr/share/edk2/ovmf/OVMF_CODE.fd\",\n\t\t\"/usr/share/edk2/ovmf/OVMF_CODE.cc.fd\",\n\t\t\"/usr/share/OVMF/OVMF_CODE.pure-efi.fd\",\n\t\t\"/usr/share/OVMF/OVMF_CODE.fd\",\n\t}\n\tovmfVarsPaths = []string{\n\t\t\"/usr/share/edk2/ovmf/OVMF_VARS.fd\",\n\t\t\"/usr/share/OVMF/OVMF_VARS.pure-efi.fd\",\n\t\t\"/usr/share/OVMF/OVMF_VARS.fd\",\n\t}\n\tovmfSecureCodePaths = []string{\n\t\t\"/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd\",\n\t\t\"/usr/share/OVMF/OVMF_CODE.secboot.fd\",\n\t}\n\tovmfSecureVarsPaths = []string{\n\t\t\"/usr/share/edk2/ovmf/OVMF_VARS.secboot.fd\",\n\t\t\"/usr/share/OVMF/OVMF_VARS.secboot.fd\",\n\t\t\"/usr/share/OVMF/OVMF_VARS.fd\",\n\t}\n)\n\nfunc existsFile(pth string) (exists bool, err error) {\n\t_, err = os.Stat(pth)\n\tif err == nil {\n\t\texists = true\n\t\treturn\n\t}\n\n\tif os.IsNotExist(err) {\n\t\terr = nil\n\t\treturn\n\t}\n\n\terr = &errortypes.ReadError{\n\t\terrors.Wrapf(err, \"paths: Failed to stat %s\", pth),\n\t}\n\treturn\n}\n\nfunc FindOvmfCodePath(secureBoot bool) (pth string, err error) {\n\tif secureBoot {\n\t\tpth = settings.Hypervisor.OvmfSecureCodePath\n\t\tif pth != \"\" {\n\t\t\treturn\n\t\t}\n\n\t\tfor _, pth = range ovmfSecureCodePaths {\n\t\t\texists, e := existsFile(pth)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif exists {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\tpth = settings.Hypervisor.OvmfCodePath\n\t\tif pth != \"\" {\n\t\t\treturn\n\t\t}\n\n\t\tfor _, pth = range ovmfCodePaths {\n\t\t\texists, e := existsFile(pth)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif exists {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tpth = \"\"\n\terr = &errortypes.NotFoundError{\n\t\terrors.New(\"paths: Failed to find OVMF code file\"),\n\t}\n\treturn\n}\n\nfunc FindOvmfVarsPath(secureBoot bool) (pth string, err error) {\n\tif secureBoot {\n\t\tpth = settings.Hypervisor.OvmfSecureVarsPath\n\t\tif pth != \"\" {\n\t\t\treturn\n\t\t}\n\n\t\tfor _, pth = range ovmfSecureVarsPaths {\n\t\t\texists, e := existsFile(pth)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif exists {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\tpth = settings.Hypervisor.OvmfVarsPath\n\t\tif pth != \"\" {\n\t\t\treturn\n\t\t}\n\n\t\tfor _, pth = range ovmfVarsPaths {\n\t\t\texists, e := existsFile(pth)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif exists {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tpth = \"\"\n\terr = &errortypes.NotFoundError{\n\t\terrors.New(\"paths: Failed to find OVMF vars file\"),\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pci/pci.go",
    "content": "package pci\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\nvar (\n\tsyncLast  time.Time\n\tsyncLock  sync.Mutex\n\tsyncCache []*Device\n)\n\ntype Device struct {\n\tSlot   string `bson:\"slot\" json:\"slot\"`\n\tClass  string `bson:\"class\" json:\"class\"`\n\tName   string `bson:\"name\" json:\"name\"`\n\tDriver string `bson:\"driver\" json:\"driver\"`\n}\n"
  },
  {
    "path": "pci/utils.go",
    "content": "package pci\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\treg = regexp.MustCompile(\n\t\t\"[a-fA-F0-9][a-fA-F0-9]:[a-fA-F0-9][a-fA-F0-9].[0-9]\")\n)\n\nfunc CheckSlot(slot string) bool {\n\treturn reg.MatchString(slot)\n}\n\nfunc GetVfio(slot string) (dev *Device, err error) {\n\tdevices, err := GetVfioAll()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, device := range devices {\n\t\tif device.Slot == slot {\n\t\t\tdev = device\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GetVfioAll() (devices []*Device, err error) {\n\tif time.Since(syncLast) < 30*time.Second {\n\t\tdevices = syncCache\n\t\treturn\n\t}\n\tsyncLock.Lock()\n\tdefer syncLock.Unlock()\n\n\tdevices = []*Device{}\n\n\toutput, err := utils.ExecOutput(\"\", \"lspci\", \"-v\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdev := &Device{}\n\n\toutputLines := strings.Split(output, \"\\n\")\n\tfor _, line := range outputLines {\n\t\tif strings.TrimSpace(line) == \"\" {\n\t\t\tif dev.Slot != \"\" && dev.Name != \"\" &&\n\t\t\t\tdev.Driver == \"vfio-pci\" && CheckSlot(dev.Slot) {\n\n\t\t\t\tdevices = append(devices, dev)\n\t\t\t}\n\n\t\t\tdev = &Device{}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif dev.Slot == \"\" {\n\t\t\tlines := strings.SplitN(line, \" \", 2)\n\t\t\tif len(lines) != 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnames := strings.SplitN(lines[1], \":\", 2)\n\t\t\tif len(names) != 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdev.Slot = strings.TrimSpace(lines[0])\n\t\t\tdev.Class = strings.TrimSpace(names[0])\n\t\t\tdev.Name = strings.TrimSpace(names[1])\n\t\t} else if strings.Contains(line, \"Kernel driver in use:\") {\n\t\t\tlines := strings.SplitN(line, \":\", 2)\n\t\t\tif len(lines) != 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdev.Driver = strings.TrimSpace(lines[1])\n\t\t}\n\t}\n\n\tsyncCache = devices\n\tsyncLast = time.Now()\n\n\treturn\n}\n"
  },
  {
    "path": "permission/permission.go",
    "content": "package permission\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc chown(virt *vm.VirtualMachine, path string) (err error) {\n\terr = os.Chown(path, virt.UnixId, 0)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Newf(\n\t\t\t\t\"permission: Failed to set owner of '%s' to '%d'\",\n\t\t\t\tpath, virt.UnixId,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc touchChown(virt *vm.VirtualMachine, path string) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"touch\", path,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = chown(virt, path)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc mkdirChown(virt *vm.VirtualMachine, path string) (err error) {\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"mkdir\", \"-p\", path,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = chown(virt, path)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Restore(pth string) (err error) {\n\terr = os.Chown(pth, 0, 0)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Newf(\n\t\t\t\t\"permission: Failed to set owner of '%s' to '0'\", pth,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Chown(virt *vm.VirtualMachine, pth string) (err error) {\n\terr = chown(virt, pth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc InitVirt(virt *vm.VirtualMachine) (err error) {\n\terr = UserAdd(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif virt.Uefi {\n\t\terr = chown(virt, paths.GetOvmfVarsPath(virt.Id))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = chown(virt, paths.GetInitPath(virt.Id))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, disk := range virt.Disks {\n\t\terr = chown(virt, disk.Path)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, device := range virt.DriveDevices {\n\t\tdrivePth := \"\"\n\t\tif device.Type == vm.Lvm {\n\t\t\tdrivePth = filepath.Join(\"/dev/mapper\",\n\t\t\t\tfmt.Sprintf(\"%s-%s\", device.VgName, device.LvName))\n\t\t} else {\n\t\t\tdrivePth = paths.GetDrivePath(device.Id)\n\t\t}\n\n\t\terr = chown(virt, drivePth)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, device := range virt.UsbDevices {\n\t\tusbDevice, _ := device.GetDevice()\n\n\t\tif usbDevice != nil {\n\t\t\terr = Chown(virt, usbDevice.BusPath)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\terr = chown(virt, paths.GetCacheDir(virt.Id))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc InitDisk(virt *vm.VirtualMachine, dsk *vm.Disk) (err error) {\n\terr = UserAdd(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = chown(virt, dsk.Path)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc InitTpm(virt *vm.VirtualMachine) (err error) {\n\ttpmPath := paths.GetTpmPath(virt.Id)\n\n\terr = chown(virt, tpmPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc InitTpmPwd(virt *vm.VirtualMachine) (err error) {\n\ttpmPath := paths.GetTpmPwdPath(virt.Id)\n\n\terr = chown(virt, tpmPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc InitImds(virt *vm.VirtualMachine) (err error) {\n\trunPath := paths.GetInstRunPath(virt.Id)\n\n\terr = chown(virt, runPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc InitMount(virt *vm.VirtualMachine, shareId string) (err error) {\n\tsockPath := paths.GetShareSockPath(virt.Id, shareId)\n\n\terr = chown(virt, sockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "permission/user.go",
    "content": "package permission\n\nimport (\n\t\"fmt\"\n\t\"os/user\"\n\t\"path\"\n\t\"strconv\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc GetUserName(vmId bson.ObjectID) string {\n\treturn fmt.Sprintf(\"pritunl-%s\", vmId.Hex())\n}\n\nfunc UserAdd(virt *vm.VirtualMachine) (err error) {\n\tname := GetUserName(virt.Id)\n\n\tusr, e := user.LookupId(strconv.Itoa(virt.UnixId))\n\tif usr != nil && e == nil {\n\t\treturn\n\t}\n\n\tif virt.UnixId == 0 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"permission: Virt missing unix id\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = utils.ExecCombinedOutputLogged(nil,\n\t\t\"useradd\",\n\t\t\"--no-user-group\",\n\t\t\"--no-create-home\",\n\t\t\"--uid\", strconv.Itoa(virt.UnixId),\n\t\tname,\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tmailPath := path.Join(\"/var/mail\", name)\n\t_ = utils.RemoveAll(mailPath)\n\n\treturn\n}\n\nfunc UserDelete(virt *vm.VirtualMachine) (err error) {\n\tname := GetUserName(virt.Id)\n\n\t_, _ = utils.ExecCombinedOutput(\"\",\n\t\t\"userdel\",\n\t\tname,\n\t)\n\n\t_, _ = utils.ExecCombinedOutput(\"\",\n\t\t\"groupdel\",\n\t\tname,\n\t)\n\n\treturn\n}\n\nfunc UserGroupAdd(virtId bson.ObjectID, group string) (err error) {\n\tname := GetUserName(virtId)\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"does not exist\",\n\t\t},\n\t\t\"gpasswd\",\n\t\t\"-a\", name,\n\t\tgroup,\n\t)\n\n\treturn\n}\n\nfunc UserGroupDelete(virtId bson.ObjectID, group string) (err error) {\n\tname := GetUserName(virtId)\n\n\t_, err = utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"not a member\",\n\t\t\t\"does not exist\",\n\t\t},\n\t\t\"gpasswd\",\n\t\t\"-d\", name,\n\t\tgroup,\n\t)\n\n\treturn\n}\n"
  },
  {
    "path": "plan/constants.go",
    "content": "package plan\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\nconst (\n\tStart   = \"start\"\n\tStop    = \"stop\"\n\tRestart = \"restart\"\n\tDestroy = \"destroy\"\n)\n\nvar actions = set.NewSet(\n\tStart,\n\tStop,\n\tRestart,\n\tDestroy,\n)\n"
  },
  {
    "path": "plan/data.go",
    "content": "package plan\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/eval\"\n)\n\ntype Data struct {\n\tUnit     Unit     `json:\"unit\"`\n\tInstance Instance `json:\"instance\"`\n}\n\ntype Unit struct {\n\tName  string `json:\"name\"`\n\tCount int    `json:\"count\"`\n}\n\ntype Instance struct {\n\tName          string `json:\"name\"`\n\tState         string `json:\"state\"`\n\tAction        string `json:\"action\"`\n\tProcessors    int    `json:\"processors\"`\n\tMemory        int    `json:\"memory\"`\n\tLastTimestamp int    `json:\"last_timestamp\"`\n\tLastHeartbeat int    `json:\"last_heartbeat\"`\n}\n\nfunc (d *Data) Export() (data eval.Data, err error) {\n\tdataByt, err := json.Marshal(d)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"plan: Failed to marshal\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdata = eval.Data{}\n\n\terr = json.Unmarshal(dataByt, &data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"plan: Failed to unmarshal\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetEmtpyData() (data eval.Data, err error) {\n\tdataStrct := Data{}\n\n\tdata, err = dataStrct.Export()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "plan/plan.go",
    "content": "package plan\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/eval\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Plan struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tComment      string        `bson:\"comment\" json:\"comment\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tStatements   []*Statement  `bson:\"statements\" json:\"statements\"`\n}\n\ntype Completion struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n}\n\ntype Statement struct {\n\tId        bson.ObjectID `bson:\"id\" json:\"id\"`\n\tStatement string        `bson:\"statement\" json:\"statement\"`\n}\n\nfunc (p *Plan) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tp.Name = utils.FilterName(p.Name)\n\n\tif p.Organization.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"organization_required\",\n\t\t\tMessage: \"Missing required organization\",\n\t\t}\n\t\treturn\n\t}\n\n\temptyData, err := GetEmtpyData()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif p.Statements == nil {\n\t\tp.Statements = []*Statement{}\n\t}\n\n\tfor _, statement := range p.Statements {\n\t\tif statement.Id.IsZero() {\n\t\t\tstatement.Id = bson.NewObjectID()\n\t\t}\n\n\t\terr = eval.Validate(statement.Statement)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, _, err = eval.Eval(emptyData, statement.Statement)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (p *Plan) UpdateStatements(inStatements []*Statement) (err error) {\n\tcurStatements := map[bson.ObjectID]*Statement{}\n\tfor _, statement := range p.Statements {\n\t\tcurStatements[statement.Id] = statement\n\t}\n\n\tnewStatements := []*Statement{}\n\tfor _, statement := range inStatements {\n\t\tcurStatement := curStatements[statement.Id]\n\t\tif curStatement != nil {\n\t\t\tif statement.Statement == curStatement.Statement {\n\t\t\t\tnewStatements = append(newStatements, curStatement)\n\t\t\t} else {\n\t\t\t\tnewStatement := &Statement{\n\t\t\t\t\tId:        bson.NewObjectID(),\n\t\t\t\t\tStatement: statement.Statement,\n\t\t\t\t}\n\t\t\t\tnewStatements = append(newStatements, newStatement)\n\t\t\t}\n\t\t} else {\n\t\t\tnewStatement := &Statement{\n\t\t\t\tId:        bson.NewObjectID(),\n\t\t\t\tStatement: statement.Statement,\n\t\t\t}\n\t\t\tnewStatements = append(newStatements, newStatement)\n\t\t}\n\t}\n\n\tp.Statements = newStatements\n\n\treturn\n}\n\nfunc (p *Plan) Commit(db *database.Database) (err error) {\n\tcoll := db.Plans()\n\n\terr = coll.Commit(p.Id, p)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Plan) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Plans()\n\n\terr = coll.CommitFields(p.Id, p, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Plan) Insert(db *database.Database) (err error) {\n\tcoll := db.Plans()\n\n\tif !p.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"domain: Plan already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = coll.InsertOne(db, p)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "plan/utils.go",
    "content": "package plan\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, plnId bson.ObjectID) (\n\tpln *Plan, err error) {\n\n\tcoll := db.Plans()\n\tpln = &Plan{}\n\n\terr = coll.FindOneId(plnId, pln)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, plnId bson.ObjectID) (\n\tpln *Plan, err error) {\n\n\tcoll := db.Plans()\n\tpln = &Plan{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          plnId,\n\t\t\"organization\": orgId,\n\t}).Decode(pln)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExistsOrg(db *database.Database, orgId, plnId bson.ObjectID) (\n\texists bool, err error) {\n\n\tcoll := db.Plans()\n\n\tn, err := coll.CountDocuments(db, &bson.M{\n\t\t\"_id\":          plnId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif n > 0 {\n\t\texists = true\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (pln *Plan, err error) {\n\tcoll := db.Plans()\n\tpln = &Plan{}\n\n\terr = coll.FindOne(db, query).Decode(pln)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tplns []*Plan, err error) {\n\n\tcoll := db.Plans()\n\tplns = []*Plan{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdmn := &Plan{}\n\t\terr = cursor.Decode(dmn)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tplns = append(plns, dmn)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (plns []*Plan, count int64, err error) {\n\n\tcoll := db.Plans()\n\tplns = []*Plan{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdmn := &Plan{}\n\t\terr = cursor.Decode(dmn)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tplns = append(plns, dmn)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllName(db *database.Database, query *bson.M) (\n\tplns []*Plan, err error) {\n\n\tcoll := db.Plans()\n\tplns = []*Plan{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetProjection(bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"organization\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdmn := &Plan{}\n\t\terr = cursor.Decode(dmn)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tplns = append(plns, dmn)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, plnId bson.ObjectID) (err error) {\n\tcoll := db.Plans()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": plnId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, plnId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Plans()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          plnId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, plnIds []bson.ObjectID) (err error) {\n\tcoll := db.Plans()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": plnIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tplnIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Plans()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": plnIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "planner/planner.go",
    "content": "package planner\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/eval\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/plan\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Planner struct {\n\tunitsMap map[bson.ObjectID]*unit.Unit\n}\n\nfunc (p *Planner) setInstanceAction(db *database.Database,\n\tdeply *deployment.Deployment, inst *instance.Instance,\n\tstatement *plan.Statement, threshold int, action string) (err error) {\n\n\tdisks, e := disk.GetInstance(db, inst.Id)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tfor _, dsk := range disks {\n\t\tif dsk.Action != \"\" {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": inst.Id.Hex(),\n\t\t\t\t\"disk_id\":     dsk.Id.Hex(),\n\t\t\t\t\"disk_action\": dsk.Action,\n\t\t\t}).Info(\"deploy: Ignoring instance plan action, \" +\n\t\t\t\t\"disk action pending\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tif inst.Action == action {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"deployment\": deply.Id.Hex(),\n\t\t\"instance\":   deply.Instance.Hex(),\n\t\t\"pod\":        deply.Pod.Hex(),\n\t\t\"unit\":       deply.Unit.Hex(),\n\t\t\"statement\":  statement.Statement,\n\t\t\"threshold\":  threshold,\n\t\t\"action\":     action,\n\t}).Info(\"scheduler: Handling plan action\")\n\n\tinst.Action = action\n\terrData, e := inst.Validate(db)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = errData.GetError()\n\t\treturn\n\t}\n\n\terr = inst.CommitFields(db, set.NewSet(\"action\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Planner) checkInstance(db *database.Database,\n\tdeply *deployment.Deployment) (err error) {\n\n\tif deply.State == deployment.Reserved {\n\t\treturn\n\t}\n\n\tinst, err := instance.Get(db, deply.Instance)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tinst = nil\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif inst == nil && deply.Kind == deployment.Instance {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"deployment\": deply.Id.Hex(),\n\t\t\t\"instance\":   deply.Instance.Hex(),\n\t\t\t\"pod\":        deply.Pod.Hex(),\n\t\t\t\"unit\":       deply.Unit.Hex(),\n\t\t}).Info(\"scheduler: Removing deployment for destroyed instance\")\n\n\t\terr = deployment.Remove(db, deply.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t}\n\n\tunt := p.unitsMap[deply.Unit]\n\tif unt == nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"deployment\": deply.Id.Hex(),\n\t\t\t\"instance\":   deply.Instance.Hex(),\n\t\t\t\"pod\":        deply.Pod.Hex(),\n\t\t\t\"unit\":       deply.Unit.Hex(),\n\t\t}).Error(\"scheduler: Failed to find unit for deployment\")\n\n\t\t// err = deployment.Remove(db, deply.Id)\n\t\t// if err != nil {\n\t\t// \treturn\n\t\t// }\n\n\t\treturn\n\t}\n\n\tif inst == nil {\n\t\treturn\n\t}\n\n\tif deply.Action == deployment.Restore && inst.IsActive() {\n\t\tdeply.Action = \"\"\n\t\tdeply.State = deployment.Deployed\n\t\terr = deply.CommitFields(db, set.NewSet(\"state\", \"action\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tstatus := deployment.Unhealthy\n\tif inst.Guest != nil {\n\t\tif inst.Guest.Status == types.Running ||\n\t\t\tinst.Guest.Status == types.ReloadingClean {\n\n\t\t\tnow := time.Now()\n\t\t\theartbeatTtl := time.Duration(\n\t\t\t\tsettings.System.InstanceTimestampTtl) * time.Second\n\n\t\t\tif now.Sub(inst.Guest.Heartbeat) <= heartbeatTtl {\n\t\t\t\tstatus = deployment.Healthy\n\t\t\t} else if now.Sub(inst.Guest.Timestamp) > heartbeatTtl {\n\t\t\t\tstatus = deployment.Unknown\n\t\t\t}\n\t\t}\n\t}\n\n\tif deply.Status != status {\n\t\tdeply.Status = status\n\t\terr = deply.CommitFields(db, set.NewSet(\"status\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif deply.Action != \"\" {\n\t\treturn\n\t}\n\n\tswitch deply.State {\n\tcase deployment.Archived:\n\t\treturn\n\t}\n\n\tif deply.State == deployment.Deployed && !unt.HasDeployment(deply.Id) {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"deployment\": deply.Id.Hex(),\n\t\t\t\"instance\":   deply.Instance.Hex(),\n\t\t\t\"pod\":        deply.Pod.Hex(),\n\t\t\t\"unit\":       deply.Unit.Hex(),\n\t\t}).Info(\"scheduler: Restoring deployment\")\n\n\t\terr = unt.RestoreDeployment(db, deply.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tspc, err := spec.Get(db, deply.Spec)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif spc.Instance == nil {\n\t\treturn\n\t}\n\n\tif deply.State != deployment.Deployed {\n\t\treturn\n\t}\n\n\tpln, err := plan.Get(db, spc.Instance.Plan)\n\tif pln == nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"deployment\": deply.Id.Hex(),\n\t\t\t\"instance\":   deply.Instance.Hex(),\n\t\t\t\"pod\":        deply.Pod.Hex(),\n\t\t\t\"unit\":       deply.Unit.Hex(),\n\t\t}).Info(\"scheduler: Failed to find plan for deployment\")\n\t\treturn\n\t}\n\n\tdata, err := buildEvalData(unt, inst)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvar statement *plan.Statement\n\taction := \"\"\n\tthreshold := 0\n\tfor _, statement = range pln.Statements {\n\t\taction, threshold, err = eval.Eval(data, statement.Statement)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tthreshold = utils.Max(deployment.ThresholdMin, threshold)\n\n\t\taction, err = deply.HandleStatement(\n\t\t\tdb, statement.Id, threshold, action)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif action != \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif action != \"\" {\n\t\tswitch action {\n\t\tcase plan.Start:\n\t\t\terr = p.setInstanceAction(db, deply, inst,\n\t\t\t\tstatement, threshold, instance.Start)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase plan.Stop:\n\t\t\terr = p.setInstanceAction(db, deply, inst,\n\t\t\t\tstatement, threshold, instance.Stop)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase plan.Restart:\n\t\t\terr = p.setInstanceAction(db, deply, inst,\n\t\t\t\tstatement, threshold, instance.Restart)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase plan.Destroy:\n\t\t\terr = p.setInstanceAction(db, deply, inst,\n\t\t\t\tstatement, threshold, instance.Destroy)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tdefault:\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"deployment\": deply.Id.Hex(),\n\t\t\t\t\"instance\":   deply.Instance.Hex(),\n\t\t\t\t\"pod\":        deply.Pod.Hex(),\n\t\t\t\t\"unit\":       deply.Unit.Hex(),\n\t\t\t\t\"statement\":  statement.Statement,\n\t\t\t\t\"threshold\":  threshold,\n\t\t\t\t\"action\":     action,\n\t\t\t}).Error(\"scheduler: Unknown plan action\")\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (p *Planner) ApplyPlans(db *database.Database) (err error) {\n\tdeployments, err := deployment.GetAll(db, &bson.M{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tp.unitsMap, err = unit.GetAllMap(db, &bson.M{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvar waiters sync.WaitGroup\n\tbatch := make(chan struct{}, settings.System.PlannerBatchSize)\n\n\tfor _, deply := range deployments {\n\t\twaiters.Add(1)\n\t\tbatch <- struct{}{}\n\n\t\tgo func(deply *deployment.Deployment) {\n\t\t\tdefer func() {\n\t\t\t\t<-batch\n\t\t\t\twaiters.Done()\n\t\t\t}()\n\n\t\t\tswitch deply.Kind {\n\t\t\tcase deployment.Instance, deployment.Image:\n\t\t\t\te := p.checkInstance(db, deply)\n\t\t\t\tif e != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"deployment\": deply.Id.Hex(),\n\t\t\t\t\t\t\"instance\":   deply.Instance.Hex(),\n\t\t\t\t\t\t\"pod\":        deply.Pod.Hex(),\n\t\t\t\t\t\t\"unit\":       deply.Unit.Hex(),\n\t\t\t\t\t\t\"error\":      e,\n\t\t\t\t\t}).Error(\"scheduler: Failed to check instance deployment\")\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif deply.State == deployment.Reserved &&\n\t\t\t\tdeply.Action == deployment.Destroy &&\n\t\t\t\ttime.Since(deply.Timestamp) > 300*time.Second {\n\n\t\t\t\terr := deployment.Remove(db, deply.Id)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"deployment_id\": deply.Id.Hex(),\n\t\t\t\t\t\t\"error\":         err,\n\t\t\t\t\t}).Error(\"deploy: Failed to remove deployment\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tevent.PublishDispatch(db, \"pod.change\")\n\t\t\t}\n\t\t}(deply)\n\t}\n\n\twaiters.Wait()\n\treturn\n}\n"
  },
  {
    "path": "planner/utils.go",
    "content": "package planner\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/eval\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/plan\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc buildEvalData(unt *unit.Unit,\n\tinst *instance.Instance) (data eval.Data, err error) {\n\n\tlastTimestamp := 0\n\tlastHeartbeat := 0\n\tif inst.IsActive() {\n\t\tnow := time.Now()\n\n\t\tuptime := int(now.Sub(inst.Timestamp).Seconds())\n\t\tif inst.Guest != nil {\n\t\t\tlastTimestamp = int(now.Sub(inst.Guest.Timestamp).Seconds())\n\t\t\tlastHeartbeat = int(now.Sub(inst.Guest.Heartbeat).Seconds())\n\t\t}\n\t\tlastTimestamp = utils.Min(lastTimestamp, uptime)\n\t\tlastHeartbeat = utils.Min(lastHeartbeat, uptime)\n\t}\n\n\tdataStrct := plan.Data{\n\t\tUnit: plan.Unit{\n\t\t\tName:  unt.Name,\n\t\t\tCount: unt.Count,\n\t\t},\n\t\tInstance: plan.Instance{\n\t\t\tName:          inst.Name,\n\t\t\tState:         inst.State,\n\t\t\tAction:        inst.Action,\n\t\t\tLastTimestamp: lastTimestamp,\n\t\t\tLastHeartbeat: lastHeartbeat,\n\t\t},\n\t}\n\n\tdata, err = dataStrct.Export()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pod/pod.go",
    "content": "package pod\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Pod struct {\n\tId               bson.ObjectID                  `bson:\"_id,omitempty\" json:\"id\"`\n\tName             string                         `bson:\"name\" json:\"name\"`\n\tComment          string                         `bson:\"comment\" json:\"comment\"`\n\tOrganization     bson.ObjectID                  `bson:\"organization\" json:\"organization\"`\n\tDeleteProtection bool                           `bson:\"delete_protection\" json:\"delete_protection\"`\n\tUserDrafts       map[bson.ObjectID][]*UnitDraft `bson:\"drafts\" json:\"-\"`\n\tDrafts           []*UnitDraft                   `bson:\"-\" json:\"drafts\"`\n}\n\ntype Completion struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n}\n\ntype UnitDraft struct {\n\tId        bson.ObjectID `bson:\"id\" json:\"id\"`\n\tName      string        `bson:\"name\" json:\"name\"`\n\tSpec      string        `bson:\"spec\" json:\"spec\"`\n\tDelete    bool          `bson:\"delete\" json:\"delete\"`\n\tTimestamp time.Time     `bson:\"timestamp\" json:\"timestamp\"`\n\tNew       bool          `bson:\"new\" json:\"new\"`\n}\n\nfunc (p *Pod) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tp.Name = utils.FilterName(p.Name)\n\n\tif p.Organization.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"missing_organization\",\n\t\t\tMessage: \"Missing organization\",\n\t\t}\n\t\treturn\n\t}\n\n\tif p.UserDrafts == nil {\n\t\tp.UserDrafts = map[bson.ObjectID][]*UnitDraft{}\n\t}\n\n\treturn\n}\n\nfunc (p *Pod) Json(usrId bson.ObjectID) {\n\tif p.UserDrafts != nil && p.UserDrafts[usrId] != nil {\n\t\tp.Drafts = p.UserDrafts[usrId]\n\t} else {\n\t\tp.Drafts = []*UnitDraft{}\n\t}\n}\n\nfunc (p *Pod) InitUnits(db *database.Database, units []*unit.UnitInput) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tnewUnits := []*unit.Unit{}\n\tnewSpecs := []*spec.Spec{}\n\tupdateSpecs := []*spec.Spec{}\n\tfor _, unitData := range units {\n\t\tif unitData.Delete {\n\t\t\tcontinue\n\t\t}\n\n\t\tunt := &unit.Unit{\n\t\t\tId:           bson.NewObjectID(),\n\t\t\tPod:          p.Id,\n\t\t\tOrganization: p.Organization,\n\t\t\tName:         unitData.Name,\n\t\t\tSpec:         unitData.Spec,\n\t\t\tSpecIndex:    1,\n\t\t\tDeployments:  []bson.ObjectID{},\n\t\t}\n\n\t\tnewSpec, updateSpec, ed, e := unt.Parse(db, true)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif ed != nil {\n\t\t\terrData = ed\n\t\t\treturn\n\t\t}\n\n\t\tnewUnits = append(newUnits, unt)\n\t\tif newSpec != nil {\n\t\t\tnewSpecs = append(newSpecs, newSpec)\n\t\t}\n\t\tif updateSpec != nil {\n\t\t\tupdateSpecs = append(updateSpecs, updateSpec)\n\t\t}\n\t}\n\n\tfor _, unt := range newUnits {\n\t\terr = unt.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, spc := range newSpecs {\n\t\terr = spc.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, spc := range updateSpecs {\n\t\terr = spc.CommitData(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (p *Pod) CommitFieldsUnits(db *database.Database,\n\tunits []*unit.UnitInput, fields set.Set) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tcurUnitsMap, err := unit.GetAllMap(db, &bson.M{\n\t\t\"pod\": p.Id,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tunitsName := set.NewSet()\n\tparsedUnits := []*unit.Unit{}\n\tparsedUnitsNew := []*unit.Unit{}\n\tparsedUnitsDel := []*unit.Unit{}\n\tnewSpecs := []*spec.Spec{}\n\tupdateSpecs := []*spec.Spec{}\n\tfor _, unitData := range units {\n\t\tcurUnit := curUnitsMap[unitData.Id]\n\t\tif unitData.Delete {\n\t\t\tif curUnit == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparsedUnitsDel = append(parsedUnitsDel, curUnit)\n\t\t} else if curUnit == nil {\n\t\t\tcurUnit := curUnitsMap[unitData.Id]\n\t\t\tif curUnit != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tunt := &unit.Unit{\n\t\t\t\tId:           bson.NewObjectID(),\n\t\t\t\tPod:          p.Id,\n\t\t\t\tOrganization: p.Organization,\n\t\t\t\tName:         unitData.Name,\n\t\t\t\tSpec:         unitData.Spec,\n\t\t\t\tSpecIndex:    1,\n\t\t\t\tDeployments:  []bson.ObjectID{},\n\t\t\t}\n\n\t\t\tnewSpec, updateSpec, ed, e := unt.Parse(db, true)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ed != nil {\n\t\t\t\terrData = ed\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif newSpec != nil {\n\t\t\t\tnewSpecs = append(newSpecs, newSpec)\n\t\t\t}\n\t\t\tif updateSpec != nil {\n\t\t\t\tupdateSpecs = append(updateSpecs, updateSpec)\n\t\t\t}\n\n\t\t\tif unitsName.Contains(unt.Name) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"unit_duplicate_name\",\n\t\t\t\t\tMessage: \"Duplicate unit name\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tunitsName.Add(unt.Name)\n\n\t\t\tparsedUnitsNew = append(parsedUnitsNew, unt)\n\t\t} else {\n\t\t\tcurUnit.Name = unitData.Name\n\t\t\tcurUnit.Spec = unitData.Spec\n\n\t\t\tif !unitData.DeploySpec.IsZero() {\n\t\t\t\tdeploySpec, e := spec.Get(db, unitData.DeploySpec)\n\t\t\t\tif e != nil || deploySpec.Unit != curUnit.Id {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"unit_deploy_spec_invalid\",\n\t\t\t\t\t\tMessage: \"Invalid unit deployment commit\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tcurUnit.DeploySpec = deploySpec.Id\n\t\t\t}\n\n\t\t\tnewSpec, updateSpec, ed, e := curUnit.Parse(db, false)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ed != nil {\n\t\t\t\terrData = ed\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif newSpec != nil {\n\t\t\t\tnewSpecs = append(newSpecs, newSpec)\n\t\t\t}\n\t\t\tif updateSpec != nil {\n\t\t\t\tupdateSpecs = append(updateSpecs, updateSpec)\n\t\t\t}\n\n\t\t\tif unitsName.Contains(curUnit.Name) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"unit_duplicate_name\",\n\t\t\t\t\tMessage: \"Duplicate unit name\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tunitsName.Add(curUnit.Name)\n\n\t\t\tparsedUnits = append(parsedUnits, curUnit)\n\t\t}\n\t}\n\n\tfor _, unt := range parsedUnitsDel {\n\t\tdeplys, e := deployment.GetAll(db, &bson.M{\n\t\t\t\"pod\":          p.Id,\n\t\t\t\"unit\":         unt.Id,\n\t\t\t\"organization\": p.Organization,\n\t\t})\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif len(deplys) > 0 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"unit_delete_active_deployments\",\n\t\t\t\tMessage: \"Cannot delete unit with active deployments\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = unit.RemoveOrg(db, p.Organization, unt.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, unt := range parsedUnits {\n\t\terr = unt.CommitFields(db, set.NewSet(\n\t\t\t\"name\",\n\t\t\t\"kind\",\n\t\t\t\"count\",\n\t\t\t\"spec\",\n\t\t\t\"last_spec\",\n\t\t\t\"deploy_spec\",\n\t\t\t\"hash\",\n\t\t))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, unt := range parsedUnitsNew {\n\t\terr = unt.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, spc := range newSpecs {\n\t\terr = spc.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, spc := range updateSpecs {\n\t\terr = spc.CommitData(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = p.CommitFields(db, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Pod) Commit(db *database.Database) (err error) {\n\tcoll := db.Pods()\n\n\terr = coll.Commit(p.Id, p)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Pod) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Pods()\n\n\terr = coll.CommitFields(p.Id, p, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Pod) Insert(db *database.Database) (err error) {\n\tcoll := db.Pods()\n\n\t_, err = coll.InsertOne(db, p)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pod/utils.go",
    "content": "package pod\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, podId bson.ObjectID) (\n\tpd *Pod, err error) {\n\n\tcoll := db.Pods()\n\tpd = &Pod{}\n\n\terr = coll.FindOneId(podId, pd)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, pdId bson.ObjectID) (\n\tpd *Pod, err error) {\n\n\tcoll := db.Pods()\n\tpd = &Pod{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          pdId,\n\t\t\"organization\": orgId,\n\t}).Decode(pd)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (pd *Pod, err error) {\n\tcoll := db.Pods()\n\tpd = &Pod{}\n\n\terr = coll.FindOne(db, query).Decode(pd)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tpods []*Pod, err error) {\n\n\tcoll := db.Pods()\n\tpods = []*Pod{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tpd := &Pod{}\n\t\terr = cursor.Decode(pd)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tpods = append(pods, pd)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (pods []*Pod, count int64, err error) {\n\n\tcoll := db.Pods()\n\tpods = []*Pod{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tpd := &Pod{}\n\t\terr = cursor.Decode(pd)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tpods = append(pods, pd)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc UpdateDrafts(db *database.Database, podId, usrId bson.ObjectID,\n\tdrafts []*UnitDraft) (err error) {\n\n\tfor _, draft := range drafts {\n\t\tdraft.Timestamp = time.Now()\n\t}\n\n\tcoll := db.Pods()\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\": podId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"drafts.\" + usrId.Hex(): drafts,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn nil\n}\n\nfunc UpdateDraftsOrg(db *database.Database, orgId, podId, usrId bson.ObjectID,\n\tdrafts []*UnitDraft) (err error) {\n\n\tfor _, draft := range drafts {\n\t\tdraft.Timestamp = time.Now()\n\t}\n\n\tcoll := db.Pods()\n\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\"_id\":          podId,\n\t\t\"organization\": orgId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"drafts.\" + usrId.Hex(): drafts,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn nil\n}\n\nfunc Remove(db *database.Database, podId bson.ObjectID) (err error) {\n\tcoll := db.Pods()\n\n\terr = spec.RemoveAll(db, &bson.M{\n\t\t\"pod\": podId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = unit.RemoveAll(db, &bson.M{\n\t\t\"pod\": podId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":               podId,\n\t\t\"delete_protection\": false,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, podId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Pods()\n\n\terr = spec.RemoveAll(db, &bson.M{\n\t\t\"pod\":          podId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = unit.RemoveAll(db, &bson.M{\n\t\t\"pod\":          podId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":               podId,\n\t\t\"organization\":      orgId,\n\t\t\"delete_protection\": false,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, podIds []bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Pods()\n\n\terr = spec.RemoveAll(db, &bson.M{\n\t\t\"pod\": &bson.M{\n\t\t\t\"$in\": podIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = unit.RemoveAll(db, &bson.M{\n\t\t\"pod\": &bson.M{\n\t\t\t\"$in\": podIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": podIds,\n\t\t},\n\t\t\"delete_protection\": false,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tpodIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Pods()\n\n\terr = spec.RemoveAll(db, &bson.M{\n\t\t\"pod\": &bson.M{\n\t\t\t\"$in\": podIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = unit.RemoveAll(db, &bson.M{\n\t\t\"pod\": &bson.M{\n\t\t\t\"$in\": podIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": podIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "policy/constants.go",
    "content": "package policy\n\nconst (\n\tOptional          = \"optional\"\n\tRequired          = \"required\"\n\tDisabled          = \"disabled\"\n\tOperatingSystem   = \"operating_system\"\n\tBrowser           = \"browser\"\n\tLocation          = \"location\"\n\tWhitelistNetworks = \"whitelist_networks\"\n\tBlacklistNetworks = \"blacklist_networks\"\n)\n"
  },
  {
    "path": "policy/policy.go",
    "content": "package policy\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/subscription\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/useragent\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Rule struct {\n\tType    string   `bson:\"type\" json:\"type\"`\n\tDisable bool     `bson:\"disable\" json:\"disable\"`\n\tValues  []string `bson:\"values\" json:\"values\"`\n}\n\ntype Policy struct {\n\tId                   bson.ObjectID    `bson:\"_id,omitempty\" json:\"id\"`\n\tName                 string           `bson:\"name\" json:\"name\"`\n\tComment              string           `bson:\"comment\" json:\"comment\"`\n\tDisabled             bool             `bson:\"disabled\" json:\"disabled\"`\n\tRoles                []string         `bson:\"roles\" json:\"roles\"`\n\tRules                map[string]*Rule `bson:\"rules\" json:\"rules\"`\n\tAdminSecondary       bson.ObjectID    `bson:\"admin_secondary,omitempty\" json:\"admin_secondary\"`\n\tUserSecondary        bson.ObjectID    `bson:\"user_secondary,omitempty\" json:\"user_secondary\"`\n\tAdminDeviceSecondary bool             `bson:\"admin_device_secondary\" json:\"admin_device_secondary\"`\n\tUserDeviceSecondary  bool             `bson:\"user_device_secondary\" json:\"user_device_secondary\"`\n}\n\nfunc (p *Policy) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tp.Name = utils.FilterName(p.Name)\n\n\tif p.Roles == nil {\n\t\tp.Roles = []string{}\n\t}\n\tif p.Rules == nil {\n\t\tp.Rules = map[string]*Rule{}\n\t}\n\n\tfor _, rule := range p.Rules {\n\t\tswitch rule.Type {\n\t\tcase OperatingSystem:\n\t\t\tbreak\n\t\tcase Browser:\n\t\t\tbreak\n\t\tcase Location:\n\t\t\tif !subscription.Sub.Active {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError: \"location_subscription_required\",\n\t\t\t\t\tMessage: \"Location policy requires subscription \" +\n\t\t\t\t\t\t\"for GeoIP service.\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase WhitelistNetworks:\n\t\t\tbreak\n\t\tcase BlacklistNetworks:\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_rule_type\",\n\t\t\t\tMessage: \"Rule type is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tif !p.AdminSecondary.IsZero() &&\n\t\tsettings.Auth.GetSecondaryProvider(p.AdminSecondary) == nil {\n\n\t\tp.AdminSecondary = bson.NilObjectID\n\t}\n\tif !p.UserSecondary.IsZero() &&\n\t\tsettings.Auth.GetSecondaryProvider(p.UserSecondary) == nil {\n\n\t\tp.UserSecondary = bson.NilObjectID\n\t}\n\n\thasWebAuthn := false\n\tnodes, err := node.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, nde := range nodes {\n\t\tif nde.WebauthnDomain != \"\" {\n\t\t\thasWebAuthn = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif (p.AdminDeviceSecondary || p.UserDeviceSecondary) && !hasWebAuthn {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError: \"webauthn_domain_unavailable\",\n\t\t\tMessage: \"At least one node must have a WebAuthn domain \" +\n\t\t\t\t\"configured to use WebAuthn device authentication\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Policy) ValidateUser(db *database.Database, usr *user.User,\n\tr *http.Request) (errData *errortypes.ErrorData, err error) {\n\n\tif p.Disabled {\n\t\treturn\n\t}\n\n\tagnt, err := useragent.Parse(db, r)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, rule := range p.Rules {\n\t\tswitch rule.Type {\n\t\tcase OperatingSystem:\n\t\t\tmatch := false\n\t\t\tfor _, value := range rule.Values {\n\t\t\t\tif value == agnt.OperatingSystem {\n\t\t\t\t\tmatch = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !match {\n\t\t\t\tif rule.Disable {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"unauthorized\",\n\t\t\t\t\t\tMessage: \"Not authorized\",\n\t\t\t\t\t}\n\n\t\t\t\t\tusr.Disabled = true\n\t\t\t\t\terr = usr.CommitFields(db, set.NewSet(\"disabled\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"operating_system_policy\",\n\t\t\t\t\t\tMessage: \"Operating system not permitted\",\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase Browser:\n\t\t\tmatch := false\n\t\t\tfor _, value := range rule.Values {\n\t\t\t\tif value == agnt.Browser {\n\t\t\t\t\tmatch = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !match {\n\t\t\t\tif rule.Disable {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"unauthorized\",\n\t\t\t\t\t\tMessage: \"Not authorized\",\n\t\t\t\t\t}\n\n\t\t\t\t\tusr.Disabled = true\n\t\t\t\t\terr = usr.CommitFields(db, set.NewSet(\"disabled\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"browser_policy\",\n\t\t\t\t\t\tMessage: \"Browser not permitted\",\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase Location:\n\t\t\tmatch := false\n\t\t\tregionKey := fmt.Sprintf(\"%s_%s\",\n\t\t\t\tagnt.CountryCode, agnt.RegionCode)\n\n\t\t\tfor _, value := range rule.Values {\n\t\t\t\tif value == agnt.CountryCode || value == regionKey {\n\t\t\t\t\tmatch = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !match {\n\t\t\t\tif rule.Disable {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"unauthorized\",\n\t\t\t\t\t\tMessage: \"Not authorized\",\n\t\t\t\t\t}\n\n\t\t\t\t\tusr.Disabled = true\n\t\t\t\t\terr = usr.CommitFields(db, set.NewSet(\"disabled\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"location_policy\",\n\t\t\t\t\t\tMessage: \"Location not permitted\",\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase WhitelistNetworks:\n\t\t\tmatch := false\n\t\t\tclientIp := net.ParseIP(agnt.Ip)\n\n\t\t\tfor _, value := range rule.Values {\n\t\t\t\t_, network, e := net.ParseCIDR(value)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\terrors.Wrap(e, \"policy: Failed to parse network\"),\n\t\t\t\t\t}\n\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"network\": value,\n\t\t\t\t\t\t\"error\":   err,\n\t\t\t\t\t}).Error(\"policy: Invalid whitelist network\")\n\t\t\t\t\terr = nil\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif network.Contains(clientIp) {\n\t\t\t\t\tmatch = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !match {\n\t\t\t\tif rule.Disable {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"unauthorized\",\n\t\t\t\t\t\tMessage: \"Not authorized\",\n\t\t\t\t\t}\n\n\t\t\t\t\tusr.Disabled = true\n\t\t\t\t\terr = usr.CommitFields(db, set.NewSet(\"disabled\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"whitelist_networks_policy\",\n\t\t\t\t\t\tMessage: \"Network not permitted\",\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tcase BlacklistNetworks:\n\t\t\tmatch := false\n\t\t\tclientIp := net.ParseIP(agnt.Ip)\n\n\t\t\tfor _, value := range rule.Values {\n\t\t\t\t_, network, e := net.ParseCIDR(value)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\terrors.Wrap(e, \"policy: Failed to parse network\"),\n\t\t\t\t\t}\n\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"network\": value,\n\t\t\t\t\t\t\"error\":   err,\n\t\t\t\t\t}).Error(\"policy: Invalid blacklist network\")\n\t\t\t\t\terr = nil\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif network.Contains(clientIp) {\n\t\t\t\t\tmatch = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif match {\n\t\t\t\tif rule.Disable {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"unauthorized\",\n\t\t\t\t\t\tMessage: \"Not authorized\",\n\t\t\t\t\t}\n\n\t\t\t\t\tusr.Disabled = true\n\t\t\t\t\terr = usr.CommitFields(db, set.NewSet(\"disabled\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"blacklist_networks_policy\",\n\t\t\t\t\t\tMessage: \"Network not permitted\",\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (p *Policy) Commit(db *database.Database) (err error) {\n\tcoll := db.Policies()\n\n\terr = coll.Commit(p.Id, p)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Policy) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Policies()\n\n\terr = coll.CommitFields(p.Id, p, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Policy) Insert(db *database.Database) (err error) {\n\tcoll := db.Policies()\n\n\tif !p.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"policy: Policy already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = coll.InsertOne(db, p)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "policy/utils.go",
    "content": "package policy\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, policyId bson.ObjectID) (\n\tpolcy *Policy, err error) {\n\n\tcoll := db.Policies()\n\tpolcy = &Policy{}\n\n\terr = coll.FindOneId(policyId, polcy)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetService(db *database.Database, podId bson.ObjectID) (\n\tpolicies []*Policy, err error) {\n\n\tcoll := db.Policies()\n\tpolicies = []*Policy{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"pods\": podId,\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tpolcy := &Policy{}\n\t\terr = cursor.Decode(polcy)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tpolicies = append(policies, polcy)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetRoles(db *database.Database, roles []string) (\n\tpolicies []*Policy, err error) {\n\n\tcoll := db.Policies()\n\tpolicies = []*Policy{}\n\n\tif roles == nil {\n\t\troles = []string{}\n\t}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"roles\": &bson.M{\n\t\t\t\t\"$in\": roles,\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tpolcy := &Policy{}\n\t\terr = cursor.Decode(polcy)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tpolicies = append(policies, polcy)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database) (policies []*Policy, err error) {\n\tcoll := db.Policies()\n\tpolicies = []*Policy{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tpolcy := &Policy{}\n\t\terr = cursor.Decode(polcy)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tpolicies = append(policies, polcy)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (polcies []*Policy, count int64, err error) {\n\n\tcoll := db.Policies()\n\tpolcies = []*Policy{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tpolcy := &Policy{}\n\t\terr = cursor.Decode(polcy)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tpolcies = append(polcies, polcy)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, policyId bson.ObjectID) (err error) {\n\tcoll := db.Policies()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": policyId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, polcyIds []bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Policies()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": polcyIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pool/constants.go",
    "content": "package pool\n\nconst (\n\tLvm = \"lvm\"\n\n\tActive = \"active\"\n)\n"
  },
  {
    "path": "pool/pool.go",
    "content": "package pool\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Pool struct {\n\tId               bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName             string        `bson:\"name\" json:\"name\"`\n\tComment          string        `bson:\"comment\" json:\"comment\"`\n\tDeleteProtection bool          `bson:\"delete_protection\" json:\"delete_protection\"`\n\tDatacenter       bson.ObjectID `bson:\"datacenter\" json:\"datacenter\"`\n\tZone             bson.ObjectID `bson:\"zone\" json:\"zone\"`\n\tType             string        `bson:\"type\" json:\"type\"`\n\tVgName           string        `bson:\"vg_name\" json:\"vg_name\"`\n}\n\ntype Completion struct {\n\tId   bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName string        `bson:\"name\" json:\"name\"`\n\tZone bson.ObjectID `bson:\"zone\" json:\"zone\"`\n}\n\nfunc (p *Pool) Json(nodeNames map[bson.ObjectID]string) {\n}\n\nfunc (p *Pool) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tp.Name = utils.FilterName(p.Name)\n\n\tif p.Datacenter.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_datacenter\",\n\t\t\tMessage: \"Missing required datacenter\",\n\t\t}\n\t\treturn\n\t}\n\n\tif p.Zone.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_zone\",\n\t\t\tMessage: \"Missing required zone\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Pool) Commit(db *database.Database) (err error) {\n\tcoll := db.Pools()\n\n\terr = coll.Commit(p.Id, p)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Pool) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Pools()\n\n\terr = coll.CommitFields(p.Id, p, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *Pool) Insert(db *database.Database) (err error) {\n\tcoll := db.Pools()\n\n\t_, err = coll.InsertOne(db, p)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pool/utils.go",
    "content": "package pool\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, poolId bson.ObjectID) (\n\tpl *Pool, err error) {\n\n\tcoll := db.Pools()\n\tpl = &Pool{}\n\n\terr = coll.FindOneId(poolId, pl)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (pl *Pool, err error) {\n\tcoll := db.Pools()\n\tpl = &Pool{}\n\n\terr = coll.FindOne(db, query).Decode(pl)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tpools []*Pool, err error) {\n\n\tcoll := db.Pools()\n\tpools = []*Pool{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Pool{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tpools = append(pools, nde)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (pools []*Pool, count int64, err error) {\n\n\tcoll := db.Pools()\n\tpools = []*Pool{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tpl := &Pool{}\n\t\terr = cursor.Decode(pl)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tpools = append(pools, pl)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNames(db *database.Database, query *bson.M) (\n\tpools []*Pool, err error) {\n\n\tcoll := db.Pools()\n\tpools = []*Pool{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetProjection(bson.D{\n\t\t\t\t{\"_id\", 1},\n\t\t\t\t{\"name\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tpl := &Pool{}\n\t\terr = cursor.Decode(pl)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tpools = append(pools, pl)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, poolId bson.ObjectID) (err error) {\n\tcoll := db.Pools()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":               poolId,\n\t\t\"delete_protection\": false,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, poolIds []bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Pools()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": poolIds,\n\t\t},\n\t\t\"delete_protection\": false,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "proxy/constants.go",
    "content": "package proxy\n\nconst (\n\tOnline      = 5\n\tUnknownHigh = 4\n\tUnknownMid  = 3\n\tUnknownLow  = 2\n\tOffline     = 1\n)\n"
  },
  {
    "path": "proxy/domain.go",
    "content": "package proxy\n\nimport (\n\t\"crypto/md5\"\n\t\"crypto/tls\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/balancer\"\n)\n\ntype Domain struct {\n\tHash              []byte\n\tRequests          *int32\n\tRequestsPrev      [5]int\n\tRequestsTotal     int\n\tRetries           *int32\n\tRetriesPrev       [5]int\n\tRetriesTotal      int\n\tLock              sync.Mutex\n\tProxyProto        string\n\tProxyPort         int\n\tSkipVerify        bool\n\tBalancer          *balancer.Balancer\n\tDomain            *balancer.Domain\n\tClientAuthority   *authority.Authority\n\tClientCertificate *tls.Certificate\n\n\tOnlineWebFirst      []*Handler\n\tUnknownHighWebFirst []*Handler\n\tUnknownMidWebFirst  []*Handler\n\tUnknownLowWebFirst  []*Handler\n\tOfflineWebFirst     []*Handler\n\n\tOnlineWebSecond      []*Handler\n\tUnknownHighWebSecond []*Handler\n\tUnknownMidWebSecond  []*Handler\n\tUnknownLowWebSecond  []*Handler\n\tOfflineWebSecond     []*Handler\n\n\tOnlineWebThird      []*Handler\n\tUnknownHighWebThird []*Handler\n\tUnknownMidWebThird  []*Handler\n\tUnknownLowWebThird  []*Handler\n\tOfflineWebThird     []*Handler\n\n\tWebSocketConns     set.Set\n\tWebSocketConnsLock sync.Mutex\n}\n\nfunc (d *Domain) CalculateHash() {\n\th := md5.New()\n\n\th.Write([]byte(d.ProxyProto))\n\th.Write([]byte(strconv.Itoa(d.ProxyPort)))\n\th.Write([]byte(strconv.FormatBool(d.SkipVerify)))\n\n\th.Write([]byte(d.Balancer.Id.Hex()))\n\th.Write([]byte(d.Balancer.Name))\n\th.Write([]byte(d.Balancer.CheckPath))\n\th.Write([]byte(strconv.FormatBool(d.Balancer.WebSockets)))\n\th.Write([]byte(d.Domain.Domain))\n\th.Write([]byte(d.Domain.Host))\n\n\tif !d.Balancer.ClientAuthority.IsZero() {\n\t\th.Write([]byte(d.Balancer.ClientAuthority.Hex()))\n\t}\n\tfor _, backend := range d.Balancer.Backends {\n\t\th.Write([]byte(backend.Protocol))\n\t\th.Write([]byte(backend.Hostname))\n\t\th.Write([]byte(strconv.Itoa(backend.Port)))\n\t}\n\n\td.Hash = h.Sum(nil)\n}\n\nfunc (d *Domain) Init() {\n\td.Lock.Lock()\n\tdefer d.Lock.Unlock()\n\n\tif !d.Balancer.ClientAuthority.IsZero() {\n\t\t//clientAuthr, err := authority.Get(db, d.Balancer.ClientAuthority)\n\t\t//if err != nil {\n\t\t//\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t//\t\terr = nil\n\t\t//\n\t\t//\t\tlogrus.WithFields(logrus.Fields{\n\t\t//\t\t\t\"balancer_id\":         d.Balancer.Id.Hex(),\n\t\t//\t\t\t\"client_authority_id\": d.Balancer.ClientAuthority.Hex(),\n\t\t//\t\t}).Warn(\"proxy: Service client authority not found\")\n\t\t//\t} else {\n\t\t//\t\treturn\n\t\t//\t}\n\t\t//}\n\t\t//\n\t\t// var cert *tls.Certificate\n\t\t//if clientAuthr != nil {\n\t\t//\tcert, err = clientAuthr.CreateClientCertificate(db)\n\t\t//\tif err != nil {\n\t\t//\t\treturn\n\t\t//\t}\n\t\t//}\n\t}\n\n\tunknownHighWebFirst := []*Handler{}\n\tunknownHighWebSecond := []*Handler{}\n\tunknownHighWebThird := []*Handler{}\n\n\tfor i, backend := range d.Balancer.Backends {\n\t\thand := NewHandler(i, UnknownHigh, d.ProxyProto, d.ProxyPort, d,\n\t\t\tbackend, d.ResponseHandler, d.ErrorHandlerFirst)\n\t\tunknownHighWebFirst = append(unknownHighWebFirst, hand)\n\n\t\thand = NewHandler(i, UnknownHigh, d.ProxyProto, d.ProxyPort, d,\n\t\t\tbackend, d.ResponseHandler, d.ErrorHandlerSecond)\n\t\tunknownHighWebSecond = append(unknownHighWebSecond, hand)\n\n\t\thand = NewHandler(i, UnknownHigh, d.ProxyProto, d.ProxyPort, d,\n\t\t\tbackend, d.ResponseHandler, d.ErrorHandlerThird)\n\t\tunknownHighWebThird = append(unknownHighWebThird, hand)\n\t}\n\n\td.OnlineWebFirst = []*Handler{}\n\td.UnknownHighWebFirst = unknownHighWebFirst\n\td.UnknownMidWebFirst = []*Handler{}\n\td.UnknownLowWebFirst = []*Handler{}\n\td.OfflineWebFirst = []*Handler{}\n\n\td.OnlineWebSecond = []*Handler{}\n\td.UnknownHighWebSecond = unknownHighWebSecond\n\td.UnknownMidWebSecond = []*Handler{}\n\td.UnknownLowWebSecond = []*Handler{}\n\td.OfflineWebSecond = []*Handler{}\n\n\td.OnlineWebThird = []*Handler{}\n\td.UnknownHighWebThird = unknownHighWebThird\n\td.UnknownMidWebThird = []*Handler{}\n\td.UnknownLowWebThird = []*Handler{}\n\td.OfflineWebThird = []*Handler{}\n\n\td.WebSocketConns = set.NewSet()\n}\n\nfunc (d *Domain) ServeHTTPFirst(rw http.ResponseWriter, r *http.Request) {\n\tatomic.AddInt32(d.Requests, 1)\n\n\tonlineWebFirst := d.OnlineWebFirst\n\tl := len(onlineWebFirst)\n\tif l != 0 {\n\t\tonlineWebFirst[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tunknownHighWebFirst := d.UnknownHighWebFirst\n\tl = len(unknownHighWebFirst)\n\tif l != 0 {\n\t\tunknownHighWebFirst[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tunknownMidWebFirst := d.UnknownMidWebFirst\n\tl = len(unknownMidWebFirst)\n\tif l != 0 {\n\t\tunknownMidWebFirst[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tunknownLowWebFirst := d.UnknownLowWebFirst\n\tl = len(unknownLowWebFirst)\n\tif l != 0 {\n\t\tunknownLowWebFirst[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tofflineWebFirst := d.OfflineWebFirst\n\tl = len(offlineWebFirst)\n\tif l != 0 {\n\t\tofflineWebFirst[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\trw.WriteHeader(http.StatusBadGateway)\n}\n\nfunc (d *Domain) ServeHTTPSecond(rw http.ResponseWriter, r *http.Request) {\n\tatomic.AddInt32(d.Retries, 1)\n\n\tonlineWebSecond := d.OnlineWebSecond\n\tl := len(onlineWebSecond)\n\tif l != 0 {\n\t\tonlineWebSecond[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tunknownHighWebSecond := d.UnknownHighWebSecond\n\tl = len(unknownHighWebSecond)\n\tif l != 0 {\n\t\tunknownHighWebSecond[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tunknownMidWebSecond := d.UnknownMidWebSecond\n\tl = len(unknownMidWebSecond)\n\tif l != 0 {\n\t\tunknownMidWebSecond[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tunknownLowWebSecond := d.UnknownLowWebSecond\n\tl = len(unknownLowWebSecond)\n\tif l != 0 {\n\t\tunknownLowWebSecond[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tofflineWebSecond := d.OfflineWebSecond\n\tl = len(offlineWebSecond)\n\tif l != 0 {\n\t\tofflineWebSecond[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\trw.WriteHeader(http.StatusBadGateway)\n}\n\nfunc (d *Domain) ServeHTTPThird(rw http.ResponseWriter, r *http.Request) {\n\tatomic.AddInt32(d.Retries, 1)\n\n\tonlineWebThird := d.OnlineWebThird\n\tl := len(onlineWebThird)\n\tif l != 0 {\n\t\tonlineWebThird[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tunknownHighWebThird := d.UnknownHighWebThird\n\tl = len(unknownHighWebThird)\n\tif l != 0 {\n\t\tunknownHighWebThird[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tunknownMidWebThird := d.UnknownMidWebThird\n\tl = len(unknownMidWebThird)\n\tif l != 0 {\n\t\tunknownMidWebThird[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tunknownLowWebThird := d.UnknownLowWebThird\n\tl = len(unknownLowWebThird)\n\tif l != 0 {\n\t\tunknownLowWebThird[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\tofflineWebThird := d.OfflineWebThird\n\tl = len(offlineWebThird)\n\tif l != 0 {\n\t\tofflineWebThird[rand.Intn(l)].Serve(rw, r)\n\t\treturn\n\t}\n\n\trw.WriteHeader(http.StatusBadGateway)\n}\n\nfunc (d *Domain) checkHandler(hand *Handler) {\n\tresp, err := hand.CheckClient.Get(hand.CheckUrl)\n\tif err != nil || resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\tif hand.State != Offline {\n\t\t\td.offlineHandler(hand)\n\t\t}\n\t} else {\n\t\tif hand.State != Online {\n\t\t\td.upgradeHandler(hand)\n\t\t}\n\t}\n}\n\nfunc (d *Domain) Check() {\n\td.Lock.Lock()\n\tdefer d.Lock.Unlock()\n\n\tfor _, hand := range d.OnlineWebFirst {\n\t\tgo d.checkHandler(hand)\n\t}\n\n\tfor _, hand := range d.UnknownHighWebFirst {\n\t\tgo d.checkHandler(hand)\n\t}\n\n\tfor _, hand := range d.UnknownMidWebFirst {\n\t\tgo d.checkHandler(hand)\n\t}\n\n\tfor _, hand := range d.UnknownLowWebFirst {\n\t\tgo d.checkHandler(hand)\n\t}\n\n\tfor _, hand := range d.OfflineWebFirst {\n\t\tgo d.checkHandler(hand)\n\t}\n\n\treturn\n}\n\nfunc (d *Domain) upgradeHandler(hand *Handler) {\n\td.Lock.Lock()\n\tdefer d.Lock.Unlock()\n\n\tindex := hand.Index\n\tstate := hand.State\n\n\tswitch state {\n\tcase Online:\n\t\tbreak\n\tcase UnknownHigh:\n\t\tif time.Since(hand.LastOnlineState) > 5*time.Second {\n\t\t\thand = d.UnknownHighWebFirst[index]\n\t\t\td.UnknownHighWebFirst[index] =\n\t\t\t\td.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1]\n\t\t\td.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1] = nil\n\t\t\td.UnknownHighWebFirst =\n\t\t\t\td.UnknownHighWebFirst[:len(d.UnknownHighWebFirst)-1]\n\t\t\tfor i, h := range d.UnknownHighWebFirst {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebFirst)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebFirst = append(d.OnlineWebFirst, hand)\n\n\t\t\thand = d.UnknownHighWebSecond[index]\n\t\t\td.UnknownHighWebSecond[index] =\n\t\t\t\td.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1]\n\t\t\td.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1] = nil\n\t\t\td.UnknownHighWebSecond =\n\t\t\t\td.UnknownHighWebSecond[:len(d.UnknownHighWebSecond)-1]\n\t\t\tfor i, h := range d.UnknownHighWebSecond {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebSecond)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebSecond = append(d.OnlineWebSecond, hand)\n\n\t\t\thand = d.UnknownHighWebThird[index]\n\t\t\td.UnknownHighWebThird[index] =\n\t\t\t\td.UnknownHighWebThird[len(d.UnknownHighWebThird)-1]\n\t\t\td.UnknownHighWebThird[len(d.UnknownHighWebThird)-1] = nil\n\t\t\td.UnknownHighWebThird =\n\t\t\t\td.UnknownHighWebThird[:len(d.UnknownHighWebThird)-1]\n\t\t\tfor i, h := range d.UnknownHighWebThird {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebThird)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebThird = append(d.OnlineWebThird, hand)\n\t\t}\n\n\t\tbreak\n\tcase UnknownMid:\n\t\tif time.Since(hand.LastOnlineState) > 5*time.Second {\n\t\t\thand = d.UnknownMidWebFirst[index]\n\t\t\td.UnknownMidWebFirst[index] =\n\t\t\t\td.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1]\n\t\t\td.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1] = nil\n\t\t\td.UnknownMidWebFirst =\n\t\t\t\td.UnknownMidWebFirst[:len(d.UnknownMidWebFirst)-1]\n\t\t\tfor i, h := range d.UnknownMidWebFirst {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebFirst)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebFirst = append(d.OnlineWebFirst, hand)\n\n\t\t\thand = d.UnknownMidWebSecond[index]\n\t\t\td.UnknownMidWebSecond[index] =\n\t\t\t\td.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1]\n\t\t\td.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1] = nil\n\t\t\td.UnknownMidWebSecond =\n\t\t\t\td.UnknownMidWebSecond[:len(d.UnknownMidWebSecond)-1]\n\t\t\tfor i, h := range d.UnknownMidWebSecond {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebSecond)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebSecond = append(d.OnlineWebSecond, hand)\n\n\t\t\thand = d.UnknownMidWebThird[index]\n\t\t\td.UnknownMidWebThird[index] =\n\t\t\t\td.UnknownMidWebThird[len(d.UnknownMidWebThird)-1]\n\t\t\td.UnknownMidWebThird[len(d.UnknownMidWebThird)-1] = nil\n\t\t\td.UnknownMidWebThird =\n\t\t\t\td.UnknownMidWebThird[:len(d.UnknownMidWebThird)-1]\n\t\t\tfor i, h := range d.UnknownMidWebThird {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebThird)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebThird = append(d.OnlineWebThird, hand)\n\t\t}\n\n\t\tbreak\n\tcase UnknownLow:\n\t\tif time.Since(hand.LastOnlineState) > 5*time.Second {\n\t\t\thand = d.UnknownLowWebFirst[index]\n\t\t\td.UnknownLowWebFirst[index] =\n\t\t\t\td.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1]\n\t\t\td.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1] = nil\n\t\t\td.UnknownLowWebFirst =\n\t\t\t\td.UnknownLowWebFirst[:len(d.UnknownLowWebFirst)-1]\n\t\t\tfor i, h := range d.UnknownLowWebFirst {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebFirst)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebFirst = append(d.OnlineWebFirst, hand)\n\n\t\t\thand = d.UnknownLowWebSecond[index]\n\t\t\td.UnknownLowWebSecond[index] =\n\t\t\t\td.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1]\n\t\t\td.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1] = nil\n\t\t\td.UnknownLowWebSecond =\n\t\t\t\td.UnknownLowWebSecond[:len(d.UnknownLowWebSecond)-1]\n\t\t\tfor i, h := range d.UnknownLowWebSecond {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebSecond)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebSecond = append(d.OnlineWebSecond, hand)\n\n\t\t\thand = d.UnknownLowWebThird[index]\n\t\t\td.UnknownLowWebThird[index] =\n\t\t\t\td.UnknownLowWebThird[len(d.UnknownLowWebThird)-1]\n\t\t\td.UnknownLowWebThird[len(d.UnknownLowWebThird)-1] = nil\n\t\t\td.UnknownLowWebThird =\n\t\t\t\td.UnknownLowWebThird[:len(d.UnknownLowWebThird)-1]\n\t\t\tfor i, h := range d.UnknownLowWebThird {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebThird)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebThird = append(d.OnlineWebThird, hand)\n\t\t}\n\n\t\tbreak\n\tcase Offline:\n\t\tif time.Since(hand.LastOnlineState) > 5*time.Second {\n\t\t\thand = d.OfflineWebFirst[index]\n\t\t\td.OfflineWebFirst[index] =\n\t\t\t\td.OfflineWebFirst[len(d.OfflineWebFirst)-1]\n\t\t\td.OfflineWebFirst[len(d.OfflineWebFirst)-1] = nil\n\t\t\td.OfflineWebFirst =\n\t\t\t\td.OfflineWebFirst[:len(d.OfflineWebFirst)-1]\n\t\t\tfor i, h := range d.OfflineWebFirst {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebFirst)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebFirst = append(d.OnlineWebFirst, hand)\n\n\t\t\thand = d.OfflineWebSecond[index]\n\t\t\td.OfflineWebSecond[index] =\n\t\t\t\td.OfflineWebSecond[len(d.OfflineWebSecond)-1]\n\t\t\td.OfflineWebSecond[len(d.OfflineWebSecond)-1] = nil\n\t\t\td.OfflineWebSecond =\n\t\t\t\td.OfflineWebSecond[:len(d.OfflineWebSecond)-1]\n\t\t\tfor i, h := range d.OfflineWebSecond {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebSecond)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebSecond = append(d.OnlineWebSecond, hand)\n\n\t\t\thand = d.OfflineWebThird[index]\n\t\t\td.OfflineWebThird[index] =\n\t\t\t\td.OfflineWebThird[len(d.OfflineWebThird)-1]\n\t\t\td.OfflineWebThird[len(d.OfflineWebThird)-1] = nil\n\t\t\td.OfflineWebThird =\n\t\t\t\td.OfflineWebThird[:len(d.OfflineWebThird)-1]\n\t\t\tfor i, h := range d.OfflineWebThird {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OnlineWebThird)\n\t\t\thand.State = Online\n\t\t\thand.LastOnlineState = time.Now()\n\t\t\td.OnlineWebThird = append(d.OnlineWebThird, hand)\n\t\t}\n\n\t\tbreak\n\t}\n}\n\nfunc (d *Domain) downgradeHandler(hand *Handler) {\n\td.Lock.Lock()\n\tdefer d.Lock.Unlock()\n\n\tindex := hand.Index\n\tstate := hand.State\n\n\tswitch state {\n\tcase Online:\n\t\thand = d.OnlineWebFirst[index]\n\t\td.OnlineWebFirst[index] = d.OnlineWebFirst[len(d.OnlineWebFirst)-1]\n\t\td.OnlineWebFirst[len(d.OnlineWebFirst)-1] = nil\n\t\td.OnlineWebFirst = d.OnlineWebFirst[:len(d.OnlineWebFirst)-1]\n\t\tfor i, h := range d.OnlineWebFirst {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.UnknownMidWebFirst)\n\t\thand.State = UnknownMid\n\t\thand.LastState = time.Now()\n\t\td.UnknownMidWebFirst = append(d.UnknownMidWebFirst, hand)\n\n\t\thand = d.OnlineWebSecond[index]\n\t\td.OnlineWebSecond[index] = d.OnlineWebSecond[len(d.OnlineWebSecond)-1]\n\t\td.OnlineWebSecond[len(d.OnlineWebSecond)-1] = nil\n\t\td.OnlineWebSecond = d.OnlineWebSecond[:len(d.OnlineWebSecond)-1]\n\t\tfor i, h := range d.OnlineWebSecond {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.UnknownMidWebSecond)\n\t\thand.State = UnknownMid\n\t\thand.LastState = time.Now()\n\t\td.UnknownMidWebSecond = append(d.UnknownMidWebSecond, hand)\n\n\t\thand = d.OnlineWebThird[index]\n\t\td.OnlineWebThird[index] = d.OnlineWebThird[len(d.OnlineWebThird)-1]\n\t\td.OnlineWebThird[len(d.OnlineWebThird)-1] = nil\n\t\td.OnlineWebThird = d.OnlineWebThird[:len(d.OnlineWebThird)-1]\n\t\tfor i, h := range d.OnlineWebThird {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.UnknownMidWebThird)\n\t\thand.State = UnknownMid\n\t\thand.LastState = time.Now()\n\t\td.UnknownMidWebThird = append(d.UnknownMidWebThird, hand)\n\n\t\tbreak\n\tcase UnknownHigh:\n\t\thand = d.UnknownHighWebFirst[index]\n\t\td.UnknownHighWebFirst[index] =\n\t\t\td.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1]\n\t\td.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1] = nil\n\t\td.UnknownHighWebFirst =\n\t\t\td.UnknownHighWebFirst[:len(d.UnknownHighWebFirst)-1]\n\t\tfor i, h := range d.UnknownHighWebFirst {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.UnknownMidWebFirst)\n\t\thand.State = UnknownMid\n\t\thand.LastState = time.Now()\n\t\td.UnknownMidWebFirst = append(d.UnknownMidWebFirst, hand)\n\n\t\thand = d.UnknownHighWebSecond[index]\n\t\td.UnknownHighWebSecond[index] =\n\t\t\td.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1]\n\t\td.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1] = nil\n\t\td.UnknownHighWebSecond =\n\t\t\td.UnknownHighWebSecond[:len(d.UnknownHighWebSecond)-1]\n\t\tfor i, h := range d.UnknownHighWebSecond {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.UnknownMidWebSecond)\n\t\thand.State = UnknownMid\n\t\thand.LastState = time.Now()\n\t\td.UnknownMidWebSecond = append(d.UnknownMidWebSecond, hand)\n\n\t\thand = d.UnknownHighWebThird[index]\n\t\td.UnknownHighWebThird[index] =\n\t\t\td.UnknownHighWebThird[len(d.UnknownHighWebThird)-1]\n\t\td.UnknownHighWebThird[len(d.UnknownHighWebThird)-1] = nil\n\t\td.UnknownHighWebThird =\n\t\t\td.UnknownHighWebThird[:len(d.UnknownHighWebThird)-1]\n\t\tfor i, h := range d.UnknownHighWebThird {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.UnknownMidWebThird)\n\t\thand.State = UnknownMid\n\t\thand.LastState = time.Now()\n\t\td.UnknownMidWebThird = append(d.UnknownMidWebThird, hand)\n\n\t\tbreak\n\tcase UnknownMid:\n\t\tif time.Since(hand.LastState) > 1*time.Second {\n\t\t\thand = d.UnknownMidWebFirst[index]\n\t\t\td.UnknownMidWebFirst[index] =\n\t\t\t\td.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1]\n\t\t\td.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1] = nil\n\t\t\td.UnknownMidWebFirst =\n\t\t\t\td.UnknownMidWebFirst[:len(d.UnknownMidWebFirst)-1]\n\t\t\tfor i, h := range d.UnknownMidWebFirst {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.UnknownLowWebFirst)\n\t\t\thand.State = UnknownLow\n\t\t\thand.LastState = time.Now()\n\t\t\td.UnknownLowWebFirst = append(d.UnknownLowWebFirst, hand)\n\n\t\t\thand = d.UnknownMidWebSecond[index]\n\t\t\td.UnknownMidWebSecond[index] =\n\t\t\t\td.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1]\n\t\t\td.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1] = nil\n\t\t\td.UnknownMidWebSecond =\n\t\t\t\td.UnknownMidWebSecond[:len(d.UnknownMidWebSecond)-1]\n\t\t\tfor i, h := range d.UnknownMidWebSecond {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.UnknownLowWebSecond)\n\t\t\thand.State = UnknownLow\n\t\t\thand.LastState = time.Now()\n\t\t\td.UnknownLowWebSecond = append(d.UnknownLowWebSecond, hand)\n\n\t\t\thand = d.UnknownMidWebThird[index]\n\t\t\td.UnknownMidWebThird[index] =\n\t\t\t\td.UnknownMidWebThird[len(d.UnknownMidWebThird)-1]\n\t\t\td.UnknownMidWebThird[len(d.UnknownMidWebThird)-1] = nil\n\t\t\td.UnknownMidWebThird =\n\t\t\t\td.UnknownMidWebThird[:len(d.UnknownMidWebThird)-1]\n\t\t\tfor i, h := range d.UnknownMidWebThird {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.UnknownLowWebThird)\n\t\t\thand.State = UnknownLow\n\t\t\thand.LastState = time.Now()\n\t\t\td.UnknownLowWebThird = append(d.UnknownLowWebThird, hand)\n\t\t}\n\n\t\tbreak\n\tcase UnknownLow:\n\t\tif time.Since(hand.LastState) > 2*time.Second {\n\t\t\thand = d.UnknownLowWebFirst[index]\n\t\t\td.UnknownLowWebFirst[index] =\n\t\t\t\td.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1]\n\t\t\td.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1] = nil\n\t\t\td.UnknownLowWebFirst =\n\t\t\t\td.UnknownLowWebFirst[:len(d.UnknownLowWebFirst)-1]\n\t\t\tfor i, h := range d.UnknownLowWebFirst {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OfflineWebFirst)\n\t\t\thand.State = Offline\n\t\t\thand.LastState = time.Now()\n\t\t\td.OfflineWebFirst = append(d.OfflineWebFirst, hand)\n\n\t\t\thand = d.UnknownLowWebSecond[index]\n\t\t\td.UnknownLowWebSecond[index] =\n\t\t\t\td.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1]\n\t\t\td.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1] = nil\n\t\t\td.UnknownLowWebSecond =\n\t\t\t\td.UnknownLowWebSecond[:len(d.UnknownLowWebSecond)-1]\n\t\t\tfor i, h := range d.UnknownLowWebSecond {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OfflineWebSecond)\n\t\t\thand.State = Offline\n\t\t\thand.LastState = time.Now()\n\t\t\td.OfflineWebSecond = append(d.OfflineWebSecond, hand)\n\n\t\t\thand = d.UnknownLowWebThird[index]\n\t\t\td.UnknownLowWebThird[index] =\n\t\t\t\td.UnknownLowWebThird[len(d.UnknownLowWebThird)-1]\n\t\t\td.UnknownLowWebThird[len(d.UnknownLowWebThird)-1] = nil\n\t\t\td.UnknownLowWebThird =\n\t\t\t\td.UnknownLowWebThird[:len(d.UnknownLowWebThird)-1]\n\t\t\tfor i, h := range d.UnknownLowWebThird {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OfflineWebThird)\n\t\t\thand.State = Offline\n\t\t\thand.LastState = time.Now()\n\t\t\td.OfflineWebThird = append(d.OfflineWebThird, hand)\n\t\t}\n\n\t\tbreak\n\tcase Offline:\n\t\tbreak\n\t}\n}\n\nfunc (d *Domain) offlineHandler(hand *Handler) {\n\td.Lock.Lock()\n\tdefer d.Lock.Unlock()\n\n\tindex := hand.Index\n\tstate := hand.State\n\n\tswitch state {\n\tcase Online:\n\t\thand = d.OnlineWebFirst[index]\n\t\td.OnlineWebFirst[index] = d.OnlineWebFirst[len(d.OnlineWebFirst)-1]\n\t\td.OnlineWebFirst[len(d.OnlineWebFirst)-1] = nil\n\t\td.OnlineWebFirst = d.OnlineWebFirst[:len(d.OnlineWebFirst)-1]\n\t\tfor i, h := range d.OnlineWebFirst {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.UnknownMidWebFirst)\n\t\thand.State = Offline\n\t\thand.LastState = time.Now()\n\t\td.OfflineWebFirst = append(d.OfflineWebFirst, hand)\n\n\t\thand = d.OnlineWebSecond[index]\n\t\td.OnlineWebSecond[index] = d.OnlineWebSecond[len(d.OnlineWebSecond)-1]\n\t\td.OnlineWebSecond[len(d.OnlineWebSecond)-1] = nil\n\t\td.OnlineWebSecond = d.OnlineWebSecond[:len(d.OnlineWebSecond)-1]\n\t\tfor i, h := range d.OnlineWebSecond {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.OfflineWebSecond)\n\t\thand.State = Offline\n\t\thand.LastState = time.Now()\n\t\td.OfflineWebSecond = append(d.OfflineWebSecond, hand)\n\n\t\thand = d.OnlineWebThird[index]\n\t\td.OnlineWebThird[index] = d.OnlineWebThird[len(d.OnlineWebThird)-1]\n\t\td.OnlineWebThird[len(d.OnlineWebThird)-1] = nil\n\t\td.OnlineWebThird = d.OnlineWebThird[:len(d.OnlineWebThird)-1]\n\t\tfor i, h := range d.OnlineWebThird {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.OfflineWebThird)\n\t\thand.State = Offline\n\t\thand.LastState = time.Now()\n\t\td.OfflineWebThird = append(d.OfflineWebThird, hand)\n\n\t\tbreak\n\tcase UnknownHigh:\n\t\thand = d.UnknownHighWebFirst[index]\n\t\td.UnknownHighWebFirst[index] =\n\t\t\td.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1]\n\t\td.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1] = nil\n\t\td.UnknownHighWebFirst =\n\t\t\td.UnknownHighWebFirst[:len(d.UnknownHighWebFirst)-1]\n\t\tfor i, h := range d.UnknownHighWebFirst {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.UnknownMidWebFirst)\n\t\thand.State = Offline\n\t\thand.LastState = time.Now()\n\t\td.OfflineWebFirst = append(d.OfflineWebFirst, hand)\n\n\t\thand = d.UnknownHighWebSecond[index]\n\t\td.UnknownHighWebSecond[index] =\n\t\t\td.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1]\n\t\td.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1] = nil\n\t\td.UnknownHighWebSecond =\n\t\t\td.UnknownHighWebSecond[:len(d.UnknownHighWebSecond)-1]\n\t\tfor i, h := range d.UnknownHighWebSecond {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.OfflineWebSecond)\n\t\thand.State = Offline\n\t\thand.LastState = time.Now()\n\t\td.OfflineWebSecond = append(d.OfflineWebSecond, hand)\n\n\t\thand = d.UnknownHighWebThird[index]\n\t\td.UnknownHighWebThird[index] =\n\t\t\td.UnknownHighWebThird[len(d.UnknownHighWebThird)-1]\n\t\td.UnknownHighWebThird[len(d.UnknownHighWebThird)-1] = nil\n\t\td.UnknownHighWebThird =\n\t\t\td.UnknownHighWebThird[:len(d.UnknownHighWebThird)-1]\n\t\tfor i, h := range d.UnknownHighWebThird {\n\t\t\th.Index = i\n\t\t}\n\t\thand.Index = len(d.OfflineWebThird)\n\t\thand.State = Offline\n\t\thand.LastState = time.Now()\n\t\td.OfflineWebThird = append(d.OfflineWebThird, hand)\n\n\t\tbreak\n\tcase UnknownMid:\n\t\tif time.Since(hand.LastState) > 1*time.Second {\n\t\t\thand = d.UnknownMidWebFirst[index]\n\t\t\td.UnknownMidWebFirst[index] =\n\t\t\t\td.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1]\n\t\t\td.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1] = nil\n\t\t\td.UnknownMidWebFirst =\n\t\t\t\td.UnknownMidWebFirst[:len(d.UnknownMidWebFirst)-1]\n\t\t\tfor i, h := range d.UnknownMidWebFirst {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.UnknownLowWebFirst)\n\t\t\thand.State = Offline\n\t\t\thand.LastState = time.Now()\n\t\t\td.OfflineWebFirst = append(d.OfflineWebFirst, hand)\n\n\t\t\thand = d.UnknownMidWebSecond[index]\n\t\t\td.UnknownMidWebSecond[index] =\n\t\t\t\td.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1]\n\t\t\td.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1] = nil\n\t\t\td.UnknownMidWebSecond =\n\t\t\t\td.UnknownMidWebSecond[:len(d.UnknownMidWebSecond)-1]\n\t\t\tfor i, h := range d.UnknownMidWebSecond {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OfflineWebSecond)\n\t\t\thand.State = Offline\n\t\t\thand.LastState = time.Now()\n\t\t\td.OfflineWebSecond = append(d.OfflineWebSecond, hand)\n\n\t\t\thand = d.UnknownMidWebThird[index]\n\t\t\td.UnknownMidWebThird[index] =\n\t\t\t\td.UnknownMidWebThird[len(d.UnknownMidWebThird)-1]\n\t\t\td.UnknownMidWebThird[len(d.UnknownMidWebThird)-1] = nil\n\t\t\td.UnknownMidWebThird =\n\t\t\t\td.UnknownMidWebThird[:len(d.UnknownMidWebThird)-1]\n\t\t\tfor i, h := range d.UnknownMidWebThird {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OfflineWebThird)\n\t\t\thand.State = Offline\n\t\t\thand.LastState = time.Now()\n\t\t\td.OfflineWebThird = append(d.OfflineWebThird, hand)\n\t\t}\n\n\t\tbreak\n\tcase UnknownLow:\n\t\tif time.Since(hand.LastState) > 2*time.Second {\n\t\t\thand = d.UnknownLowWebFirst[index]\n\t\t\td.UnknownLowWebFirst[index] =\n\t\t\t\td.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1]\n\t\t\td.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1] = nil\n\t\t\td.UnknownLowWebFirst =\n\t\t\t\td.UnknownLowWebFirst[:len(d.UnknownLowWebFirst)-1]\n\t\t\tfor i, h := range d.UnknownLowWebFirst {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OfflineWebFirst)\n\t\t\thand.State = Offline\n\t\t\thand.LastState = time.Now()\n\t\t\td.OfflineWebFirst = append(d.OfflineWebFirst, hand)\n\n\t\t\thand = d.UnknownLowWebSecond[index]\n\t\t\td.UnknownLowWebSecond[index] =\n\t\t\t\td.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1]\n\t\t\td.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1] = nil\n\t\t\td.UnknownLowWebSecond =\n\t\t\t\td.UnknownLowWebSecond[:len(d.UnknownLowWebSecond)-1]\n\t\t\tfor i, h := range d.UnknownLowWebSecond {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OfflineWebSecond)\n\t\t\thand.State = Offline\n\t\t\thand.LastState = time.Now()\n\t\t\td.OfflineWebSecond = append(d.OfflineWebSecond, hand)\n\n\t\t\thand = d.UnknownLowWebThird[index]\n\t\t\td.UnknownLowWebThird[index] =\n\t\t\t\td.UnknownLowWebThird[len(d.UnknownLowWebThird)-1]\n\t\t\td.UnknownLowWebThird[len(d.UnknownLowWebThird)-1] = nil\n\t\t\td.UnknownLowWebThird =\n\t\t\t\td.UnknownLowWebThird[:len(d.UnknownLowWebThird)-1]\n\t\t\tfor i, h := range d.UnknownLowWebThird {\n\t\t\t\th.Index = i\n\t\t\t}\n\t\t\thand.Index = len(d.OfflineWebThird)\n\t\t\thand.State = Offline\n\t\t\thand.LastState = time.Now()\n\t\t\td.OfflineWebThird = append(d.OfflineWebThird, hand)\n\t\t}\n\n\t\tbreak\n\tcase Offline:\n\t\tbreak\n\t}\n}\n\nfunc (d *Domain) ResponseHandler(hand *Handler, resp *http.Response) error {\n\tif hand.State != Online && resp.StatusCode < 500 {\n\t\td.upgradeHandler(hand)\n\t}\n\n\treturn nil\n}\n\nfunc (d *Domain) ErrorHandlerFirst(hand *Handler, rw http.ResponseWriter,\n\tr *http.Request, err error) {\n\n\tif _, ok := err.(*WebSocketBlock); ok {\n\t\treturn\n\t}\n\n\td.downgradeHandler(hand)\n\td.ServeHTTPSecond(rw, r)\n}\n\nfunc (d *Domain) ErrorHandlerSecond(hand *Handler, rw http.ResponseWriter,\n\tr *http.Request, err error) {\n\n\tif _, ok := err.(*WebSocketBlock); ok {\n\t\treturn\n\t}\n\n\td.downgradeHandler(hand)\n\td.ServeHTTPThird(rw, r)\n}\n\nfunc (d *Domain) ErrorHandlerThird(hand *Handler, rw http.ResponseWriter,\n\tr *http.Request, err error) {\n\n\tif _, ok := err.(*WebSocketBlock); ok {\n\t\treturn\n\t}\n\n\td.downgradeHandler(hand)\n\trw.WriteHeader(http.StatusBadGateway)\n}\n"
  },
  {
    "path": "proxy/errortypes.go",
    "content": "package proxy\n\nimport \"github.com/dropbox/godropbox/errors\"\n\ntype WebSocketBlock struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "proxy/proxy.go",
    "content": "package proxy\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/balancer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Proxy struct {\n\tDomains map[string]*Domain\n\tlock    sync.Mutex\n}\n\ntype balancerState struct {\n\tBalancer *balancer.Balancer\n\tState    *balancer.State\n}\n\nfunc (p *Proxy) ServeHTTP(hst string, rw http.ResponseWriter,\n\tr *http.Request) {\n\n\tdomain := p.Domains[hst]\n\tif domain == nil {\n\t\tutils.WriteStatus(rw, 404)\n\t\treturn\n\t}\n\n\tdomain.ServeHTTPFirst(rw, r)\n}\n\nfunc (p *Proxy) Update(db *database.Database, balncs []*balancer.Balancer) (\n\terr error) {\n\n\tdomains := map[string]*Domain{}\n\tdomainsName := set.NewSet()\n\tremDomains := []*Domain{}\n\tstates := []*balancerState{}\n\n\tproxyProto := node.Self.Protocol\n\tproxyPort := node.Self.Port\n\n\tp.lock.Lock()\n\tfor _, balnc := range balncs {\n\t\tif !balnc.State {\n\t\t\tcontinue\n\t\t}\n\n\t\tonlineWeb := set.NewSet()\n\t\tunknownHighWeb := set.NewSet()\n\t\tunknownMidWeb := set.NewSet()\n\t\tunknownLowWeb := set.NewSet()\n\t\tofflineWeb := set.NewSet()\n\n\t\tstate := &balancer.State{\n\t\t\tTimestamp:   time.Now(),\n\t\t\tOnline:      []string{},\n\t\t\tUnknownHigh: []string{},\n\t\t\tUnknownMid:  []string{},\n\t\t\tUnknownLow:  []string{},\n\t\t\tOffline:     []string{},\n\t\t}\n\n\t\tfor _, domain := range balnc.Domains {\n\t\t\tif domains[domain.Domain] != nil {\n\t\t\t\tconflictDomain := domains[domain.Domain]\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"first_balancer_id\":    conflictDomain.Balancer.Id.Hex(),\n\t\t\t\t\t\"first_balancer_name\":  conflictDomain.Balancer.Name,\n\t\t\t\t\t\"second_balancer_id\":   balnc.Id.Hex(),\n\t\t\t\t\t\"second_balancer_name\": balnc.Name,\n\t\t\t\t\t\"conflict_domain\":      domain.Domain,\n\t\t\t\t}).Error(\"proxy: Balancer domain conflict\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdomainsName.Add(domain.Domain)\n\n\t\t\tproxyDomain := &Domain{\n\t\t\t\tSkipVerify: settings.Router.SkipVerify,\n\t\t\t\tProxyProto: proxyProto,\n\t\t\t\tProxyPort:  proxyPort,\n\t\t\t\tBalancer:   balnc,\n\t\t\t\tDomain:     domain,\n\t\t\t\tRequests:   new(int32),\n\t\t\t\tRetries:    new(int32),\n\t\t\t}\n\t\t\tproxyDomain.CalculateHash()\n\n\t\t\tcurDomain := p.Domains[domain.Domain]\n\t\t\tif curDomain != nil && curDomain.Balancer.Id == balnc.Id {\n\t\t\t\tstate.Requests += curDomain.RequestsTotal\n\t\t\t\tstate.Retries += curDomain.RetriesTotal\n\t\t\t\tstate.WebSockets += curDomain.WebSocketConns.Len()\n\n\t\t\t\tcurDomain.Lock.Lock()\n\t\t\t\tfor _, hand := range curDomain.OnlineWebFirst {\n\t\t\t\t\tonlineWeb.Add(hand.Key)\n\t\t\t\t}\n\t\t\t\tfor _, hand := range curDomain.UnknownHighWebFirst {\n\t\t\t\t\tunknownHighWeb.Add(hand.Key)\n\t\t\t\t}\n\t\t\t\tfor _, hand := range curDomain.UnknownMidWebFirst {\n\t\t\t\t\tunknownMidWeb.Add(hand.Key)\n\t\t\t\t}\n\t\t\t\tfor _, hand := range curDomain.UnknownLowWebFirst {\n\t\t\t\t\tunknownLowWeb.Add(hand.Key)\n\t\t\t\t}\n\t\t\t\tfor _, hand := range curDomain.OfflineWebFirst {\n\t\t\t\t\tofflineWeb.Add(hand.Key)\n\t\t\t\t}\n\n\t\t\t\tif bytes.Equal(curDomain.Hash, proxyDomain.Hash) {\n\t\t\t\t\tdomains[domain.Domain] = curDomain\n\t\t\t\t\tcurDomain.Lock.Unlock()\n\t\t\t\t\tcontinue\n\t\t\t\t} else {\n\t\t\t\t\tproxyDomain.Requests = curDomain.Requests\n\t\t\t\t\tproxyDomain.RequestsPrev = curDomain.RequestsPrev\n\t\t\t\t\tproxyDomain.RequestsTotal = curDomain.RequestsTotal\n\t\t\t\t\tproxyDomain.Retries = curDomain.Retries\n\t\t\t\t\tproxyDomain.RetriesPrev = curDomain.RetriesPrev\n\t\t\t\t\tproxyDomain.RetriesTotal = curDomain.RetriesTotal\n\t\t\t\t\tcurDomain.Lock.Unlock()\n\n\t\t\t\t\tremDomains = append(remDomains, curDomain)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tproxyDomain.Init()\n\n\t\t\tdomains[domain.Domain] = proxyDomain\n\t\t}\n\n\t\trecorded := set.NewSet()\n\t\tfor keyInf := range offlineWeb.Iter() {\n\t\t\tif recorded.Contains(keyInf) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trecorded.Add(keyInf)\n\n\t\t\tstate.Offline = append(state.Offline, keyInf.(string))\n\t\t}\n\t\tfor keyInf := range unknownLowWeb.Iter() {\n\t\t\tif recorded.Contains(keyInf) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trecorded.Add(keyInf)\n\n\t\t\tstate.UnknownLow = append(state.UnknownLow, keyInf.(string))\n\t\t}\n\t\tfor keyInf := range unknownMidWeb.Iter() {\n\t\t\tif recorded.Contains(keyInf) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trecorded.Add(keyInf)\n\n\t\t\tstate.UnknownMid = append(state.UnknownMid, keyInf.(string))\n\t\t}\n\t\tfor keyInf := range unknownHighWeb.Iter() {\n\t\t\tif recorded.Contains(keyInf) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trecorded.Add(keyInf)\n\n\t\t\tstate.UnknownHigh = append(state.UnknownHigh, keyInf.(string))\n\t\t}\n\t\tfor keyInf := range onlineWeb.Iter() {\n\t\t\tif recorded.Contains(keyInf) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trecorded.Add(keyInf)\n\n\t\t\tstate.Online = append(state.Online, keyInf.(string))\n\t\t}\n\n\t\tstates = append(states, &balancerState{\n\t\t\tBalancer: balnc,\n\t\t\tState:    state,\n\t\t})\n\t}\n\n\tcurDomains := p.Domains\n\tfor name, domain := range curDomains {\n\t\tif !domainsName.Contains(name) {\n\t\t\tremDomains = append(remDomains, domain)\n\t\t}\n\t}\n\n\tp.Domains = domains\n\tp.lock.Unlock()\n\n\tfor _, domain := range remDomains {\n\t\tdomain.WebSocketConnsLock.Lock()\n\t\tfor socketInf := range domain.WebSocketConns.Iter() {\n\t\t\tfunc() {\n\t\t\t\tsocket := socketInf.(*webSocketConn)\n\t\t\t\tsocket.Close()\n\t\t\t}()\n\t\t}\n\t\tdomain.WebSocketConns = set.NewSet()\n\t\tdomain.WebSocketConnsLock.Unlock()\n\t}\n\n\tfor _, balncState := range states {\n\t\terr = balncState.Balancer.CommitState(db, balncState.State)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (p *Proxy) syncCount() {\n\tp.lock.Lock()\n\tdefer p.lock.Unlock()\n\n\tdomains := p.Domains\n\tfor _, dom := range domains {\n\t\treq := dom.Requests\n\t\tdom.Requests = new(int32)\n\t\treqPrev := dom.RequestsPrev\n\t\treqTotal := reqPrev[0] + reqPrev[1] + reqPrev[2] +\n\t\t\treqPrev[3] + reqPrev[4]\n\t\treqPrev[0] = reqPrev[1]\n\t\treqPrev[1] = reqPrev[2]\n\t\treqPrev[2] = reqPrev[3]\n\t\treqPrev[3] = reqPrev[4]\n\t\treqPrev[4] = int(*req)\n\t\treqTotal += int(*req)\n\t\tdom.RequestsPrev = reqPrev\n\t\tdom.RequestsTotal = reqTotal\n\n\t\tret := dom.Retries\n\t\tdom.Retries = new(int32)\n\t\tretPrev := dom.RetriesPrev\n\t\tretTotal := retPrev[0] + retPrev[1] + retPrev[2] +\n\t\t\tretPrev[3] + retPrev[4]\n\t\tretPrev[0] = retPrev[1]\n\t\tretPrev[1] = retPrev[2]\n\t\tretPrev[2] = retPrev[3]\n\t\tretPrev[3] = retPrev[4]\n\t\tretPrev[4] = int(*ret)\n\t\tretTotal += int(*ret)\n\t\tdom.RetriesPrev = retPrev\n\t\tdom.RetriesTotal = retTotal\n\t}\n}\n\nfunc (p *Proxy) runCounter() {\n\tfor {\n\t\ttime.Sleep(10 * time.Second)\n\t\tp.syncCount()\n\t}\n}\n\nfunc (p *Proxy) healthCheck() {\n\tp.lock.Lock()\n\tdefer p.lock.Unlock()\n\n\tdomains := p.Domains\n\tfor _, dom := range domains {\n\t\tdom.Check()\n\t}\n}\n\nfunc (p *Proxy) runHealthCheck() {\n\tfor {\n\t\ttime.Sleep(5 * time.Second)\n\t\tp.healthCheck()\n\t}\n}\n\nfunc (p *Proxy) Init() {\n\tp.Domains = map[string]*Domain{}\n\tgo p.runCounter()\n\tgo p.runHealthCheck()\n}\n"
  },
  {
    "path": "proxy/resolver.go",
    "content": "package proxy\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nvar (\n\tResolverLock    sync.RWMutex\n\tIpDatabase      = set.NewSet()\n\tResolverCache   = map[string]*Remote{}\n\tHostNetwork     *net.IPNet\n\tNodePortNetwork *net.IPNet\n)\n\ntype Remote struct {\n\tTimestamp time.Time\n\tRemote    net.IP\n}\n\nfunc ResolverRefresh(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\tipDatabase := set.NewSet()\n\tttl := time.Duration(settings.Router.ProxyResolverTtl) * time.Second\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{},\n\t\toptions.Find().\n\t\t\tSetProjection(&bson.M{\n\t\t\t\t\"public_ips\":        1,\n\t\t\t\t\"public_ips6\":       1,\n\t\t\t\t\"cloud_private_ips\": 1,\n\t\t\t\t\"cloud_public_ips\":  1,\n\t\t\t\t\"cloud_public_ips6\": 1,\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tinst := &instance.Instance{}\n\n\t\terr = cursor.Decode(inst)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, ipStr := range inst.PublicIps {\n\t\t\tip := net.ParseIP(ipStr)\n\t\t\tif ip != nil {\n\t\t\t\tipDatabase.Add(ip.String())\n\t\t\t}\n\t\t}\n\n\t\tfor _, ipStr := range inst.PublicIps6 {\n\t\t\tip := net.ParseIP(ipStr)\n\t\t\tif ip != nil {\n\t\t\t\tipDatabase.Add(ip.String())\n\t\t\t}\n\t\t}\n\n\t\tfor _, ipStr := range inst.CloudPublicIps {\n\t\t\tip := net.ParseIP(ipStr)\n\t\t\tif ip != nil {\n\t\t\t\tipDatabase.Add(ip.String())\n\t\t\t}\n\t\t}\n\n\t\tfor _, ipStr := range inst.CloudPublicIps6 {\n\t\t\tip := net.ParseIP(ipStr)\n\t\t\tif ip != nil {\n\t\t\t\tipDatabase.Add(ip.String())\n\t\t\t}\n\t\t}\n\n\t\tfor _, ipStr := range inst.CloudPrivateIps {\n\t\t\tip := net.ParseIP(ipStr)\n\t\t\tif ip != nil {\n\t\t\t\tipDatabase.Add(ip.String())\n\t\t\t}\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, hostNetwork, err := net.ParseCIDR(\n\t\tsettings.Hypervisor.HostNetwork)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, nodePortNetwork, err := net.ParseCIDR(\n\t\tsettings.Hypervisor.NodePortNetwork)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnow := time.Now()\n\tResolverLock.Lock()\n\tfor hostname, cached := range ResolverCache {\n\t\tif now.Sub(cached.Timestamp) > ttl {\n\t\t\tdelete(ResolverCache, hostname)\n\t\t}\n\t}\n\tIpDatabase = ipDatabase\n\tHostNetwork = hostNetwork\n\tNodePortNetwork = nodePortNetwork\n\tResolverLock.Unlock()\n\n\treturn\n}\n\nfunc ResolverValidate(ip net.IP) bool {\n\tif IpDatabase.Contains(ip.String()) {\n\t\treturn true\n\t}\n\tif HostNetwork.Contains(ip) {\n\t\treturn true\n\t}\n\tif NodePortNetwork.Contains(ip) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc Resolve(hostname string) (remote net.IP, err error) {\n\tResolverLock.RLock()\n\tcached, ok := ResolverCache[hostname]\n\tif ok {\n\t\tResolverLock.RUnlock()\n\n\t\tremote = cached.Remote\n\t\treturn\n\t}\n\tResolverLock.RUnlock()\n\n\tip := net.ParseIP(hostname)\n\tif ip != nil {\n\t\tResolverLock.RLock()\n\t\tcontains := ResolverValidate(ip)\n\t\tResolverLock.RUnlock()\n\n\t\tif !contains {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.New(\"proxy: Balancer resolved address not in database\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tremote = ip\n\t} else {\n\t\tips, e := net.LookupIP(hostname)\n\t\tif e != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(e, \"proxy: Balancer resolve error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tResolverLock.RLock()\n\t\tfor _, ip := range ips {\n\t\t\tif ResolverValidate(ip) {\n\t\t\t\tremote = ip\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tResolverLock.RUnlock()\n\n\t\tif remote == nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.New(\"proxy: Balancer resolved address not in database\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tResolverLock.Lock()\n\tResolverCache[hostname] = &Remote{\n\t\tTimestamp: time.Now(),\n\t\tRemote:    remote,\n\t}\n\tResolverLock.Unlock()\n\n\treturn\n}\n\ntype StaticDialer struct {\n\tdialer *net.Dialer\n}\n\nfunc (d *StaticDialer) DialContext(ctx context.Context, network, addr string) (\n\tconn net.Conn, err error) {\n\n\thost, port, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tremote, err := Resolve(host)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tconn, err = d.dialer.DialContext(\n\t\tctx, network, net.JoinHostPort(remote.String(), port))\n\tif err == nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc NewStaticDialer(dialer *net.Dialer) *StaticDialer {\n\treturn &StaticDialer{\n\t\tdialer: dialer,\n\t}\n}\n\nfunc init() {\n\t_, HostNetwork, _ = net.ParseCIDR(\"0.0.0.0/32\")\n\t_, NodePortNetwork, _ = net.ParseCIDR(\"0.0.0.0/32\")\n}\n"
  },
  {
    "path": "proxy/reverse.go",
    "content": "package proxy\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/pritunl/pritunl-cloud/balancer\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/logger\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Handler struct {\n\tKey                string\n\tIndex              int\n\tState              int\n\tDomain             *Domain\n\tCheckUrl           string\n\tCheckClient        *http.Client\n\tLastState          time.Time\n\tLastOnlineState    time.Time\n\tBackendHost        string\n\tBackendProto       string\n\tBackendProtoWs     string\n\tRequestHost        string\n\tForwardedProto     string\n\tForwardedPort      string\n\tTlsConfig          *tls.Config\n\tDialer             *StaticDialer\n\tWebSockets         bool\n\tWebSocketsUpgrader *websocket.Upgrader\n\tErrorHandler       ErrorHandler\n\t*httputil.ReverseProxy\n}\n\nfunc (h *Handler) ServeWS(rw http.ResponseWriter, r *http.Request) {\n\theader := utils.CloneHeader(r.Header)\n\tu := &url.URL{}\n\t*u = *r.URL\n\n\tu.Scheme = h.BackendProtoWs\n\tu.Host = h.BackendHost\n\n\tif h.RequestHost != \"\" {\n\t\tr.Host = h.RequestHost\n\t}\n\n\theader.Set(\"X-Forwarded-For\",\n\t\tnode.Self.GetRemoteAddr(r))\n\theader.Set(\"X-Forwarded-Host\", r.Host)\n\theader.Set(\"X-Forwarded-Proto\", h.ForwardedProto)\n\theader.Set(\"X-Forwarded-Port\", h.ForwardedPort)\n\n\theader.Del(\"Upgrade\")\n\theader.Del(\"Connection\")\n\theader.Del(\"Sec-Websocket-Key\")\n\theader.Del(\"Sec-Websocket-Version\")\n\theader.Del(\"Sec-Websocket-Extensions\")\n\n\tvar backConn *websocket.Conn\n\tvar backResp *http.Response\n\tvar err error\n\n\tdialer := &websocket.Dialer{\n\t\tNetDialContext: h.Dialer.DialContext,\n\t\tProxy: func(req *http.Request) (url *url.URL, err error) {\n\t\t\tif h.RequestHost != \"\" {\n\t\t\t\treq.Host = h.RequestHost\n\t\t\t} else {\n\t\t\t\treq.Host = r.Host\n\t\t\t}\n\t\t\treturn\n\t\t},\n\t\tHandshakeTimeout: 45 * time.Second,\n\t\tTLSClientConfig:  h.TlsConfig,\n\t}\n\n\tbackConn, backResp, err = dialer.Dial(u.String(), header)\n\tif err != nil {\n\t\tif backResp != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrapf(err, \"proxy: WebSocket dial error %d\",\n\t\t\t\t\tbackResp.StatusCode),\n\t\t\t}\n\t\t} else {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"proxy: WebSocket dial error\"),\n\t\t\t}\n\t\t}\n\n\t\th.ErrorHandler(h, rw, r, err)\n\t\treturn\n\t}\n\tdefer backConn.Close()\n\n\tupgradeHeaders := http.Header{}\n\tval := backResp.Header.Get(\"Sec-Websocket-Protocol\")\n\tif val != \"\" {\n\t\tupgradeHeaders.Set(\"Sec-Websocket-Protocol\", val)\n\t}\n\tval = backResp.Header.Get(\"Set-Cookie\")\n\tif val != \"\" {\n\t\tupgradeHeaders.Set(\"Set-Cookie\", val)\n\t}\n\n\tfrontConn, err := h.WebSocketsUpgrader.Upgrade(rw, r, upgradeHeaders)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"proxy: WebSocket upgrade error\"),\n\t\t}\n\n\t\th.ErrorHandler(h, rw, r, err)\n\t\treturn\n\t}\n\tdefer frontConn.Close()\n\n\tconn := &webSocketConn{\n\t\tfront: frontConn,\n\t\tback:  backConn,\n\t\tr:     r,\n\t}\n\n\tconn.Run(h.Domain)\n}\n\nfunc (h *Handler) Serve(rw http.ResponseWriter, r *http.Request) {\n\tif h.WebSockets && strings.ToLower(\n\t\tr.Header.Get(\"Upgrade\")) == \"websocket\" {\n\n\t\th.ServeWS(rw, r)\n\t} else {\n\t\th.ServeHTTP(rw, r)\n\t}\n}\n\nfunc NewHandler(index, state int, proxyProto string, proxyPort int,\n\tdomain *Domain, backend *balancer.Backend, respHandler RespHandler,\n\terrHandler ErrorHandler) (hand *Handler) {\n\n\tproxyPortStr := strconv.Itoa(proxyPort)\n\treqHost := domain.Domain.Host\n\tbackendProto := backend.Protocol\n\tbackendHost := utils.FormatHostPort(backend.Hostname, backend.Port)\n\n\tbackendProtoWs := \"\"\n\tif backendProto == \"https\" {\n\t\tbackendProtoWs = \"wss\"\n\t} else {\n\t\tbackendProtoWs = \"ws\"\n\t}\n\n\thandUrl := fmt.Sprintf(\n\t\t\"%s://%s:%d\",\n\t\tbackend.Protocol,\n\t\tbackend.Hostname,\n\t\tbackend.Port,\n\t)\n\n\tcheckUrl, err := url.Parse(handUrl)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"balancer\":   domain.Balancer.Name,\n\t\t\t\"domain\":     domain.Domain.Domain,\n\t\t\t\"protocol\":   backend.Protocol,\n\t\t\t\"hostname\":   backend.Hostname,\n\t\t\t\"port\":       backend.Port,\n\t\t\t\"check_path\": domain.Balancer.CheckPath,\n\t\t}).Error(\"proxy: Error parsing balancer backend URL\")\n\n\t\tcheckUrl, _ = url.Parse(\"http://0.0.0.0\")\n\t}\n\tcheckUrl.Path = domain.Balancer.CheckPath\n\n\tdialTimeout := time.Duration(\n\t\tsettings.Router.DialTimeout) * time.Second\n\tdialKeepAlive := time.Duration(\n\t\tsettings.Router.DialKeepAlive) * time.Second\n\tmaxIdleConns := settings.Router.MaxIdleConns\n\tmaxIdleConnsPerHost := settings.Router.MaxIdleConnsPerHost\n\tidleConnTimeout := time.Duration(\n\t\tsettings.Router.IdleConnTimeout) * time.Second\n\thandshakeTimeout := time.Duration(\n\t\tsettings.Router.HandshakeTimeout) * time.Second\n\tcontinueTimeout := time.Duration(\n\t\tsettings.Router.ContinueTimeout) * time.Second\n\n\ttlsConfig := &tls.Config{\n\t\tMinVersion: tls.VersionTLS12,\n\t\tMaxVersion: tls.VersionTLS13,\n\t\tServerName: backend.Hostname,\n\t}\n\tif domain.SkipVerify || net.ParseIP(backend.Hostname) != nil {\n\t\ttlsConfig.InsecureSkipVerify = true\n\t}\n\n\tif domain.ClientCertificate != nil {\n\t\ttlsConfig.Certificates = []tls.Certificate{\n\t\t\t*domain.ClientCertificate,\n\t\t}\n\t}\n\n\twriter := &logger.ErrorWriter{\n\t\tMessage: \"proxy: Balancer server error\",\n\t\tFields: logrus.Fields{\n\t\t\t\"balancer\": domain.Balancer.Name,\n\t\t\t\"domain\":   domain.Domain.Domain,\n\t\t\t\"server\":   handUrl,\n\t\t},\n\t\tFilters: []string{\n\t\t\t\"context canceled\",\n\t\t},\n\t}\n\n\tdialer := NewStaticDialer(&net.Dialer{\n\t\tTimeout:   dialTimeout,\n\t\tKeepAlive: dialKeepAlive,\n\t\tDualStack: true,\n\t})\n\n\tcheckClient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext:         dialer.DialContext,\n\t\t\tDisableKeepAlives:   true,\n\t\t\tTLSHandshakeTimeout: 5 * time.Second,\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t\tMinVersion:         tls.VersionTLS12,\n\t\t\t\tMaxVersion:         tls.VersionTLS13,\n\t\t\t},\n\t\t},\n\t\tTimeout: 5 * time.Second,\n\t}\n\n\thand = &Handler{\n\t\tKey:            fmt.Sprintf(\"%s:%d\", backend.Hostname, backend.Port),\n\t\tIndex:          index,\n\t\tState:          state,\n\t\tDomain:         domain,\n\t\tCheckUrl:       checkUrl.String(),\n\t\tCheckClient:    checkClient,\n\t\tBackendHost:    backendHost,\n\t\tBackendProto:   backendProto,\n\t\tBackendProtoWs: backendProtoWs,\n\t\tRequestHost:    reqHost,\n\t\tForwardedProto: proxyProto,\n\t\tForwardedPort:  proxyPortStr,\n\t\tWebSockets:     domain.Balancer.WebSockets,\n\t\tTlsConfig:      tlsConfig,\n\t\tDialer:         dialer,\n\t\tErrorHandler:   errHandler,\n\t\tReverseProxy: &httputil.ReverseProxy{\n\t\t\tDirector: func(req *http.Request) {\n\t\t\t\treq.Header.Set(\"X-Forwarded-Host\", req.Host)\n\t\t\t\treq.Header.Set(\"X-Forwarded-Proto\", proxyProto)\n\t\t\t\treq.Header.Set(\"X-Forwarded-Port\", proxyPortStr)\n\n\t\t\t\tif reqHost != \"\" {\n\t\t\t\t\treq.Host = reqHost\n\t\t\t\t}\n\n\t\t\t\treq.URL.Scheme = backendProto\n\t\t\t\treq.URL.Host = backendHost\n\t\t\t},\n\t\t\tTransport: &TransportFix{\n\t\t\t\ttransport: &http.Transport{\n\t\t\t\t\tProxy:                 http.ProxyFromEnvironment,\n\t\t\t\t\tDialContext:           dialer.DialContext,\n\t\t\t\t\tMaxIdleConns:          maxIdleConns,\n\t\t\t\t\tMaxIdleConnsPerHost:   maxIdleConnsPerHost,\n\t\t\t\t\tIdleConnTimeout:       idleConnTimeout,\n\t\t\t\t\tTLSHandshakeTimeout:   handshakeTimeout,\n\t\t\t\t\tExpectContinueTimeout: continueTimeout,\n\t\t\t\t\tTLSClientConfig:       tlsConfig,\n\t\t\t\t},\n\t\t\t},\n\t\t\tErrorLog: log.New(writer, \"\", 0),\n\t\t\tModifyResponse: func(resp *http.Response) error {\n\t\t\t\treturn respHandler(hand, resp)\n\t\t\t},\n\t\t\tErrorHandler: func(rw http.ResponseWriter,\n\t\t\t\tr *http.Request, err error) {\n\n\t\t\t\terrHandler(hand, rw, r, err)\n\t\t\t},\n\t\t},\n\t}\n\n\tif hand.WebSockets {\n\t\thand.WebSocketsUpgrader = &websocket.Upgrader{\n\t\t\tHandshakeTimeout: time.Duration(\n\t\t\t\tsettings.Router.HandshakeTimeout) * time.Second,\n\t\t\tReadBufferSize:  1024,\n\t\t\tWriteBufferSize: 1024,\n\t\t\tCheckOrigin: func(r *http.Request) bool {\n\t\t\t\treturn true\n\t\t\t},\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "proxy/transport.go",
    "content": "package proxy\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\ntype TransportFix struct {\n\ttransport *http.Transport\n}\n\nfunc (t *TransportFix) RoundTrip(r *http.Request) (\n\tres *http.Response, err error) {\n\n\tr.Header.Set(\"X-Forwarded-For\", node.Self.GetRemoteAddr(r))\n\n\tres, err = t.transport.RoundTrip(r)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif res.StatusCode == http.StatusSwitchingProtocols {\n\t\terr = &WebSocketBlock{\n\t\t\terrors.New(\"proxy: Blocking websocket connection\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "proxy/types.go",
    "content": "package proxy\n\nimport (\n\t\"net/http\"\n)\n\ntype RespHandler func(hand *Handler, resp *http.Response) (err error)\n\ntype ErrorHandler func(hand *Handler, rw http.ResponseWriter,\n\tr *http.Request, err error)\n"
  },
  {
    "path": "proxy/utils.go",
    "content": "package proxy\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc WriteError(w http.ResponseWriter, r *http.Request, code int, err error) {\n\thttp.Error(w, utils.GetStatusMessage(code), code)\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"client\": node.Self.GetRemoteAddr(r),\n\t\t\"error\":  err,\n\t}).Error(\"proxy: Serve error\")\n}\n"
  },
  {
    "path": "proxy/ws.go",
    "content": "package proxy\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype webSocketConn struct {\n\tr     *http.Request\n\tback  *websocket.Conn\n\tfront *websocket.Conn\n}\n\nfunc (w *webSocketConn) Run(domain *Domain) {\n\tdomain.WebSocketConnsLock.Lock()\n\tdomain.WebSocketConns.Add(w)\n\tdomain.WebSocketConnsLock.Unlock()\n\n\tdefer func() {\n\t\tdomain.WebSocketConnsLock.Lock()\n\t\tdomain.WebSocketConns.Remove(w)\n\t\tdomain.WebSocketConnsLock.Unlock()\n\t}()\n\n\twait := make(chan bool, 4)\n\tgo func() {\n\t\tdefer func() {\n\t\t\trec := recover()\n\t\t\tif rec != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"panic\": rec,\n\t\t\t\t}).Error(\"proxy: WebSocket back panic\")\n\t\t\t\twait <- true\n\t\t\t}\n\t\t}()\n\n\t\tfor {\n\t\t\tmsgType, msg, err := w.front.ReadMessage()\n\t\t\tif err != nil {\n\t\t\t\tcloseMsg := websocket.FormatCloseMessage(\n\t\t\t\t\twebsocket.CloseNormalClosure, fmt.Sprintf(\"%v\", err))\n\t\t\t\tif e, ok := err.(*websocket.CloseError); ok {\n\t\t\t\t\tif e.Code != websocket.CloseNoStatusReceived {\n\t\t\t\t\t\tcloseMsg = websocket.FormatCloseMessage(e.Code, e.Text)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_ = w.back.WriteMessage(websocket.CloseMessage, closeMsg)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t_ = w.back.WriteMessage(msgType, msg)\n\t\t}\n\n\t\twait <- true\n\t}()\n\tgo func() {\n\t\tdefer func() {\n\t\t\trec := recover()\n\t\t\tif rec != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"panic\": rec,\n\t\t\t\t}).Error(\"proxy: WebSocket front panic\")\n\t\t\t\twait <- true\n\t\t\t}\n\t\t}()\n\n\t\tfor {\n\t\t\tmsgType, msg, err := w.back.ReadMessage()\n\t\t\tif err != nil {\n\t\t\t\tcloseMsg := websocket.FormatCloseMessage(\n\t\t\t\t\twebsocket.CloseNormalClosure, fmt.Sprintf(\"%v\", err))\n\t\t\t\tif e, ok := err.(*websocket.CloseError); ok {\n\t\t\t\t\tif e.Code != websocket.CloseNoStatusReceived {\n\t\t\t\t\t\tcloseMsg = websocket.FormatCloseMessage(e.Code, e.Text)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_ = w.front.WriteMessage(websocket.CloseMessage, closeMsg)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t_ = w.front.WriteMessage(msgType, msg)\n\t\t}\n\n\t\twait <- true\n\t}()\n\t<-wait\n\n\tw.Close()\n}\n\nfunc (w *webSocketConn) Close() {\n\tdefer func() {\n\t\trecover()\n\t}()\n\tif w.back != nil {\n\t\tw.back.Close()\n\t}\n\tif w.front != nil {\n\t\tw.front.Close()\n\t}\n}\n"
  },
  {
    "path": "qemu/constants.go",
    "content": "package qemu\n\nconst systemdTemplate = `# PritunlData=%s\n\n[Unit]\nDescription=Pritunl Cloud Virtual Machine\nAfter=network.target\n\n[Service]%s\nEnvironment=XDG_CACHE_HOME=%s\nType=simple\nUser=root\nExecStart=%s\nTimeoutStopSec=5\nPrivateTmp=%s\nProtectHome=%s\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\nPrivateIPC=true\nNetworkNamespacePath=/var/run/netns/%s\n`\n\nconst systemdTemplateExternalNet = `# PritunlData=%s\n\n[Unit]\nDescription=Pritunl Cloud Virtual Machine\nAfter=network.target\n\n[Service]%s\nEnvironment=XDG_CACHE_HOME=%s\nType=simple\nUser=root\nExecStart=%s\nTimeoutStopSec=5\nPrivateTmp=%s\nProtectHome=%s\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\nPrivateIPC=true\n`\n"
  },
  {
    "path": "qemu/data.go",
    "content": "package qemu\n\nimport (\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/dhcpc\"\n\t\"github.com/pritunl/pritunl-cloud/dhcps\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/guest\"\n\t\"github.com/pritunl/pritunl-cloud/hugepages\"\n\t\"github.com/pritunl/pritunl-cloud/imds\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/permission\"\n\t\"github.com/pritunl/pritunl-cloud/qmp\"\n\t\"github.com/pritunl/pritunl-cloud/qms\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/store\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/tpm\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/virtiofs\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc initDirs(virt *vm.VirtualMachine) (err error) {\n\tvmPath := paths.GetVmPath(virt.Id)\n\n\terr = utils.ExistsMkdir(settings.Hypervisor.LibPath, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(settings.Hypervisor.RunPath, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(paths.GetImdsPath(), 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(vmPath, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc initHugepage(virt *vm.VirtualMachine) (err error) {\n\tif !virt.Hugepages {\n\t\treturn\n\t}\n\n\terr = hugepages.UpdateHugepagesSize()\n\tif err != nil {\n\t\treturn\n\t}\n\n\thugepagesPath := paths.GetHugepagePath(virt.Id)\n\t_ = os.Remove(hugepagesPath)\n\n\treturn\n}\n\nfunc cleanRun(virt *vm.VirtualMachine) (err error) {\n\t_ = tpm.Stop(virt)\n\t_ = dhcpc.Stop(virt)\n\t_ = imds.Stop(virt)\n\t_ = dhcps.Stop(virt)\n\t_ = virtiofs.StopAll(virt)\n\n\trunPath := paths.GetInstRunPath(virt.Id)\n\tpidPath := paths.GetPidPath(virt.Id)\n\tsockPath := paths.GetSockPath(virt.Id)\n\tqmpSockPath := paths.GetQmpSockPath(virt.Id)\n\tguestPath := paths.GetGuestPath(virt.Id)\n\n\terr = utils.RemoveAll(runPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(pidPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(sockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(qmpSockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(guestPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc initCache(virt *vm.VirtualMachine) (err error) {\n\terr = utils.ExistsMkdir(paths.GetCachesDir(), 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(paths.GetCacheDir(virt.Id), 0700)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc initRun(virt *vm.VirtualMachine) (err error) {\n\trunPath := paths.GetInstRunPath(virt.Id)\n\n\terr = utils.ExistsMkdir(runPath, 0700)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc initPermissions(virt *vm.VirtualMachine) (err error) {\n\terr = permission.InitVirt(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = permission.InitImds(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, mount := range virt.Mounts {\n\t\tshareId := paths.GetShareId(virt.Id, mount.Name)\n\n\t\terr = permission.InitMount(virt, shareId)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc writeOvmfVars(virt *vm.VirtualMachine) (err error) {\n\tif !virt.Uefi {\n\t\treturn\n\t}\n\n\tovmfVarsPath := paths.GetOvmfVarsPath(virt.Id)\n\tovmfVarsPathSource, err := paths.FindOvmfVarsPath(virt.SecureBoot)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(paths.GetOvmfDir(), 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.Exec(\"\", \"cp\", ovmfVarsPathSource, ovmfVarsPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.Chmod(ovmfVarsPath, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc activateDisks(db *database.Database,\n\tvirt *vm.VirtualMachine) (err error) {\n\n\tfor _, virtDsk := range virt.Disks {\n\t\tdsk, e := disk.Get(db, virtDsk.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\terr = data.ActivateDisk(db, dsk)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc deactivateDisks(db *database.Database,\n\tvirt *vm.VirtualMachine) (err error) {\n\n\tfor _, virtDsk := range virt.Disks {\n\t\tdsk, e := disk.Get(db, virtDsk.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\terr = data.DeactivateDisk(db, dsk)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc writeService(virt *vm.VirtualMachine) (err error) {\n\tunitPath := paths.GetUnitPath(virt.Id)\n\n\tqm, err := NewQemu(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\toutput, err := qm.Marshal()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.CreateWrite(unitPath, output, 0644)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Reload()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Destroy(db *database.Database, virt *vm.VirtualMachine) (err error) {\n\tvmPath := paths.GetVmPath(virt.Id)\n\tunitName := paths.GetUnitName(virt.Id)\n\tunitPath := paths.GetUnitPath(virt.Id)\n\tunitPathServer4 := paths.GetUnitPathDhcp4(virt.Id, 0)\n\tunitPathServer6 := paths.GetUnitPathDhcp6(virt.Id, 0)\n\tunitPathServerNdp := paths.GetUnitPathNdp(virt.Id, 0)\n\ttpmPath := paths.GetTpmPath(virt.Id)\n\trunPath := paths.GetInstRunPath(virt.Id)\n\tunitPathTpm := paths.GetUnitPathTpm(virt.Id)\n\tunitPathImds := paths.GetUnitPathImds(virt.Id)\n\tunitPathDhcpc := paths.GetUnitPathDhcpc(virt.Id)\n\tunitPathShares := paths.GetUnitPathShares(virt.Id)\n\tsockPath := paths.GetSockPath(virt.Id)\n\tsockQmpPath := paths.GetQmpSockPath(virt.Id)\n\t// TODO Backward compatibility\n\tsockPathOld := paths.GetSockPath(virt.Id)\n\tguestPath := paths.GetGuestPath(virt.Id)\n\t// TODO Backward compatibility\n\tguestPathOld := paths.GetGuestPathOld(virt.Id)\n\tpidPath := paths.GetPidPath(virt.Id)\n\t// TODO Backward compatibility\n\tpidPathOld := paths.GetPidPathOld(virt.Id)\n\tovmfVarsPath := paths.GetOvmfVarsPath(virt.Id)\n\thugepagesPath := paths.GetHugepagePath(virt.Id)\n\tcachePath := paths.GetCacheDir(virt.Id)\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\": virt.Id.Hex(),\n\t}).Info(\"qemu: Destroying virtual machine\")\n\n\texists, err := utils.Exists(unitPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif exists {\n\t\tvrt, e := GetVmInfo(db, virt.Id, false, true)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif vrt != nil && vrt.State == vm.Running {\n\t\t\tguestShutdown := true\n\t\t\terr = guest.Shutdown(virt.Id)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\t\t\"error\":       err,\n\t\t\t\t}).Warn(\"qemu: Failed to send shutdown to guest agent\")\n\t\t\t\terr = nil\n\t\t\t\tguestShutdown = false\n\t\t\t}\n\n\t\t\tlogged := false\n\t\t\tfor i := 0; i < 10; i++ {\n\t\t\t\terr = qmp.Shutdown(virt.Id)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tif guestShutdown {\n\t\t\t\t\terr = nil\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tif !logged {\n\t\t\t\t\tlogged = true\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\t\t\t\"error\":       err,\n\t\t\t\t\t}).Warn(\n\t\t\t\t\t\t\"qemu: Failed to send shutdown to virtual machine\")\n\t\t\t\t}\n\n\t\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\t}\n\n\t\t\tshutdown := false\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"id\":    virt.Id.Hex(),\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"qemu: Power off virtual machine error\")\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\tfor i := 0; i < settings.Hypervisor.StopTimeout; i++ {\n\t\t\t\t\tvrt, err = GetVmInfo(db, virt.Id, false, true)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif vrt == nil || vrt.State == vm.Stopped ||\n\t\t\t\t\t\tvrt.State == vm.Failed {\n\n\t\t\t\t\t\tif vrt != nil {\n\t\t\t\t\t\t\terr = vrt.Commit(db)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tshutdown = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\t\t\tif (i+1)%15 == 0 {\n\t\t\t\t\t\tgo func() {\n\t\t\t\t\t\t\tqmp.Shutdown(virt.Id)\n\t\t\t\t\t\t\tqms.Shutdown(virt.Id)\n\t\t\t\t\t\t}()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !shutdown {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"id\": virt.Id.Hex(),\n\t\t\t\t}).Warning(\"qemu: Force power off virtual machine\")\n\t\t\t}\n\t\t}\n\n\t\terr = systemd.Stop(unitName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\t_ = tpm.Stop(virt)\n\t_ = dhcpc.Stop(virt)\n\t_ = imds.Stop(virt)\n\t_ = dhcps.Stop(virt)\n\t_ = virtiofs.StopAll(virt)\n\n\terr = NetworkConfClear(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(3 * time.Second)\n\n\tfor _, dsk := range virt.Disks {\n\t\tds, e := disk.Get(db, dsk.GetId())\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = data.DeactivateDisk(db, ds)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif ds.Index == \"0\" && ds.SourceInstance == virt.Id {\n\t\t\terr = disk.Delete(db, ds.Id)\n\t\t\tif err != nil {\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\terr = disk.Detach(db, dsk.GetId())\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tfor i, dsk := range virt.DriveDevices {\n\t\tif dsk.Type != vm.Lvm {\n\t\t\tcontinue\n\t\t}\n\n\t\tdskId, ok := utils.ParseObjectId(dsk.Id)\n\t\tif dskId.IsZero() || !ok {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"qemu: Failed to parse LVM disk ID '%s'\", dsk.Id),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tds, e := disk.Get(db, dskId)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif i == 0 && ds.SourceInstance == virt.Id {\n\t\t\terr = disk.Delete(db, ds.Id)\n\t\t\tif err != nil {\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\terr = disk.Detach(db, dskId)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\terr = utils.RemoveAll(vmPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(unitPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(tpmPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(runPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(unitPathTpm)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(unitPathImds)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(unitPathDhcpc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(unitPathServer4)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(unitPathServer6)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(unitPathServerNdp)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = utils.RemoveWildcard(unitPathShares)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(sockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(sockQmpPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// TODO Backward compatibility\n\terr = utils.RemoveAll(sockPathOld)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(guestPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// TODO Backward compatibility\n\terr = utils.RemoveAll(guestPathOld)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(pidPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// TODO Backward compatibility\n\terr = utils.RemoveAll(pidPathOld)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(paths.GetInitPath(virt.Id))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(unitPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(ovmfVarsPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(hugepagesPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.RemoveAll(cachePath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = permission.UserDelete(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstore.RemVirt(virt.Id)\n\tstore.RemDisks(virt.Id)\n\tstore.RemAddress(virt.Id)\n\tstore.RemRoutes(virt.Id)\n\tstore.RemArp(virt.Id)\n\n\treturn\n}\n\nfunc Cleanup(db *database.Database, virt *vm.VirtualMachine) {\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\": virt.Id.Hex(),\n\t}).Info(\"qemu: Stopped virtual machine\")\n\n\t_ = tpm.Stop(virt)\n\t_ = dhcpc.Stop(virt)\n\t_ = imds.Stop(virt)\n\t_ = dhcps.Stop(virt)\n\t_ = virtiofs.StopAll(virt)\n\n\thugepagesPath := paths.GetHugepagePath(virt.Id)\n\t_ = os.Remove(hugepagesPath)\n\n\terr := NetworkConfClear(db, virt)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"id\":    virt.Id.Hex(),\n\t\t\t\"error\": err,\n\t\t}).Error(\"qemu: Failed to cleanup virtual machine network\")\n\t}\n\n\ttime.Sleep(3 * time.Second)\n\n\terr = deactivateDisks(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstore.RemVirt(virt.Id)\n\tstore.RemDisks(virt.Id)\n\n\treturn\n}\n"
  },
  {
    "path": "qemu/disk.go",
    "content": "package qemu\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/qmp\"\n\t\"github.com/pritunl/pritunl-cloud/store\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc UpdateVmDisk(virt *vm.VirtualMachine) (err error) {\n\tfor i := 0; i < 10; i++ {\n\t\tif virt.State == vm.Running {\n\t\t\t_, disks, e := qmp.GetDisks(virt.Id)\n\t\t\tif e != nil {\n\t\t\t\tif i < 9 {\n\t\t\t\t\ttime.Sleep(300 * time.Millisecond)\n\t\t\t\t\t_ = UpdateState(virt)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\terr = e\n\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvirt.Disks = disks\n\n\t\t\tstore.SetDisks(virt.Id, disks)\n\t\t}\n\n\t\tbreak\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qemu/manage.go",
    "content": "package qemu\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/cloudinit\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/dhcps\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/qmp\"\n\t\"github.com/pritunl/pritunl-cloud/qms\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/store\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/tpm\"\n\t\"github.com/pritunl/pritunl-cloud/virtiofs\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tserviceReg = regexp.MustCompile(\"pritunl_cloud_([a-z0-9]+).service\")\n)\n\ntype InfoCache struct {\n\tTimestamp time.Time\n\tVirt      *vm.VirtualMachine\n}\n\nfunc GetVmInfo(db *database.Database, vmId bson.ObjectID,\n\tqueryQms, force bool) (virt *vm.VirtualMachine, err error) {\n\n\trefreshRate := time.Duration(\n\t\tsettings.Hypervisor.RefreshRate) * time.Second\n\n\tvirtStore, ok := store.GetVirt(vmId)\n\tif !ok {\n\t\tunitPath := paths.GetUnitPath(vmId)\n\n\t\tunitData, e := ioutil.ReadFile(unitPath)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"qemu: Failed to read service\"),\n\t\t\t}\n\t\t\t_ = ForcePowerOffErr(db, virt, err)\n\t\t\treturn\n\t\t}\n\n\t\tvirt = &vm.VirtualMachine{}\n\t\tfor _, line := range strings.Split(string(unitData), \"\\n\") {\n\t\t\tif !strings.HasPrefix(line, \"PritunlData=\") &&\n\t\t\t\t!strings.HasPrefix(line, \"# PritunlData=\") {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlineSpl := strings.SplitN(line, \"=\", 2)\n\t\t\tif len(lineSpl) != 2 || len(lineSpl[1]) < 6 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\terr = json.Unmarshal([]byte(lineSpl[1]), virt)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err, \"qemu: Failed to parse service data\"),\n\t\t\t\t}\n\t\t\t\t_ = ForcePowerOffErr(db, virt, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\n\t\tif virt.Id.IsZero() {\n\t\t\tvirt = nil\n\t\t\treturn\n\t\t}\n\n\t\t_ = UpdateState(virt)\n\t} else {\n\t\tvirt = &virtStore.Virt\n\n\t\tif force || virt.State != vm.Running ||\n\t\t\ttime.Since(virtStore.Timestamp) > 6*time.Second {\n\n\t\t\t_ = UpdateState(virt)\n\t\t}\n\t}\n\n\tif virt.State == vm.Running && queryQms {\n\t\tvirt.DisksAvailable = true\n\t\tdisksUpdated := false\n\t\tdisksStore, ok := store.GetDisks(vmId)\n\n\t\tif !ok || time.Since(disksStore.Timestamp) > refreshRate {\n\t\t\tfor i := 0; i < 10; i++ {\n\t\t\t\tif virt.State == vm.Running {\n\t\t\t\t\tinfo, disks, e := qmp.GetDisks(vmId)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\tif i < 9 {\n\t\t\t\t\t\t\ttime.Sleep(300 * time.Millisecond)\n\t\t\t\t\t\t\t_ = UpdateState(virt)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\t\t\t\"error\":       e,\n\t\t\t\t\t\t}).Error(\"qemu: Failed to get VM disk state\")\n\n\t\t\t\t\t\tvirt.DisksAvailable = false\n\n\t\t\t\t\t\te = nil\n\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\tvirt.QemuVersion = fmt.Sprintf(\n\t\t\t\t\t\t\"%d.%d.%d\",\n\t\t\t\t\t\tinfo.VersionMajor,\n\t\t\t\t\t\tinfo.VersionMinor,\n\t\t\t\t\t\tinfo.VersionMicro,\n\t\t\t\t\t)\n\t\t\t\t\tvirt.Disks = disks\n\t\t\t\t\tstore.SetDisks(vmId, disks)\n\t\t\t\t\tdisksUpdated = true\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif ok && !disksUpdated {\n\t\t\tdisks := []*vm.Disk{}\n\t\t\tfor _, dsk := range disksStore.Disks {\n\t\t\t\tdisks = append(disks, dsk.Copy())\n\t\t\t}\n\t\t\tvirt.Disks = disks\n\t\t}\n\t}\n\n\tif virt.State == vm.Running && queryQms && node.Self.UsbPassthrough {\n\t\tvirt.UsbDevicesAvailable = true\n\t\tusbsUpdated := false\n\t\tusbsStore, ok := store.GetUsbs(vmId)\n\t\tif !ok || time.Since(usbsStore.Timestamp) > refreshRate {\n\t\t\tfor i := 0; i < 10; i++ {\n\t\t\t\tif virt.State == vm.Running {\n\t\t\t\t\tusbs, e := qms.GetUsbDevices(vmId)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\tif i < 9 {\n\t\t\t\t\t\t\ttime.Sleep(300 * time.Millisecond)\n\t\t\t\t\t\t\t_ = UpdateState(virt)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\t\t\t\"error\":       e,\n\t\t\t\t\t\t}).Error(\"qemu: Failed to get VM usb state\")\n\n\t\t\t\t\t\tvirt.UsbDevicesAvailable = false\n\n\t\t\t\t\t\te = nil\n\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\tvirt.UsbDevices = usbs\n\t\t\t\t\tstore.SetUsbs(vmId, usbs)\n\t\t\t\t\tusbsUpdated = true\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif ok && !usbsUpdated {\n\t\t\tusbs := []*vm.UsbDevice{}\n\t\t\tfor _, usb := range usbsStore.Usbs {\n\t\t\t\tusbs = append(usbs, usb.Copy())\n\t\t\t}\n\t\t\tvirt.UsbDevices = usbs\n\t\t}\n\t}\n\n\taddrStore, ok := store.GetAddress(virt.Id)\n\tif !ok {\n\t\taddr := \"\"\n\t\taddr6 := \"\"\n\n\t\tnamespace := vm.GetNamespace(virt.Id, 0)\n\n\t\tnodeNetworkMode := node.Self.NetworkMode\n\t\tif nodeNetworkMode == \"\" {\n\t\t\tnodeNetworkMode = node.Dhcp\n\t\t}\n\t\tnodeNetworkMode6 := node.Self.NetworkMode6\n\t\tif nodeNetworkMode6 == \"\" {\n\t\t\tnodeNetworkMode6 = node.Dhcp\n\t\t}\n\n\t\tifaceExternal := vm.GetIfaceExternal(virt.Id, 0)\n\n\t\tif nodeNetworkMode != node.Disabled &&\n\t\t\tnodeNetworkMode != node.Cloud {\n\n\t\t\taddress, address6, e := iproute.AddressGetIfaceMod(\n\t\t\t\tnamespace, ifaceExternal)\n\t\t\tif e != nil {\n\t\t\t\tif addrStore != nil {\n\t\t\t\t\tif len(virt.NetworkAdapters) > 0 {\n\t\t\t\t\t\tvirt.NetworkAdapters[0].IpAddress = addrStore.Addr\n\t\t\t\t\t\tvirt.NetworkAdapters[0].IpAddress6 = addrStore.Addr6\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\terr = e\n\t\t\t\t\t_ = ForcePowerOffErr(db, virt, err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif address != nil {\n\t\t\t\taddr = address.Local\n\t\t\t}\n\n\t\t\tif address6 != nil {\n\t\t\t\taddr6 = address6.Local\n\t\t\t}\n\t\t} else if nodeNetworkMode6 != node.Disabled &&\n\t\t\tnodeNetworkMode6 != node.Cloud {\n\n\t\t\t_, address6, e := iproute.AddressGetIfaceMod(\n\t\t\t\tnamespace, ifaceExternal)\n\t\t\tif e != nil {\n\t\t\t\tif addrStore != nil {\n\t\t\t\t\tif len(virt.NetworkAdapters) > 0 {\n\t\t\t\t\t\tvirt.NetworkAdapters[0].IpAddress = addrStore.Addr\n\t\t\t\t\t\tvirt.NetworkAdapters[0].IpAddress6 = addrStore.Addr6\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\terr = e\n\t\t\t\t\t_ = ForcePowerOffErr(db, virt, err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif address6 != nil {\n\t\t\t\taddr6 = address6.Local\n\t\t\t}\n\t\t}\n\n\t\tif len(virt.NetworkAdapters) > 0 {\n\t\t\tvirt.NetworkAdapters[0].IpAddress = addr\n\t\t\tvirt.NetworkAdapters[0].IpAddress6 = addr6\n\t\t}\n\t\tstore.SetAddress(virt.Id, addr, addr6)\n\t} else {\n\t\tif len(virt.NetworkAdapters) > 0 {\n\t\t\tvirt.NetworkAdapters[0].IpAddress = addrStore.Addr\n\t\t\tvirt.NetworkAdapters[0].IpAddress6 = addrStore.Addr6\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc updateState(virt *vm.VirtualMachine, retry bool) (err error) {\n\tunitName := paths.GetUnitName(virt.Id)\n\tstate, timestamp, err := systemd.GetState(unitName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tswitch state {\n\tcase \"active\":\n\t\tvirt.State = vm.Running\n\t\tbreak\n\tcase \"deactivating\":\n\t\tvirt.State = vm.Running\n\t\tbreak\n\tcase \"inactive\":\n\t\tvirt.State = vm.Stopped\n\t\tbreak\n\tcase \"failed\":\n\t\tvirt.State = vm.Failed\n\t\tbreak\n\tcase \"unknown\":\n\t\tvirt.State = vm.Stopped\n\t\tbreak\n\tdefault:\n\t\tif retry {\n\t\t\ttime.Sleep(2 * time.Second)\n\t\t\terr = updateState(virt, false)\n\t\t\treturn\n\t\t} else {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\t\"state\":       state,\n\t\t\t}).Info(\"qemu: Unknown virtual machine state\")\n\t\t\tvirt.State = vm.Failed\n\t\t}\n\t\tbreak\n\t}\n\n\tvirt.Timestamp = timestamp\n\n\tstore.SetVirt(virt.Id, virt)\n\n\treturn\n}\n\nfunc UpdateState(virt *vm.VirtualMachine) (err error) {\n\terr = updateState(virt, true)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\"error\":       err,\n\t\t}).Error(\"deploy: Error updating VM state\")\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc SetState(virt *vm.VirtualMachine, state string) {\n\tvirt.State = state\n\tstore.SetVirt(virt.Id, virt)\n}\n\nfunc GetVms(db *database.Database) (\n\tvirts []*vm.VirtualMachine, err error) {\n\n\tsystemdPath := settings.Hypervisor.SystemdPath\n\tvirts = []*vm.VirtualMachine{}\n\n\titems, err := ioutil.ReadDir(systemdPath)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to read systemd directory\"),\n\t\t}\n\t\treturn\n\t}\n\n\tunits := []string{}\n\tfor _, item := range items {\n\t\tif strings.HasPrefix(item.Name(), \"pritunl_cloud\") {\n\t\t\tunits = append(units, item.Name())\n\t\t}\n\t}\n\n\twaiter := sync.WaitGroup{}\n\tvirtsLock := sync.Mutex{}\n\n\tfor _, unit := range units {\n\t\tmatch := serviceReg.FindStringSubmatch(unit)\n\t\tif match == nil || len(match) != 2 {\n\t\t\tcontinue\n\t\t}\n\n\t\tvmId, err := bson.ObjectIDFromHex(match[1])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\twaiter.Add(1)\n\t\tgo func() {\n\t\t\tdefer waiter.Done()\n\n\t\t\tvirt, e := GetVmInfo(db, vmId, true, false)\n\t\t\tif e != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\t\"error\":       e,\n\t\t\t\t}).Error(\"qemu: Failed to get VM state\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif virt != nil {\n\t\t\t\tvirtsLock.Lock()\n\t\t\t\tvirts = append(virts, virt)\n\t\t\t\tvirtsLock.Unlock()\n\t\t\t}\n\t\t}()\n\t}\n\n\twaiter.Wait()\n\n\treturn\n}\n\nfunc Wait(db *database.Database, virt *vm.VirtualMachine) (err error) {\n\tunitName := paths.GetUnitName(virt.Id)\n\n\tfor i := 0; i < settings.Hypervisor.StartTimeout; i++ {\n\t\terr = UpdateState(virt)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif virt.State == vm.Running {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\n\tif virt.State != vm.Running {\n\t\terr = systemd.Stop(unitName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = &errortypes.TimeoutError{\n\t\t\terrors.New(\"qemu: Power on timeout\"),\n\t\t}\n\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Create(db *database.Database, inst *instance.Instance,\n\tvirt *vm.VirtualMachine) (err error) {\n\n\tunitName := paths.GetUnitName(virt.Id)\n\n\tif constants.Interrupt {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\": virt.Id.Hex(),\n\t}).Info(\"qemu: Creating virtual machine\")\n\n\tvirt.State = vm.Provisioning\n\terr = virt.Commit(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = inst.InitUnixId(db)\n\tif err != nil {\n\t\treturn\n\t}\n\tvirt.UnixId = inst.UnixId\n\n\tif inst.Vnc {\n\t\terr = inst.InitVncDisplay(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tvirt.VncDisplay = inst.VncDisplay\n\t}\n\n\tif inst.Spice {\n\t\terr = inst.InitSpicePort(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tvirt.SpicePort = inst.SpicePort\n\t}\n\n\terr = initDirs(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = cleanRun(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdsk, err := disk.GetInstanceIndex(db, inst.Id, \"0\")\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tdsk = nil\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif dsk == nil {\n\t\tdsk = &disk.Disk{\n\t\t\tId:               bson.NewObjectID(),\n\t\t\tName:             inst.Name,\n\t\t\tState:            disk.Available,\n\t\t\tType:             virt.DiskType,\n\t\t\tPool:             virt.DiskPool,\n\t\t\tNode:             node.Self.Id,\n\t\t\tDeployment:       inst.Deployment,\n\t\t\tOrganization:     inst.Organization,\n\t\t\tInstance:         inst.Id,\n\t\t\tDatacenter:       node.Self.Datacenter,\n\t\t\tZone:             node.Self.Zone,\n\t\t\tSourceInstance:   inst.Id,\n\t\t\tImage:            virt.Image,\n\t\t\tBacking:          inst.ImageBacking,\n\t\t\tIndex:            \"0\",\n\t\t\tSize:             inst.InitDiskSize,\n\t\t\tDeleteProtection: inst.DeleteProtection,\n\t\t}\n\n\t\terrData, e := dsk.Validate(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif errData != nil {\n\t\t\terr = errData.GetError()\n\t\t\treturn\n\t\t}\n\n\t\tbackingImage := \"\"\n\n\t\tnewSize := 0\n\t\tif virt.Image.IsZero() {\n\t\t\tnewSize, backingImage, err = data.CreateDisk(db, dsk)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"deploy: Failed to provision disk\")\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\timg, e := image.Get(db, dsk.Image)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdsk.SystemType = img.GetSystemType()\n\t\t\tdsk.SystemKind = img.GetSystemKind()\n\n\t\t\tnewSize, backingImage, err = data.WriteImage(db, dsk)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif newSize != 0 {\n\t\t\tdsk.Size = newSize\n\t\t}\n\n\t\tdsk.BackingImage = backingImage\n\n\t\terr = dsk.Insert(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_ = event.PublishDispatch(db, \"disk.change\")\n\n\t\tif virt.DiskType == disk.Lvm {\n\t\t\tpl, e := pool.Get(db, dsk.Pool)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvirt.DriveDevices = append(virt.DriveDevices, &vm.DriveDevice{\n\t\t\t\tId:     dsk.Id.Hex(),\n\t\t\t\tType:   vm.Lvm,\n\t\t\t\tVgName: pl.VgName,\n\t\t\t\tLvName: dsk.Id.Hex(),\n\t\t\t})\n\t\t} else {\n\t\t\tvirt.Disks = append(virt.Disks, &vm.Disk{\n\t\t\t\tId:    dsk.Id,\n\t\t\t\tIndex: 0,\n\t\t\t\tPath:  paths.GetDiskPath(dsk.Id),\n\t\t\t})\n\t\t}\n\t}\n\n\tif len(virt.NetworkAdapters) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"cloudinit: Instance missing network adapters\"),\n\t\t}\n\t\treturn\n\t}\n\n\tadapter := virt.NetworkAdapters[0]\n\n\tif adapter.Vpc.IsZero() {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"cloudinit: Instance missing VPC\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif adapter.Subnet.IsZero() {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"cloudinit: Instance missing VPC subnet\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdc, err := datacenter.Get(db, node.Self.Datacenter)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzne, err := zone.Get(db, node.Self.Zone)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvc, err := vpc.Get(db, adapter.Vpc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = virt.GenerateImdsSecret()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = cloudinit.Write(db, inst, virt, dc, zne, vc, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initCache(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initHugepage(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = writeOvmfVars(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = activateDisks(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = writeService(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initRun(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvirt.State = vm.Starting\n\terr = virt.Commit(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = virtiofs.StartAll(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initPermissions(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif virt.DhcpServer {\n\t\terr = dhcps.Start(db, virt, dc, zne, vc)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = dhcps.Stop(virt)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif virt.Tpm {\n\t\terr = tpm.Start(db, virt)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = tpm.Stop(virt)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = systemd.Start(unitName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = Wait(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif virt.Vnc {\n\t\terr = qmp.VncPassword(virt.Id, inst.VncPassword)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif virt.Spice {\n\t\terr = qmp.SetPassword(virt.Id, qmp.Spice, inst.SpicePassword)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = NetworkConf(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstore.RemVirt(virt.Id)\n\tstore.RemDisks(virt.Id)\n\n\treturn\n}\n"
  },
  {
    "path": "qemu/network.go",
    "content": "package qemu\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/dhcps\"\n\t\"github.com/pritunl/pritunl-cloud/netconf\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc NetworkConfClear(db *database.Database,\n\tvirt *vm.VirtualMachine) (err error) {\n\n\terr = dhcps.Stop(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnc := netconf.New(virt)\n\terr = nc.Clean(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc NetworkConf(db *database.Database,\n\tvirt *vm.VirtualMachine) (err error) {\n\n\tnc := netconf.New(virt)\n\terr = nc.Init(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qemu/power.go",
    "content": "package qemu\n\nimport (\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/cloudinit\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/dhcpc\"\n\t\"github.com/pritunl/pritunl-cloud/dhcps\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/guest\"\n\t\"github.com/pritunl/pritunl-cloud/imds\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/qmp\"\n\t\"github.com/pritunl/pritunl-cloud/qms\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/store\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/tpm\"\n\t\"github.com/pritunl/pritunl-cloud/virtiofs\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc PowerOn(db *database.Database, inst *instance.Instance,\n\tvirt *vm.VirtualMachine) (err error) {\n\tunitName := paths.GetUnitName(virt.Id)\n\n\tif constants.Interrupt {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\": virt.Id.Hex(),\n\t}).Info(\"qemu: Starting virtual machine\")\n\n\terr = inst.InitUnixId(db)\n\tif err != nil {\n\t\treturn\n\t}\n\tvirt.UnixId = inst.UnixId\n\n\tif inst.Vnc {\n\t\terr = inst.InitVncDisplay(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tvirt.VncDisplay = inst.VncDisplay\n\t}\n\n\tif inst.Spice {\n\t\terr = inst.InitSpicePort(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tvirt.SpicePort = inst.SpicePort\n\t}\n\n\terr = initDirs(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = cleanRun(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(virt.NetworkAdapters) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"cloudinit: Instance missing network adapters\"),\n\t\t}\n\t\treturn\n\t}\n\n\tadapter := virt.NetworkAdapters[0]\n\n\tif adapter.Vpc.IsZero() {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"cloudinit: Instance missing VPC\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif adapter.Subnet.IsZero() {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.Wrap(err, \"cloudinit: Instance missing VPC subnet\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdc, err := datacenter.Get(db, node.Self.Datacenter)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzne, err := zone.Get(db, node.Self.Zone)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvc, err := vpc.Get(db, adapter.Vpc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = virt.GenerateImdsSecret()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = cloudinit.Write(db, inst, virt, dc, zne, vc, false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initCache(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initHugepage(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = writeOvmfVars(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = activateDisks(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = writeService(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initRun(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = virtiofs.StartAll(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = initPermissions(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif virt.DhcpServer {\n\t\terr = dhcps.Start(db, virt, dc, zne, vc)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = dhcps.Stop(virt)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif virt.Tpm {\n\t\terr = tpm.Start(db, virt)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = tpm.Stop(virt)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = systemd.Start(unitName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = Wait(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif virt.Vnc {\n\t\terr = qmp.VncPassword(virt.Id, inst.VncPassword)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif virt.Spice {\n\t\terr = qmp.SetPassword(virt.Id, qmp.Spice, inst.SpicePassword)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = NetworkConf(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstore.RemVirt(virt.Id)\n\tstore.RemDisks(virt.Id)\n\n\treturn\n}\n\nfunc PowerOff(db *database.Database, virt *vm.VirtualMachine) (err error) {\n\tunitName := paths.GetUnitName(virt.Id)\n\n\tif constants.Interrupt {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\": virt.Id.Hex(),\n\t}).Info(\"qemu: Stopping virtual machine\")\n\n\tguestShutdown := true\n\terr = guest.Shutdown(virt.Id)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\"error\":       err,\n\t\t}).Warn(\"qemu: Failed to send shutdown to guest agent\")\n\t\terr = nil\n\t\tguestShutdown = false\n\t}\n\n\tlogged := false\n\tfor i := 0; i < 10; i++ {\n\t\terr = qmp.Shutdown(virt.Id)\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif guestShutdown {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\n\t\tif !logged {\n\t\t\tlogged = true\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\t\"error\":       err,\n\t\t\t}).Warn(\"qemu: Failed to send shutdown to virtual machine\")\n\t\t}\n\n\t\ttime.Sleep(500 * time.Millisecond)\n\t}\n\n\tshutdown := false\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"id\":    virt.Id.Hex(),\n\t\t\t\"error\": err,\n\t\t}).Error(\"qemu: Power off virtual machine error\")\n\t\terr = nil\n\t} else {\n\t\tfor i := 0; i < settings.Hypervisor.StopTimeout; i++ {\n\t\t\tvrt, e := GetVmInfo(db, virt.Id, false, true)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif vrt == nil || vrt.State == vm.Stopped ||\n\t\t\t\tvrt.State == vm.Failed {\n\n\t\t\t\tif vrt != nil {\n\t\t\t\t\terr = vrt.Commit(db)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tshutdown = true\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\tif (i+1)%15 == 0 {\n\t\t\t\tgo func() {\n\t\t\t\t\tqmp.Shutdown(virt.Id)\n\t\t\t\t\tqms.Shutdown(virt.Id)\n\t\t\t\t}()\n\t\t\t}\n\t\t}\n\t}\n\n\tif !shutdown {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t}).Warning(\"qemu: Force power off virtual machine\")\n\n\t\terr = systemd.Stop(unitName)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t_ = tpm.Stop(virt)\n\t_ = dhcpc.Stop(virt)\n\t_ = imds.Stop(virt)\n\t_ = dhcps.Stop(virt)\n\t_ = virtiofs.StopAll(virt)\n\n\thugepagesPath := paths.GetHugepagePath(virt.Id)\n\t_ = os.Remove(hugepagesPath)\n\n\terr = NetworkConfClear(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(3 * time.Second)\n\n\terr = deactivateDisks(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstore.RemVirt(virt.Id)\n\tstore.RemDisks(virt.Id)\n\n\treturn\n}\n\nfunc ForcePowerOffErr(db *database.Database, virt *vm.VirtualMachine,\n\te error) (err error) {\n\n\tunitName := paths.GetUnitName(virt.Id)\n\n\tif constants.Interrupt {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\"error\":       e,\n\t}).Error(\"qemu: Force power off virtual machine\")\n\n\tgo guest.Shutdown(virt.Id)\n\tgo qmp.Shutdown(virt.Id)\n\tgo qms.Shutdown(virt.Id)\n\n\ttime.Sleep(15 * time.Second)\n\n\terr = systemd.Stop(unitName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_ = tpm.Stop(virt)\n\t_ = dhcpc.Stop(virt)\n\t_ = imds.Stop(virt)\n\t_ = dhcps.Stop(virt)\n\t_ = virtiofs.StopAll(virt)\n\n\terr = NetworkConfClear(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(3 * time.Second)\n\n\terr = deactivateDisks(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstore.RemVirt(virt.Id)\n\tstore.RemDisks(virt.Id)\n\n\treturn\n}\n\nfunc ForcePowerOff(db *database.Database, virt *vm.VirtualMachine) (\n\terr error) {\n\n\tunitName := paths.GetUnitName(virt.Id)\n\n\tif constants.Interrupt {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"instance_id\": virt.Id.Hex(),\n\t}).Warning(\"qemu: Force power off virtual machine\")\n\n\tgo guest.Shutdown(virt.Id)\n\tgo qmp.Shutdown(virt.Id)\n\tgo qms.Shutdown(virt.Id)\n\n\ttime.Sleep(5 * time.Second)\n\n\terr = systemd.Stop(unitName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_ = tpm.Stop(virt)\n\t_ = dhcpc.Stop(virt)\n\t_ = imds.Stop(virt)\n\t_ = dhcps.Stop(virt)\n\t_ = virtiofs.StopAll(virt)\n\n\terr = NetworkConfClear(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(3 * time.Second)\n\n\terr = deactivateDisks(db, virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstore.RemVirt(virt.Id)\n\tstore.RemDisks(virt.Id)\n\n\treturn\n}\n"
  },
  {
    "path": "qemu/qemu.go",
    "content": "package qemu\n\nimport (\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"math\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/compositor\"\n\t\"github.com/pritunl/pritunl-cloud/drive\"\n\t\"github.com/pritunl/pritunl-cloud/features\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/permission\"\n\t\"github.com/pritunl/pritunl-cloud/render\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\ntype Disk struct {\n\tId     string\n\tIndex  int\n\tFile   string\n\tFormat string\n}\n\ntype Network struct {\n\tIface      string\n\tMacAddress string\n}\n\ntype Iso struct {\n\tName string\n}\n\ntype UsbDevice struct {\n\tId      string\n\tVendor  string\n\tProduct string\n\tBus     string\n\tAddress string\n\tBusPath string\n}\n\ntype PciDevice struct {\n\tSlot string\n\tGpu  bool\n}\n\ntype DriveDevice struct {\n\tId     string\n\tType   string\n\tVgName string\n\tLvName string\n}\n\ntype Mount struct {\n\tId   string\n\tName string\n\tSock string\n}\n\ntype IscsiDevice struct {\n\tUri string\n}\n\ntype Qemu struct {\n\tId           bson.ObjectID\n\tData         string\n\tKvm          bool\n\tMachine      string\n\tCpu          string\n\tCpus         int\n\tCores        int\n\tThreads      int\n\tDies         int\n\tSockets      int\n\tBoot         string\n\tUefi         bool\n\tSecureBoot   bool\n\tTpm          bool\n\tOvmfCodePath string\n\tOvmfVarsPath string\n\tMemory       int\n\tHugepages    bool\n\tVnc          bool\n\tVncDisplay   int\n\tSpice        bool\n\tSpicePort    int\n\tGui          bool\n\tGuiUser      string\n\tGuiMode      string\n\tProtectHome  bool\n\tProtectTmp   bool\n\tNamespace    string\n\tDisks        Disks\n\tNetworks     []*Network\n\tIsos         []*Iso\n\tUsbDevices   []*UsbDevice\n\tPciDevices   []*PciDevice\n\tDriveDevices []*DriveDevice\n\tIscsiDevices []*IscsiDevice\n\tMounts       []*Mount\n}\n\nfunc (q *Qemu) GetDiskQueues() (queues int) {\n\tqueues = int(math.Ceil(float64(q.Cores) / 2))\n\n\tif queues > settings.Hypervisor.DiskQueuesMax {\n\t\tqueues = settings.Hypervisor.DiskQueuesMax\n\t} else if queues < settings.Hypervisor.DiskQueuesMin {\n\t\tqueues = settings.Hypervisor.DiskQueuesMin\n\t}\n\n\treturn\n}\n\nfunc (q *Qemu) GetNetworkQueues() (queues int) {\n\tqueues = int(math.Ceil(float64(q.Cores) / 2))\n\n\tif queues > settings.Hypervisor.NetworkQueuesMax {\n\t\tqueues = settings.Hypervisor.NetworkQueuesMax\n\t} else if queues < settings.Hypervisor.NetworkQueuesMin {\n\t\tqueues = settings.Hypervisor.NetworkQueuesMin\n\t}\n\n\treturn\n}\n\nfunc (q *Qemu) GetNetworkVectors() (vectors int) {\n\tvectors = int(math.Ceil(float64(q.Cores) / 2))\n\n\tif vectors > settings.Hypervisor.NetworkQueuesMax {\n\t\tvectors = settings.Hypervisor.NetworkQueuesMax\n\t} else if vectors < settings.Hypervisor.NetworkQueuesMin {\n\t\tvectors = settings.Hypervisor.NetworkQueuesMin\n\t}\n\n\tvectors = (2 * vectors) + 2\n\n\treturn\n}\n\nfunc (q *Qemu) Marshal() (output string, err error) {\n\tlocalIsosPath := paths.GetLocalIsosPath()\n\tslot := -1\n\n\tqemuPath, err := features.GetQemuPath()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcmd := []string{\n\t\tqemuPath,\n\t\t\"-nographic\",\n\t}\n\n\tcmd = append(cmd, \"-uuid\")\n\tcmd = append(cmd, paths.GetVmUuid(q.Id))\n\n\tnodeVga := node.Self.Vga\n\tnodeVgaRenderPath := \"\"\n\tif nodeVga == \"\" {\n\t\tnodeVga = node.Virtio\n\t}\n\n\tif node.VgaRenderModes.Contains(nodeVga) {\n\t\tnodeVgaRender := node.Self.VgaRender\n\t\tif nodeVgaRender != \"\" {\n\t\t\tnodeVgaRenderPath, err = render.GetRender(nodeVgaRender)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tmemoryBackend, err := features.GetMemoryBackendSupport()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpciPassthrough := false\n\tgpuPassthrough := false\n\tif node.Self.PciPassthrough && len(q.PciDevices) > 0 {\n\t\tpciPassthrough = true\n\n\t\tfor i, device := range q.PciDevices {\n\t\t\tslot += 1\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd,\n\t\t\t\tfmt.Sprintf(\"pcie-root-port,id=pcibus%d,slot=%d\", i, slot))\n\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tif device.Gpu {\n\t\t\t\tgpuPassthrough = true\n\t\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\t\"vfio-pci,host=0000:%s,bus=pcibus%d,\"+\n\t\t\t\t\t\t\"multifunction=on,x-vga=on\",\n\t\t\t\t\tdevice.Slot,\n\t\t\t\t\ti,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\t\"vfio-pci,host=0000:%s,bus=pcibus%d\",\n\t\t\t\t\tdevice.Slot, i,\n\t\t\t\t))\n\t\t\t}\n\t\t}\n\n\t\tif gpuPassthrough {\n\t\t\tcmd = append(cmd, \"-display\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\t}\n\t}\n\n\tvgaPrime := false\n\tif !gpuPassthrough && (q.Vnc || q.Spice || q.Gui) {\n\t\tif q.Gui {\n\t\t\tcmd = append(cmd, \"-display\")\n\t\t\tif q.GuiMode == node.Gtk && !settings.Hypervisor.NoGuiFullscreen {\n\t\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\t\"%s,gl=on,window-close=off,full-screen=on\", q.GuiMode))\n\t\t\t} else {\n\t\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\t\"%s,gl=on,window-close=off\", q.GuiMode))\n\t\t\t}\n\t\t} else if node.VgaRenderModes.Contains(nodeVga) {\n\t\t\tcmd = append(cmd, \"-display\")\n\t\t\toptions := \"egl-headless\"\n\t\t\tif nodeVgaRenderPath != \"\" {\n\t\t\t\toptions += fmt.Sprintf(\",rendernode=%s\", nodeVgaRenderPath)\n\t\t\t}\n\t\t\tcmd = append(cmd, options)\n\t\t}\n\n\t\tswitch nodeVga {\n\t\tcase node.Std:\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"std\")\n\t\tcase node.Vmware:\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"vmware\")\n\t\tcase node.Virtio:\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"virtio\")\n\t\tcase node.VirtioPci:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"virtio-gpu-pci\")\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\tcase node.VirtioPciPrime:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"virtio-gpu-pci\")\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\t\tvgaPrime = true\n\t\tcase node.VirtioVgaGl:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"virtio-vga-gl\")\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\tcase node.VirtioVgaGlPrime:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"virtio-vga-gl\")\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\t\tvgaPrime = true\n\t\tcase node.VirtioVgaGlVulkan:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"virtio-vga-gl,blob=true,hostmem=%dM,venus=true\",\n\t\t\t\tsettings.Hypervisor.GlHostMem,\n\t\t\t))\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\tcase node.VirtioVgaGlVulkanPrime:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"virtio-vga-gl,blob=true,hostmem=%dM,venus=true\",\n\t\t\t\tsettings.Hypervisor.GlHostMem,\n\t\t\t))\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\t\tvgaPrime = true\n\t\tcase node.VirtioGl:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"virtio-gpu-gl\")\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\tcase node.VirtioGlPrime:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"virtio-gpu-gl\")\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\t\tvgaPrime = true\n\t\tcase node.VirtioGlVulkan:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"virtio-gpu-gl,blob=true,hostmem=%dM,venus=true\",\n\t\t\t\tsettings.Hypervisor.GlHostMem,\n\t\t\t))\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\tcase node.VirtioGlVulkanPrime:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"virtio-gpu-gl,blob=true,hostmem=%dM,venus=true\",\n\t\t\t\tsettings.Hypervisor.GlHostMem,\n\t\t\t))\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\t\tvgaPrime = true\n\t\tcase node.VirtioPciGl:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"virtio-gpu-gl-pci\")\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\tcase node.VirtioPciGlPrime:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"virtio-gpu-gl-pci\")\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\t\tvgaPrime = true\n\t\tcase node.VirtioPciGlVulkan:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"virtio-gpu-gl-pci,blob=true,hostmem=%dM,venus=true\",\n\t\t\t\tsettings.Hypervisor.GlHostMem,\n\t\t\t))\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\tcase node.VirtioPciGlVulkanPrime:\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"virtio-gpu-gl-pci,blob=true,hostmem=%dM,venus=true\",\n\t\t\t\tsettings.Hypervisor.GlHostMem,\n\t\t\t))\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, \"none\")\n\t\t\tvgaPrime = true\n\t\tdefault:\n\t\t\tcmd = append(cmd, \"-vga\")\n\t\t\tcmd = append(cmd, nodeVga)\n\t\t}\n\n\t\tif q.Vnc {\n\t\t\tcmd = append(cmd, \"-vnc\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\":%d,websocket=%d,password=on,share=allow-exclusive\",\n\t\t\t\tq.VncDisplay,\n\t\t\t\tq.VncDisplay+15900,\n\t\t\t))\n\t\t}\n\n\t\tif q.Spice {\n\t\t\tcmd = append(cmd, \"-spice\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"ipv4=on,port=%d,image-compression=off\",\n\t\t\t\tq.SpicePort,\n\t\t\t))\n\t\t}\n\t}\n\n\tif q.Uefi {\n\t\tcmd = append(cmd, \"-drive\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"if=pflash,format=raw,unit=0,readonly=on,file=%s\",\n\t\t\tq.OvmfCodePath,\n\t\t))\n\t\tcmd = append(cmd, \"-drive\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"if=pflash,format=raw,unit=1,file=%s\",\n\t\t\tq.OvmfVarsPath,\n\t\t))\n\t}\n\n\tif q.Kvm {\n\t\tcmd = append(cmd, \"-enable-kvm\")\n\t}\n\n\tcmd = append(cmd, \"-name\")\n\tcmd = append(cmd, fmt.Sprintf(\"pritunl_%s\", q.Id.Hex()))\n\n\tif !pciPassthrough {\n\t\tsupported, e := features.GetRunWithSupport()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif supported {\n\t\t\tcmd = append(cmd, \"-run-with\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"user=%s\", permission.GetUserName(q.Id)))\n\t\t} else {\n\t\t\tcmd = append(cmd, \"-runas\")\n\t\t\tcmd = append(cmd, permission.GetUserName(q.Id))\n\t\t}\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tslot += 1\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd,\n\t\t\tfmt.Sprintf(\"pcie-root-port,id=diskbus%d,slot=%d\", i, slot))\n\t}\n\n\tcmd = append(cmd, \"-machine\")\n\toptions := \",mem-merge=on\"\n\tif q.SecureBoot {\n\t\toptions += \",smm=on\"\n\t}\n\tif q.Hugepages && memoryBackend {\n\t\toptions += \",memory-backend=pc.ram\"\n\t}\n\tif q.Kvm {\n\t\toptions += \",accel=kvm\"\n\t}\n\tif gpuPassthrough || (!q.Vnc && !q.Spice && !q.Gui) {\n\t\toptions += \",vmport=off\"\n\t}\n\tcmd = append(cmd, fmt.Sprintf(\"type=%s%s\", q.Machine, options))\n\n\tif q.Kvm {\n\t\tcmd = append(cmd, \"-cpu\")\n\t\tcmd = append(cmd, q.Cpu)\n\t}\n\n\t//cmd = append(cmd, \"-no-hpet\")\n\tcmd = append(cmd, \"-rtc\", \"base=utc,driftfix=slew\")\n\tcmd = append(cmd, \"-msg\", \"timestamp=on\")\n\tcmd = append(cmd, \"-global\", \"kvm-pit.lost_tick_policy=delay\")\n\tcmd = append(cmd, \"-global\", \"ICH9-LPC.disable_s3=1\")\n\tcmd = append(cmd, \"-global\", \"ICH9-LPC.disable_s4=1\")\n\tif q.SecureBoot {\n\t\tcmd = append(\n\t\t\tcmd,\n\t\t\t\"-global\",\n\t\t\t\"driver=cfi.pflash01,property=secure,value=on\",\n\t\t)\n\t}\n\n\tcmd = append(cmd, \"-smp\")\n\tcmd = append(cmd, fmt.Sprintf(\n\t\t\"cores=%d,threads=%d,dies=%d,sockets=%d\",\n\t\tq.Cores,\n\t\tq.Threads,\n\t\tq.Dies,\n\t\tq.Sockets,\n\t))\n\n\tif q.Isos != nil && len(q.Isos) > 0 {\n\t\tcmd = append(cmd, \"-boot\")\n\t\tcmd = append(\n\t\t\tcmd,\n\t\t\tfmt.Sprintf(\n\t\t\t\t\"order=d,menu=on,splash-time=%d\",\n\t\t\t\tsettings.Hypervisor.SplashTime*1000,\n\t\t\t),\n\t\t)\n\t} else {\n\t\tcmd = append(cmd, \"-boot\")\n\t\tcmd = append(cmd, q.Boot)\n\t}\n\n\tcmd = append(cmd, \"-m\")\n\tcmd = append(cmd, fmt.Sprintf(\"%dM\", q.Memory))\n\n\tmemShare := \"off\"\n\tif len(q.Mounts) > 0 {\n\t\tmemShare = \"on\"\n\t}\n\n\tif q.Hugepages {\n\t\tif memoryBackend {\n\t\t\tcmd = append(cmd, \"-object\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"memory-backend-file,id=pc.ram,\"+\n\t\t\t\t\t\"size=%dM,mem-path=%s,prealloc=on,share=%s,merge=off\",\n\t\t\t\tq.Memory,\n\t\t\t\tpaths.GetHugepagePath(q.Id),\n\t\t\t\tmemShare,\n\t\t\t))\n\t\t} else {\n\t\t\tcmd = append(cmd, \"-mem-path\")\n\t\t\tcmd = append(cmd, paths.GetHugepagePath(q.Id))\n\t\t}\n\t}\n\n\tif settings.Hypervisor.VirtRng {\n\t\tcmd = append(cmd, \"-object\",\n\t\t\t\"rng-random,filename=/dev/random,id=rng0\")\n\t\tcmd = append(cmd, \"-device\", \"virtio-rng-pci,rng=rng0\")\n\t}\n\n\tif pciPassthrough {\n\t\tcmd = append(cmd, \"-device\", \"intel-iommu,intremap=on,caching-mode=on\")\n\t}\n\n\tdiskAio := settings.Hypervisor.DiskAio\n\tif diskAio == \"\" {\n\t\tsupported, e := features.GetUringSupport()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif supported {\n\t\t\tdiskAio = \"io_uring\"\n\t\t} else {\n\t\t\tdiskAio = \"native\"\n\t\t}\n\t}\n\n\tfor _, disk := range q.Disks {\n\t\tdskId := fmt.Sprintf(\"fd_%s\", disk.Id)\n\t\tdskFileId := fmt.Sprintf(\"fdf_%s\", disk.Id)\n\t\tdskDevId := fmt.Sprintf(\"fdd_%s\", disk.Id)\n\n\t\tcmd = append(cmd, \"-blockdev\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"driver=file,node-name=%s,filename=%s,aio=%s,\"+\n\t\t\t\t\"discard=unmap,cache.direct=on,cache.no-flush=off\",\n\t\t\tdskFileId,\n\t\t\tdisk.File,\n\t\t\tdiskAio,\n\t\t))\n\n\t\tcmd = append(cmd, \"-blockdev\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"driver=%s,node-name=%s,file=%s,\"+\n\t\t\t\t\"cache.direct=on,cache.no-flush=off\",\n\t\t\tdisk.Format,\n\t\t\tdskId,\n\t\t\tdskFileId,\n\t\t))\n\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"virtio-blk-pci,drive=%s,num-queues=%d,id=%s,\"+\n\t\t\t\t\"bus=diskbus%d,write-cache=on,packed=on\",\n\t\t\tdskId,\n\t\t\tq.GetDiskQueues(),\n\t\t\tdskDevId,\n\t\t\tdisk.Index,\n\t\t))\n\t}\n\n\tfor _, device := range q.DriveDevices {\n\t\tdrivePth := \"\"\n\t\tif device.Type == vm.Lvm {\n\t\t\tdrivePth = filepath.Join(\"/dev/mapper\",\n\t\t\t\tfmt.Sprintf(\"%s-%s\", device.VgName, device.LvName))\n\t\t} else {\n\t\t\tdrivePth = paths.GetDrivePath(device.Id)\n\t\t}\n\n\t\tdskHashId := drive.GetDriveHashId(device.Id)\n\t\tdskId := fmt.Sprintf(\"pd_%s\", dskHashId)\n\t\tdskFileId := fmt.Sprintf(\"pdf_%s\", dskHashId)\n\t\tdskDevId := fmt.Sprintf(\"pdd_%s\", dskHashId)\n\t\tdskBusId := fmt.Sprintf(\"pdb_%s\", dskHashId)\n\t\tslot += 1\n\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"pcie-root-port,id=%s,slot=%d\",\n\t\t\tdskBusId, slot,\n\t\t))\n\n\t\tcmd = append(cmd, \"-blockdev\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"driver=file,node-name=%s,filename=%s,aio=%s,\"+\n\t\t\t\t\"discard=unmap,cache.direct=on,cache.no-flush=off\",\n\t\t\tdskFileId,\n\t\t\tdrivePth,\n\t\t\tdiskAio,\n\t\t))\n\n\t\tcmd = append(cmd, \"-blockdev\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"driver=raw,node-name=%s,file=%s,\"+\n\t\t\t\t\"cache.direct=on,cache.no-flush=off\",\n\t\t\tdskId,\n\t\t\tdskFileId,\n\t\t))\n\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"virtio-blk-pci,drive=%s,num-queues=%d,id=%s,\"+\n\t\t\t\t\"bus=%s,write-cache=on,packed=on\",\n\t\t\tdskId,\n\t\t\tq.GetDiskQueues(),\n\t\t\tdskDevId,\n\t\t\tdskBusId,\n\t\t))\n\t}\n\n\thasIscsi := false\n\tif node.Self.Iscsi {\n\t\tfor _, device := range q.IscsiDevices {\n\t\t\tif !hasIscsi {\n\t\t\t\tcmd = append(cmd, \"-iscsi\")\n\t\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\t\"initiator-name=iqn.2008-11.org.linux-kvm:%s\",\n\t\t\t\t\tq.Id.Hex(),\n\t\t\t\t))\n\t\t\t\thasIscsi = true\n\t\t\t}\n\n\t\t\tiscsiHash := md5.New()\n\t\t\tiscsiHash.Write([]byte(device.Uri))\n\t\t\tiscsiId := fmt.Sprintf(\"%x\", iscsiHash.Sum(nil))\n\n\t\t\tdskId := fmt.Sprintf(\"id_%s\", iscsiId)\n\t\t\tdskFileId := fmt.Sprintf(\"idf_%s\", iscsiId)\n\t\t\tdskDevId := fmt.Sprintf(\"idd_%s\", iscsiId)\n\t\t\tdskBusId := fmt.Sprintf(\"idb_%s\", iscsiId)\n\t\t\tslot += 1\n\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"pcie-root-port,id=%s,slot=%d\",\n\t\t\t\tdskBusId, slot,\n\t\t\t))\n\n\t\t\tcmd = append(cmd, \"-blockdev\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"driver=iscsi,node-name=%s,transport=tcp,\"+\n\t\t\t\t\t\"url=%s,cache.direct=on\",\n\t\t\t\tdskFileId,\n\t\t\t\tdevice.Uri,\n\t\t\t))\n\n\t\t\tcmd = append(cmd, \"-blockdev\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"driver=raw,node-name=%s,file=%s,\"+\n\t\t\t\t\t\"cache.direct=on,cache.no-flush=off\",\n\t\t\t\tdskId,\n\t\t\t\tdskFileId,\n\t\t\t))\n\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"virtio-blk-pci,drive=%s,num-queues=%d,id=%s,\"+\n\t\t\t\t\t\"bus=%s,write-cache=on,packed=on\",\n\t\t\t\tdskId,\n\t\t\t\tq.GetDiskQueues(),\n\t\t\t\tdskDevId,\n\t\t\t\tdskBusId,\n\t\t\t))\n\t\t}\n\t}\n\n\tfor _, mount := range q.Mounts {\n\t\tvfsId := fmt.Sprintf(\"vfs_%s\", mount.Id)\n\t\tvfsDevId := fmt.Sprintf(\"vfsd_%s\", mount.Id)\n\t\tvfsBusId := fmt.Sprintf(\"vfsb_%s\", mount.Id)\n\t\tslot += 1\n\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"pcie-root-port,id=%s,slot=%d\",\n\t\t\tvfsBusId, slot,\n\t\t))\n\n\t\tcmd = append(cmd, \"-chardev\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"socket,id=%s,path=%s\",\n\t\t\tvfsId,\n\t\t\tmount.Sock,\n\t\t))\n\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"vhost-user-fs-pci,chardev=%s,tag=\\\"%s\\\",id=%s,bus=%s\",\n\t\t\tvfsId,\n\t\t\tmount.Name,\n\t\t\tvfsDevId,\n\t\t\tvfsBusId,\n\t\t))\n\t}\n\n\tfor i, network := range q.Networks {\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"virtio-net-pci,netdev=net%d,mac=%s,mq=on,\"+\n\t\t\t\t\"packed=on,rss=on,vectors=%d\",\n\t\t\ti,\n\t\t\tnetwork.MacAddress,\n\t\t\tq.GetNetworkVectors(),\n\t\t))\n\n\t\tcmd = append(cmd, \"-netdev\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"tap,id=net%d,ifname=%s,script=no,vhost=on,queues=%d\",\n\t\t\ti,\n\t\t\tnetwork.Iface,\n\t\t\tq.GetNetworkQueues(),\n\t\t))\n\t}\n\n\tcmd = append(cmd, \"-drive\")\n\tcmd = append(cmd, fmt.Sprintf(\n\t\t\"file=%s,media=cdrom,index=0\",\n\t\tpaths.GetInitPath(q.Id),\n\t))\n\n\tif len(q.Isos) > 0 {\n\t\tfor i, iso := range q.Isos {\n\t\t\tcmd = append(cmd, \"-drive\")\n\t\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\t\"file=%s,media=cdrom,index=%d\",\n\t\t\t\tpath.Join(\n\t\t\t\t\tlocalIsosPath,\n\t\t\t\t\tpath.Base(utils.FilterRelPath(iso.Name)),\n\t\t\t\t),\n\t\t\t\ti+1,\n\t\t\t))\n\t\t}\n\t}\n\n\tcmd = append(cmd, \"-monitor\")\n\tcmd = append(cmd, fmt.Sprintf(\n\t\t\"unix:%s,server=on,wait=off\",\n\t\tpaths.GetSockPath(q.Id),\n\t))\n\n\tcmd = append(cmd, \"-qmp\")\n\tcmd = append(cmd, fmt.Sprintf(\n\t\t\"unix:%s,server=on,wait=off\",\n\t\tpaths.GetQmpSockPath(q.Id),\n\t))\n\n\tcmd = append(cmd, \"-pidfile\")\n\tcmd = append(cmd, paths.GetPidPath(q.Id))\n\n\tif q.Tpm {\n\t\tcmd = append(cmd, \"-chardev\")\n\t\tcmd = append(cmd, fmt.Sprintf(\n\t\t\t\"socket,id=tpmsock0,path=%s\",\n\t\t\tpaths.GetTpmSockPath(q.Id),\n\t\t))\n\t\tcmd = append(cmd, \"-tpmdev\")\n\t\tcmd = append(cmd, \"emulator,id=tpmdev0,chardev=tpmsock0\")\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, \"tpm-tis,tpmdev=tpmdev0\")\n\t}\n\n\tguestPath := paths.GetGuestPath(q.Id)\n\tcmd = append(cmd, \"-chardev\")\n\tcmd = append(cmd, fmt.Sprintf(\n\t\t\"socket,path=%s,server=on,wait=off,id=guest\",\n\t\tguestPath,\n\t))\n\tcmd = append(cmd, \"-device\")\n\tcmd = append(cmd, \"virtio-serial\")\n\tcmd = append(cmd, \"-device\")\n\tcmd = append(cmd,\n\t\t\"virtserialport,chardev=guest,name=org.qemu.guest_agent.0\")\n\n\tif !settings.Hypervisor.NoSandbox {\n\t\tcmd = append(cmd, \"-sandbox\")\n\t\tif q.Gui {\n\t\t\tcmd = append(cmd, \"on,obsolete=deny,elevateprivileges=allow,\"+\n\t\t\t\t\"spawn=allow,resourcecontrol=deny\")\n\t\t} else {\n\t\t\tcmd = append(cmd, \"on,obsolete=deny,elevateprivileges=allow,\"+\n\t\t\t\t\"spawn=deny,resourcecontrol=deny\")\n\t\t}\n\t}\n\n\tif q.Gui && !settings.Hypervisor.NoVirtioHid {\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, \"virtio-tablet-pci\")\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, \"virtio-keyboard-pci\")\n\t}\n\n\tif node.Self.UsbPassthrough || q.Vnc || q.Spice || q.Gui {\n\t\tslot += 1\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd,\n\t\t\tfmt.Sprintf(\"pcie-root-port,id=usbbus,slot=%d\", slot))\n\n\t\tcmd = append(cmd, \"-device\")\n\t\tcmd = append(cmd, fmt.Sprintf(\"qemu-xhci,bus=usbbus,p2=%d,p3=%d\",\n\t\t\tsettings.Hypervisor.UsbHsPorts,\n\t\t\tsettings.Hypervisor.UsbSsPorts,\n\t\t))\n\n\t\tif (q.Vnc || q.Spice) || (q.Gui && settings.Hypervisor.NoVirtioHid) {\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"usb-tablet\")\n\t\t\tcmd = append(cmd, \"-device\")\n\t\t\tcmd = append(cmd, \"usb-kbd\")\n\t\t}\n\n\t\tfor _, device := range q.UsbDevices {\n\t\t\tcmd = append(cmd,\n\t\t\t\t\"-device\",\n\t\t\t\tfmt.Sprintf(\n\t\t\t\t\t\"usb-host,hostdevice=%s,id=%s\",\n\t\t\t\t\tdevice.BusPath,\n\t\t\t\t\tdevice.Id,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t}\n\n\tcompositorEnv := \"\"\n\tif q.Gui {\n\t\tcompositorEnv, err = compositor.GetEnv(\n\t\t\tq.GuiUser, nodeVgaRenderPath, vgaPrime)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tprotectTmp := \"\"\n\tif q.ProtectTmp {\n\t\tprotectTmp = \"true\"\n\t} else {\n\t\tprotectTmp = \"false\"\n\t}\n\n\tprotectHome := \"\"\n\tif q.ProtectHome {\n\t\tprotectHome = \"true\"\n\t} else {\n\t\tprotectHome = \"read-only\"\n\t}\n\n\tif q.Namespace == \"\" {\n\t\toutput = fmt.Sprintf(\n\t\t\tsystemdTemplateExternalNet,\n\t\t\tq.Data,\n\t\t\tcompositorEnv,\n\t\t\tpaths.GetCacheDir(q.Id),\n\t\t\tstrings.Join(cmd, \" \"),\n\t\t\tprotectTmp,\n\t\t\tprotectHome,\n\t\t)\n\t} else {\n\t\toutput = fmt.Sprintf(\n\t\t\tsystemdTemplate,\n\t\t\tq.Data,\n\t\t\tcompositorEnv,\n\t\t\tpaths.GetCacheDir(q.Id),\n\t\t\tstrings.Join(cmd, \" \"),\n\t\t\tprotectTmp,\n\t\t\tprotectHome,\n\t\t\tq.Namespace,\n\t\t)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qemu/routes.go",
    "content": "package qemu\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\nfunc GetRoutes(instId bson.ObjectID) (icmpRedirects bool,\n\troutes []vpc.Route, routes6 []vpc.Route, err error) {\n\n\tnamespace := vm.GetNamespace(instId, 0)\n\n\ticmpRedirects = true\n\toutput, _ := utils.ExecCombinedOutputLogged(\n\t\tnil,\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"sysctl\", \"net.ipv4.conf.br0.send_redirects\",\n\t)\n\tif output != \"\" {\n\t\tparts := strings.Split(strings.TrimSpace(output), \"=\")\n\t\tif len(parts) == 2 {\n\t\t\tvalueStr := strings.TrimSpace(parts[1])\n\t\t\tvalue, _ := strconv.Atoi(valueStr)\n\t\t\ticmpRedirects = value == 1\n\t\t}\n\t}\n\n\toutput, _ = utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"not configured in this system\",\n\t\t},\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"route\", \"-n\",\n\t)\n\n\tif output == \"\" {\n\t\treturn\n\t}\n\n\troutes = []vpc.Route{}\n\troutes6 = []vpc.Route{}\n\n\tlines := strings.Split(output, \"\\n\")\n\tif len(lines) > 2 {\n\t\tfor _, line := range lines[2:] {\n\t\t\tif line == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfields := strings.Fields(line)\n\t\t\tif len(fields) < 8 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif fields[4] != \"97\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif fields[0] == \"0.0.0.0\" || fields[1] == \"0.0.0.0\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmask := utils.ParseIpMask(fields[2])\n\t\t\tif mask == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcidr, _ := mask.Size()\n\n\t\t\troute := vpc.Route{\n\t\t\t\tDestination: fmt.Sprintf(\"%s/%d\", fields[0], cidr),\n\t\t\t\tTarget:      fields[1],\n\t\t\t}\n\n\t\t\troutes = append(routes, route)\n\t\t}\n\t}\n\n\toutput, _ = utils.ExecCombinedOutputLogged(\n\t\t[]string{\n\t\t\t\"not configured in this system\",\n\t\t},\n\t\t\"ip\", \"netns\", \"exec\", namespace,\n\t\t\"route\", \"-6\", \"-n\",\n\t)\n\n\tif output == \"\" {\n\t\treturn\n\t}\n\n\tlines = strings.Split(output, \"\\n\")\n\tif len(lines) > 2 {\n\t\tfor _, line := range lines[2:] {\n\t\t\tif line == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfields := strings.Fields(line)\n\t\t\tif len(fields) < 7 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif fields[3] != \"97\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t_, destination, e := net.ParseCIDR(fields[0])\n\t\t\tif e != nil || destination == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttarget := net.ParseIP(fields[1])\n\t\t\tif target == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\troute := vpc.Route{\n\t\t\t\tDestination: destination.String(),\n\t\t\t\tTarget:      target.String(),\n\t\t\t}\n\n\t\t\troutes6 = append(routes6, route)\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qemu/sort.go",
    "content": "package qemu\n\ntype Disks []*Disk\n\nfunc (d Disks) Len() int {\n\treturn len(d)\n}\n\nfunc (d Disks) Swap(i, j int) {\n\td[i], d[j] = d[j], d[i]\n}\n\nfunc (d Disks) Less(i, j int) bool {\n\treturn d[i].Index < d[j].Index\n}\n"
  },
  {
    "path": "qemu/usb.go",
    "content": "package qemu\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/qms\"\n\t\"github.com/pritunl/pritunl-cloud/store\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc UpdateVmUsb(virt *vm.VirtualMachine) (err error) {\n\tfor i := 0; i < 10; i++ {\n\t\tif virt.State == vm.Running {\n\t\t\tusbs, e := qms.GetUsbDevices(virt.Id)\n\t\t\tif e != nil {\n\t\t\t\tif i < 9 {\n\t\t\t\t\ttime.Sleep(300 * time.Millisecond)\n\t\t\t\t\t_ = UpdateState(virt)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\terr = e\n\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvirt.UsbDevices = usbs\n\n\t\t\tstore.SetUsbs(virt.Id, usbs)\n\t\t}\n\n\t\tbreak\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qemu/utils.go",
    "content": "package qemu\n\nimport (\n\t\"encoding/json\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/pci\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc NewQemu(virt *vm.VirtualMachine) (qm *Qemu, err error) {\n\tdata, err := json.Marshal(virt)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to marshal virt\"),\n\t\t}\n\t\treturn\n\t}\n\n\tovmfCodePath := \"\"\n\tif virt.Uefi {\n\t\tovmfCodePath, err = paths.FindOvmfCodePath(virt.SecureBoot)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tguiUser := node.Self.GuiUser\n\tguiMode := node.Self.GuiMode\n\tif guiMode == \"\" {\n\t\tguiMode = node.Sdl\n\t}\n\n\tnamespace := \"\"\n\tif !virt.HasExternalNetwork() {\n\t\tnamespace = vm.GetNamespace(virt.Id, 0)\n\t}\n\n\tqm = &Qemu{\n\t\tId:           virt.Id,\n\t\tData:         string(data),\n\t\tKvm:          node.Self.Hypervisor == node.Kvm,\n\t\tMachine:      \"q35\",\n\t\tCpu:          \"host\",\n\t\tCores:        virt.Processors,\n\t\tThreads:      1,\n\t\tDies:         1,\n\t\tSockets:      1,\n\t\tBoot:         \"c\",\n\t\tUefi:         virt.Uefi,\n\t\tSecureBoot:   virt.SecureBoot,\n\t\tTpm:          virt.Tpm,\n\t\tOvmfCodePath: ovmfCodePath,\n\t\tOvmfVarsPath: paths.GetOvmfVarsPath(virt.Id),\n\t\tMemory:       virt.Memory,\n\t\tHugepages:    virt.Hugepages,\n\t\tVnc:          virt.Vnc && virt.VncDisplay != 0,\n\t\tVncDisplay:   virt.VncDisplay,\n\t\tSpice:        virt.Spice && virt.SpicePort != 0,\n\t\tSpicePort:    virt.SpicePort,\n\t\tGui:          virt.Gui && node.Self.Gui && guiUser != \"\",\n\t\tGuiUser:      guiUser,\n\t\tGuiMode:      guiMode,\n\t\tProtectHome:  virt.ProtectHome(),\n\t\tProtectTmp:   virt.ProtectTmp(),\n\t\tNamespace:    namespace,\n\t\tDisks:        []*Disk{},\n\t\tNetworks:     []*Network{},\n\t\tIsos:         []*Iso{},\n\t\tUsbDevices:   []*UsbDevice{},\n\t\tPciDevices:   []*PciDevice{},\n\t\tDriveDevices: []*DriveDevice{},\n\t\tIscsiDevices: []*IscsiDevice{},\n\t\tMounts:       []*Mount{},\n\t}\n\n\tfor _, disk := range virt.Disks {\n\t\tqm.Disks = append(qm.Disks, &Disk{\n\t\t\tId:     disk.Id.Hex(),\n\t\t\tIndex:  disk.Index,\n\t\t\tFile:   disk.Path,\n\t\t\tFormat: \"qcow2\",\n\t\t})\n\t}\n\n\tsort.Sort(qm.Disks)\n\n\tfor i, net := range virt.NetworkAdapters {\n\t\tqm.Networks = append(qm.Networks, &Network{\n\t\t\tMacAddress: net.MacAddress,\n\t\t\tIface:      vm.GetIface(virt.Id, i),\n\t\t})\n\t}\n\n\tfor _, is := range virt.Isos {\n\t\tqm.Isos = append(qm.Isos, &Iso{\n\t\t\tName: is.Name,\n\t\t})\n\t}\n\n\tfor _, device := range virt.UsbDevices {\n\t\tusbDevice, _ := device.GetDevice()\n\t\tif usbDevice == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tqm.UsbDevices = append(qm.UsbDevices, &UsbDevice{\n\t\t\tId:      usbDevice.GetQemuId(),\n\t\t\tVendor:  usbDevice.Vendor,\n\t\t\tProduct: usbDevice.Product,\n\t\t\tBus:     usbDevice.Bus,\n\t\t\tAddress: usbDevice.Address,\n\t\t\tBusPath: usbDevice.BusPath,\n\t\t})\n\t}\n\n\tfor _, device := range virt.PciDevices {\n\t\tdev, e := pci.GetVfio(device.Slot)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif dev == nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\t\"device_slot\": device.Slot,\n\t\t\t}).Error(\"qemu: Failed to find vfio device\")\n\n\t\t\tcontinue\n\t\t}\n\n\t\tname := strings.ToLower(dev.Name)\n\n\t\tqm.PciDevices = append(qm.PciDevices, &PciDevice{\n\t\t\tSlot: device.Slot,\n\t\t\tGpu: strings.Contains(name, \"vga compatible\") ||\n\t\t\t\tstrings.Contains(name, \"vga controller\") ||\n\t\t\t\tstrings.Contains(name, \"graphics controller\") ||\n\t\t\t\tstrings.Contains(name, \"display controller\"),\n\t\t})\n\t}\n\n\tfor _, device := range virt.DriveDevices {\n\t\tqm.DriveDevices = append(qm.DriveDevices, &DriveDevice{\n\t\t\tId:     device.Id,\n\t\t\tType:   device.Type,\n\t\t\tVgName: device.VgName,\n\t\t\tLvName: device.LvName,\n\t\t})\n\t}\n\n\tfor _, device := range virt.IscsiDevices {\n\t\tqm.IscsiDevices = append(qm.IscsiDevices, &IscsiDevice{\n\t\t\tUri: device.Uri,\n\t\t})\n\t}\n\n\tfor _, mount := range virt.Mounts {\n\t\tshareId := paths.GetShareId(virt.Id, mount.Name)\n\t\tsockPath := paths.GetShareSockPath(virt.Id, shareId)\n\n\t\tqm.Mounts = append(qm.Mounts, &Mount{\n\t\t\tId:   shareId,\n\t\t\tName: utils.FilterNameCmd(mount.Name),\n\t\t\tSock: sockPath,\n\t\t})\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qga/qga.go",
    "content": "package qga\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype Command struct {\n\tExecute string `json:\"execute\"`\n}\n\ntype Address struct {\n\tType    string `json:\"ip-address-type\"`\n\tAddress string `json:\"ip-address\"`\n\tPrefix  int    `json:\"prefix\"`\n}\n\ntype Interface struct {\n\tName       string     `json:\"name\"`\n\tMacAddress string     `json:\"hardware-address\"`\n\tAddresses  []*Address `json:\"ip-addresses\"`\n}\n\ntype Interfaces struct {\n\tInterfaces []*Interface `json:\"return\"`\n}\n\nfunc (i *Interfaces) GetAddr(macAddr string) (guestAddr, guestAddr6 string) {\n\tmacAddr = strings.ToLower(macAddr)\n\n\tif i.Interfaces != nil {\n\t\tfor _, iface := range i.Interfaces {\n\t\t\tif strings.ToLower(iface.MacAddress) != macAddr {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif iface.Addresses != nil {\n\t\t\t\tfor _, addr := range iface.Addresses {\n\t\t\t\t\tif addr.Type == \"ipv4\" && guestAddr == \"\" {\n\t\t\t\t\t\tguestAddr = addr.Address\n\t\t\t\t\t} else if addr.Type == \"ipv6\" && guestAddr6 == \"\" {\n\t\t\t\t\t\tipAddr := strings.ToLower(addr.Address)\n\t\t\t\t\t\tif !strings.HasPrefix(ipAddr, \"fe\") {\n\t\t\t\t\t\t\tguestAddr6 = strings.ToLower(addr.Address)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GetInterfaces(sockPath string) (ifaces *Interfaces, err error) {\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ConnectionError{\n\t\t\terrors.Wrap(err, \"qga: Failed to connect to guest agent\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(5 * time.Second))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcmd := &Command{\n\t\tExecute: \"guest-network-get-interfaces\",\n\t}\n\n\tcmdByte, err := json.Marshal(cmd)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"qga: Failed to parse guest agent command\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = conn.Write(cmdByte)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"qga: Failed to write to guest agent\"),\n\t\t}\n\t\treturn\n\t}\n\n\tbuffer := make([]byte, 5000000)\n\tn, err := conn.Read(buffer)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"qga: Failed to read from guest agent\"),\n\t\t}\n\t\treturn\n\t}\n\tbuffer = buffer[:n]\n\n\trespByt := bytes.Trim(buffer, \"\\x00\")\n\trespByt = bytes.TrimSpace(respByt)\n\n\tifaces = &Interfaces{}\n\terr = json.Unmarshal(respByt, ifaces)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"qga: Failed to parse guest agent response\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qmp/backup.go",
    "content": "package qmp\n\nimport (\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype driveBackupArgs struct {\n\tDevice string `json:\"device\"`\n\tSync   string `json:\"sync\"`\n\tTarget string `json:\"target\"`\n\tFormat string `json:\"format\"`\n}\n\ntype blockDeviceImage struct {\n\tFilename string `json:\"filename\"`\n}\n\ntype blockDeviceInserted struct {\n\tImage blockDeviceImage `json:\"image\"`\n}\n\ntype blockDevice struct {\n\tDevice   string              `json:\"device\"`\n\tInserted blockDeviceInserted `json:\"inserted\"`\n}\n\ntype blockDeviceReturn struct {\n\tReturn []*blockDevice `json:\"return\"`\n\tError  *CommandError  `json:\"error\"`\n}\n\nfunc driveGetDevice(vmId bson.ObjectID, dsk *disk.Disk) (\n\tname string, err error) {\n\n\tcmd := &Command{\n\t\tExecute: \"query-block\",\n\t}\n\n\treturnData := &blockDeviceReturn{}\n\terr = RunCommand(vmId, cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\tif returnData.Return == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"qmp: Return nil\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, blockDev := range returnData.Return {\n\t\tidStr := strings.Split(path.Base(\n\t\t\tblockDev.Inserted.Image.Filename), \".\")[0]\n\n\t\tdiskId, err := bson.ObjectIDFromHex(idStr)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif diskId == dsk.Id {\n\t\t\tname = blockDev.Device\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc driveBackup(vmId bson.ObjectID, dsk *disk.Disk,\n\tdestPth string) (deviceName string, err error) {\n\n\tdeviceName, err = driveGetDevice(vmId, dsk)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif deviceName == \"\" {\n\t\terr = &DiskNotFound{\n\t\t\terrors.Newf(\"qmp: Disk not found %s\", dsk.Id.Hex()),\n\t\t}\n\t\treturn\n\t}\n\n\tcmd := &Command{\n\t\tExecute: \"drive-backup\",\n\t\tArguments: &driveBackupArgs{\n\t\t\tDevice: deviceName,\n\t\t\tSync:   \"full\",\n\t\t\tTarget: destPth,\n\t\t\tFormat: \"qcow2\",\n\t\t},\n\t}\n\n\treturnData := &CommandReturn{}\n\terr = RunCommand(vmId, cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\treturn\n}\n\nfunc driveBackupCheck(vmId bson.ObjectID, deviceName string) (\n\tcomplete bool, err error) {\n\n\tcmd := &Command{\n\t\tExecute: \"query-jobs\",\n\t}\n\n\treturnData := &JobStatusReturn{}\n\terr = RunCommand(vmId, cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\tif returnData.Return == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"qmp: Return nil\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, status := range returnData.Return {\n\t\tif status.Type == \"backup\" &&\n\t\t\tstatus.Id == deviceName &&\n\t\t\tstatus.Status != \"concluded\" {\n\n\t\t\treturn\n\t\t}\n\t}\n\n\tcomplete = true\n\n\treturn\n}\n\nfunc BackupDisk(vmId bson.ObjectID, dsk *disk.Disk,\n\tdestPth string) (err error) {\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"instance_id\": vmId.Hex(),\n\t\t\"disk_id\":     dsk.Id.Hex(),\n\t}).Info(\"qmp: Backing up disk\")\n\n\tdeviceName, err := driveBackup(vmId, dsk, destPth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor {\n\t\tcomplete, e := driveBackupCheck(vmId, deviceName)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif complete {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(3 * time.Second)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qmp/disk.go",
    "content": "package qmp\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/features\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype blockDevFileArgs struct {\n\tDriver   string        `json:\"driver\"`\n\tNodeName string        `json:\"node-name\"`\n\tAio      string        `json:\"aio\"`\n\tDiscard  string        `json:\"discard\"`\n\tFilename string        `json:\"filename\"`\n\tCache    blockDevCache `json:\"cache\"`\n}\n\ntype blockDevArgs struct {\n\tDriver   string        `json:\"driver\"`\n\tNodeName string        `json:\"node-name\"`\n\tFile     string        `json:\"file\"`\n\tCache    blockDevCache `json:\"cache\"`\n}\n\ntype blockDevCache struct {\n\tNoFlush bool `json:\"no-flush\"`\n\tDirect  bool `json:\"direct\"`\n}\n\ntype deviceAddArgs struct {\n\tId     string `json:\"id\"`\n\tDriver string `json:\"driver\"`\n\tDrive  string `json:\"drive\"`\n\tBus    string `json:\"bus\"`\n}\n\ntype blockDevEventData struct {\n\tDevice string `json:\"device\"`\n\tPath   string `json:\"path\"`\n}\n\ntype blockDevEvent struct {\n\tEvent string            `json:\"event\"`\n\tData  blockDevEventData `json:\"data\"`\n}\n\nfunc AddDisk(vmId bson.ObjectID, dsk *vm.Disk) (err error) {\n\tdskId := fmt.Sprintf(\"fd_%s\", dsk.Id.Hex())\n\tdskFileId := fmt.Sprintf(\"fdf_%s\", dsk.Id.Hex())\n\tdskDevId := fmt.Sprintf(\"fdd_%s\", dsk.Id.Hex())\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"instance_id\": vmId.Hex(),\n\t\t\"disk_id\":     dsk.Id.Hex(),\n\t\t\"disk_index\":  dsk.Index,\n\t}).Info(\"qmp: Connecting virtual disk\")\n\n\tdiskAio := settings.Hypervisor.DiskAio\n\tif diskAio == \"\" {\n\t\tsupported, e := features.GetUringSupport()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif supported {\n\t\t\tdiskAio = \"io_uring\"\n\t\t} else {\n\t\t\tdiskAio = \"threads\"\n\t\t}\n\t}\n\n\tconn := NewConnection(vmId, true)\n\tdefer conn.Close()\n\n\t_, err = conn.Connect()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcmd := &Command{\n\t\tExecute: \"blockdev-add\",\n\t\tArguments: &blockDevFileArgs{\n\t\t\tDriver:   \"file\",\n\t\t\tNodeName: dskFileId,\n\t\t\tAio:      diskAio,\n\t\t\tDiscard:  \"unmap\",\n\t\t\tFilename: dsk.Path,\n\t\t\tCache: blockDevCache{\n\t\t\t\tNoFlush: false,\n\t\t\t\tDirect:  true,\n\t\t\t},\n\t\t},\n\t}\n\n\treturnData := &CommandReturn{}\n\terr = conn.Send(cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil &&\n\t\t!strings.Contains(\n\t\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\t\"duplicate\",\n\t\t) {\n\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\tcmd = &Command{\n\t\tExecute: \"blockdev-add\",\n\t\tArguments: &blockDevArgs{\n\t\t\tDriver:   \"qcow2\",\n\t\t\tNodeName: dskId,\n\t\t\tFile:     dskFileId,\n\t\t\tCache: blockDevCache{\n\t\t\t\tNoFlush: false,\n\t\t\t\tDirect:  true,\n\t\t\t},\n\t\t},\n\t}\n\n\treturnData = &CommandReturn{}\n\terr = conn.Send(cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil &&\n\t\t!strings.Contains(\n\t\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\t\"duplicate\",\n\t\t) {\n\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\tcmd = &Command{\n\t\tExecute: \"device_add\",\n\t\tArguments: &deviceAddArgs{\n\t\t\tId:     dskDevId,\n\t\t\tDriver: \"virtio-blk-pci\",\n\t\t\tDrive:  dskId,\n\t\t\tBus:    fmt.Sprintf(\"diskbus%d\", dsk.Index),\n\t\t},\n\t}\n\n\treturnData = &CommandReturn{}\n\terr = conn.Send(cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\treturn\n}\n\nfunc RemoveDisk(vmId bson.ObjectID, dsk *vm.Disk) (err error) {\n\tdskId := fmt.Sprintf(\"fd_%s\", dsk.Id.Hex())\n\tdskFileId := fmt.Sprintf(\"fdf_%s\", dsk.Id.Hex())\n\tdskDevId := fmt.Sprintf(\"fdd_%s\", dsk.Id.Hex())\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"instance_id\": vmId.Hex(),\n\t\t\"disk_id\":     dsk.Id.Hex(),\n\t\t\"disk_index\":  dsk.Index,\n\t}).Info(\"qmp: Disconnecting virtual disk\")\n\n\tdiskAio := settings.Hypervisor.DiskAio\n\tif diskAio == \"\" {\n\t\tsupported, e := features.GetUringSupport()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif supported {\n\t\t\tdiskAio = \"io_uring\"\n\t\t} else {\n\t\t\tdiskAio = \"threads\"\n\t\t}\n\t}\n\n\tconn := NewConnection(vmId, true)\n\tdefer conn.Close()\n\n\tconn.SetDeadline(30 * time.Second)\n\n\t_, err = conn.Connect()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcmd := &Command{\n\t\tExecute: \"device_del\",\n\t\tArguments: &CommandId{\n\t\t\tId: dskDevId,\n\t\t},\n\t}\n\n\treturnData := &CommandReturn{}\n\terr = conn.Send(cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tskipEvent := false\n\tif returnData.Error != nil && (strings.Contains(\n\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\"process of unplug\") || strings.Contains(\n\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\"not found\") || strings.Contains(\n\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\"failed to find\")) {\n\n\t\tskipEvent = true\n\t} else if returnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\tif !skipEvent {\n\t\tevent := &blockDevEvent{}\n\t\terr = conn.Event(event, func() (resp interface{}, err error) {\n\t\t\tif event.Event == \"DEVICE_DELETED\" &&\n\t\t\t\tevent.Data.Device == dskDevId {\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tevent = &blockDevEvent{}\n\t\t\tresp = event\n\n\t\t\treturn\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tcmd = &Command{\n\t\tExecute: \"blockdev-del\",\n\t\tArguments: &CommandNode{\n\t\t\tNodeName: dskId,\n\t\t},\n\t}\n\n\treturnData = &CommandReturn{}\n\terr = conn.Send(cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil && !strings.Contains(\n\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\"process of unplug\") && !strings.Contains(\n\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\"not found\") && !strings.Contains(\n\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\"failed to find\") {\n\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\tcmd = &Command{\n\t\tExecute: \"blockdev-del\",\n\t\tArguments: &CommandNode{\n\t\t\tNodeName: dskFileId,\n\t\t},\n\t}\n\n\treturnData = &CommandReturn{}\n\terr = conn.Send(cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil && !strings.Contains(\n\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\"process of unplug\") && !strings.Contains(\n\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\"not found\") && !strings.Contains(\n\t\tstrings.ToLower(returnData.Error.Desc),\n\t\t\"failed to find\") {\n\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\ntype blockQueryReturn struct {\n\tReturn []blockQueryDevice `json:\"return\"`\n\tError  *CommandError      `json:\"error\"`\n}\n\ntype blockQueryDevice struct {\n\tDevice    string             `json:\"device\"`\n\tLocked    bool               `json:\"locked\"`\n\tRemovable bool               `json:\"removable\"`\n\tInserted  blockQueryInserted `json:\"inserted\"`\n}\n\ntype blockQueryInserted struct {\n\tNodeName string          `json:\"node-name\"`\n\tDrv      string          `json:\"drv\"`\n\tFile     string          `json:\"file\"`\n\tCache    blockQueryCache `json:\"cache\"`\n\tImage    blockQueryImage `json:\"image\"`\n}\n\ntype blockQueryCache struct {\n\tNoFlush   bool `json:\"no-flush\"`\n\tDirect    bool `json:\"direct\"`\n\tWriteback bool `json:\"writeback\"`\n}\n\ntype blockQueryImage struct {\n\tVirtualSize int64  `json:\"virtual-size\"`\n\tFilename    string `json:\"filename\"`\n\tFormat      string `json:\"format\"`\n\tActualSize  int64  `json:\"actual-size\"`\n}\n\ntype pciQueryReturn struct {\n\tReturn []pciQueryBus `json:\"return\"`\n\tError  *CommandError `json:\"error\"`\n}\n\ntype pciQueryBus struct {\n\tBus       int            `json:\"bus\"`\n\tSlot      int            `json:\"slot\"`\n\tQdevId    string         `json:\"qdev_id\"`\n\tDevices   []pciQueryBus  `json:\"devices,omitempty\"`\n\tPciBridge pciQueryBridge `json:\"pci_bridge,omitempty\"`\n}\n\ntype pciQueryBridge struct {\n\tDevices []pciQueryDevice `json:\"devices,omitempty\"`\n}\n\ntype pciQueryDevice struct {\n\tBus    int    `json:\"bus\"`\n\tSlot   int    `json:\"slot\"`\n\tQdevId string `json:\"qdev_id\"`\n}\n\nfunc GetDisks(vmId bson.ObjectID) (info *QemuInfo, disks []*vm.Disk,\n\terr error) {\n\n\tconn := NewConnection(vmId, false)\n\tdefer conn.Close()\n\n\tinfo, err = conn.Connect()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcmd := &Command{\n\t\tExecute: \"query-block\",\n\t}\n\n\treturnData := &blockQueryReturn{}\n\terr = conn.Send(cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\tdisksMap := map[bson.ObjectID]*vm.Disk{}\n\n\tindex := 0\n\tfor _, disk := range returnData.Return {\n\t\tvar idSpl []string\n\t\tif strings.HasPrefix(disk.Device, \"disk_\") {\n\t\t\tidSpl = strings.Split(disk.Device, \"_\")\n\t\t} else if strings.HasPrefix(disk.Inserted.NodeName, \"fd_\") {\n\t\t\tidSpl = strings.Split(disk.Inserted.NodeName, \"_\")\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(idSpl) < 2 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\":   vmId.Hex(),\n\t\t\t\t\"qmp_names\":     idSpl,\n\t\t\t\t\"qmp_device\":    disk.Device,\n\t\t\t\t\"qmp_node_name\": disk.Inserted.NodeName,\n\t\t\t\t\"qmp_file\":      disk.Inserted.File,\n\t\t\t\t\"qmp_filename\":  disk.Inserted.Image.Filename,\n\t\t\t}).Error(\"qmp: Disk id invalid\")\n\t\t\tcontinue\n\t\t}\n\n\t\tdskId, ok := utils.ParseObjectId(idSpl[1])\n\t\tif !ok {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\":   vmId.Hex(),\n\t\t\t\t\"qmp_names\":     idSpl,\n\t\t\t\t\"qmp_device\":    disk.Device,\n\t\t\t\t\"qmp_node_name\": disk.Inserted.NodeName,\n\t\t\t\t\"qmp_file\":      disk.Inserted.File,\n\t\t\t\t\"qmp_filename\":  disk.Inserted.Image.Filename,\n\t\t\t}).Error(\"qmp: Disk id parse failed\")\n\t\t\tcontinue\n\t\t}\n\n\t\tfilename := disk.Inserted.Image.Filename\n\t\tif filename == \"\" {\n\t\t\tfilename = disk.Inserted.File\n\t\t\tif filename == \"\" {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\":   vmId.Hex(),\n\t\t\t\t\t\"qmp_names\":     idSpl,\n\t\t\t\t\t\"qmp_device\":    disk.Device,\n\t\t\t\t\t\"qmp_node_name\": disk.Inserted.NodeName,\n\t\t\t\t\t\"qmp_file\":      disk.Inserted.File,\n\t\t\t\t\t\"qmp_filename\":  disk.Inserted.Image.Filename,\n\t\t\t\t}).Error(\"qmp: Disk filename invalid\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tdsk := &vm.Disk{\n\t\t\tId:    dskId,\n\t\t\tIndex: index,\n\t\t\tPath:  filename,\n\t\t}\n\t\tdisks = append(disks, dsk)\n\t\tdisksMap[dsk.Id] = dsk\n\n\t\tindex += 1\n\t}\n\n\tcmd = &Command{\n\t\tExecute: \"query-pci\",\n\t}\n\n\tpciReturnData := &pciQueryReturn{}\n\terr = conn.Send(cmd, pciReturnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif pciReturnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", pciReturnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, rootBus := range pciReturnData.Return {\n\t\tif rootBus.Devices == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, subBus := range rootBus.Devices {\n\t\t\tif !strings.HasPrefix(subBus.QdevId, \"diskbus\") ||\n\t\t\t\tsubBus.PciBridge.Devices == nil {\n\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, device := range subBus.PciBridge.Devices {\n\t\t\t\tif !strings.HasPrefix(device.QdevId, \"fdd_\") {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tdskIndex, e := strconv.Atoi(subBus.QdevId[7:])\n\t\t\t\tif e != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\t\t\"qmp_diskbus\": subBus.QdevId,\n\t\t\t\t\t\t\"qmp_device\":  device.QdevId,\n\t\t\t\t\t}).Error(\"qmp: Disk bus parse failed\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tdskId, ok := utils.ParseObjectId(device.QdevId[4:])\n\t\t\t\tif !ok {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\t\t\"qmp_diskbus\": subBus.QdevId,\n\t\t\t\t\t\t\"qmp_device\":  device.QdevId,\n\t\t\t\t\t}).Error(\"qmp: Disk bus id parse failed\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tdsk := disksMap[dskId]\n\t\t\t\tif dsk == nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\t\t\"disk_id\":     dskId.Hex(),\n\t\t\t\t\t\t\"qmp_diskbus\": subBus.QdevId,\n\t\t\t\t\t\t\"qmp_device\":  device.QdevId,\n\t\t\t\t\t}).Error(\"qmp: Unknown disk found\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tdsk.Index = dskIndex\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qmp/errors.go",
    "content": "package qmp\n\nimport \"github.com/dropbox/godropbox/errors\"\n\ntype DiskNotFound struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "qmp/password.go",
    "content": "package qmp\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nconst (\n\tSpice = \"spice\"\n\tVnc   = \"vnc\"\n)\n\ntype setPasswordArgs struct {\n\tProtocol string `json:\"protocol\"`\n\tPassword string `json:\"password\"`\n}\n\nfunc SetPassword(vmId bson.ObjectID, proto, passwd string) (err error) {\n\tcmd := &Command{\n\t\tExecute: \"set_password\",\n\t\tArguments: &setPasswordArgs{\n\t\t\tProtocol: proto,\n\t\t\tPassword: passwd,\n\t\t},\n\t}\n\n\treturnData := &CommandReturn{}\n\terr = RunCommand(vmId, cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(50 * time.Millisecond)\n\n\treturn\n}\n"
  },
  {
    "path": "qmp/power.go",
    "content": "package qmp\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc Shutdown(vmId bson.ObjectID) (err error) {\n\tcmd := &Command{\n\t\tExecute: \"system_powerdown\",\n\t}\n\n\treturnData := &CommandReturn{}\n\terr = RunCommand(vmId, cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(1000 * time.Millisecond)\n\n\treturn\n}\n"
  },
  {
    "path": "qmp/qmp.go",
    "content": "package qmp\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Command struct {\n\tExecute   string      `json:\"execute\"`\n\tArguments interface{} `json:\"arguments,omitempty\"`\n}\n\ntype CommandId struct {\n\tId string `json:\"id\"`\n}\n\ntype CommandNode struct {\n\tNodeName string `json:\"node-name\"`\n}\n\ntype JobStatus struct {\n\tId     string `json:\"id\"`\n\tType   string `json:\"type\"`\n\tStatus string `json:\"status\"`\n}\n\ntype JobStatusReturn struct {\n\tReturn []*JobStatus  `json:\"return\"`\n\tError  *CommandError `json:\"error\"`\n}\n\ntype QmpVersionData struct {\n\tMajor int `json:\"major\"`\n\tMinor int `json:\"minor\"`\n\tMicro int `json:\"micro\"`\n}\n\ntype QmpVersion struct {\n\tQemu QmpVersionData `json:\"qemu\"`\n}\n\ntype QmpData struct {\n\tVersion QmpVersion `json:\"version\"`\n}\n\ntype QmpCapabilities struct {\n\tQMP QmpData `json:\"QMP\"`\n}\n\ntype QemuInfo struct {\n\tVersionMajor int\n\tVersionMinor int\n\tVersionMicro int\n}\n\ntype CommandError struct {\n\tClass string `json:\"class\"`\n\tDesc  string `json:\"desc\"`\n}\n\ntype CommandReturn struct {\n\tReturn interface{}   `json:\"return\"`\n\tError  *CommandError `json:\"error\"`\n}\n\ntype EventCallback func() (resp interface{}, err error)\n\nvar (\n\tsocketsLock = utils.NewMultiTimeoutLock(1 * time.Minute)\n)\n\ntype Connection struct {\n\tvmId     bson.ObjectID\n\tsock     net.Conn\n\tlockId   bson.ObjectID\n\tdeadline time.Duration\n\tlogging  bool\n\tcommand  interface{}\n\tresponse interface{}\n}\n\nfunc (c *Connection) connect() (info *QemuInfo, err error) {\n\t// TODO Backward compatibility\n\tsockPath := paths.GetQmpSockPath(c.vmId)\n\tsockPathOld := paths.GetQmpSockPathOld(c.vmId)\n\n\texists, err := utils.Exists(sockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !exists {\n\t\tsockPath = sockPathOld\n\t}\n\n\tc.lockId = socketsLock.Lock(c.vmId.Hex())\n\n\tc.sock, err = net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t10*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qmp: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdeadline := c.deadline\n\tif deadline == 0 {\n\t\tdeadline = 6 * time.Second\n\t}\n\n\terr = c.sock.SetDeadline(time.Now().Add(deadline))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qmp: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvar infoByt []byte\n\tfor {\n\t\tbuffer := make([]byte, 5000000)\n\t\tn, e := c.sock.Read(buffer)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"qmp: Failed to read socket\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbuffer = buffer[:n]\n\n\t\tlines := bytes.Split(buffer, []byte(\"\\n\"))\n\t\tfor _, line := range lines {\n\t\t\tif !constants.Production && c.logging {\n\t\t\t\tfmt.Println(string(line))\n\t\t\t}\n\n\t\t\tif bytes.Contains(line, []byte(`\"QMP\"`)) {\n\t\t\t\tinfoByt = line\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif infoByt != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif infoByt == nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.New(\"qmp: No info message from socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\tconnInfo := &QmpCapabilities{}\n\n\terr = json.Unmarshal(infoByt, connInfo)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrapf(\n\t\t\t\terr,\n\t\t\t\t\"qmp: Failed to unmarshal info '%s'\",\n\t\t\t\tstring(infoByt),\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tinfo = &QemuInfo{\n\t\tVersionMajor: connInfo.QMP.Version.Qemu.Major,\n\t\tVersionMinor: connInfo.QMP.Version.Qemu.Minor,\n\t\tVersionMicro: connInfo.QMP.Version.Qemu.Micro,\n\t}\n\n\treturn\n}\n\nfunc (c *Connection) Close() {\n\tsock := c.sock\n\tif sock != nil {\n\t\t_ = sock.Close()\n\t}\n\n\tsocketsLock.Unlock(c.vmId.Hex(), c.lockId)\n}\n\nfunc (c *Connection) SetDeadline(deadline time.Duration) {\n\tc.deadline = deadline\n}\n\nfunc (c *Connection) Send(command interface{}, resp interface{}) (\n\terr error) {\n\n\tcmdData, err := json.Marshal(command)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"qmp: Failed to marshal command\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif !constants.Production && c.logging {\n\t\tfmt.Println(string(cmdData))\n\t}\n\n\tcmdData = append(cmdData, '\\n')\n\n\t_, err = c.sock.Write(cmdData)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qmp: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvar returnData []byte\n\treturnWait := make(chan bool, 2)\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\treturnWait <- true\n\t\t}()\n\n\t\tfor {\n\t\t\tbuffer := make([]byte, 5000000)\n\t\t\tn, e := c.sock.Read(buffer)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ReadError{\n\t\t\t\t\terrors.Wrap(e, \"qmp: Failed to read socket\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbuffer = buffer[:n]\n\n\t\t\tlines := bytes.Split(buffer, []byte(\"\\n\"))\n\t\t\tfor _, line := range lines {\n\t\t\t\tif !constants.Production && c.logging {\n\t\t\t\t\tfmt.Println(string(line))\n\t\t\t\t}\n\n\t\t\t\tif bytes.Contains(line, []byte(`\"return\"`)) ||\n\t\t\t\t\tbytes.Contains(line, []byte(`\"error\"`)) {\n\n\t\t\t\t\treturnData = line\n\t\t\t\t\treturnWait <- true\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\t<-returnWait\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData == nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.New(\"qmp: No data from socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = json.Unmarshal(returnData, resp)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrapf(\n\t\t\t\terr,\n\t\t\t\t\"qmp: Failed to unmarshal return '%s'\",\n\t\t\t\tstring(returnData),\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Connection) Event(resp interface{}, callback EventCallback) (\n\terr error) {\n\n\tfor {\n\t\tbuffer := make([]byte, 5000000)\n\t\tn, e := c.sock.Read(buffer)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"qmp: Failed to read socket\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbuffer = buffer[:n]\n\n\t\tlines := bytes.Split(buffer, []byte(\"\\n\"))\n\t\tfor _, line := range lines {\n\t\t\tif !constants.Production && c.logging {\n\t\t\t\tfmt.Println(string(line))\n\t\t\t}\n\n\t\t\tif bytes.Contains(line, []byte(`\"event\"`)) {\n\t\t\t\terr = json.Unmarshal(line, resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\t\terrors.Wrapf(\n\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t\t\"qmp: Failed to unmarshal return '%s'\",\n\t\t\t\t\t\t\tstring(line),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tresp, err = callback()\n\t\t\t\tif err != nil || resp == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (c *Connection) Connect() (info *QemuInfo, err error) {\n\tinfo, err = c.connect()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tinitCmd := &Command{\n\t\tExecute: \"qmp_capabilities\",\n\t}\n\n\tinitResp := &CommandReturn{}\n\terr = c.Send(initCmd, initResp)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif initResp.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error '%s'\", initResp.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc NewConnection(vmId bson.ObjectID, logging bool) (conn *Connection) {\n\tconn = &Connection{\n\t\tvmId:    vmId,\n\t\tlogging: logging,\n\t}\n\n\treturn\n}\n\nfunc RunCommand(vmId bson.ObjectID, cmd interface{},\n\tresp interface{}) (err error) {\n\n\tconn := NewConnection(vmId, true)\n\tdefer conn.Close()\n\n\t_, err = conn.Connect()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = conn.Send(cmd, resp)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qmp/vnc.go",
    "content": "package qmp\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype vncPasswordArgs struct {\n\tPassword string `json:\"password\"`\n}\n\nfunc VncPassword(vmId bson.ObjectID, passwd string) (err error) {\n\tcmd := &Command{\n\t\tExecute: \"change-vnc-password\",\n\t\tArguments: &vncPasswordArgs{\n\t\t\tPassword: passwd,\n\t\t},\n\t}\n\n\treturnData := &CommandReturn{}\n\terr = RunCommand(vmId, cmd, returnData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif returnData.Error != nil {\n\t\terr = &errortypes.ApiError{\n\t\t\terrors.Newf(\"qmp: Return error %s\", returnData.Error.Desc),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\treturn\n}\n"
  },
  {
    "path": "qms/disk.go",
    "content": "package qms\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc GetDisks(vmId bson.ObjectID) (disks []*vm.Disk, err error) {\n\tdisks = []*vm.Disk{}\n\n\tsockPath, err := GetSockPath(vmId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlockId := socketsLock.Lock(vmId.Hex())\n\tdefer socketsLock.Unlock(vmId.Hex(), lockId)\n\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(3 * time.Second))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\tbuffer := []byte{}\n\tfor {\n\t\tbuf := make([]byte, 5000000)\n\t\tn, e := conn.Read(buf)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"qemu: Failed to read socket\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbuffer = append(buffer, buf[:n]...)\n\n\t\tif bytes.Contains(bytes.TrimSpace(buffer), []byte(\"(qemu)\")) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\t_, err = conn.Write([]byte(\"info block\\n\"))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\tbuffer = []byte{}\n\tfor {\n\t\tbuf := make([]byte, 5000000)\n\t\tn, e := conn.Read(buf)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"qemu: Failed to read socket\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbuffer = append(buffer, buf[:n]...)\n\n\t\tif bytes.Contains(bytes.TrimSpace(buffer), []byte(\"(qemu)\")) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tindex := 0\n\tfor _, line := range strings.Split(string(buffer), \"\\n\") {\n\t\tif len(line) < 10 {\n\t\t\tcontinue\n\t\t}\n\n\t\t// TODO Backwards compatibility\n\t\tif strings.HasPrefix(line, \"virtio\") {\n\t\t\tline = strings.Replace(line, \"\\r\", \"\", -1)\n\n\t\t\tif !strings.HasPrefix(line, \"virtio\") || len(line) < 10 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tline = strings.Replace(line, \"\\r\", \"\", -1)\n\n\t\t\tlineSpl := strings.SplitN(line[6:], \":\", 2)\n\t\t\tif len(lineSpl) != 2 {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\t\"line\":        line,\n\t\t\t\t}).Error(\"qemu: Unexpected qemu disk path\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tindexStr := strings.Fields(strings.TrimSpace(lineSpl[0]))[0]\n\t\t\tindx, e := strconv.Atoi(indexStr)\n\t\t\tif e != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\t\"line\":        line,\n\t\t\t\t}).Error(\"qemu: Unexpected qemu disk path index\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdiskPath := strings.Fields(strings.TrimSpace(lineSpl[1]))[0]\n\n\t\t\tidStr := strings.Split(path.Base(diskPath), \".\")[0]\n\n\t\t\tdiskId, err := bson.ObjectIDFromHex(idStr)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdsk := &vm.Disk{\n\t\t\t\tId:    diskId,\n\t\t\t\tIndex: indx,\n\t\t\t\tPath:  diskPath,\n\t\t\t}\n\t\t\tdisks = append(disks, dsk)\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif !strings.HasPrefix(line, \"disk_\") &&\n\t\t\t!strings.HasPrefix(line, \"fd_\") {\n\n\t\t\tcontinue\n\t\t}\n\t\tline = strings.Replace(line, \"\\r\", \"\", -1)\n\n\t\tlineSpl := strings.SplitN(line, \":\", 2)\n\t\tif len(lineSpl) != 2 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\"line\":        line,\n\t\t\t}).Error(\"qemu: Unexpected qemu disk id\")\n\t\t\tcontinue\n\t\t}\n\n\t\tidIndexStr := strings.Fields(strings.TrimSpace(lineSpl[0]))[0]\n\t\tif len(idIndexStr) < 6 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\"line\":        line,\n\t\t\t}).Error(\"qemu: Unexpected qemu disk id length\")\n\t\t\tcontinue\n\t\t}\n\n\t\tidIndexStrSpl := strings.Split(idIndexStr, \"_\")\n\t\tif len(idIndexStrSpl) < 2 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\"line\":        line,\n\t\t\t}).Error(\"qemu: Unexpected qemu disk id invalid\")\n\t\t\tcontinue\n\t\t}\n\n\t\tdskId, ok := utils.ParseObjectId(idIndexStrSpl[1])\n\t\tif !ok {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\"line\":        line,\n\t\t\t}).Error(\"qemu: Unexpected qemu disk id parse\")\n\t\t\tcontinue\n\t\t}\n\n\t\tdiskPath := strings.Fields(strings.TrimSpace(lineSpl[1]))[0]\n\n\t\tdsk := &vm.Disk{\n\t\t\tId:    dskId,\n\t\t\tIndex: index,\n\t\t\tPath:  diskPath,\n\t\t}\n\t\tdisks = append(disks, dsk)\n\n\t\tindex += 1\n\t}\n\n\treturn\n}\n\nfunc AddDisk(vmId bson.ObjectID, dsk *vm.Disk,\n\tvirt *vm.VirtualMachine) (err error) {\n\n\tdskId := fmt.Sprintf(\"disk_%s\", dsk.Id.Hex())\n\tdskDevId := fmt.Sprintf(\"diskdev_%s\", dsk.Id.Hex())\n\n\tsockPath, err := GetSockPath(vmId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"instance_id\": vmId.Hex(),\n\t\t\"disk_path\":   dsk.Path,\n\t}).Info(\"qemu: Connecting virtual machine disk\")\n\n\tlockId := socketsLock.Lock(vmId.Hex())\n\tdefer socketsLock.Unlock(vmId.Hex(), lockId)\n\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(5 * time.Second))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdrive := fmt.Sprintf(\n\t\t\"file=%s,media=disk,format=qcow2,cache=none,\"+\n\t\t\t\"discard=unmap,if=none,id=%s\",\n\t\tdsk.Path,\n\t\tdskId,\n\t)\n\n\t_, err = conn.Write([]byte(fmt.Sprintf(\n\t\t\"drive_add 0 %s\\n\", drive,\n\t)))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\tqueues := virt.Processors / 2\n\n\tif queues > settings.Hypervisor.DiskQueuesMax {\n\t\tqueues = settings.Hypervisor.DiskQueuesMax\n\t} else if queues < settings.Hypervisor.DiskQueuesMin {\n\t\tqueues = settings.Hypervisor.DiskQueuesMin\n\t}\n\n\tdevice := fmt.Sprintf(\n\t\t\"virtio-blk-pci,drive=%s,num-queues=%d,id=%s,bus=diskbus%d\",\n\t\tdskId,\n\t\tqueues,\n\t\tdskDevId,\n\t\tdsk.Index,\n\t)\n\n\t_, err = conn.Write([]byte(fmt.Sprintf(\n\t\t\"device_add %s\\n\", device,\n\t)))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\treturn\n}\n\nfunc RemoveDisk(vmId bson.ObjectID, dsk *vm.Disk) (err error) {\n\tsockPath, err := GetSockPath(vmId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"instance_id\": vmId.Hex(),\n\t\t\"disk_path\":   dsk.Path,\n\t}).Info(\"qemu: Disconnecting virtual machine disk\")\n\n\tlockId := socketsLock.Lock(vmId.Hex())\n\tdefer socketsLock.Unlock(vmId.Hex(), lockId)\n\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(5 * time.Second))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = conn.Write([]byte(\n\t\tfmt.Sprintf(\"device_del diskdev_%s\\n\", dsk.Id.Hex())))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(50 * time.Millisecond)\n\n\t_, err = conn.Write([]byte(\n\t\tfmt.Sprintf(\"drive_del disk_%s\\n\", dsk.Id.Hex())))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\treturn\n}\n"
  },
  {
    "path": "qms/power.go",
    "content": "package qms\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc Shutdown(vmId bson.ObjectID) (err error) {\n\tsockPath, err := GetSockPath(vmId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlockId := socketsLock.Lock(vmId.Hex())\n\tdefer socketsLock.Unlock(vmId.Hex(), lockId)\n\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(5 * time.Second))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = conn.Write([]byte(\"system_powerdown\\n\"))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(500 * time.Millisecond)\n\n\treturn\n}\n"
  },
  {
    "path": "qms/qms.go",
    "content": "package qms\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tsocketsLock = utils.NewMultiTimeoutLock(1 * time.Minute)\n)\n"
  },
  {
    "path": "qms/usb.go",
    "content": "package qms\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/permission\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc GetUsbDevices(vmId bson.ObjectID) (\n\tdevices []*vm.UsbDevice, err error) {\n\n\tsockPath, err := GetSockPath(vmId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlockId := socketsLock.Lock(vmId.Hex())\n\tdefer socketsLock.Unlock(vmId.Hex(), lockId)\n\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(3 * time.Second))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\tbuffer := []byte{}\n\tfor {\n\t\tbuf := make([]byte, 5000000)\n\t\tn, e := conn.Read(buf)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"qemu: Failed to read socket\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbuffer = append(buffer, buf[:n]...)\n\n\t\tif bytes.Contains(bytes.TrimSpace(buffer), []byte(\"(qemu)\")) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\t_, err = conn.Write([]byte(\"info usb\\n\"))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\tbuffer = []byte{}\n\tfor {\n\t\tbuf := make([]byte, 5000000)\n\t\tn, e := conn.Read(buf)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"qemu: Failed to read socket\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tbuffer = append(buffer, buf[:n]...)\n\n\t\tif bytes.Contains(bytes.TrimSpace(buffer), []byte(\"(qemu)\")) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tfor _, line := range strings.Split(string(buffer), \"\\n\") {\n\t\tline = strings.TrimSpace(line)\n\n\t\tif !strings.HasPrefix(line, \"Device\") || len(line) < 10 {\n\t\t\tcontinue\n\t\t}\n\t\tline = strings.Replace(line, \"\\r\", \"\", -1)\n\n\t\tif !strings.Contains(line, \"ID:\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tlineSpl := strings.Split(line, \"ID:\")\n\t\tif len(lineSpl) != 2 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\"line\":        line,\n\t\t\t}).Error(\"qemu: Unexpected qemu usb info\")\n\t\t\tcontinue\n\t\t}\n\n\t\tdeviceId := strings.Fields(lineSpl[1])[0]\n\n\t\tif strings.HasPrefix(deviceId, \"usb_\") {\n\t\t\tlineSpl = strings.Split(deviceId, \"_\")\n\t\t\tif len(lineSpl) != 5 && len(lineSpl) != 6 {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"instance_id\": vmId.Hex(),\n\t\t\t\t\t\"line\":        line,\n\t\t\t\t}).Error(\"qemu: Unexpected qemu usb id\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdevice := &vm.UsbDevice{\n\t\t\t\tId:      deviceId,\n\t\t\t\tBus:     lineSpl[1],\n\t\t\t\tAddress: lineSpl[2],\n\t\t\t\tVendor:  lineSpl[3],\n\t\t\t\tProduct: lineSpl[4],\n\t\t\t}\n\t\t\tdevices = append(devices, device)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc AddUsb(virt *vm.VirtualMachine, device *vm.UsbDevice) (err error) {\n\tsockPath, err := GetSockPath(virt.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tusbDevice, err := device.GetDevice()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif usbDevice == nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\"usb_vendor\":  device.Vendor,\n\t\t\t\"usb_product\": device.Product,\n\t\t\t\"usb_bus\":     device.Bus,\n\t\t\t\"usb_address\": device.Address,\n\t\t}).Warn(\"qemu: Failed to find usb device for attachment\")\n\t\treturn\n\t}\n\n\tif usbDevice.Bus == \"\" || usbDevice.Address == \"\" ||\n\t\tusbDevice.Vendor == \"\" || usbDevice.Product == \"\" {\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\"usb_name\":    usbDevice.Name,\n\t\t\t\"usb_vendor\":  usbDevice.Vendor,\n\t\t\t\"usb_product\": usbDevice.Product,\n\t\t\t\"usb_bus\":     usbDevice.Bus,\n\t\t\t\"usb_address\": usbDevice.Address,\n\t\t\t\"usb_path\":    usbDevice.BusPath,\n\t\t}).Warn(\"qemu: Failed to load usb device info for attachment\")\n\t\treturn\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\"usb_name\":    usbDevice.Name,\n\t\t\"usb_vendor\":  usbDevice.Vendor,\n\t\t\"usb_product\": usbDevice.Product,\n\t\t\"usb_bus\":     usbDevice.Bus,\n\t\t\"usb_address\": usbDevice.Address,\n\t\t\"usb_path\":    usbDevice.BusPath,\n\t}).Info(\"qemu: Connecting virtual machine usb\")\n\n\terr = permission.Chown(virt, usbDevice.BusPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlockId := socketsLock.Lock(virt.Id.Hex())\n\tdefer socketsLock.Unlock(virt.Id.Hex(), lockId)\n\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(5 * time.Second))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdeviceLine := fmt.Sprintf(\n\t\t\"usb-host,hostdevice=%s,id=%s\",\n\t\tusbDevice.BusPath,\n\t\tusbDevice.GetQemuId(),\n\t)\n\n\t_, err = conn.Write([]byte(\n\t\tfmt.Sprintf(\"device_add %s\\n\", deviceLine)))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\treturn\n}\n\nfunc RemoveUsb(virt *vm.VirtualMachine, device *vm.UsbDevice) (err error) {\n\tsockPath, err := GetSockPath(virt.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tusbDevice, err := device.GetDevice()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif usbDevice != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\"usb_id\":      device.Id,\n\t\t\t\"usb_name\":    usbDevice.Name,\n\t\t\t\"usb_vendor\":  usbDevice.Vendor,\n\t\t\t\"usb_product\": usbDevice.Product,\n\t\t\t\"usb_bus\":     usbDevice.Bus,\n\t\t\t\"usb_address\": usbDevice.Address,\n\t\t\t\"usb_path\":    usbDevice.BusPath,\n\t\t}).Info(\"qemu: Disconnecting active usb device\")\n\t} else {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"instance_id\": virt.Id.Hex(),\n\t\t\t\"usb_id\":      device.Id,\n\t\t\t\"usb_vendor\":  device.Vendor,\n\t\t\t\"usb_product\": device.Product,\n\t\t\t\"usb_bus\":     device.Bus,\n\t\t\t\"usb_address\": device.Address,\n\t\t}).Info(\"qemu: Disconnecting inactive usb device\")\n\t}\n\n\tlockId := socketsLock.Lock(virt.Id.Hex())\n\tdefer socketsLock.Unlock(virt.Id.Hex(), lockId)\n\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(5 * time.Second))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif device.Id != \"\" {\n\t\t_, err = conn.Write([]byte(\n\t\t\tfmt.Sprintf(\"device_del %s\\n\", device.Id)))\n\t\tif err != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\tif usbDevice != nil && usbDevice.BusPath != \"\" {\n\t\terr = permission.Restore(usbDevice.BusPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qms/utils.go",
    "content": "package qms\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\n// TODO Backward compatibility\nfunc GetSockPath(virtId bson.ObjectID) (pth string, err error) {\n\tsockPath := paths.GetSockPath(virtId)\n\tsockPathOld := paths.GetSockPathOld(virtId)\n\n\texists, err := utils.Exists(sockPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif exists {\n\t\tpth = sockPath\n\t} else {\n\t\tpth = sockPathOld\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "qms/vnc.go",
    "content": "package qms\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc VncPassword(vmId bson.ObjectID, passwd string) (err error) {\n\tsockPath, err := GetSockPath(vmId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlockId := socketsLock.Lock(vmId.Hex())\n\tdefer socketsLock.Unlock(vmId.Hex(), lockId)\n\n\tconn, err := net.DialTimeout(\n\t\t\"unix\",\n\t\tsockPath,\n\t\t3*time.Second,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to open socket\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetDeadline(time.Now().Add(5 * time.Second))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed set deadline\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = conn.Write(\n\t\t[]byte(fmt.Sprintf(\"change vnc password\\n%s\\n\", passwd)))\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"qemu: Failed to write socket\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttime.Sleep(800 * time.Millisecond)\n\n\treturn\n}\n"
  },
  {
    "path": "redirect/acme.go",
    "content": "package main\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype Challenge struct {\n\tToken    string `json:\"token\"`\n\tResponse string `json:\"response\"`\n}\n\nvar (\n\tchallenges     = map[string]*Challenge{}\n\tchallengesLock = sync.Mutex{}\n\tclearTimer     *time.Timer\n\ttimerLock      = sync.Mutex{}\n)\n\nfunc AddChallenge(chal *Challenge) {\n\ttimerLock.Lock()\n\tif clearTimer != nil {\n\t\tclearTimer.Stop()\n\t\tclearTimer = nil\n\t}\n\n\tclearTimer = time.AfterFunc(60*time.Second, func() {\n\t\tchallengesLock.Lock()\n\t\tchallenges = map[string]*Challenge{}\n\t\tchallengesLock.Unlock()\n\n\t\ttimerLock.Lock()\n\t\tclearTimer = nil\n\t\ttimerLock.Unlock()\n\t})\n\ttimerLock.Unlock()\n\n\tchallengesLock.Lock()\n\tchallenges[chal.Token] = chal\n\tchallengesLock.Unlock()\n}\n\nfunc GetChallenge(token string) (chal *Challenge) {\n\tchallengesLock.Lock()\n\tchal = challenges[token]\n\tchallengesLock.Unlock()\n\treturn\n}\n"
  },
  {
    "path": "redirect/crypto/crypto.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/sha512\"\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/pritunl/tools/errors\"\n\t\"github.com/pritunl/tools/errortypes\"\n\t\"golang.org/x/crypto/nacl/secretbox\"\n\t\"golang.org/x/crypto/nacl/sign\"\n)\n\ntype Message struct {\n\tNonce     string\n\tMessage   string\n\tSignature string\n}\n\ntype AsymNaclHmacKey struct {\n\tKey        string\n\tSecret     string\n\tPublicKey  string\n\tPrivateKey string\n}\n\ntype AsymNaclHmac struct {\n\tkey          *[32]byte\n\tsecret       *[32]byte\n\tpublicKey    *[32]byte\n\tprivateKey   *[64]byte\n\tnonceHandler func(nonce []byte) error\n}\n\nfunc (a *AsymNaclHmac) RegisterNonce(handler func(nonce []byte) error) {\n\ta.nonceHandler = handler\n}\n\nfunc (a *AsymNaclHmac) Seal(input any) (msg *Message, err error) {\n\tif a.key == nil || a.secret == nil || a.privateKey == nil {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"crypto: Private key and secret not loaded\"),\n\t\t}\n\t\treturn\n\t}\n\n\tnonce := new([24]byte)\n\t_, err = io.ReadFull(rand.Reader, nonce[:])\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to generate nonce\"),\n\t\t}\n\t\treturn\n\t}\n\tnonceStr := base64.StdEncoding.EncodeToString(nonce[:])\n\n\tdata, err := json.Marshal(input)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to marshal json data\"),\n\t\t}\n\t\treturn\n\t}\n\n\tencByt := secretbox.Seal(nil, data, nonce, a.key)\n\tsigEncByt := sign.Sign(nil, encByt, a.privateKey)\n\tsigEncStr := base64.StdEncoding.EncodeToString(sigEncByt)\n\n\thashFunc := hmac.New(sha512.New, a.secret[:])\n\thashFunc.Write([]byte(sigEncStr))\n\trawSignature := hashFunc.Sum(nil)\n\tsigStr := base64.StdEncoding.EncodeToString(rawSignature)\n\n\tmsg = &Message{\n\t\tNonce:     nonceStr,\n\t\tMessage:   sigEncStr,\n\t\tSignature: sigStr,\n\t}\n\n\treturn\n}\n\nfunc (a *AsymNaclHmac) SealJson(input any) (output string, err error) {\n\tmsg, err := a.Seal(input)\n\tif err != nil {\n\t\treturn\n\t}\n\n\toutputByt, err := json.Marshal(msg)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to marshal message\"),\n\t\t}\n\t\treturn\n\t}\n\n\toutput = string(outputByt)\n\treturn\n}\n\nfunc (a *AsymNaclHmac) Unseal(msg *Message, output any) (err error) {\n\tif a.key == nil || a.secret == nil || a.publicKey == nil {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"crypto: Private key and secret not loaded\"),\n\t\t}\n\t\treturn\n\t}\n\n\thashFunc := hmac.New(sha512.New, a.secret[:])\n\thashFunc.Write([]byte(msg.Message))\n\trawSignature := hashFunc.Sum(nil)\n\tsigStr := base64.StdEncoding.EncodeToString(rawSignature)\n\n\tif subtle.ConstantTimeCompare([]byte(sigStr), []byte(msg.Signature)) != 1 {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"crypto: Invalid message signature\"),\n\t\t}\n\t\treturn\n\t}\n\n\tnonceByt, err := base64.StdEncoding.DecodeString(msg.Nonce)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode nonce\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(nonceByt) != 24 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"crypto: Invalid nonce length\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif a.nonceHandler != nil {\n\t\terr = a.nonceHandler(nonceByt)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"crypto: Nonce validate failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tnonce := new([24]byte)\n\tcopy(nonce[:], nonceByt)\n\n\tsigEncByt, err := base64.StdEncoding.DecodeString(msg.Message)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode message\"),\n\t\t}\n\t\treturn\n\t}\n\n\tencByt, valid := sign.Open(nil, sigEncByt, a.publicKey)\n\tif !valid {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to verify message signature\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdecByt, ok := secretbox.Open(nil, encByt, nonce, a.key)\n\tif !ok {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"crypto: Failed to decrypt message\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = json.Unmarshal(decByt, output)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to unmarshal data\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (a *AsymNaclHmac) UnsealJson(input string, output any) (err error) {\n\tmsg := &Message{}\n\n\terr = json.Unmarshal([]byte(input), msg)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to unmarshal message\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = a.Unseal(msg, output)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (a *AsymNaclHmac) Export() AsymNaclHmacKey {\n\treturn AsymNaclHmacKey{\n\t\tKey:        base64.StdEncoding.EncodeToString(a.key[:]),\n\t\tSecret:     base64.StdEncoding.EncodeToString(a.secret[:]),\n\t\tPublicKey:  base64.StdEncoding.EncodeToString(a.publicKey[:]),\n\t\tPrivateKey: base64.StdEncoding.EncodeToString(a.privateKey[:]),\n\t}\n}\n\nfunc (a *AsymNaclHmac) Import(key AsymNaclHmacKey) (err error) {\n\tkeyByt, err := base64.StdEncoding.DecodeString(key.Key)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(keyByt) != 32 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"crypto: Invalid key length\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsecrByt, err := base64.StdEncoding.DecodeString(key.Secret)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode secret key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(secrByt) != 32 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"crypto: Invalid secret key length\"),\n\t\t}\n\t\treturn\n\t}\n\n\tpubKeyByt, err := base64.StdEncoding.DecodeString(\n\t\tkey.PublicKey)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to decode public key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(pubKeyByt) != 32 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"crypto: Invalid public key length\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif key.PrivateKey != \"\" {\n\t\tprivKeyByt, e := base64.StdEncoding.DecodeString(\n\t\t\tkey.PrivateKey)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"crypto: Failed to decode private key\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif len(privKeyByt) != 64 {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"crypto: Invalid private key length\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif a.privateKey == nil {\n\t\t\ta.privateKey = new([64]byte)\n\t\t}\n\t\tcopy(a.privateKey[:], privKeyByt)\n\t}\n\n\tif a.key == nil {\n\t\ta.key = new([32]byte)\n\t}\n\tif a.secret == nil {\n\t\ta.secret = new([32]byte)\n\t}\n\tif a.publicKey == nil {\n\t\ta.publicKey = new([32]byte)\n\t}\n\n\tcopy(a.key[:], keyByt)\n\tcopy(a.secret[:], secrByt)\n\tcopy(a.publicKey[:], pubKeyByt)\n\n\treturn\n}\n\nfunc (a *AsymNaclHmac) Generate() (err error) {\n\tkey := new([32]byte)\n\t_, err = io.ReadFull(rand.Reader, key[:])\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to generate key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsecKey := new([32]byte)\n\t_, err = io.ReadFull(rand.Reader, secKey[:])\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to generate secret key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsignPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"crypto: Failed to generate signing key\"),\n\t\t}\n\t\treturn\n\t}\n\n\ta.key = key\n\ta.secret = secKey\n\ta.publicKey = signPubKey\n\ta.privateKey = signPrivKey\n\n\treturn\n}\n"
  },
  {
    "path": "redirect/go.mod",
    "content": "module github.com/pritunl/pritunl-cloud/redirect\n\ngo 1.24.0\n\ntoolchain go1.24.6\n\nrequire (\n\tgithub.com/pritunl/tools v1.2.6\n\tgolang.org/x/crypto v0.45.0\n)\n\nrequire golang.org/x/sys v0.38.0 // indirect\n"
  },
  {
    "path": "redirect/go.sum",
    "content": "github.com/pritunl/tools v1.2.6 h1:rxqJjmLEHc/SLf8wjWpZX0J+2JoRO0ShbMZ/R19efY8=\ngithub.com/pritunl/tools v1.2.6/go.mod h1:BiNzTb2ZCesQ5k/Mx0mhOwGXNJNdZk+4jqg39GjRXKU=\ngolang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=\ngolang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\n"
  },
  {
    "path": "redirect/main.go",
    "content": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/redirect/crypto\"\n\t\"github.com/pritunl/tools/errors\"\n\t\"github.com/pritunl/tools/errortypes\"\n\t\"github.com/pritunl/tools/logger\"\n)\n\nfunc main() {\n\tpublicKey := os.Getenv(\"PUBLIC_KEY\")\n\tkey := os.Getenv(\"KEY\")\n\tsecret := os.Getenv(\"SECRET\")\n\tos.Unsetenv(\"PUBLIC_KEY\")\n\tos.Unsetenv(\"KEY\")\n\tos.Unsetenv(\"SECRET\")\n\n\tlogger.Init()\n\tlogger.AddHandler(func(record *logger.Record) {\n\t\tfmt.Print(record.String())\n\t})\n\n\terr := runServer(publicKey, key, secret)\n\tif err != nil {\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"redirect: Redirect server error\")\n\t\tos.Exit(1)\n\t}\n}\n\nfunc runServer(publicKey, key, secret string) (err error) {\n\twebPort, err := strconv.Atoi(os.Getenv(\"WEB_PORT\"))\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrapf(err, \"redirect: Failed to parse web port\"),\n\t\t}\n\t\treturn\n\t}\n\n\tnaclKey := crypto.AsymNaclHmacKey{\n\t\tPublicKey: publicKey,\n\t\tKey:       key,\n\t\tSecret:    secret,\n\t}\n\n\tbox := &crypto.AsymNaclHmac{}\n\n\terr = box.Import(naclKey)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlogger.WithFields(logger.Fields{\n\t\t\"port\":     80,\n\t\t\"web_port\": webPort,\n\t}).Info(\"redirect: Starting HTTP redirect server\")\n\n\tgo sandboxTest()\n\n\tfile := os.NewFile(uintptr(3), \"systemd-socket\")\n\tlistener, err := net.FileListener(file)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"redirect: Failed to get socket listener\"),\n\t\t}\n\t\treturn\n\t}\n\n\tserver := &http.Server{\n\t\tAddr:         \":80\",\n\t\tReadTimeout:  1 * time.Minute,\n\t\tWriteTimeout: 1 * time.Minute,\n\n\t\tHandler: http.HandlerFunc(func(\n\t\t\tw http.ResponseWriter, req *http.Request) {\n\n\t\t\tif req.Method == \"GET\" && strings.HasPrefix(req.URL.Path,\n\t\t\t\t\"/.well-known/acme-challenge/\") {\n\n\t\t\t\tpathSplit := strings.Split(req.URL.Path, \"/\")\n\t\t\t\ttoken := pathSplit[len(pathSplit)-1]\n\n\t\t\t\tchal := GetChallenge(token)\n\t\t\t\tif chal == nil {\n\t\t\t\t\tw.WriteHeader(404)\n\t\t\t\t\tfmt.Fprint(w, \"404 page not found\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tw.WriteHeader(200)\n\t\t\t\tfmt.Fprint(w, chal.Response)\n\t\t\t\treturn\n\t\t\t} else if req.Method == \"POST\" && req.URL.Path == \"/token\" {\n\t\t\t\tbodyBytes := make([]byte, 8096)\n\t\t\t\tn, err := io.LimitReader(req.Body, 8096).Read(bodyBytes)\n\t\t\t\tif err != nil && err != io.EOF {\n\t\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\t\tfmt.Fprint(w, \"Internal server error\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tbodyBytes = bodyBytes[:n]\n\n\t\t\t\tchal := &Challenge{}\n\t\t\t\terr = box.UnsealJson(string(bodyBytes), chal)\n\t\t\t\tif err != nil && err != io.EOF {\n\t\t\t\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\t\t\t\tfmt.Fprint(w, \"Failed to authorize\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tAddChallenge(chal)\n\n\t\t\t\tw.WriteHeader(200)\n\t\t\t\tfmt.Fprint(w, \"success\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treq.URL.Scheme = \"https\"\n\t\t\treq.URL.Host = StripPort(req.Host)\n\t\t\tif webPort != 443 {\n\t\t\t\treq.URL.Host += fmt.Sprintf(\":%d\", webPort)\n\t\t\t}\n\n\t\t\thttp.Redirect(w, req, req.URL.String(),\n\t\t\t\thttp.StatusMovedPermanently)\n\t\t}),\n\t}\n\n\terr = server.Serve(listener)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"redirect: Failed to bind web server\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc sandboxTest() {\n\ttime.Sleep(3 * time.Second)\n\n\tclient := &http.Client{\n\t\tTimeout: 3 * time.Second,\n\t\tTransport: &http.Transport{\n\t\t\tTLSHandshakeTimeout: 3 * time.Second,\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t},\n\t\t},\n\t}\n\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\t\"https://127.0.0.1\",\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"acme: Sandbox test request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := client.Do(req)\n\tif err == nil {\n\t\tlogger.WithFields(logger.Fields{\n\t\t\t\"status_code\": resp.StatusCode,\n\t\t}).Error(\"redirect: Sandbox escape test failed\")\n\t} else {\n\t\tlogger.Info(\"redirect: Sandbox escape test successful\")\n\t}\n}\n"
  },
  {
    "path": "redirect/utils.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n)\n\nfunc StripPort(hostport string) string {\n\tcolon := strings.IndexByte(hostport, ':')\n\tif colon == -1 {\n\t\treturn hostport\n\t}\n\n\tn := strings.Count(hostport, \":\")\n\tif n > 1 {\n\t\tif i := strings.IndexByte(hostport, ']'); i != -1 {\n\t\t\treturn strings.TrimPrefix(hostport[:i], \"[\")\n\t\t}\n\t\treturn hostport\n\t}\n\n\treturn hostport[:colon]\n}\n"
  },
  {
    "path": "relations/definitions/block.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Block = relations.Query{\n\tLabel:      \"Block\",\n\tCollection: \"blocks\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}, {\n\t\tKey:   \"subnets\",\n\t\tLabel: \"Subnets\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"blocks_ip\",\n\t\tLabel:        \"Block IP\",\n\t\tFrom:         \"blocks_ip\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"block\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"ip\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"ip\",\n\t\t\tLabel: \"IP\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\treturn utils.Int2IpAddress(vals[0].(int64)).String()\n\t\t\t},\n\t\t}, {\n\t\t\tKey: \"instance\",\n\t\t}},\n\t\tRelations: []relations.Relation{{\n\t\t\tKey:          \"instances\",\n\t\t\tLabel:        \"Instance\",\n\t\t\tFrom:         \"instances\",\n\t\t\tLocalField:   \"instance\",\n\t\t\tForeignField: \"_id\",\n\t\t\tSort: map[string]int{\n\t\t\t\t\"name\": 1,\n\t\t\t},\n\t\t\tProject: []relations.Project{{\n\t\t\t\tKey:   \"name\",\n\t\t\t\tLabel: \"Name\",\n\t\t\t}, {\n\t\t\t\tKeys: []string{\n\t\t\t\t\t\"action\",\n\t\t\t\t\t\"state\",\n\t\t\t\t},\n\t\t\t\tLabel: \"Status\",\n\t\t\t\tFormat: func(vals ...any) any {\n\t\t\t\t\taction, _ := vals[0].(string)\n\t\t\t\t\tstate, _ := vals[1].(string)\n\n\t\t\t\t\tswitch action {\n\t\t\t\t\tcase instance.Start:\n\t\t\t\t\t\tswitch state {\n\t\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\t\treturn \"Running\"\n\t\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t\tcase \"\":\n\t\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t\t}\n\t\t\t\t\tcase instance.Cleanup:\n\t\t\t\t\t\tswitch state {\n\t\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\tcase \"\":\n\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\t}\n\t\t\t\t\tcase instance.Stop:\n\t\t\t\t\t\tswitch state {\n\t\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\t\treturn \"Failed\"\n\t\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t\tcase \"\":\n\t\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t\t}\n\t\t\t\t\tcase instance.Restart:\n\t\t\t\t\t\treturn \"Restarting\"\n\t\t\t\t\tcase instance.Destroy:\n\t\t\t\t\t\treturn \"Destroying\"\n\t\t\t\t\t}\n\n\t\t\t\t\treturn state\n\t\t\t\t},\n\t\t\t}, {\n\t\t\t\tKeys: []string{\n\t\t\t\t\t\"timestamp\",\n\t\t\t\t\t\"action\",\n\t\t\t\t\t\"state\",\n\t\t\t\t},\n\t\t\t\tLabel: \"Uptime\",\n\t\t\t\tFormat: func(vals ...any) any {\n\t\t\t\t\tval := vals[0]\n\t\t\t\t\taction, _ := vals[1].(string)\n\t\t\t\t\tstate, _ := vals[2].(string)\n\t\t\t\t\tisActive := action == instance.Start ||\n\t\t\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\t\t\tstate == vm.Provisioning\n\n\t\t\t\t\tif !isActive {\n\t\t\t\t\t\treturn \"-\"\n\t\t\t\t\t}\n\n\t\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t\t}\n\n\t\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn \"-\"\n\t\t\t\t},\n\t\t\t}, {\n\t\t\t\tKey:   \"processors\",\n\t\t\t\tLabel: \"Processors\",\n\t\t\t}, {\n\t\t\t\tKey:   \"memory\",\n\t\t\t\tLabel: \"Memory\",\n\t\t\t}, {\n\t\t\t\tKey:   \"private_ips\",\n\t\t\t\tLabel: \"Private IPv4\",\n\t\t\t}, {\n\t\t\t\tKey:   \"public_ips\",\n\t\t\t\tLabel: \"Public IPv4\",\n\t\t\t}},\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"block\", Block)\n}\n"
  },
  {
    "path": "relations/definitions/certificate.go",
    "content": "package definitions\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n)\n\nvar Certificate = relations.Query{\n\tLabel:      \"Certificate\",\n\tCollection: \"certificates\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}, {\n\t\tKey:   \"roles\",\n\t\tLabel: \"Roles\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"nodes\",\n\t\tLabel:        \"Node\",\n\t\tFrom:         \"nodes\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"certificates\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"types\",\n\t\t\tLabel: \"Modes\",\n\t\t}, {\n\t\t\tKey:   \"admin_domain\",\n\t\t\tLabel: \"Admin Domain\",\n\t\t}, {\n\t\t\tKey:   \"user_domain\",\n\t\t\tLabel: \"User Domain\",\n\t\t}, {\n\t\t\tKey:   \"webauthn_domain\",\n\t\t\tLabel: \"WebAuthn Domain\",\n\t\t}, {\n\t\t\tKey:   \"network_mode\",\n\t\t\tLabel: \"Network Mode IPv4\",\n\t\t}, {\n\t\t\tKey:   \"network_mode6\",\n\t\t\tLabel: \"Network Mode IPv6\",\n\t\t}},\n\t}, {\n\t\tKey:          \"balancers\",\n\t\tLabel:        \"Load Balancer\",\n\t\tFrom:         \"balancers\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"certificates\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"state\",\n\t\t\tLabel: \"State\",\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"certificate\", Certificate)\n}\n"
  },
  {
    "path": "relations/definitions/datacenter.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Datacenter = relations.Query{\n\tLabel:      \"Datacenter\",\n\tCollection: \"datacenters\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"zones\",\n\t\tLabel:        \"Zone\",\n\t\tFrom:         \"zones\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"datacenter\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"nodes\",\n\t\tLabel:        \"Node\",\n\t\tFrom:         \"nodes\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"datacenter\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"types\",\n\t\t\tLabel: \"Modes\",\n\t\t}, {\n\t\t\tKey:   \"admin_domain\",\n\t\t\tLabel: \"Admin Domain\",\n\t\t}, {\n\t\t\tKey:   \"user_domain\",\n\t\t\tLabel: \"User Domain\",\n\t\t}, {\n\t\t\tKey:   \"webauthn_domain\",\n\t\t\tLabel: \"WebAuthn Domain\",\n\t\t}, {\n\t\t\tKey:   \"network_mode\",\n\t\t\tLabel: \"Network Mode IPv4\",\n\t\t}, {\n\t\t\tKey:   \"network_mode6\",\n\t\t\tLabel: \"Network Mode IPv6\",\n\t\t}},\n\t}, {\n\t\tKey:          \"vpcs\",\n\t\tLabel:        \"VPC\",\n\t\tFrom:         \"vpcs\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"datacenter\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"vpc_id\",\n\t\t\tLabel: \"VPC ID\",\n\t\t}, {\n\t\t\tKey:   \"network\",\n\t\t\tLabel: \"Network IPv4\",\n\t\t}},\n\t}, {\n\t\tKey:          \"balancers\",\n\t\tLabel:        \"Load Balancer\",\n\t\tFrom:         \"balancers\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"datacenter\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"state\",\n\t\t\tLabel: \"State\",\n\t\t}},\n\t}, {\n\t\tKey:          \"deployments\",\n\t\tLabel:        \"Deployment\",\n\t\tFrom:         \"deployments\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"datacenter\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"kind\",\n\t\t\tLabel: \"Kind\",\n\t\t}, {\n\t\t\tKey:   \"state\",\n\t\t\tLabel: \"State\",\n\t\t}, {\n\t\t\tKey:   \"status\",\n\t\t\tLabel: \"Status\",\n\t\t}, {\n\t\t\tKey:   \"timestamp\",\n\t\t\tLabel: \"Age\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\tval := vals[0]\n\n\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t}\n\n\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t}\n\n\t\t\t\treturn \"-\"\n\t\t\t},\n\t\t}},\n\t}, {\n\t\tKey:          \"instances\",\n\t\tLabel:        \"Instance\",\n\t\tFrom:         \"instances\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"datacenter\",\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Status\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\taction, _ := vals[0].(string)\n\t\t\t\tstate, _ := vals[1].(string)\n\n\t\t\t\tswitch action {\n\t\t\t\tcase instance.Start:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Running\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Cleanup:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Stop:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Failed\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Restart:\n\t\t\t\t\treturn \"Restarting\"\n\t\t\t\tcase instance.Destroy:\n\t\t\t\t\treturn \"Destroying\"\n\t\t\t\t}\n\n\t\t\t\treturn state\n\t\t\t},\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"timestamp\",\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Uptime\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\tval := vals[0]\n\t\t\t\taction, _ := vals[1].(string)\n\t\t\t\tstate, _ := vals[2].(string)\n\t\t\t\tisActive := action == instance.Start ||\n\t\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\t\tstate == vm.Provisioning\n\n\t\t\t\tif !isActive {\n\t\t\t\t\treturn \"-\"\n\t\t\t\t}\n\n\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t}\n\n\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t}\n\n\t\t\t\treturn \"-\"\n\t\t\t},\n\t\t}, {\n\t\t\tKey:   \"processors\",\n\t\t\tLabel: \"Processors\",\n\t\t}, {\n\t\t\tKey:   \"memory\",\n\t\t\tLabel: \"Memory\",\n\t\t}, {\n\t\t\tKey:   \"private_ips\",\n\t\t\tLabel: \"Private IPv4\",\n\t\t}, {\n\t\t\tKey:   \"public_ips\",\n\t\t\tLabel: \"Public IPv4\",\n\t\t}},\n\t}, {\n\t\tKey:          \"disks\",\n\t\tLabel:        \"Disk\",\n\t\tFrom:         \"disks\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"datacenter\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"index\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"index\",\n\t\t\tLabel: \"Index\",\n\t\t}, {\n\t\t\tKey:   \"size\",\n\t\t\tLabel: \"Size\",\n\t\t}},\n\t}, {\n\t\tKey:          \"nodeports\",\n\t\tLabel:        \"Nodeport\",\n\t\tFrom:         \"nodeports\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"datacenter\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"port\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"port\",\n\t\t\tLabel: \"Port\",\n\t\t}, {\n\t\t\tKey:   \"protocol\",\n\t\t\tLabel: \"Protocol\",\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"datacenter\", Datacenter)\n}\n"
  },
  {
    "path": "relations/definitions/definitions.go",
    "content": "package definitions\n\nfunc Init() {\n}\n"
  },
  {
    "path": "relations/definitions/firewall.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Firewall = relations.Query{\n\tLabel:      \"Firewall\",\n\tCollection: \"firewalls\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}, {\n\t\tKey:   \"roles\",\n\t\tLabel: \"Roles\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"instances\",\n\t\tLabel:        \"Instance\",\n\t\tFrom:         \"instances\",\n\t\tLocalField:   \"roles\",\n\t\tForeignField: \"roles\",\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Status\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\taction, _ := vals[0].(string)\n\t\t\t\tstate, _ := vals[1].(string)\n\n\t\t\t\tswitch action {\n\t\t\t\tcase instance.Start:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Running\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Cleanup:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Stop:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Failed\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Restart:\n\t\t\t\t\treturn \"Restarting\"\n\t\t\t\tcase instance.Destroy:\n\t\t\t\t\treturn \"Destroying\"\n\t\t\t\t}\n\n\t\t\t\treturn state\n\t\t\t},\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"timestamp\",\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Uptime\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\tval := vals[0]\n\t\t\t\taction, _ := vals[1].(string)\n\t\t\t\tstate, _ := vals[2].(string)\n\t\t\t\tisActive := action == instance.Start ||\n\t\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\t\tstate == vm.Provisioning\n\n\t\t\t\tif !isActive {\n\t\t\t\t\treturn \"-\"\n\t\t\t\t}\n\n\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t}\n\n\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t}\n\n\t\t\t\treturn \"-\"\n\t\t\t},\n\t\t}, {\n\t\t\tKey:   \"processors\",\n\t\t\tLabel: \"Processors\",\n\t\t}, {\n\t\t\tKey:   \"memory\",\n\t\t\tLabel: \"Memory\",\n\t\t}, {\n\t\t\tKey:   \"private_ips\",\n\t\t\tLabel: \"Private IPv4\",\n\t\t}, {\n\t\t\tKey:   \"public_ips\",\n\t\t\tLabel: \"Public IPv4\",\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"firewall\", Firewall)\n}\n"
  },
  {
    "path": "relations/definitions/instance.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Instance = relations.Query{\n\tLabel:      \"Instance\",\n\tCollection: \"instances\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}, {\n\t\tKey:   \"roles\",\n\t\tLabel: \"Roles\",\n\t}, {\n\t\tKey: \"node\",\n\t}, {\n\t\tKeys: []string{\n\t\t\t\"action\",\n\t\t\t\"state\",\n\t\t},\n\t\tLabel: \"Status\",\n\t\tFormat: func(vals ...any) any {\n\t\t\taction, _ := vals[0].(string)\n\t\t\tstate, _ := vals[1].(string)\n\n\t\t\tswitch action {\n\t\t\tcase instance.Start:\n\t\t\t\tswitch state {\n\t\t\t\tcase vm.Starting:\n\t\t\t\t\treturn \"Starting\"\n\t\t\t\tcase vm.Running:\n\t\t\t\t\treturn \"Running\"\n\t\t\t\tcase vm.Stopped:\n\t\t\t\t\treturn \"Starting\"\n\t\t\t\tcase vm.Failed:\n\t\t\t\t\treturn \"Starting\"\n\t\t\t\tcase vm.Updating:\n\t\t\t\t\treturn \"Updating\"\n\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\tcase \"\":\n\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t}\n\t\t\tcase instance.Cleanup:\n\t\t\t\tswitch state {\n\t\t\t\tcase vm.Starting:\n\t\t\t\t\treturn \"Stopping\"\n\t\t\t\tcase vm.Running:\n\t\t\t\t\treturn \"Stopping\"\n\t\t\t\tcase vm.Stopped:\n\t\t\t\t\treturn \"Stopping\"\n\t\t\t\tcase vm.Failed:\n\t\t\t\t\treturn \"Stopping\"\n\t\t\t\tcase vm.Updating:\n\t\t\t\t\treturn \"Updating\"\n\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\treturn \"Stopping\"\n\t\t\t\tcase \"\":\n\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t}\n\t\t\tcase instance.Stop:\n\t\t\t\tswitch state {\n\t\t\t\tcase vm.Starting:\n\t\t\t\t\treturn \"Stopping\"\n\t\t\t\tcase vm.Running:\n\t\t\t\t\treturn \"Stopping\"\n\t\t\t\tcase vm.Stopped:\n\t\t\t\t\treturn \"Stopped\"\n\t\t\t\tcase vm.Failed:\n\t\t\t\t\treturn \"Failed\"\n\t\t\t\tcase vm.Updating:\n\t\t\t\t\treturn \"Updating\"\n\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\treturn \"Stopped\"\n\t\t\t\tcase \"\":\n\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t}\n\t\t\tcase instance.Restart:\n\t\t\t\treturn \"Restarting\"\n\t\t\tcase instance.Destroy:\n\t\t\t\treturn \"Destroying\"\n\t\t\t}\n\n\t\t\treturn state\n\t\t},\n\t}, {\n\t\tKeys: []string{\n\t\t\t\"timestamp\",\n\t\t\t\"action\",\n\t\t\t\"state\",\n\t\t},\n\t\tLabel: \"Uptime\",\n\t\tFormat: func(vals ...any) any {\n\t\t\tval := vals[0]\n\t\t\taction, _ := vals[1].(string)\n\t\t\tstate, _ := vals[2].(string)\n\t\t\tisActive := action == instance.Start ||\n\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\tstate == vm.Provisioning\n\n\t\t\tif !isActive {\n\t\t\t\treturn \"-\"\n\t\t\t}\n\n\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t}\n\n\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t}\n\n\t\t\treturn \"-\"\n\t\t},\n\t}, {\n\t\tKey:   \"processors\",\n\t\tLabel: \"Processors\",\n\t}, {\n\t\tKey:   \"memory\",\n\t\tLabel: \"Memory\",\n\t}, {\n\t\tKey:   \"private_ips\",\n\t\tLabel: \"Private IPv4\",\n\t}, {\n\t\tKey:   \"public_ips\",\n\t\tLabel: \"Public IPv4\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"nodes\",\n\t\tLabel:        \"Node\",\n\t\tFrom:         \"nodes\",\n\t\tLocalField:   \"node\",\n\t\tForeignField: \"_id\",\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"types\",\n\t\t\tLabel: \"Modes\",\n\t\t}, {\n\t\t\tKey:   \"network_mode\",\n\t\t\tLabel: \"Network Mode IPv4\",\n\t\t}, {\n\t\t\tKey:   \"network_mode6\",\n\t\t\tLabel: \"Network Mode IPv6\",\n\t\t}},\n\t}, {\n\t\tKey:          \"disks\",\n\t\tLabel:        \"Disk\",\n\t\tFrom:         \"disks\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"instance\",\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"type\",\n\t\t\tLabel: \"Type\",\n\t\t}, {\n\t\t\tKey:   \"state\",\n\t\t\tLabel: \"State\",\n\t\t}, {\n\t\t\tKey:   \"size\",\n\t\t\tLabel: \"Size\",\n\t\t}},\n\t},\n\t// {\n\t// TODO Match organization\n\t// \tKey:          \"firewalls\",\n\t// \tLabel:        \"Firewall\",\n\t// \tFrom:         \"firewalls\",\n\t// \tLocalField:   \"roles\",\n\t// \tForeignField: \"roles\",\n\t// \tSort: map[string]int{\n\t// \t\t\"name\": 1,\n\t// \t},\n\t// \tProject: []relations.Project{{\n\t// \t\tKey:   \"name\",\n\t// \t\tLabel: \"Name\",\n\t// \t}, {\n\t// \t\tKey:   \"roles\",\n\t// \t\tLabel: \"Roles\",\n\t// \t}, {\n\t// \t\tKey:   \"ingress\",\n\t// \t\tLabel: \"Ingress\",\n\t// \t\tFormat: func(vals ...any) any {\n\t// \t\t\trules := vals[0].(bson.A)\n\t// \t\t\trulesStr := []string{}\n\n\t// \t\t\tfor _, ruleInf := range rules {\n\t// \t\t\t\trule := ruleInf.(primitive.M)\n\t// \t\t\t\truleStr := \"\"\n\n\t// \t\t\t\tprotocol := rule[\"protocol\"].(string)\n\t// \t\t\t\tport := rule[\"port\"].(string)\n\t// \t\t\t\tsourceIps := rule[\"source_ips\"].(bson.A)\n\n\t// \t\t\t\tswitch protocol {\n\t// \t\t\t\tcase firewall.All, firewall.Icmp:\n\t// \t\t\t\t\truleStr = protocol\n\t// \t\t\t\tdefault:\n\t// \t\t\t\t\truleStr = port + \"/\" + protocol\n\t// \t\t\t\t}\n\n\t// \t\t\t\truleStr += \" (\"\n\t// \t\t\t\tsourceIpsLen := len(sourceIps)\n\t// \t\t\t\tfor i, sourceIp := range sourceIps {\n\t// \t\t\t\t\truleStr += sourceIp.(string)\n\t// \t\t\t\t\tif i+1 < sourceIpsLen {\n\t// \t\t\t\t\t\truleStr += \", \"\n\t// \t\t\t\t\t}\n\t// \t\t\t\t}\n\t// \t\t\t\truleStr += \")\"\n\n\t// \t\t\t\trulesStr = append(rulesStr, ruleStr)\n\t// \t\t\t}\n\n\t// \t\t\treturn rulesStr\n\t// \t\t},\n\t// \t}},\n\t// }\n\t},\n}\n\nfunc init() {\n\trelations.Register(\"instance\", Instance)\n}\n"
  },
  {
    "path": "relations/definitions/node.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Node = relations.Query{\n\tLabel:      \"Node\",\n\tCollection: \"nodes\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"instances\",\n\t\tLabel:        \"Instance\",\n\t\tFrom:         \"instances\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"node\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Status\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\taction, _ := vals[0].(string)\n\t\t\t\tstate, _ := vals[1].(string)\n\n\t\t\t\tswitch action {\n\t\t\t\tcase instance.Start:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Running\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Cleanup:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Stop:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Failed\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Restart:\n\t\t\t\t\treturn \"Restarting\"\n\t\t\t\tcase instance.Destroy:\n\t\t\t\t\treturn \"Destroying\"\n\t\t\t\t}\n\n\t\t\t\treturn state\n\t\t\t},\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"timestamp\",\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Uptime\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\tval := vals[0]\n\t\t\t\taction, _ := vals[1].(string)\n\t\t\t\tstate, _ := vals[2].(string)\n\t\t\t\tisActive := action == instance.Start ||\n\t\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\t\tstate == vm.Provisioning\n\n\t\t\t\tif !isActive {\n\t\t\t\t\treturn \"-\"\n\t\t\t\t}\n\n\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t}\n\n\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t}\n\n\t\t\t\treturn \"-\"\n\t\t\t},\n\t\t}},\n\t}, {\n\t\tKey:          \"disks\",\n\t\tLabel:        \"Disk\",\n\t\tFrom:         \"disks\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"node\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"type\",\n\t\t\tLabel: \"Type\",\n\t\t}, {\n\t\t\tKey:   \"state\",\n\t\t\tLabel: \"State\",\n\t\t}, {\n\t\t\tKey:   \"size\",\n\t\t\tLabel: \"Size\",\n\t\t}},\n\t},\n\t// {\n\t// TODO Match organization\n\t// \tKey:          \"firewalls\",\n\t// \tLabel:        \"Firewall\",\n\t// \tFrom:         \"firewalls\",\n\t// \tLocalField:   \"roles\",\n\t// \tForeignField: \"roles\",\n\t// \tSort: map[string]int{\n\t// \t\t\"name\": 1,\n\t// \t},\n\t// \tProject: []relations.Project{{\n\t// \t\tKey:   \"name\",\n\t// \t\tLabel: \"Name\",\n\t// \t}, {\n\t// \t\tKey:   \"roles\",\n\t// \t\tLabel: \"Roles\",\n\t// \t}, {\n\t// \t\tKey:   \"ingress\",\n\t// \t\tLabel: \"Ingress\",\n\t// \t\tFormat: func(vals ...any) any {\n\t// \t\t\trules := vals[0].(bson.A)\n\t// \t\t\trulesStr := []string{}\n\n\t// \t\t\tfor _, ruleInf := range rules {\n\t// \t\t\t\trule := ruleInf.(primitive.M)\n\t// \t\t\t\truleStr := \"\"\n\n\t// \t\t\t\tprotocol := rule[\"protocol\"].(string)\n\t// \t\t\t\tport := rule[\"port\"].(string)\n\t// \t\t\t\tsourceIps := rule[\"source_ips\"].(bson.A)\n\n\t// \t\t\t\tswitch protocol {\n\t// \t\t\t\tcase firewall.All, firewall.Icmp:\n\t// \t\t\t\t\truleStr = protocol\n\t// \t\t\t\tdefault:\n\t// \t\t\t\t\truleStr = port + \"/\" + protocol\n\t// \t\t\t\t}\n\n\t// \t\t\t\truleStr += \" (\"\n\t// \t\t\t\tsourceIpsLen := len(sourceIps)\n\t// \t\t\t\tfor i, sourceIp := range sourceIps {\n\t// \t\t\t\t\truleStr += sourceIp.(string)\n\t// \t\t\t\t\tif i+1 < sourceIpsLen {\n\t// \t\t\t\t\t\truleStr += \", \"\n\t// \t\t\t\t\t}\n\t// \t\t\t\t}\n\t// \t\t\t\truleStr += \")\"\n\n\t// \t\t\t\trulesStr = append(rulesStr, ruleStr)\n\t// \t\t\t}\n\n\t// \t\t\treturn rulesStr\n\t// \t\t},\n\t// \t}},\n\t// }\n\t},\n}\n\nfunc init() {\n\trelations.Register(\"node\", Node)\n}\n"
  },
  {
    "path": "relations/definitions/organization.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Organization = relations.Query{\n\tLabel:      \"Organization\",\n\tCollection: \"organizations\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}, {\n\t\tKey:   \"roles\",\n\t\tLabel: \"Roles\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"certificates\",\n\t\tLabel:        \"Certificate\",\n\t\tFrom:         \"certificates\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"secrets\",\n\t\tLabel:        \"Secret\",\n\t\tFrom:         \"secrets\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"vpc\",\n\t\tLabel:        \"VPCs\",\n\t\tFrom:         \"vpc\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"domains\",\n\t\tLabel:        \"Domain\",\n\t\tFrom:         \"domains\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"balancers\",\n\t\tLabel:        \"Load Balancer\",\n\t\tFrom:         \"balancers\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"images\",\n\t\tLabel:        \"Image\",\n\t\tFrom:         \"images\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"plans\",\n\t\tLabel:        \"Plan\",\n\t\tFrom:         \"plans\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"disks\",\n\t\tLabel:        \"Disk\",\n\t\tFrom:         \"disks\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"size\",\n\t\t\tLabel: \"Size\",\n\t\t}},\n\t}, {\n\t\tKey:          \"instances\",\n\t\tLabel:        \"Instance\",\n\t\tFrom:         \"instances\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Status\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\taction, _ := vals[0].(string)\n\t\t\t\tstate, _ := vals[1].(string)\n\n\t\t\t\tswitch action {\n\t\t\t\tcase instance.Start:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Running\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Cleanup:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Stop:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Failed\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Restart:\n\t\t\t\t\treturn \"Restarting\"\n\t\t\t\tcase instance.Destroy:\n\t\t\t\t\treturn \"Destroying\"\n\t\t\t\t}\n\n\t\t\t\treturn state\n\t\t\t},\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"timestamp\",\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Uptime\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\tval := vals[0]\n\t\t\t\taction, _ := vals[1].(string)\n\t\t\t\tstate, _ := vals[2].(string)\n\t\t\t\tisActive := action == instance.Start ||\n\t\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\t\tstate == vm.Provisioning\n\n\t\t\t\tif !isActive {\n\t\t\t\t\treturn \"-\"\n\t\t\t\t}\n\n\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t}\n\n\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t}\n\n\t\t\t\treturn \"-\"\n\t\t\t},\n\t\t}, {\n\t\t\tKey:   \"processors\",\n\t\t\tLabel: \"Processors\",\n\t\t}, {\n\t\t\tKey:   \"memory\",\n\t\t\tLabel: \"Memory\",\n\t\t}, {\n\t\t\tKey:   \"private_ips\",\n\t\t\tLabel: \"Private IPv4\",\n\t\t}, {\n\t\t\tKey:   \"public_ips\",\n\t\t\tLabel: \"Public IPv4\",\n\t\t}},\n\t}, {\n\t\tKey:          \"pods\",\n\t\tLabel:        \"Pod\",\n\t\tFrom:         \"pods\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"deployments\",\n\t\tLabel:        \"Deployment\",\n\t\tFrom:         \"deployments\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"firewalls\",\n\t\tLabel:        \"Firewall\",\n\t\tFrom:         \"firewalls\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"authorities\",\n\t\tLabel:        \"Authority\",\n\t\tFrom:         \"authorities\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}, {\n\t\tKey:          \"alerts\",\n\t\tLabel:        \"Alert\",\n\t\tFrom:         \"alerts\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"organization\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"organization\", Organization)\n}\n"
  },
  {
    "path": "relations/definitions/pod.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Pod = relations.Query{\n\tLabel:      \"Pod\",\n\tCollection: \"pods\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"units\",\n\t\tLabel:        \"Unit\",\n\t\tFrom:         \"units\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"pod\",\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"kind\",\n\t\t\tLabel: \"Kind\",\n\t\t}, {\n\t\t\tKey:   \"count\",\n\t\t\tLabel: \"Count\",\n\t\t}},\n\t\tRelations: []relations.Relation{{\n\t\t\tKey:          \"deployments\",\n\t\t\tLabel:        \"Deployment\",\n\t\t\tFrom:         \"deployments\",\n\t\t\tLocalField:   \"_id\",\n\t\t\tForeignField: \"unit\",\n\t\t\tBlockDelete:  true,\n\t\t\tSort: map[string]int{\n\t\t\t\t\"name\": 1,\n\t\t\t},\n\t\t\tProject: []relations.Project{{\n\t\t\t\tKey:   \"name\",\n\t\t\t\tLabel: \"Name\",\n\t\t\t}, {\n\t\t\t\tKey:   \"kind\",\n\t\t\t\tLabel: \"Kind\",\n\t\t\t}, {\n\t\t\t\tKey:   \"state\",\n\t\t\t\tLabel: \"State\",\n\t\t\t}, {\n\t\t\t\tKey:   \"status\",\n\t\t\t\tLabel: \"Status\",\n\t\t\t}, {\n\t\t\t\tKey:   \"timestamp\",\n\t\t\t\tLabel: \"Age\",\n\t\t\t\tFormat: func(vals ...any) any {\n\t\t\t\t\tval := vals[0]\n\n\t\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t\t}\n\n\t\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn \"-\"\n\t\t\t\t},\n\t\t\t}},\n\t\t\tRelations: []relations.Relation{{\n\t\t\t\tKey:          \"instances\",\n\t\t\t\tLabel:        \"Instance\",\n\t\t\t\tFrom:         \"instances\",\n\t\t\t\tLocalField:   \"_id\",\n\t\t\t\tForeignField: \"deployment\",\n\t\t\t\tBlockDelete:  true,\n\t\t\t\tSort: map[string]int{\n\t\t\t\t\t\"name\": 1,\n\t\t\t\t},\n\t\t\t\tProject: []relations.Project{{\n\t\t\t\t\tKey:   \"name\",\n\t\t\t\t\tLabel: \"Name\",\n\t\t\t\t}, {\n\t\t\t\t\tKeys: []string{\n\t\t\t\t\t\t\"action\",\n\t\t\t\t\t\t\"state\",\n\t\t\t\t\t},\n\t\t\t\t\tLabel: \"Status\",\n\t\t\t\t\tFormat: func(vals ...any) any {\n\t\t\t\t\t\taction, _ := vals[0].(string)\n\t\t\t\t\t\tstate, _ := vals[1].(string)\n\n\t\t\t\t\t\tswitch action {\n\t\t\t\t\t\tcase instance.Start:\n\t\t\t\t\t\t\tswitch state {\n\t\t\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\t\t\treturn \"Running\"\n\t\t\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t\t\tcase \"\":\n\t\t\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase instance.Cleanup:\n\t\t\t\t\t\t\tswitch state {\n\t\t\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\t\tcase \"\":\n\t\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase instance.Stop:\n\t\t\t\t\t\t\tswitch state {\n\t\t\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\t\t\treturn \"Failed\"\n\t\t\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t\t\tcase \"\":\n\t\t\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase instance.Restart:\n\t\t\t\t\t\t\treturn \"Restarting\"\n\t\t\t\t\t\tcase instance.Destroy:\n\t\t\t\t\t\t\treturn \"Destroying\"\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn state\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tKeys: []string{\n\t\t\t\t\t\t\"timestamp\",\n\t\t\t\t\t\t\"action\",\n\t\t\t\t\t\t\"state\",\n\t\t\t\t\t},\n\t\t\t\t\tLabel: \"Uptime\",\n\t\t\t\t\tFormat: func(vals ...any) any {\n\t\t\t\t\t\tval := vals[0]\n\t\t\t\t\t\taction, _ := vals[1].(string)\n\t\t\t\t\t\tstate, _ := vals[2].(string)\n\t\t\t\t\t\tisActive := action == instance.Start ||\n\t\t\t\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\t\t\t\tstate == vm.Provisioning\n\n\t\t\t\t\t\tif !isActive {\n\t\t\t\t\t\t\treturn \"-\"\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn \"-\"\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tKey:   \"processors\",\n\t\t\t\t\tLabel: \"Processors\",\n\t\t\t\t}, {\n\t\t\t\t\tKey:   \"memory\",\n\t\t\t\t\tLabel: \"Memory\",\n\t\t\t\t}, {\n\t\t\t\t\tKey:   \"private_ips\",\n\t\t\t\t\tLabel: \"Private IPv4\",\n\t\t\t\t}, {\n\t\t\t\t\tKey:   \"public_ips\",\n\t\t\t\t\tLabel: \"Public IPv4\",\n\t\t\t\t}},\n\t\t\t}, {\n\t\t\t\tKey:          \"disks\",\n\t\t\t\tLabel:        \"Disk\",\n\t\t\t\tFrom:         \"disks\",\n\t\t\t\tLocalField:   \"_id\",\n\t\t\t\tForeignField: \"deployment\",\n\t\t\t\tSort: map[string]int{\n\t\t\t\t\t\"index\": 1,\n\t\t\t\t},\n\t\t\t\tProject: []relations.Project{{\n\t\t\t\t\tKey:   \"name\",\n\t\t\t\t\tLabel: \"Name\",\n\t\t\t\t}, {\n\t\t\t\t\tKey:   \"index\",\n\t\t\t\t\tLabel: \"Index\",\n\t\t\t\t}, {\n\t\t\t\t\tKey:   \"size\",\n\t\t\t\t\tLabel: \"Size\",\n\t\t\t\t}},\n\t\t\t}},\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"pod\", Pod)\n}\n"
  },
  {
    "path": "relations/definitions/policy.go",
    "content": "package definitions\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n)\n\nvar Policy = relations.Query{\n\tLabel:      \"Policy\",\n\tCollection: \"policies\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}, {\n\t\tKey:   \"roles\",\n\t\tLabel: \"Roles\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"users\",\n\t\tLabel:        \"User\",\n\t\tFrom:         \"users\",\n\t\tLocalField:   \"roles\",\n\t\tForeignField: \"roles\",\n\t\tSort: map[string]int{\n\t\t\t\"username\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"username\",\n\t\t\tLabel: \"Username\",\n\t\t}, {\n\t\t\tKey:   \"type\",\n\t\t\tLabel: \"Type\",\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"policy\", Policy)\n}\n"
  },
  {
    "path": "relations/definitions/secret.go",
    "content": "package definitions\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n)\n\nvar Secret = relations.Query{\n\tLabel:      \"Secret\",\n\tCollection: \"secrets\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"domains\",\n\t\tLabel:        \"Domain\",\n\t\tFrom:         \"domains\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"secret\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"root_domain\",\n\t\t\tLabel: \"Root Domain\",\n\t\t}},\n\t}, {\n\t\tKey:          \"certificates\",\n\t\tLabel:        \"Certificate\",\n\t\tFrom:         \"certificates\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"acme_secret\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"type\",\n\t\t\tLabel: \"Type\",\n\t\t}, {\n\t\t\tKey:   \"acme_domains\",\n\t\t\tLabel: \"Lets Encrypt Domains\",\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"secret\", Secret)\n}\n"
  },
  {
    "path": "relations/definitions/shape.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Shape = relations.Query{\n\tLabel:      \"Shape\",\n\tCollection: \"shapes\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}, {\n\t\tKey:   \"roles\",\n\t\tLabel: \"Roles\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"nodes\",\n\t\tLabel:        \"Node\",\n\t\tFrom:         \"nodes\",\n\t\tLocalField:   \"roles\",\n\t\tForeignField: \"roles\",\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"types\",\n\t\t\tLabel: \"Modes\",\n\t\t}, {\n\t\t\tKey:   \"admin_domain\",\n\t\t\tLabel: \"Admin Domain\",\n\t\t}, {\n\t\t\tKey:   \"user_domain\",\n\t\t\tLabel: \"User Domain\",\n\t\t}, {\n\t\t\tKey:   \"webauthn_domain\",\n\t\t\tLabel: \"WebAuthn Domain\",\n\t\t}, {\n\t\t\tKey:   \"network_mode\",\n\t\t\tLabel: \"Network Mode IPv4\",\n\t\t}, {\n\t\t\tKey:   \"network_mode6\",\n\t\t\tLabel: \"Network Mode IPv6\",\n\t\t}},\n\t}, {\n\t\tKey:          \"instances\",\n\t\tLabel:        \"Instance\",\n\t\tFrom:         \"instances\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"shape\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Status\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\taction, _ := vals[0].(string)\n\t\t\t\tstate, _ := vals[1].(string)\n\n\t\t\t\tswitch action {\n\t\t\t\tcase instance.Start:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Running\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Cleanup:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Stop:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Failed\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Restart:\n\t\t\t\t\treturn \"Restarting\"\n\t\t\t\tcase instance.Destroy:\n\t\t\t\t\treturn \"Destroying\"\n\t\t\t\t}\n\n\t\t\t\treturn state\n\t\t\t},\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"timestamp\",\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Uptime\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\tval := vals[0]\n\t\t\t\taction, _ := vals[1].(string)\n\t\t\t\tstate, _ := vals[2].(string)\n\t\t\t\tisActive := action == instance.Start ||\n\t\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\t\tstate == vm.Provisioning\n\n\t\t\t\tif !isActive {\n\t\t\t\t\treturn \"-\"\n\t\t\t\t}\n\n\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t}\n\n\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t}\n\n\t\t\t\treturn \"-\"\n\t\t\t},\n\t\t}, {\n\t\t\tKey:   \"processors\",\n\t\t\tLabel: \"Processors\",\n\t\t}, {\n\t\t\tKey:   \"memory\",\n\t\t\tLabel: \"Memory\",\n\t\t}, {\n\t\t\tKey:   \"private_ips\",\n\t\t\tLabel: \"Private IPv4\",\n\t\t}, {\n\t\t\tKey:   \"public_ips\",\n\t\t\tLabel: \"Public IPv4\",\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"shape\", Shape)\n}\n"
  },
  {
    "path": "relations/definitions/vpc.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Vpc = relations.Query{\n\tLabel:      \"VPC\",\n\tCollection: \"vpcs\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}, {\n\t\tKey:   \"roles\",\n\t\tLabel: \"Roles\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"instances\",\n\t\tLabel:        \"Instance\",\n\t\tFrom:         \"instances\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"vpc\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Status\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\taction, _ := vals[0].(string)\n\t\t\t\tstate, _ := vals[1].(string)\n\n\t\t\t\tswitch action {\n\t\t\t\tcase instance.Start:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Running\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Cleanup:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Stop:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Failed\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Restart:\n\t\t\t\t\treturn \"Restarting\"\n\t\t\t\tcase instance.Destroy:\n\t\t\t\t\treturn \"Destroying\"\n\t\t\t\t}\n\n\t\t\t\treturn state\n\t\t\t},\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"timestamp\",\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Uptime\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\tval := vals[0]\n\t\t\t\taction, _ := vals[1].(string)\n\t\t\t\tstate, _ := vals[2].(string)\n\t\t\t\tisActive := action == instance.Start ||\n\t\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\t\tstate == vm.Provisioning\n\n\t\t\t\tif !isActive {\n\t\t\t\t\treturn \"-\"\n\t\t\t\t}\n\n\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t}\n\n\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t}\n\n\t\t\t\treturn \"-\"\n\t\t\t},\n\t\t}, {\n\t\t\tKey:   \"processors\",\n\t\t\tLabel: \"Processors\",\n\t\t}, {\n\t\t\tKey:   \"memory\",\n\t\t\tLabel: \"Memory\",\n\t\t}, {\n\t\t\tKey:   \"private_ips\",\n\t\t\tLabel: \"Private IPv4\",\n\t\t}, {\n\t\t\tKey:   \"public_ips\",\n\t\t\tLabel: \"Public IPv4\",\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"vpc\", Vpc)\n}\n"
  },
  {
    "path": "relations/definitions/zone.go",
    "content": "package definitions\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar Zone = relations.Query{\n\tLabel:      \"Zone\",\n\tCollection: \"zones\",\n\tProject: []relations.Project{{\n\t\tKey:   \"name\",\n\t\tLabel: \"Name\",\n\t}, {\n\t\tKey:   \"roles\",\n\t\tLabel: \"Roles\",\n\t}},\n\tRelations: []relations.Relation{{\n\t\tKey:          \"nodes\",\n\t\tLabel:        \"Node\",\n\t\tFrom:         \"nodes\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"zone\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"types\",\n\t\t\tLabel: \"Modes\",\n\t\t}, {\n\t\t\tKey:   \"admin_domain\",\n\t\t\tLabel: \"Admin Domain\",\n\t\t}, {\n\t\t\tKey:   \"user_domain\",\n\t\t\tLabel: \"User Domain\",\n\t\t}, {\n\t\t\tKey:   \"webauthn_domain\",\n\t\t\tLabel: \"WebAuthn Domain\",\n\t\t}, {\n\t\t\tKey:   \"network_mode\",\n\t\t\tLabel: \"Network Mode IPv4\",\n\t\t}, {\n\t\t\tKey:   \"network_mode6\",\n\t\t\tLabel: \"Network Mode IPv6\",\n\t\t}},\n\t}, {\n\t\tKey:          \"deployments\",\n\t\tLabel:        \"Deployment\",\n\t\tFrom:         \"deployments\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"zone\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"kind\",\n\t\t\tLabel: \"Kind\",\n\t\t}, {\n\t\t\tKey:   \"state\",\n\t\t\tLabel: \"State\",\n\t\t}, {\n\t\t\tKey:   \"status\",\n\t\t\tLabel: \"Status\",\n\t\t}, {\n\t\t\tKey:   \"timestamp\",\n\t\t\tLabel: \"Age\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\tval := vals[0]\n\n\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t}\n\n\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t}\n\n\t\t\t\treturn \"-\"\n\t\t\t},\n\t\t}},\n\t}, {\n\t\tKey:          \"instances\",\n\t\tLabel:        \"Instance\",\n\t\tFrom:         \"instances\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"zone\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"name\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Status\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\taction, _ := vals[0].(string)\n\t\t\t\tstate, _ := vals[1].(string)\n\n\t\t\t\tswitch action {\n\t\t\t\tcase instance.Start:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Running\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Starting\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Provisioning\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Cleanup:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Stop:\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase vm.Starting:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Running:\n\t\t\t\t\t\treturn \"Stopping\"\n\t\t\t\t\tcase vm.Stopped:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase vm.Failed:\n\t\t\t\t\t\treturn \"Failed\"\n\t\t\t\t\tcase vm.Updating:\n\t\t\t\t\t\treturn \"Updating\"\n\t\t\t\t\tcase vm.Provisioning:\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\tcase \"\":\n\t\t\t\t\t\treturn \"Stopped\"\n\t\t\t\t\t}\n\t\t\t\tcase instance.Restart:\n\t\t\t\t\treturn \"Restarting\"\n\t\t\t\tcase instance.Destroy:\n\t\t\t\t\treturn \"Destroying\"\n\t\t\t\t}\n\n\t\t\t\treturn state\n\t\t\t},\n\t\t}, {\n\t\t\tKeys: []string{\n\t\t\t\t\"timestamp\",\n\t\t\t\t\"action\",\n\t\t\t\t\"state\",\n\t\t\t},\n\t\t\tLabel: \"Uptime\",\n\t\t\tFormat: func(vals ...any) any {\n\t\t\t\tval := vals[0]\n\t\t\t\taction, _ := vals[1].(string)\n\t\t\t\tstate, _ := vals[2].(string)\n\t\t\t\tisActive := action == instance.Start ||\n\t\t\t\t\tstate == vm.Running || state == vm.Starting ||\n\t\t\t\t\tstate == vm.Provisioning\n\n\t\t\t\tif !isActive {\n\t\t\t\t\treturn \"-\"\n\t\t\t\t}\n\n\t\t\t\tif mongoTime, ok := val.(bson.DateTime); ok {\n\t\t\t\t\tvalTime := mongoTime.Time()\n\t\t\t\t\treturn systemd.FormatUptimeShort(valTime)\n\t\t\t\t}\n\n\t\t\t\tif goTime, ok := val.(time.Time); ok {\n\t\t\t\t\treturn systemd.FormatUptimeShort(goTime)\n\t\t\t\t}\n\n\t\t\t\treturn \"-\"\n\t\t\t},\n\t\t}, {\n\t\t\tKey:   \"processors\",\n\t\t\tLabel: \"Processors\",\n\t\t}, {\n\t\t\tKey:   \"memory\",\n\t\t\tLabel: \"Memory\",\n\t\t}, {\n\t\t\tKey:   \"private_ips\",\n\t\t\tLabel: \"Private IPv4\",\n\t\t}, {\n\t\t\tKey:   \"public_ips\",\n\t\t\tLabel: \"Public IPv4\",\n\t\t}},\n\t}, {\n\t\tKey:          \"disks\",\n\t\tLabel:        \"Disk\",\n\t\tFrom:         \"disks\",\n\t\tLocalField:   \"_id\",\n\t\tForeignField: \"zone\",\n\t\tBlockDelete:  true,\n\t\tSort: map[string]int{\n\t\t\t\"index\": 1,\n\t\t},\n\t\tProject: []relations.Project{{\n\t\t\tKey:   \"name\",\n\t\t\tLabel: \"Name\",\n\t\t}, {\n\t\t\tKey:   \"index\",\n\t\t\tLabel: \"Index\",\n\t\t}, {\n\t\t\tKey:   \"size\",\n\t\t\tLabel: \"Size\",\n\t\t}},\n\t}},\n}\n\nfunc init() {\n\trelations.Register(\"zone\", Zone)\n}\n"
  },
  {
    "path": "relations/registry.go",
    "content": "package relations\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/imds/server/errortypes\"\n)\n\nvar registry = map[string]Query{}\n\nfunc Register(kind string, definition Query) {\n\tregistry[kind] = definition\n}\n\nfunc Aggregate(db *database.Database, kind string, id bson.ObjectID) (\n\tresp *Response, err error) {\n\n\tdefinition, ok := registry[kind]\n\tif !ok {\n\t\treturn\n\t}\n\n\tdefinition.Id = id\n\n\tresp, err = definition.Aggregate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc AggregateOrg(db *database.Database, kind string,\n\torgId, id bson.ObjectID) (resp *Response, err error) {\n\n\tdefinition, ok := registry[kind]\n\tif !ok {\n\t\treturn\n\t}\n\n\tdefinition.Id = id\n\tdefinition.Organization = orgId\n\n\tresp, err = definition.Aggregate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc blockDelete(resources []Resource) string {\n\tfor _, resource := range resources {\n\t\tif resource.BlockDelete {\n\t\t\treturn resource.Type\n\t\t}\n\n\t\tfor _, related := range resource.Relations {\n\t\t\tlabel := blockDelete(related.Resources)\n\t\t\tif label != \"\" {\n\t\t\t\treturn label\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc CanDelete(db *database.Database, kind string, id bson.ObjectID) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tdefinition, ok := registry[kind]\n\tif !ok {\n\t\treturn\n\t}\n\n\tdefinition.Id = id\n\n\tresp, err := definition.Aggregate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif resp.DeleteProtection {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"delete_protected_resource\",\n\t\t\tMessage: \"Cannot delete resource with delete protection enabled\",\n\t\t}\n\t\treturn\n\t}\n\n\tlabels := []string{}\n\tfor _, related := range resp.Relations {\n\t\tlabel := blockDelete(related.Resources)\n\t\tif label != \"\" {\n\t\t\tlabels = append(labels, label)\n\t\t}\n\t}\n\n\tif len(labels) > 0 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError: \"related_resources_exist\",\n\t\t\tMessage: fmt.Sprintf(\n\t\t\t\t\"Related [%s] resources must be deleted first. \"+\n\t\t\t\t\t\"Check resource overview\",\n\t\t\t\tstrings.Join(labels, \", \"),\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc CanDeleteOrg(db *database.Database, kind string,\n\torgId, id bson.ObjectID) (errData *errortypes.ErrorData, err error) {\n\n\tdefinition, ok := registry[kind]\n\tif !ok {\n\t\treturn\n\t}\n\n\tdefinition.Id = id\n\tdefinition.Organization = orgId\n\n\tresp, err := definition.Aggregate(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif resp.DeleteProtection {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"delete_protected_resource\",\n\t\t\tMessage: \"Cannot delete resource with delete protection enabled\",\n\t\t}\n\t\treturn\n\t}\n\n\tlabels := []string{}\n\tfor _, related := range resp.Relations {\n\t\tlabel := blockDelete(related.Resources)\n\t\tif label != \"\" {\n\t\t\tlabels = append(labels, label)\n\t\t}\n\t}\n\n\tif len(labels) > 0 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError: \"related_resources_exist\",\n\t\t\tMessage: fmt.Sprintf(\n\t\t\t\t\"Related [%s] resources must be deleted first. \"+\n\t\t\t\t\t\"Check resource overview\",\n\t\t\t\tstrings.Join(labels, \", \"),\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc CanDeleteAll(db *database.Database, kind string,\n\tids []bson.ObjectID) (errData *errortypes.ErrorData, err error) {\n\n\tfor _, id := range ids {\n\t\terrData, err = CanDelete(db, kind, id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc CanDeleteOrgAll(db *database.Database, kind string,\n\torgId bson.ObjectID, ids []bson.ObjectID) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tfor _, id := range ids {\n\t\terrData, err = CanDeleteOrg(db, kind, orgId, id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "relations/relations.go",
    "content": "package relations\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\ntype Query struct {\n\tId           any\n\tOrganization bson.ObjectID\n\tLabel        string\n\tCollection   string\n\tProject      []Project\n\tRelations    []Relation\n}\n\ntype Relation struct {\n\tKey          string\n\tLabel        string\n\tFrom         string\n\tLocalField   string\n\tForeignField string\n\tSort         map[string]int\n\tProject      []Project\n\tRelations    []Relation\n\tBlockDelete  bool\n}\n\ntype Project struct {\n\tKey    string\n\tKeys   []string\n\tLabel  string\n\tFormat func(values ...any) any\n}\n\nfunc (r *Query) addRelation(pipeline []bson.M, relation Relation) []bson.M {\n\tlookup := bson.M{\n\t\t\"from\":         relation.From,\n\t\t\"localField\":   relation.LocalField,\n\t\t\"foreignField\": relation.ForeignField,\n\t\t\"as\":           relation.From,\n\t}\n\n\tif len(relation.Project) > 0 || len(relation.Sort) > 0 ||\n\t\tlen(relation.Relations) > 0 {\n\n\t\tnestedPipeline := []bson.M{}\n\t\tif len(relation.Project) > 0 {\n\t\t\tprojection := bson.M{\n\t\t\t\t\"_id\": 1,\n\t\t\t}\n\t\t\tfor _, proj := range relation.Project {\n\t\t\t\tif len(proj.Keys) > 0 {\n\t\t\t\t\tfor _, key := range proj.Keys {\n\t\t\t\t\t\tprojection[key] = 1\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tprojection[proj.Key] = 1\n\t\t\t\t}\n\t\t\t}\n\t\t\tnestedPipeline = append(nestedPipeline, bson.M{\n\t\t\t\t\"$project\": projection,\n\t\t\t})\n\t\t}\n\n\t\tif len(relation.Sort) > 0 {\n\t\t\tnestedPipeline = append(nestedPipeline, bson.M{\n\t\t\t\t\"$sort\": relation.Sort,\n\t\t\t})\n\t\t}\n\n\t\tfor _, nestedRelation := range relation.Relations {\n\t\t\tnestedPipeline = r.addRelation(nestedPipeline, nestedRelation)\n\t\t}\n\n\t\tlookup[\"pipeline\"] = nestedPipeline\n\t}\n\n\treturn append(pipeline, bson.M{\n\t\t\"$lookup\": lookup,\n\t})\n}\n\nfunc (r *Query) convertToResponse(doc bson.M) *Response {\n\tresponse := &Response{\n\t\tId:        doc[\"_id\"],\n\t\tLabel:     r.Label,\n\t\tFields:    []Field{},\n\t\tRelations: []Related{},\n\t}\n\n\tdeleteProtection, _ := doc[\"delete_protection\"].(bool)\n\tif deleteProtection {\n\t\tresponse.DeleteProtection = true\n\t}\n\n\tfor _, proj := range r.Project {\n\t\tif proj.Label == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(proj.Keys) > 0 {\n\t\t\tvalue, ok := doc[proj.Keys[0]]\n\t\t\tif ok {\n\t\t\t\tif proj.Format != nil {\n\t\t\t\t\tvalues := []any{}\n\n\t\t\t\t\tfor _, key := range proj.Keys {\n\t\t\t\t\t\tval, ok := doc[key]\n\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\tvalues = append(values, nil)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tvalues = append(values, val)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tvalue = proj.Format(values...)\n\t\t\t\t}\n\n\t\t\t\tresponse.Fields = append(response.Fields, Field{\n\t\t\t\t\tKey:   proj.Key,\n\t\t\t\t\tLabel: proj.Label,\n\t\t\t\t\tValue: value,\n\t\t\t\t})\n\t\t\t}\n\t\t} else {\n\t\t\tvalue, ok := doc[proj.Key]\n\t\t\tif ok {\n\t\t\t\tif proj.Format != nil {\n\t\t\t\t\tvalue = proj.Format(value)\n\t\t\t\t}\n\n\t\t\t\tresponse.Fields = append(response.Fields, Field{\n\t\t\t\t\tKey:   proj.Key,\n\t\t\t\t\tLabel: proj.Label,\n\t\t\t\t\tValue: value,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, relation := range r.Relations {\n\t\tdocs, ok := doc[relation.From].(bson.A)\n\t\tif ok {\n\t\t\tresponse.Relations = append(\n\t\t\t\tresponse.Relations,\n\t\t\t\tr.convertToRelated(relation, docs),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn response\n}\n\nfunc (r *Query) convertToRelated(relation Relation,\n\tdocs bson.A) Related {\n\n\trelated := Related{\n\t\tLabel:     relation.Label,\n\t\tResources: []Resource{},\n\t}\n\n\tfor _, docInf := range docs {\n\t\tvar doc bson.M\n\t\tif bsonDoc, ok := docInf.(bson.D); ok {\n\t\t\tdoc = make(bson.M)\n\t\t\tfor _, elem := range bsonDoc {\n\t\t\t\tdoc[elem.Key] = elem.Value\n\t\t\t}\n\t\t} else if mapDoc, ok := docInf.(bson.M); ok {\n\t\t\tdoc = mapDoc\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\n\t\tresource := Resource{\n\t\t\tId:          doc[\"_id\"],\n\t\t\tType:        relation.Label,\n\t\t\tFields:      []Field{},\n\t\t\tRelations:   []Related{},\n\t\t\tBlockDelete: relation.BlockDelete,\n\t\t}\n\n\t\tfor _, proj := range relation.Project {\n\t\t\tif proj.Label == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif len(proj.Keys) > 0 {\n\t\t\t\tvalue, ok := doc[proj.Keys[0]]\n\t\t\t\tif ok {\n\t\t\t\t\tif proj.Format != nil {\n\t\t\t\t\t\tvalues := []any{}\n\n\t\t\t\t\t\tfor _, key := range proj.Keys {\n\t\t\t\t\t\t\tval, ok := doc[key]\n\t\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\t\tvalues = append(values, nil)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tvalues = append(values, val)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvalue = proj.Format(values...)\n\t\t\t\t\t}\n\n\t\t\t\t\tresource.Fields = append(resource.Fields, Field{\n\t\t\t\t\t\tKey:   proj.Key,\n\t\t\t\t\t\tLabel: proj.Label,\n\t\t\t\t\t\tValue: value,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvalue, ok := doc[proj.Key]\n\t\t\t\tif ok {\n\t\t\t\t\tif proj.Format != nil {\n\t\t\t\t\t\tvalue = proj.Format(value)\n\t\t\t\t\t}\n\n\t\t\t\t\tresource.Fields = append(resource.Fields, Field{\n\t\t\t\t\t\tKey:   proj.Key,\n\t\t\t\t\t\tLabel: proj.Label,\n\t\t\t\t\t\tValue: value,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor _, relation := range relation.Relations {\n\t\t\tdocs, ok := doc[relation.From].(bson.A)\n\t\t\tif ok {\n\t\t\t\tresource.Relations = append(\n\t\t\t\t\tresource.Relations,\n\t\t\t\t\tr.convertToRelated(relation, docs),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\trelated.Resources = append(related.Resources, resource)\n\t}\n\n\treturn related\n}\n\nfunc (r *Query) Aggregate(db *database.Database) (\n\tresp *Response, err error) {\n\n\tcoll := db.GetCollection(r.Collection)\n\n\tquery := bson.M{\n\t\t\"_id\": r.Id,\n\t}\n\tif !r.Organization.IsZero() {\n\t\tquery[\"organization\"] = r.Organization\n\t}\n\n\tpipeline := []bson.M{\n\t\t{\n\t\t\t\"$match\": query,\n\t\t},\n\t}\n\n\tif len(r.Project) > 0 {\n\t\tprojection := bson.M{\"_id\": 1, \"delete_protection\": 1}\n\t\tfor _, proj := range r.Project {\n\t\t\tif len(proj.Keys) > 0 {\n\t\t\t\tfor _, key := range proj.Keys {\n\t\t\t\t\tprojection[key] = 1\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tprojection[proj.Key] = 1\n\t\t\t}\n\t\t}\n\t\tpipeline = append(pipeline, bson.M{\"$project\": projection})\n\t}\n\n\tfor _, relation := range r.Relations {\n\t\tpipeline = r.addRelation(pipeline, relation)\n\t}\n\n\tcursor, err := coll.Aggregate(db, pipeline)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tvar results []bson.M\n\terr = cursor.All(db, &results)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif len(results) == 0 {\n\t\terr = &database.NotFoundError{\n\t\t\terrors.New(\"relations: Resource not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp = r.convertToResponse(results[0])\n\treturn\n}\n"
  },
  {
    "path": "relations/response.go",
    "content": "package relations\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n)\n\ntype Response struct {\n\tId               any\n\tLabel            string\n\tFields           []Field\n\tRelations        []Related\n\tDeleteProtection bool\n}\n\ntype Related struct {\n\tLabel     string\n\tResources []Resource\n}\n\ntype Resource struct {\n\tId          any\n\tType        string\n\tFields      []Field\n\tRelations   []Related\n\tBlockDelete bool\n}\n\ntype Field struct {\n\tKey   string\n\tLabel string\n\tValue any\n}\n\nfunc (r *Response) Yaml() string {\n\tvar output strings.Builder\n\n\toutput.WriteString(fmt.Sprintf(\"ID: %v\\n\", r.Id))\n\toutput.WriteString(fmt.Sprintf(\"Label: %s\\n\", r.Label))\n\n\tif len(r.Fields) > 0 {\n\t\toutput.WriteString(\"Fields:\\n\")\n\t\tfor _, field := range r.Fields {\n\t\t\toutput.WriteString(fmt.Sprintf(\n\t\t\t\t\"  %s: %s\\n\",\n\t\t\t\tfield.Label,\n\t\t\t\tfield.yaml(),\n\t\t\t))\n\t\t}\n\t}\n\n\tif len(r.Relations) > 0 {\n\t\toutput.WriteString(\"Relations:\\n\")\n\t\tfor _, rel := range r.Relations {\n\t\t\tfor _, resource := range rel.Resources {\n\t\t\t\toutput.WriteString(resource.yaml(0))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn strings.TrimRight(output.String(), \"\\n\")\n}\n\nfunc (r Resource) yaml(indent int) string {\n\tvar output strings.Builder\n\tindentStr := strings.Repeat(\" \", indent)\n\n\toutput.WriteString(fmt.Sprintf(\"%s- ID: %v\\n\", indentStr, r.Id))\n\toutput.WriteString(fmt.Sprintf(\"%s  Type: %s\\n\", indentStr, r.Type))\n\n\tif len(r.Fields) > 0 {\n\t\toutput.WriteString(fmt.Sprintf(\"%s  Fields:\\n\", indentStr))\n\t\tfor _, field := range r.Fields {\n\t\t\toutput.WriteString(fmt.Sprintf(\n\t\t\t\t\"%s    %s: %s\\n\",\n\t\t\t\tindentStr,\n\t\t\t\tfield.Label,\n\t\t\t\tfield.yaml(),\n\t\t\t))\n\t\t}\n\t}\n\n\tif len(r.Relations) > 0 {\n\t\toutput.WriteString(fmt.Sprintf(\"%s  Relations:\\n\", indentStr))\n\t\tfor _, rel := range r.Relations {\n\t\t\tfor _, resource := range rel.Resources {\n\t\t\t\toutput.WriteString(resource.yaml(indent + 2))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn output.String()\n}\n\nfunc (f Field) yaml() string {\n\tif f.Value == nil {\n\t\treturn \"null\"\n\t}\n\n\tv := reflect.ValueOf(f.Value)\n\tif v.Kind() == reflect.Slice || v.Kind() == reflect.Array {\n\t\tvar items []string\n\t\tfor i := 0; i < v.Len(); i++ {\n\t\t\titem := v.Index(i).Interface()\n\t\t\titems = append(items, fmt.Sprintf(\"%v\", item))\n\t\t}\n\t\treturn \"[\" + strings.Join(items, \", \") + \"]\"\n\t}\n\n\tif v.Kind() == reflect.String {\n\t\ts := f.Value.(string)\n\t\tif strings.ContainsAny(s, \":#{}[]&*!|>'\\\"\\n\") {\n\t\t\treturn \"\\\"\" + strings.ReplaceAll(s, \"\\\"\", \"\\\\\\\"\") + \"\\\"\"\n\t\t}\n\t\treturn s\n\t}\n\n\treturn fmt.Sprintf(\"%v\", f.Value)\n}\n"
  },
  {
    "path": "relations/utils.go",
    "content": "package relations\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc PrintPipeline(pipeline []bson.M) {\n\tprintln(\"**************************************************\")\n\tfor _, stage := range pipeline {\n\t\tjsonData, err := json.MarshalIndent(stage, \"\", \"  \")\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"relations: Failed to marshal json\"),\n\t\t\t}\n\t\t\tfmt.Println(err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Printf(\"%s\\n\", string(jsonData))\n\t}\n\tprintln(\"**************************************************\")\n}\n\nfunc PrintResults(results []bson.M) {\n\tjsonData, err := json.MarshalIndent(results, \"\", \"  \")\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"relations: Failed to marshal json\"),\n\t\t}\n\t\tfmt.Println(err.Error())\n\t\treturn\n\t}\n\n\tprintln(\"**************************************************\")\n\tfmt.Printf(\"%s\\n\", string(jsonData))\n\tprintln(\"**************************************************\")\n}\n"
  },
  {
    "path": "render/constants.go",
    "content": "package render\n\nconst (\n\tRendersDir = \"/dev/dri/by-path\"\n)\n"
  },
  {
    "path": "render/render.go",
    "content": "package render\n\nimport (\n\t\"io/ioutil\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\trenders         = []string{}\n\tlastRendersSync time.Time\n)\n\nfunc GetRenders() (rendrs []string, err error) {\n\tif time.Since(lastRendersSync) < 300*time.Second {\n\t\trendrs = renders\n\t\treturn\n\t}\n\n\trendersNew := []string{}\n\n\texists, err := utils.ExistsDir(RendersDir)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !exists {\n\t\treturn\n\t}\n\n\trenderFiles, err := ioutil.ReadDir(RendersDir)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"backup: Failed to read renders directory\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, item := range renderFiles {\n\t\tname := item.Name()\n\t\tif !strings.Contains(name, \"render\") {\n\t\t\tcontinue\n\t\t}\n\n\t\trendersNew = append(rendersNew, item.Name())\n\t}\n\n\trenders = rendersNew\n\tlastRendersSync = time.Now()\n\trendrs = rendersNew\n\n\treturn\n}\n\nfunc GetRender(render string) (pth string, err error) {\n\trendrs, err := GetRenders()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, rendr := range rendrs {\n\t\tif rendr == render {\n\t\t\tpth = path.Join(RendersDir, rendr)\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = &errortypes.ReadError{\n\t\terrors.Newf(\"render: Failed to find render '%s'\", render),\n\t}\n\treturn\n}\n"
  },
  {
    "path": "requires/errors.go",
    "content": "package requires\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype InitError struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "requires/requires.go",
    "content": "// Init system with before and after constraints.\npackage requires\n\nimport (\n\t\"container/list\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\nvar (\n\tmodules = list.New()\n)\n\ntype Module struct {\n\tname    string\n\tbefore  set.Set\n\tafter   set.Set\n\tHandler func() (err error)\n}\n\nfunc (m *Module) Before(name string) {\n\tm.before.Add(name)\n}\n\nfunc (m *Module) After(name string) {\n\tm.after.Add(name)\n}\n\nfunc New(name string) (module *Module) {\n\tmodule = &Module{\n\t\tname:   name,\n\t\tbefore: set.NewSet(),\n\t\tafter:  set.NewSet(),\n\t}\n\tmodules.PushBack(module)\n\treturn\n}\n\nfunc Init(ignore []string) {\n\tloaded := false\n\tignoreSet := set.NewSet()\n\n\tif ignore != nil {\n\t\tfor _, name := range ignore {\n\t\t\tignoreSet.Add(name)\n\t\t}\n\t}\n\nLoop:\n\tfor count := 0; count < 100; count += 1 {\n\t\ti := modules.Front()\n\t\tfor i != nil {\n\t\t\tmodule := i.Value.(*Module)\n\n\t\t\tj := i.Prev()\n\t\t\tfor j != nil {\n\t\t\t\tif module.before.Contains(j.Value.(*Module).name) {\n\t\t\t\t\tmodules.MoveBefore(i, j)\n\t\t\t\t\tcontinue Loop\n\t\t\t\t}\n\t\t\t\tj = j.Prev()\n\t\t\t}\n\n\t\t\tj = i.Next()\n\t\t\tfor j != nil {\n\t\t\t\tif module.after.Contains(j.Value.(*Module).name) {\n\t\t\t\t\tmodules.MoveAfter(i, j)\n\t\t\t\t\tcontinue Loop\n\t\t\t\t}\n\t\t\t\tj = j.Next()\n\t\t\t}\n\n\t\t\ti = i.Next()\n\t\t}\n\n\t\tloaded = true\n\t\tbreak Loop\n\t}\n\n\tif !loaded {\n\t\tfmt.Fprint(os.Stderr, \"Requires failed to satisfy constraints\\n\")\n\n\t\ti := modules.Front()\n\t\tfor i != nil {\n\t\t\tmodule := i.Value.(*Module)\n\t\t\tvar line strings.Builder\n\t\t\tline.WriteString(module.name)\n\n\t\t\tfor val := range module.before.Iter() {\n\t\t\t\tline.WriteString(fmt.Sprintf(\"   before: %s\", val.(string)))\n\t\t\t}\n\t\t\tfor val := range module.after.Iter() {\n\t\t\t\tline.WriteString(fmt.Sprintf(\"   after: %s\", val.(string)))\n\t\t\t}\n\n\t\t\tfmt.Fprint(os.Stderr, line.String()+\"\\n\")\n\t\t\ti = i.Next()\n\t\t}\n\n\t\ti = modules.Front()\n\tLoop2:\n\t\tfor i != nil {\n\t\t\tmodule := i.Value.(*Module)\n\n\t\t\tj := i.Prev()\n\t\t\tfor j != nil {\n\t\t\t\tval := j.Value.(*Module).name\n\t\t\t\tif module.before.Contains(val) {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"'%s' not before '%s'\\n\",\n\t\t\t\t\t\tmodule.name, val)\n\t\t\t\t\tbreak Loop2\n\t\t\t\t}\n\t\t\t\tj = j.Prev()\n\t\t\t}\n\n\t\t\tj = i.Next()\n\t\t\tfor j != nil {\n\t\t\t\tval := j.Value.(*Module).name\n\t\t\t\tif module.after.Contains(val) {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"'%s' not after '%s'\\n\",\n\t\t\t\t\t\tmodule.name, val)\n\t\t\t\t\tbreak Loop2\n\t\t\t\t}\n\t\t\t\tj = j.Next()\n\t\t\t}\n\n\t\t\ti = i.Next()\n\t\t}\n\n\t\tos.Exit(1)\n\t}\n\n\ti := modules.Front()\n\tfor i != nil {\n\t\tif !ignoreSet.Contains(i.Value.(*Module).name) {\n\t\t\terr := i.Value.(*Module).Handler()\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t}\n\n\t\ti = i.Next()\n\t}\n}\n"
  },
  {
    "path": "rokey/cache.go",
    "content": "package rokey\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n)\n\nvar (\n\tcache         = map[bson.ObjectID]*Rokey{}\n\tcacheLock     = sync.RWMutex{}\n\tcacheTime     = map[string]*Rokey{}\n\tcacheTimeLock = sync.RWMutex{}\n)\n\nfunc GetCache(typ string, timeblock time.Time) *Rokey {\n\tcacheTimeLock.RLock()\n\trkey := cacheTime[fmt.Sprintf(\"%s-%d\", typ, timeblock.Unix())]\n\tcacheTimeLock.RUnlock()\n\tif rkey != nil && rkey.Type == typ {\n\t\treturn rkey\n\t}\n\treturn nil\n}\n\nfunc GetCacheId(typ string, rkeyId bson.ObjectID) *Rokey {\n\tcacheLock.RLock()\n\trkey := cache[rkeyId]\n\tcacheLock.RUnlock()\n\tif rkey != nil && rkey.Type == typ {\n\t\treturn rkey\n\t}\n\treturn nil\n}\n\nfunc PutCache(rkey *Rokey) {\n\tcacheLock.Lock()\n\tcache[rkey.Id] = rkey\n\tcacheLock.Unlock()\n\tcacheTimeLock.Lock()\n\tcacheTime[fmt.Sprintf(\"%s-%d\", rkey.Type, rkey.Timeblock.Unix())] = rkey\n\tcacheTimeLock.Unlock()\n}\n\nfunc CleanCache() {\n\tcacheLock.Lock()\n\tfor key, rkey := range cache {\n\t\tif time.Since(rkey.Timestamp) >= 721*time.Hour {\n\t\t\tdelete(cache, key)\n\t\t}\n\t}\n\tcacheLock.Unlock()\n\n\tcacheTimeLock.Lock()\n\tfor key, rkey := range cacheTime {\n\t\tif time.Since(rkey.Timestamp) >= 721*time.Hour {\n\t\t\tdelete(cacheTime, key)\n\t\t}\n\t}\n\tcacheTimeLock.Unlock()\n}\n\nfunc init() {\n\tgo func() {\n\t\ttime.Sleep(1 * time.Hour)\n\t\tCleanCache()\n\t}()\n}\n"
  },
  {
    "path": "rokey/rokey.go",
    "content": "package rokey\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n)\n\ntype Rokey struct {\n\tId        bson.ObjectID `bson:\"_id,omitempty\" json:\"_id\"`\n\tType      string        `bson:\"type\" json:\"type\"`\n\tTimeblock time.Time     `bson:\"timeblock\" json:\"timeblock\"`\n\tTimestamp time.Time     `bson:\"timestamp\" json:\"timestamp\"`\n\tSecret    string        `bson:\"secret\" json:\"-\"`\n}\n"
  },
  {
    "path": "rokey/utils.go",
    "content": "package rokey\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, typ string) (rkey *Rokey, err error) {\n\ttimestamp := time.Now()\n\ttimeblock := time.Date(\n\t\ttimestamp.Year(),\n\t\ttimestamp.Month(),\n\t\ttimestamp.Day(),\n\t\ttimestamp.Hour(),\n\t\t0,\n\t\t0,\n\t\t0,\n\t\ttimestamp.Location(),\n\t)\n\n\trkey = GetCache(typ, timeblock)\n\tif rkey != nil {\n\t\treturn\n\t}\n\n\tsecret, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcoll := db.Rokeys()\n\trkey = &Rokey{\n\t\tType:      typ,\n\t\tTimeblock: timeblock,\n\t\tTimestamp: timestamp,\n\t\tSecret:    secret,\n\t}\n\n\terr = coll.FindOneAndUpdate(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"type\":      typ,\n\t\t\t\"timeblock\": timeblock,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$setOnInsert\": rkey,\n\t\t},\n\t\toptions.FindOneAndUpdate().\n\t\t\tSetUpsert(true).\n\t\t\tSetReturnDocument(options.After),\n\t).Decode(rkey)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tPutCache(rkey)\n\n\treturn\n}\n\nfunc GetId(db *database.Database, typ string,\n\trkeyId bson.ObjectID) (rkey *Rokey, err error) {\n\n\trkey = GetCacheId(typ, rkeyId)\n\tif rkey != nil {\n\t\treturn\n\t}\n\n\tcoll := db.Rokeys()\n\trkey = &Rokey{}\n\n\terr = coll.FindOneId(rkeyId, rkey)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\trkey = nil\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif rkey != nil {\n\t\tPutCache(rkey)\n\t\tif rkey.Type != typ {\n\t\t\trkey = nil\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "router/certificates.go",
    "content": "package router\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/balancer\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Certificates struct {\n\tselfCert    *tls.Certificate\n\tdomainMap   map[string]*tls.Certificate\n\twildcardMap map[string]*tls.Certificate\n}\n\nfunc (c *Certificates) Init() (err error) {\n\tif c.domainMap == nil {\n\t\tc.domainMap = map[string]*tls.Certificate{}\n\t}\n\tif c.wildcardMap == nil {\n\t\tc.wildcardMap = map[string]*tls.Certificate{}\n\t}\n\n\tif c.selfCert == nil {\n\t\terr = c.loadSelfCert()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (c *Certificates) loadSelfCert() (err error) {\n\tcertPem, keyPem, err := node.SelfCert()\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"router: Web server self certificate error\")\n\t\treturn\n\t}\n\n\tkeypair, err := tls.X509KeyPair(certPem, keyPem)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(\n\t\t\t\terr,\n\t\t\t\t\"router: Failed to load self certificate\",\n\t\t\t),\n\t\t}\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"router: Web server self certificate error\")\n\t\treturn\n\t}\n\n\tc.selfCert = &keypair\n\n\treturn\n}\n\nfunc (c *Certificates) GetCertificate(info *tls.ClientHelloInfo) (\n\tcert *tls.Certificate, err error) {\n\n\tname := strings.ToLower(info.ServerName)\n\tfor len(name) > 0 && name[len(name)-1] == '.' {\n\t\tname = name[:len(name)-1]\n\t}\n\n\tcert = c.domainMap[name]\n\tif cert == nil {\n\t\tindex := strings.Index(name, \".\")\n\t\tif index > 0 {\n\t\t\tcert = c.wildcardMap[name[index+1:]]\n\t\t}\n\t}\n\n\tif cert == nil {\n\t\tcert = c.selfCert\n\t}\n\n\treturn\n}\n\nfunc (c *Certificates) Update(db *database.Database,\n\tbalncs []*balancer.Balancer) (err error) {\n\n\tloaded := set.NewSet()\n\tcertificates := []*certificate.Certificate{}\n\n\tnodeCerts := node.Self.Certificates\n\tif nodeCerts != nil {\n\t\tfor _, certId := range nodeCerts {\n\t\t\tif loaded.Contains(certId) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tloaded.Add(certId)\n\n\t\t\tcert, e := certificate.Get(db, certId)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\tcert = nil\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif cert != nil {\n\t\t\t\tcertificates = append(certificates, cert)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, balnc := range balncs {\n\t\tfor _, certId := range balnc.Certificates {\n\t\t\tcert, e := certificate.Get(db, certId)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\tcert = nil\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif cert != nil {\n\t\t\t\tif cert.Organization != balnc.Organization ||\n\t\t\t\t\tloaded.Contains(certId) {\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tloaded.Add(certId)\n\n\t\t\t\tcertificates = append(certificates, cert)\n\t\t\t}\n\t\t}\n\t}\n\n\tdomainMap := map[string]*tls.Certificate{}\n\twildcardMap := map[string]*tls.Certificate{}\n\tfor _, cert := range certificates {\n\t\tif cert.Certificate == \"\" || cert.Key == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tkeypair, e := tls.X509KeyPair(\n\t\t\t[]byte(cert.Certificate),\n\t\t\t[]byte(cert.Key),\n\t\t)\n\t\tif e != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(e, \"router: Failed to load certificate\"),\n\t\t\t}\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"router: Web server certificate error\")\n\t\t\terr = nil\n\t\t\tcontinue\n\t\t}\n\t\ttlsCert := &keypair\n\n\t\tx509Cert := tlsCert.Leaf\n\t\tif x509Cert == nil {\n\t\t\tvar e error\n\t\t\tx509Cert, e = x509.ParseCertificate(tlsCert.Certificate[0])\n\t\t\tif e != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif len(x509Cert.Subject.CommonName) > 0 {\n\t\t\tif strings.HasPrefix(x509Cert.Subject.CommonName, \"*.\") {\n\t\t\t\tbase := strings.Replace(\n\t\t\t\t\tx509Cert.Subject.CommonName,\n\t\t\t\t\t\"*.\", \"\", 1,\n\t\t\t\t)\n\t\t\t\twildcardMap[base] = tlsCert\n\t\t\t} else {\n\t\t\t\tdomainMap[x509Cert.Subject.CommonName] = tlsCert\n\t\t\t}\n\t\t}\n\t\tfor _, san := range x509Cert.DNSNames {\n\t\t\tif strings.HasPrefix(san, \"*.\") {\n\t\t\t\tbase := strings.Replace(san, \"*.\", \"\", 1)\n\t\t\t\twildcardMap[base] = tlsCert\n\t\t\t} else {\n\t\t\t\tdomainMap[san] = tlsCert\n\t\t\t}\n\t\t}\n\t}\n\n\tc.domainMap = domainMap\n\tc.wildcardMap = wildcardMap\n\n\treturn\n}\n"
  },
  {
    "path": "router/constants.go",
    "content": "package router\n\nimport (\n\t\"text/template\"\n)\n\nconst redirectConfTempl = `# pritunl-zero redirect server environment\nWEB_PORT={{.WebPort}}\nPUBLIC_KEY={{.PublicKey}}\nKEY={{.Key}}\nSECRET={{.Secret}}\n`\n\nvar (\n\tredirectConf = template.Must(\n\t\ttemplate.New(\"redirect\").Parse(redirectConfTempl))\n)\n\ntype redirectConfData struct {\n\tWebPort   int\n\tPublicKey string\n\tKey       string\n\tSecret    string\n}\n"
  },
  {
    "path": "router/router.go",
    "content": "package router\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/md5\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/acme\"\n\t\"github.com/pritunl/pritunl-cloud/ahandlers\"\n\t\"github.com/pritunl/pritunl-cloud/balancer\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/crypto\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/proxy\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/uhandlers\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/tools/commander\"\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/net/http2\"\n\t\"golang.org/x/net/http2/h2c\"\n)\n\nvar (\n\tclient = &http.Client{\n\t\tTimeout: 10 * time.Second,\n\t}\n\tlastAlertLog = time.Time{}\n)\n\ntype Router struct {\n\tnodeHash             []byte\n\tsingleType           bool\n\tadminType            bool\n\tuserType             bool\n\tbalancerType         bool\n\thttp2                bool\n\tport                 int\n\tnoRedirectServer     bool\n\tredirectSystemd      bool\n\tforceRedirectSystemd bool\n\tprotocol             string\n\tadminDomain          string\n\tuserDomain           string\n\tstateLock            sync.Mutex\n\tbalancers            []*balancer.Balancer\n\tcertificates         *Certificates\n\tbox                  *crypto.AsymNaclHmac\n\taRouter              *gin.Engine\n\tuRouter              *gin.Engine\n\twaiter               *sync.WaitGroup\n\tlock                 sync.Mutex\n\tredirectServer       *http.Server\n\tredirectContext      context.Context\n\tredirectCancel       context.CancelFunc\n\twebServer            *http.Server\n\tproxy                *proxy.Proxy\n\tstop                 bool\n}\n\nfunc (r *Router) ServeHTTP(w http.ResponseWriter, re *http.Request) {\n\tif node.Self.ForwardedProtoHeader != \"\" &&\n\t\tstrings.ToLower(re.Header.Get(\n\t\t\tnode.Self.ForwardedProtoHeader)) == \"http\" {\n\n\t\tre.URL.Host = utils.StripPort(re.Host)\n\t\tre.URL.Scheme = \"https\"\n\n\t\thttp.Redirect(w, re, re.URL.String(),\n\t\t\thttp.StatusMovedPermanently)\n\t\treturn\n\t}\n\n\tif r.singleType {\n\t\tif r.adminType {\n\t\t\tr.aRouter.ServeHTTP(w, re)\n\t\t} else if r.userType {\n\t\t\tr.uRouter.ServeHTTP(w, re)\n\t\t} else if r.balancerType {\n\t\t\tr.proxy.ServeHTTP(utils.StripPort(re.Host), w, re)\n\t\t} else {\n\t\t\tutils.WriteStatus(w, 520)\n\t\t}\n\t\treturn\n\t} else {\n\t\thst := utils.StripPort(re.Host)\n\t\tif r.adminType && hst == r.adminDomain {\n\t\t\tr.aRouter.ServeHTTP(w, re)\n\t\t\treturn\n\t\t} else if r.userType && hst == r.userDomain {\n\t\t\tr.uRouter.ServeHTTP(w, re)\n\t\t\treturn\n\t\t} else if r.balancerType {\n\t\t\tr.proxy.ServeHTTP(hst, w, re)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif re.URL.Path == \"/check\" {\n\t\tutils.WriteText(w, 200, \"ok\")\n\t\treturn\n\t}\n\n\tutils.WriteStatus(w, 404)\n}\n\nfunc (r *Router) initRedirect() (err error) {\n\tif r.redirectSystemd {\n\t\tlibPath := settings.Hypervisor.LibPath\n\t\terr = utils.ExistsMkdir(libPath, 0755)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tredirectPth := path.Join(libPath, \"redirect.conf\")\n\n\t\tr.box = &crypto.AsymNaclHmac{}\n\t\terr = r.box.Generate()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tkey := r.box.Export()\n\n\t\tredirectOutput := &bytes.Buffer{}\n\t\tredirectData := &redirectConfData{\n\t\t\tWebPort:   r.port,\n\t\t\tPublicKey: key.PublicKey,\n\t\t\tKey:       key.Key,\n\t\t\tSecret:    key.Secret,\n\t\t}\n\n\t\terr = redirectConf.Execute(redirectOutput, redirectData)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"router: Failed to exec redirect template\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = utils.CreateWrite(\n\t\t\tredirectPth,\n\t\t\tredirectOutput.String(),\n\t\t\t0600,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tr.redirectServer = &http.Server{\n\t\tAddr:           \":80\",\n\t\tReadTimeout:    1 * time.Minute,\n\t\tWriteTimeout:   1 * time.Minute,\n\t\tIdleTimeout:    1 * time.Minute,\n\t\tMaxHeaderBytes: 8192,\n\t\tHandler: http.HandlerFunc(func(\n\t\t\tw http.ResponseWriter, req *http.Request) {\n\n\t\t\tif strings.HasPrefix(req.URL.Path, acme.AcmePath) {\n\t\t\t\ttoken := acme.ParsePath(req.URL.Path)\n\t\t\t\ttoken = utils.FilterStr(token, 96)\n\t\t\t\tif token != \"\" {\n\t\t\t\t\tchal, err := acme.GetChallenge(token)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tutils.WriteStatus(w, 400)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"token\": token,\n\t\t\t\t\t\t}).Info(\"router: Acme challenge requested\")\n\t\t\t\t\t\tutils.WriteText(w, 200, chal.Resource)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else if req.URL.Path == \"/check\" {\n\t\t\t\tutils.WriteText(w, 200, \"ok\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnewHost := utils.StripPort(req.Host)\n\t\t\tif r.port != 443 {\n\t\t\t\tnewHost += fmt.Sprintf(\":%d\", r.port)\n\t\t\t}\n\n\t\t\treq.URL.Host = newHost\n\t\t\treq.URL.Scheme = \"https\"\n\n\t\t\thttp.Redirect(w, req, req.URL.String(),\n\t\t\t\thttp.StatusMovedPermanently)\n\t\t}),\n\t}\n\n\treturn\n}\n\nfunc (r *Router) redirectChallengeListen(ctx context.Context) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tlst, e := event.SubscribeListener(db, []string{\"acme\"})\n\tif e != nil {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": e,\n\t\t}).Error(\"acme: Event watch error\")\n\t\treturn\n\t}\n\n\tsub := lst.Listen()\n\tdefer lst.Close()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase msg, ok := <-sub:\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tgo func() {\n\t\t\t\terr := r.sendChallenge(msg.Data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"error\": err,\n\t\t\t\t\t}).Error(\"router: Failed to send challenge \" +\n\t\t\t\t\t\t\"to redirect server\")\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n}\n\nfunc (r *Router) stopRedirectSystemd() {\n\t_, _ = commander.Exec(&commander.Opt{\n\t\tName: \"systemctl\",\n\t\tArgs: []string{\n\t\t\t\"stop\",\n\t\t\t\"pritunl-cloud-redirect.service\",\n\t\t},\n\t\tTimeout: 10 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\t_, _ = commander.Exec(&commander.Opt{\n\t\tName: \"systemctl\",\n\t\tArgs: []string{\n\t\t\t\"stop\",\n\t\t\t\"pritunl-cloud-redirect.socket\",\n\t\t},\n\t\tTimeout: 10 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n}\n\nfunc (r *Router) startRedirectSystemd() (err error) {\n\tr.stopRedirectSystemd()\n\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName: \"systemctl\",\n\t\tArgs: []string{\n\t\t\t\"start\",\n\t\t\t\"pritunl-cloud-redirect.service\",\n\t\t},\n\t\tTimeout: 30 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tlogrus.WithFields(resp.Map()).Error(\n\t\t\t\"router: Failed to start systemd redirect server\")\n\t\treturn\n\t}\n\n\tfor i := 0; i < 32; i++ {\n\t\ttime.Sleep(250 * time.Millisecond)\n\n\t\tresp, err = commander.Exec(&commander.Opt{\n\t\t\tName: \"systemctl\",\n\t\t\tArgs: []string{\n\t\t\t\t\"is-active\",\n\t\t\t\t\"pritunl-cloud-redirect.service\",\n\t\t\t},\n\t\t\tTimeout: 5 * time.Second,\n\t\t\tPipeOut: true,\n\t\t\tPipeErr: true,\n\t\t})\n\t\tif err == nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tr.stopRedirectSystemd()\n\n\terr = &errortypes.ExecError{\n\t\terrors.New(\"router: Timeout on systemd redirect server\"),\n\t}\n\treturn\n}\n\nfunc (r *Router) startRedirect() {\n\tdefer r.waiter.Done()\n\n\tif r.port == 80 || r.noRedirectServer {\n\t\treturn\n\t}\n\n\tif r.redirectSystemd {\n\t\tdefer r.stopRedirectSystemd()\n\n\t\terr := r.startRedirectSystemd()\n\t\tif err != nil {\n\t\t\tif r.forceRedirectSystemd {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"router: Failed to start systemd redirect server\")\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"router: Falling back to main process redirect server\")\n\t\t\t}\n\t\t} else {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"production\": constants.Production,\n\t\t\t\t\"protocol\":   \"http\",\n\t\t\t\t\"port\":       80,\n\t\t\t}).Info(\"router: Started systemd redirect server\")\n\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tr.redirectContext = ctx\n\t\t\tr.redirectCancel = cancel\n\n\t\t\tfor {\n\t\t\t\tr.redirectChallengeListen(ctx)\n\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tr.stopRedirectSystemd()\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"production\": constants.Production,\n\t\t\"protocol\":   \"http\",\n\t\t\"port\":       80,\n\t}).Error(\"router: Starting fallback main process redirect server\")\n\n\terr := r.redirectServer.ListenAndServe()\n\tif err != nil {\n\t\tif err == http.ErrServerClosed {\n\t\t\terr = nil\n\t\t} else {\n\t\t\terr = &errortypes.UnknownError{\n\t\t\t\terrors.Wrap(err, \"router: Server listen failed\"),\n\t\t\t}\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"router: Redirect server error\")\n\t\t}\n\t}\n}\n\nfunc (r *Router) sendChallenge(chal any) (err error) {\n\tencData, err := r.box.SealJson(chal)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq, err := http.NewRequest(\n\t\t\"POST\",\n\t\t\"http://127.0.0.1:80/token\",\n\t\tbytes.NewReader([]byte(encData)),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"acme: Redirect token request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"acme: Redirect token request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"status_code\": resp.StatusCode,\n\t\t}).Error(\"acme: Redirect request bad status\")\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (r *Router) initWeb() (err error) {\n\tr.adminType = node.Self.IsAdmin()\n\tr.userType = node.Self.IsUser()\n\tr.balancerType = node.Self.IsBalancer()\n\tr.adminDomain = node.Self.AdminDomain\n\tr.userDomain = node.Self.UserDomain\n\tr.http2 = node.Self.Http2\n\tr.noRedirectServer = node.Self.NoRedirectServer\n\tr.redirectSystemd = utils.IsSystemd() ||\n\t\tsettings.Router.ForceRedirectSystemd\n\tr.forceRedirectSystemd = settings.Router.ForceRedirectSystemd\n\n\tif r.adminType && !r.userType && !r.balancerType {\n\t\tr.singleType = true\n\t} else if r.userType && !r.balancerType && !r.adminType {\n\t\tr.singleType = true\n\t} else if r.balancerType && !r.adminType && !r.userType {\n\t\tr.singleType = true\n\t} else {\n\t\tr.singleType = false\n\t}\n\n\tr.port = node.Self.Port\n\tif r.port == 0 {\n\t\tr.port = 443\n\t}\n\n\tr.protocol = node.Self.Protocol\n\tif r.protocol == \"\" {\n\t\tr.protocol = \"https\"\n\t}\n\n\tif r.adminType {\n\t\tr.aRouter = gin.New()\n\n\t\tif constants.DebugWeb {\n\t\t\tr.aRouter.Use(gin.Logger())\n\t\t}\n\n\t\tahandlers.Register(r.aRouter)\n\t}\n\n\tif r.userType {\n\t\tr.uRouter = gin.New()\n\n\t\tif constants.DebugWeb {\n\t\t\tr.uRouter.Use(gin.Logger())\n\t\t}\n\n\t\tuhandlers.Register(r.uRouter)\n\t}\n\n\treadTimeout := time.Duration(settings.Router.ReadTimeout) * time.Second\n\treadHeaderTimeout := time.Duration(\n\t\tsettings.Router.ReadHeaderTimeout) * time.Second\n\twriteTimeout := time.Duration(settings.Router.WriteTimeout) * time.Second\n\tidleTimeout := time.Duration(settings.Router.IdleTimeout) * time.Second\n\n\tr.webServer = &http.Server{\n\t\tAddr:              fmt.Sprintf(\":%d\", r.port),\n\t\tHandler:           r,\n\t\tReadTimeout:       readTimeout,\n\t\tReadHeaderTimeout: readHeaderTimeout,\n\t\tWriteTimeout:      writeTimeout,\n\t\tIdleTimeout:       idleTimeout,\n\t\tMaxHeaderBytes:    settings.Router.MaxHeaderBytes,\n\t}\n\n\tif !r.http2 {\n\t\tr.webServer.TLSNextProto = make(map[string]func(\n\t\t\t*http.Server, *tls.Conn, http.Handler))\n\t}\n\n\tif r.http2 && r.protocol == \"http\" {\n\t\th2s := &http2.Server{\n\t\t\tIdleTimeout:     idleTimeout,\n\t\t\tReadIdleTimeout: readTimeout,\n\t\t}\n\n\t\tr.webServer.Handler = h2c.NewHandler(r, h2s)\n\t}\n\n\treturn\n}\n\nfunc (r *Router) startWeb() {\n\tdefer r.waiter.Done()\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"production\":          constants.Production,\n\t\t\"protocol\":            r.protocol,\n\t\t\"port\":                r.port,\n\t\t\"http2\":               r.http2,\n\t\t\"read_timeout\":        settings.Router.ReadTimeout,\n\t\t\"write_timeout\":       settings.Router.WriteTimeout,\n\t\t\"idle_timeout\":        settings.Router.IdleTimeout,\n\t\t\"read_header_timeout\": settings.Router.ReadHeaderTimeout,\n\t}).Info(\"router: Starting web server\")\n\n\tif r.protocol == \"http\" {\n\t\terr := r.webServer.ListenAndServe()\n\t\tif err != nil {\n\t\t\tif err == http.ErrServerClosed {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\terr = &errortypes.UnknownError{\n\t\t\t\t\terrors.Wrap(err, \"router: Server listen failed\"),\n\t\t\t\t}\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"router: Web server error\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t} else {\n\t\ttlsConfig := &tls.Config{\n\t\t\tMinVersion: tls.VersionTLS12,\n\t\t\tMaxVersion: tls.VersionTLS13,\n\t\t\tCipherSuites: []uint16{\n\t\t\t\ttls.TLS_AES_128_GCM_SHA256,                        // 0x1301\n\t\t\t\ttls.TLS_AES_256_GCM_SHA384,                        // 0x1302\n\t\t\t\ttls.TLS_CHACHA20_POLY1305_SHA256,                  // 0x1303\n\t\t\t\ttls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,       // 0xc02b\n\t\t\t\ttls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,         // 0xc02f\n\t\t\t\ttls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,       // 0xc02c\n\t\t\t\ttls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,         // 0xc030\n\t\t\t\ttls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, // 0xcca9\n\t\t\t\ttls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,   // 0xcca8\n\t\t\t},\n\t\t\tGetCertificate: r.certificates.GetCertificate,\n\t\t}\n\t\tif r.http2 {\n\t\t\ttlsConfig.NextProtos = []string{\"h2\"}\n\t\t}\n\n\t\tlistener, err := tls.Listen(\"tcp\", r.webServer.Addr, tlsConfig)\n\t\tif err != nil {\n\t\t\terr = &errortypes.UnknownError{\n\t\t\t\terrors.Wrap(err, \"router: TLS listen failed\"),\n\t\t\t}\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"router: Web server TLS error\")\n\t\t\treturn\n\t\t}\n\n\t\terr = r.webServer.Serve(listener)\n\t\tif err != nil {\n\t\t\tif err == http.ErrServerClosed {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\terr = &errortypes.UnknownError{\n\t\t\t\t\terrors.Wrap(err, \"router: Server listen failed\"),\n\t\t\t\t}\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"router: Web server error\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (r *Router) initServers() (err error) {\n\tr.lock.Lock()\n\tdefer r.lock.Unlock()\n\n\terr = r.certificates.Init()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = r.updateState()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = r.initWeb()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = r.initRedirect()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (r *Router) startServers() {\n\tr.lock.Lock()\n\tdefer r.lock.Unlock()\n\n\tif r.webServer == nil {\n\t\treturn\n\t}\n\n\tif !r.redirectSystemd && r.redirectServer == nil {\n\t\treturn\n\t}\n\n\tr.waiter.Add(2)\n\tgo r.startRedirect()\n\tgo r.startWeb()\n\n\ttime.Sleep(250 * time.Millisecond)\n\n\treturn\n}\n\nfunc (r *Router) Restart() {\n\tr.lock.Lock()\n\tdefer r.lock.Unlock()\n\n\tif r.redirectServer != nil {\n\t\tredirectCtx, redirectCancel := context.WithTimeout(\n\t\t\tcontext.Background(),\n\t\t\t1*time.Second,\n\t\t)\n\t\tdefer redirectCancel()\n\t\tr.redirectServer.Shutdown(redirectCtx)\n\t}\n\tif r.webServer != nil {\n\t\twebCtx, webCancel := context.WithTimeout(\n\t\t\tcontext.Background(),\n\t\t\t1*time.Second,\n\t\t)\n\t\tdefer webCancel()\n\t\tr.webServer.Shutdown(webCtx)\n\t}\n\n\tfunc() {\n\t\tdefer func() {\n\t\t\trecover()\n\t\t}()\n\t\tif r.redirectServer != nil {\n\t\t\tr.redirectServer.Close()\n\t\t}\n\t\tif r.webServer != nil {\n\t\t\tr.webServer.Close()\n\t\t}\n\t\tif r.redirectCancel != nil {\n\t\t\tr.redirectCancel()\n\t\t}\n\t}()\n\n\tevent.WebSocketsStop()\n\n\tr.redirectServer = nil\n\tr.webServer = nil\n\n\ttime.Sleep(250 * time.Millisecond)\n}\n\nfunc (r *Router) Shutdown() {\n\tr.stop = true\n\tr.Restart()\n\ttime.Sleep(1 * time.Second)\n\tr.Restart()\n\ttime.Sleep(1 * time.Second)\n\tr.Restart()\n}\n\nfunc (r *Router) hashNode() []byte {\n\thash := md5.New()\n\tfor _, typ := range node.Self.Types {\n\t\tio.WriteString(hash, typ)\n\t}\n\tio.WriteString(hash, node.Self.AdminDomain)\n\tio.WriteString(hash, node.Self.UserDomain)\n\tio.WriteString(hash, strconv.Itoa(node.Self.Port))\n\tio.WriteString(hash, fmt.Sprintf(\"%t\", node.Self.NoRedirectServer))\n\tio.WriteString(hash, fmt.Sprintf(\"%t\", node.Self.Http2))\n\tio.WriteString(hash, node.Self.Protocol)\n\n\tio.WriteString(hash, strconv.Itoa(settings.Router.ReadTimeout))\n\tio.WriteString(hash, strconv.Itoa(settings.Router.ReadHeaderTimeout))\n\tio.WriteString(hash, strconv.Itoa(settings.Router.WriteTimeout))\n\tio.WriteString(hash, strconv.Itoa(settings.Router.IdleTimeout))\n\tio.WriteString(hash, strconv.Itoa(settings.Router.MaxHeaderBytes))\n\tio.WriteString(hash, strconv.FormatBool(\n\t\tutils.IsSystemd() || settings.Router.ForceRedirectSystemd))\n\tio.WriteString(hash, strconv.FormatBool(\n\t\tsettings.Router.ForceRedirectSystemd))\n\n\treturn hash.Sum(nil)\n}\n\nfunc (r *Router) watchNode() {\n\tfor {\n\t\ttime.Sleep(1 * time.Second)\n\n\t\tif settings.Local.DisableWeb {\n\t\t\tr.Restart()\n\t\t\tcontinue\n\t\t}\n\n\t\thash := r.hashNode()\n\t\tif bytes.Compare(r.nodeHash, hash) != 0 {\n\t\t\tr.nodeHash = hash\n\t\t\ttime.Sleep(time.Duration(rand.Intn(3)) * time.Second)\n\t\t\tr.Restart()\n\t\t\ttime.Sleep(2 * time.Second)\n\t\t}\n\t}\n}\n\nfunc (r *Router) refreshResolver() {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\terr := proxy.ResolverRefresh(db)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"proxy: Failed to load proxy state\")\n\t}\n}\n\nfunc (r *Router) watchResolver() {\n\tfor {\n\t\ttime.Sleep(time.Duration(\n\t\t\tsettings.Router.ProxyResolverRefresh) * time.Second)\n\n\t\tif node.Self.IsBalancer() {\n\t\t\tr.refreshResolver()\n\t\t}\n\t}\n}\n\nfunc (r *Router) updateState() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tif node.Self.IsBalancer() {\n\t\tdcId, e := node.Self.GetDatacenter(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tbalncs, e := balancer.GetAll(db, &bson.M{\n\t\t\t\"datacenter\": dcId,\n\t\t})\n\t\tif e != nil {\n\t\t\tr.balancers = []*balancer.Balancer{}\n\t\t\treturn\n\t\t}\n\n\t\tr.balancers = balncs\n\t} else {\n\t\tr.balancers = []*balancer.Balancer{}\n\t}\n\n\tr.stateLock.Lock()\n\tdefer r.stateLock.Unlock()\n\n\terr = r.certificates.Update(db, r.balancers)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = r.proxy.Update(db, r.balancers)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (r *Router) watchState() {\n\tfor {\n\t\ttime.Sleep(4 * time.Second)\n\n\t\terr := r.updateState()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"proxy: Failed to load proxy state\")\n\t\t}\n\t}\n}\n\nfunc (r *Router) Run() (err error) {\n\tr.nodeHash = r.hashNode()\n\tgo r.watchNode()\n\tgo r.watchState()\n\tgo r.watchResolver()\n\n\tfor {\n\t\tif settings.Local.DisableWeb {\n\t\t\tif time.Since(lastAlertLog) > 3*time.Minute {\n\t\t\t\tlastAlertLog = time.Now()\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"message\": settings.Local.DisableMsg,\n\t\t\t\t}).Error(\"router: Web server disabled from vulnerability alert\")\n\t\t\t}\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tcontinue\n\t\t}\n\n\t\tif !node.Self.IsAdmin() && !node.Self.IsUser() &&\n\t\t\t!node.Self.IsBalancer() {\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\tcontinue\n\t\t}\n\n\t\tr.refreshResolver()\n\n\t\terr = r.initServers()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"router: Failed to init web servers\")\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tcontinue\n\t\t}\n\n\t\tr.waiter = &sync.WaitGroup{}\n\t\tr.startServers()\n\t\tr.waiter.Wait()\n\n\t\tif r.stop {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (r *Router) Init() {\n\tif constants.DebugWeb {\n\t\tgin.SetMode(gin.DebugMode)\n\t} else {\n\t\tgin.SetMode(gin.ReleaseMode)\n\t}\n\n\tr.certificates = &Certificates{}\n\tr.proxy = &proxy.Proxy{}\n\tr.proxy.Init()\n}\n"
  },
  {
    "path": "scheduler/constants.go",
    "content": "package scheduler\n\nconst (\n\tUnitKind         = \"unit\"\n\tInstanceUnitKind = \"unit-instance\"\n\n\tOffsetCount = 3\n\tOffsetInit  = 15\n\tOffsetInc   = 10\n)\n"
  },
  {
    "path": "scheduler/scheduler.go",
    "content": "package scheduler\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\ntype Scheduler struct {\n\tId            bson.ObjectID         `bson:\"_id\" json:\"id\"`\n\tOrganization  bson.ObjectID         `bson:\"organization\" json:\"organization\"`\n\tPod           bson.ObjectID         `bson:\"pod\" json:\"pod\"`\n\tKind          string                `bson:\"kind\" json:\"kind\"`\n\tCreated       time.Time             `bson:\"created\" json:\"created\"`\n\tModified      time.Time             `bson:\"modified\" json:\"modified\"`\n\tCount         int                   `bson:\"count\" json:\"count\"`\n\tSpec          bson.ObjectID         `bson:\"spec\" json:\"spec\"`\n\tOverrideCount int                   `bson:\"override_count\" json:\"override_count\"`\n\tConsumed      int                   `bson:\"consumed\" json:\"consumed\"`\n\tTickets       TicketsStore          `bson:\"tickets\" json:\"tickets\"`\n\tFailures      map[bson.ObjectID]int `bson:\"failures\" json:\"failures\"`\n}\n\ntype Ticket struct {\n\tNode   bson.ObjectID `bson:\"n\" json:\"n\"`\n\tOffset int           `bson:\"t\" json:\"t\"`\n}\n\ntype TicketsStore map[bson.ObjectID][]*Ticket\n\nfunc (s *Scheduler) Refresh(db *database.Database) (exists bool, err error) {\n\tcoll := db.Schedulers()\n\tschd := &Scheduler{}\n\n\terr = coll.FindOne(db, bson.M{\n\t\t\"_id\": s.Id,\n\t}, database.FindOneProject(\n\t\t\"count\",\n\t\t\"consumed\",\n\t\t\"tickets\",\n\t\t\"failures\",\n\t)).Decode(schd)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\texists = true\n\ts.Count = schd.Count\n\ts.Consumed = schd.Consumed\n\ts.Tickets = schd.Tickets\n\ts.Failures = schd.Failures\n\n\treturn\n}\n\nfunc (s *Scheduler) ClearTickets(db *database.Database) (err error) {\n\tcoll := db.Schedulers()\n\tschd := &Scheduler{}\n\n\terr = coll.FindOneAndUpdate(db, bson.M{\n\t\t\"_id\": s.Id,\n\t}, bson.M{\n\t\t\"$unset\": bson.M{\n\t\t\t\"tickets.\" + node.Self.Id.Hex(): \"\",\n\t\t},\n\t}, options.FindOneAndUpdate().SetReturnDocument(\n\t\toptions.After)).Decode(schd)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\ts.Count = schd.Count\n\ts.Consumed = schd.Consumed\n\ts.Tickets = schd.Tickets\n\ts.Failures = schd.Failures\n\n\treturn\n}\n\nfunc (s *Scheduler) Failure(db *database.Database) (limit bool, err error) {\n\tcoll := db.Schedulers()\n\tschd := &Scheduler{}\n\n\tif s.Failures == nil {\n\t\ts.Failures = map[bson.ObjectID]int{}\n\t}\n\ts.Failures[node.Self.Id] += 1\n\n\tupdate := bson.M{\n\t\t\"$inc\": bson.M{\n\t\t\t\"failures.\" + node.Self.Id.Hex(): 1,\n\t\t},\n\t}\n\n\tif s.Failures[node.Self.Id] >= settings.Hypervisor.MaxDeploymentFailures {\n\t\tlimit = true\n\t\tupdate[\"$unset\"] = bson.M{\n\t\t\t\"tickets.\" + node.Self.Id.Hex(): \"\",\n\t\t}\n\t}\n\n\terr = coll.FindOneAndUpdate(db, bson.M{\n\t\t\"_id\": s.Id,\n\t}, update, options.FindOneAndUpdate().SetReturnDocument(\n\t\toptions.After)).Decode(schd)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\ts.Count = schd.Count\n\ts.Consumed = schd.Consumed\n\ts.Tickets = schd.Tickets\n\ts.Failures = schd.Failures\n\n\treturn\n}\n\nfunc (s *Scheduler) Ready() bool {\n\tif s.Failures == nil {\n\t\treturn true\n\t}\n\treturn s.Failures[node.Self.Id] < settings.Hypervisor.MaxDeploymentFailures\n}\n\nfunc (s *Scheduler) Consume(db *database.Database) (err error) {\n\tcoll := db.Schedulers()\n\tschd := &Scheduler{}\n\n\terr = coll.FindOneAndUpdate(db, bson.M{\n\t\t\"_id\": s.Id,\n\t\t\"$expr\": bson.M{\n\t\t\t\"$lt\": []interface{}{\"$consumed\", \"$count\"},\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"modified\": time.Now(),\n\t\t},\n\t\t\"$inc\": bson.M{\n\t\t\t\"consumed\": 1,\n\t\t},\n\t}, options.FindOneAndUpdate().SetReturnDocument(\n\t\toptions.After)).Decode(schd)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\ts.Count = schd.Count\n\ts.Consumed = schd.Consumed\n\ts.Failures = schd.Failures\n\n\treturn\n}\n\nfunc (s *Scheduler) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\treturn\n}\n\nfunc (s *Scheduler) Commit(db *database.Database) (err error) {\n\tcoll := db.Schedulers()\n\n\terr = coll.Commit(s.Id, s)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Scheduler) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Schedulers()\n\n\terr = coll.CommitFields(s.Id, s, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Scheduler) Insert(db *database.Database) (err error) {\n\tcoll := db.Schedulers()\n\n\t_, err = coll.InsertOne(db, s)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "scheduler/unit.go",
    "content": "package scheduler\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype InstanceUnit struct {\n\tunit  *unit.Unit\n\tspec  *spec.Spec\n\tcount int\n\tnodes spec.Nodes\n}\n\nfunc (u *InstanceUnit) Schedule(db *database.Database, count int) (err error) {\n\tif u.unit.Kind != deployment.Instance && u.unit.Kind != deployment.Image {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"scheduler: Invalid unit kind\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif u.spec.Instance == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"scheduler: Missing instance data\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif u.spec.Instance.Shape.IsZero() && u.spec.Instance.Node.IsZero() {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"scheduler: Missing shape or node\"),\n\t\t}\n\t\treturn\n\t}\n\n\toverrideCount := 0\n\tif count == 0 {\n\t\tu.count = u.unit.Count - len(u.unit.Deployments)\n\t} else {\n\t\tu.count = count\n\t\toverrideCount = len(u.unit.Deployments) + count\n\t}\n\n\tschd := &Scheduler{\n\t\tId:            u.unit.Id,\n\t\tOrganization:  u.unit.Organization,\n\t\tPod:           u.unit.Pod,\n\t\tKind:          InstanceUnitKind,\n\t\tSpec:          u.spec.Id,\n\t\tCount:         u.count,\n\t\tOverrideCount: overrideCount,\n\t\tFailures:      map[bson.ObjectID]int{},\n\t}\n\n\tif !u.spec.Instance.Node.IsZero() {\n\t\tnde, e := node.Get(db, u.spec.Instance.Node)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tu.nodes = []*node.Node{nde}\n\t} else {\n\t\tndes, offlineCount, noMountCount, e := u.spec.GetAllNodes(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tu.nodes = ndes\n\n\t\tif len(u.nodes) == 0 {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"unit\":                u.unit.Id.Hex(),\n\t\t\t\t\"shape\":               u.spec.Instance.Shape.Hex(),\n\t\t\t\t\"offline_count\":       offlineCount,\n\t\t\t\t\"missing_mount_count\": noMountCount,\n\t\t\t}).Error(\"scheduler: Failed to find nodes to schedule\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tif u.count == 0 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"scheduler: Cannot schedule zero count unit\"),\n\t\t}\n\t\treturn\n\t}\n\n\tprimaryNodes, backupNodes := u.processNodes(u.nodes)\n\n\tvar tickets TicketsStore\n\tif u.count < len(primaryNodes) {\n\t\ttickets, err = u.scheduleSimple(db, primaryNodes, backupNodes)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\ttickets, err = u.scheduleComplex(db, primaryNodes, backupNodes)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tschd.Tickets = tickets\n\tschd.Created = time.Now()\n\tschd.Modified = time.Now()\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"unit\":          u.unit.Id.Hex(),\n\t\t\"count\":         u.count,\n\t\t\"primary_nodes\": len(primaryNodes),\n\t\t\"backup_nodes\":  len(backupNodes),\n\t\t\"tickets\":       len(tickets),\n\t}).Info(\"scheduler: Scheduling unit\")\n\n\terr = schd.Insert(db)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *InstanceUnit) processNodes(nodes spec.Nodes) (\n\tprimaryNodes, backupNodes spec.Nodes) {\n\n\tnodes.Sort()\n\n\tfor _, nde := range nodes {\n\t\tif nde.SizeResource(u.spec.Instance.Memory,\n\t\t\tu.spec.Instance.Processors) {\n\n\t\t\tprimaryNodes = append(primaryNodes, nde)\n\t\t} else {\n\t\t\tbackupNodes = append(backupNodes, nde)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (u *InstanceUnit) scheduleSimple(db *database.Database,\n\tprimaryNodes, backupNodes spec.Nodes) (tickets TicketsStore, err error) {\n\n\ttickets = TicketsStore{}\n\tcount := u.count\n\toffset := 0\n\n\tfor _, nde := range primaryNodes {\n\t\tif count <= 0 {\n\t\t\tcount = u.count\n\t\t\tif offset == 0 {\n\t\t\t\toffset += OffsetInit\n\t\t\t} else {\n\t\t\t\toffset += OffsetInc\n\t\t\t}\n\t\t}\n\n\t\ttickets[nde.Id] = append(tickets[nde.Id], &Ticket{\n\t\t\tNode:   nde.Id,\n\t\t\tOffset: offset,\n\t\t})\n\t\tcount -= 1\n\t}\n\n\tfor _, nde := range backupNodes {\n\t\tif count <= 0 {\n\t\t\tcount = u.count\n\t\t\tif offset == 0 {\n\t\t\t\toffset += OffsetInit\n\t\t\t} else {\n\t\t\t\toffset += OffsetInc\n\t\t\t}\n\t\t}\n\n\t\ttickets[nde.Id] = append(tickets[nde.Id], &Ticket{\n\t\t\tNode:   nde.Id,\n\t\t\tOffset: offset,\n\t\t})\n\t\tcount -= 1\n\t}\n\n\treturn\n}\n\nfunc (u *InstanceUnit) scheduleComplex(db *database.Database,\n\tprimaryNodes, backupNodes spec.Nodes) (tickets TicketsStore, err error) {\n\n\ttickets = TicketsStore{}\n\tcount := u.count\n\toffset := 0\n\toverscheduled := 0\n\n\tif primaryNodes.Len() != 0 {\n\t\tfor _, nde := range primaryNodes {\n\t\t\ttickets[nde.Id] = append(tickets[nde.Id], &Ticket{\n\t\t\t\tNode:   nde.Id,\n\t\t\t\tOffset: offset,\n\t\t\t})\n\t\t\tcount -= 1\n\n\t\t\tnde.CpuUnitsRes += u.spec.Instance.Processors\n\t\t\tnde.MemoryUnitsRes += u.spec.Instance.MemoryUnits()\n\n\t\t\tif count <= 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor _, nde := range backupNodes {\n\t\t\ttickets[nde.Id] = append(tickets[nde.Id], &Ticket{\n\t\t\t\tNode:   nde.Id,\n\t\t\t\tOffset: offset,\n\t\t\t})\n\t\t\tcount -= 1\n\t\t\toverscheduled += 1\n\n\t\t\tnde.CpuUnitsRes += u.spec.Instance.Processors\n\t\t\tnde.MemoryUnitsRes += u.spec.Instance.MemoryUnits()\n\n\t\t\tif count <= 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tfor i := 0; i < OffsetCount; i++ {\n\t\tattempts := 0\n\t\tfor attempts = 0; attempts < 100; attempts++ {\n\t\t\tif count <= 0 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tfor {\n\t\t\t\tprimaryNodes, _ = u.processNodes(u.nodes)\n\t\t\t\tif primaryNodes.Len() == 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tfor _, nde := range primaryNodes {\n\t\t\t\t\ttickets[nde.Id] = append(tickets[nde.Id], &Ticket{\n\t\t\t\t\t\tNode:   nde.Id,\n\t\t\t\t\t\tOffset: offset,\n\t\t\t\t\t})\n\t\t\t\t\tcount -= 1\n\n\t\t\t\t\tnde.CpuUnitsRes += u.spec.Instance.Processors\n\t\t\t\t\tnde.MemoryUnitsRes += u.spec.Instance.MemoryUnits()\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tif count <= 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif count <= 0 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tfor {\n\t\t\t\t_, backupNodes = u.processNodes(u.nodes)\n\t\t\t\tif backupNodes.Len() == 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tfor _, nde := range backupNodes {\n\t\t\t\t\ttickets[nde.Id] = append(tickets[nde.Id], &Ticket{\n\t\t\t\t\t\tNode:   nde.Id,\n\t\t\t\t\t\tOffset: offset,\n\t\t\t\t\t})\n\t\t\t\t\tcount -= 1\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\toverscheduled += 1\n\t\t\t\t\t}\n\n\t\t\t\t\tnde.CpuUnitsRes += u.spec.Instance.Processors\n\t\t\t\t\tnde.MemoryUnitsRes += u.spec.Instance.MemoryUnits()\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tif count <= 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif count != 0 {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"schedule: Count %d remaining after %d \"+\n\t\t\t\t\t\"complex schedule attempts\", count, attempts),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tcount = u.count\n\t\tif offset == 0 {\n\t\t\toffset += OffsetInit\n\t\t} else {\n\t\t\toffset += OffsetInc\n\t\t}\n\t}\n\n\tif overscheduled > 0 {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"unit\":          u.unit.Id.Hex(),\n\t\t\t\"kind\":          u.unit.Kind,\n\t\t\t\"shape\":         u.spec.Instance.Shape.Hex(),\n\t\t\t\"overscheduled\": overscheduled,\n\t\t}).Info(\"scheduler: Overscheduled unit\")\n\t}\n\n\treturn\n}\n\nfunc NewInstanceUnit(unt *unit.Unit, spc *spec.Spec) (\n\tinstUnit *InstanceUnit) {\n\n\tinstUnit = &InstanceUnit{\n\t\tunit: unt,\n\t\tspec: spc,\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "scheduler/utils.go",
    "content": "package scheduler\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n)\n\nfunc Exists(db *database.Database, schdId bson.ObjectID) (\n\texists bool, err error) {\n\n\tcoll := db.Schedulers()\n\tschd := &Scheduler{}\n\n\terr = coll.FindOneId(schdId, schd)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\texists = true\n\treturn\n}\n\nfunc Get(db *database.Database, schdId bson.ObjectID) (\n\tschd *Scheduler, err error) {\n\n\tcoll := db.Schedulers()\n\tschd = &Scheduler{}\n\n\terr = coll.FindOneId(schdId, schd)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database) (schds []*Scheduler, err error) {\n\tcoll := db.Schedulers()\n\tschds = []*Scheduler{}\n\n\tcursor, err := coll.Find(db, bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tschd := &Scheduler{}\n\t\terr = cursor.Decode(schd)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tschds = append(schds, schd)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllActive(db *database.Database) (schds []*Scheduler, err error) {\n\tcoll := db.Schedulers()\n\tschds = []*Scheduler{}\n\n\tcursor, err := coll.Find(db, bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tschd := &Scheduler{}\n\t\terr = cursor.Decode(schd)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif schd.Consumed < schd.Count {\n\t\t\tschds = append(schds, schd)\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, schdId bson.ObjectID) (\n\tdeleted bool, err error) {\n\n\tcoll := db.Schedulers()\n\n\tresp, err := coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": schdId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif resp.DeletedCount > 0 {\n\t\tdeleted = true\n\t}\n\n\treturn\n}\n\nfunc Schedule(db *database.Database, unt *unit.Unit) (err error) {\n\texists, e := Exists(db, unt.Id)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tif exists {\n\t\treturn\n\t}\n\n\tspc, err := spec.Get(db, unt.DeploySpec)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terrData, err := spc.Refresh(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = errData.GetError()\n\t\treturn\n\t}\n\n\tswitch unt.Kind {\n\tcase deployment.Instance, deployment.Image:\n\t\tschd := NewInstanceUnit(unt, spc)\n\t\terr = schd.Schedule(db, 0)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc ManualSchedule(db *database.Database, unt *unit.Unit,\n\tspecId bson.ObjectID, count int) (\n\terrData *errortypes.ErrorData, err error) {\n\n\texists, e := Exists(db, unt.Id)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tif exists {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"scheduler_active\",\n\t\t\tMessage: \"Cannot schedule deployments while scheduler is active\",\n\t\t}\n\t\treturn\n\t}\n\n\tif specId.IsZero() {\n\t\tspecId = unt.DeploySpec\n\t}\n\n\tspc, err := spec.Get(db, specId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terrData, err = spc.Refresh(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\treturn\n\t}\n\n\tif spc.Unit != unt.Id {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_deploy_spec_invalid\",\n\t\t\tMessage: \"Invalid unit deployment commit\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch unt.Kind {\n\tcase deployment.Instance, deployment.Image:\n\t\tif unt.Kind == deployment.Image {\n\t\t\tcount = 1\n\t\t}\n\n\t\tschd := NewInstanceUnit(unt, spc)\n\t\terr = schd.Schedule(db, count)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\tdefault:\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"scheduler: Unknown unit kind %s\", unt.Kind),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "secondary/constants.go",
    "content": "package secondary\n\nimport \"github.com/pritunl/mongo-go-driver/v2/bson\"\n\nconst (\n\tDuo      = \"duo\"\n\tOneLogin = \"one_login\"\n\tOkta     = \"okta\"\n\tPush     = \"push\"\n\tPhone    = \"phone\"\n\tPasscode = \"passcode\"\n\tSms      = \"sms\"\n\n\tAdmin                    = \"admin\"\n\tAdminDevice              = \"admin_device\"\n\tAdminDeviceRegister      = \"admin_device_register\"\n\tUser                     = \"user\"\n\tUserDevice               = \"user_device\"\n\tUserDeviceRegister       = \"user_device_register\"\n\tUserManage               = \"user_manage\"\n\tUserManageDevice         = \"user_manage_device\"\n\tUserManageDeviceRegister = \"user_manage_device_register\"\n)\n\nvar (\n\tDeviceProvider, _ = bson.ObjectIDFromHex(\"100000000000000000000000\")\n)\n"
  },
  {
    "path": "secondary/duo.go",
    "content": "package secondary\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/dropbox/godropbox/errors\"\n\tduoapi \"github.com/duosecurity/duo_api_golang\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n)\n\ntype duoApiResp struct {\n\tResult    string `json:\"result\"`\n\tStatus    string `json:\"status\"`\n\tStatusMsg string `json:\"status_msg\"`\n}\n\ntype duoApi struct {\n\tStat     string     `json:\"stat\"`\n\tCode     int        `json:\"code\"`\n\tMessage  string     `json:\"message\"`\n\tResponse duoApiResp `json:\"response\"`\n}\n\nfunc duo(db *database.Database, provider *settings.SecondaryProvider,\n\tr *http.Request, usr *user.User, factor, passcode string) (\n\tresult bool, err error) {\n\n\tif factor == Passcode && passcode == \"\" {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Duo passcode empty\"),\n\t\t}\n\t\treturn\n\t}\n\n\tapi := duoapi.NewDuoApi(\n\t\tprovider.DuoKey,\n\t\tprovider.DuoSecret,\n\t\tprovider.DuoHostname,\n\t\t\"pritunl-cloud\",\n\t)\n\n\tquery := url.Values{}\n\tquery.Set(\"username\", usr.Username)\n\tquery.Set(\"ipaddr\", node.Self.GetRemoteAddr(r))\n\n\tswitch factor {\n\tcase Push:\n\t\tquery.Set(\"factor\", \"push\")\n\t\tquery.Set(\"device\", \"auto\")\n\t\tbreak\n\tcase Phone:\n\t\tquery.Set(\"factor\", \"phone\")\n\t\tquery.Set(\"device\", \"auto\")\n\t\tbreak\n\tcase Passcode:\n\t\tquery.Set(\"factor\", \"passcode\")\n\t\tquery.Set(\"passcode\", passcode)\n\t\tbreak\n\tcase Sms:\n\t\tquery.Set(\"factor\", \"sms\")\n\t\tquery.Set(\"device\", \"auto\")\n\t\tbreak\n\t}\n\n\tresp, data, err := api.SignedCall(\n\t\t\"POST\",\n\t\t\"/auth/v2/auth\",\n\t\tquery,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: Duo auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif data == nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Newf(\n\t\t\t\t\"secondary: Duo auth request failed %d\",\n\t\t\t\tresp.StatusCode,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tduoData := &duoApi{}\n\terr = json.Unmarshal(data, duoData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrapf(\n\t\t\t\terr,\n\t\t\t\t\"secondary: Failed to parse Duo response %d\",\n\t\t\t\tresp.StatusCode,\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"username\":       usr.Username,\n\t\t\t\"status_code\":    resp.StatusCode,\n\t\t\t\"duo_factor\":     factor,\n\t\t\t\"duo_stat\":       duoData.Stat,\n\t\t\t\"duo_code\":       duoData.Code,\n\t\t\t\"duo_msg\":        duoData.Message,\n\t\t\t\"duo_result\":     duoData.Response.Result,\n\t\t\t\"duo_status\":     duoData.Response.Status,\n\t\t\t\"duo_status_msg\": duoData.Response.StatusMsg,\n\t\t}).Error(\"secondary: Duo auth request failed\")\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.New(\"secondary: Duo auth request failed\"),\n\t\t}\n\t}\n\n\tswitch duoData.Response.Result {\n\tcase \"allow\":\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tr,\n\t\t\tusr.Id,\n\t\t\taudit.DuoApprove,\n\t\t\taudit.Fields{\n\t\t\t\t\"duo_factor\": factor,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tresult = true\n\n\t\tbreak\n\tcase \"deny\":\n\t\tif factor != Sms {\n\t\t\terr = audit.New(\n\t\t\t\tdb,\n\t\t\t\tr,\n\t\t\t\tusr.Id,\n\t\t\t\taudit.DuoDeny,\n\t\t\t\taudit.Fields{\n\t\t\t\t\t\"duo_factor\":     factor,\n\t\t\t\t\t\"duo_status\":     duoData.Response.Status,\n\t\t\t\t\t\"duo_status_msg\": duoData.Response.StatusMsg,\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tbreak\n\tdefault:\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"username\":       usr.Username,\n\t\t\t\"status_code\":    resp.StatusCode,\n\t\t\t\"duo_factor\":     factor,\n\t\t\t\"duo_stat\":       duoData.Stat,\n\t\t\t\"duo_code\":       duoData.Code,\n\t\t\t\"duo_msg\":        duoData.Message,\n\t\t\t\"duo_result\":     duoData.Response.Result,\n\t\t\t\"duo_status\":     duoData.Response.Status,\n\t\t\t\"duo_status_msg\": duoData.Response.StatusMsg,\n\t\t}).Error(\"secondary: Duo auth request unknown\")\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.New(\"secondary: Duo auth request unknown\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "secondary/errors.go",
    "content": "package secondary\n\nimport (\n\t\"github.com/dropbox/godropbox/errors\"\n)\n\ntype IncompleteError struct {\n\terrors.DropboxError\n}\n"
  },
  {
    "path": "secondary/okta.go",
    "content": "package secondary\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\toktaClient = &http.Client{\n\t\tTimeout: 20 * time.Second,\n\t}\n)\n\ntype oktaProfile struct {\n\tEmail string `json:\"email\"`\n\tLogin string `json:\"login\"`\n}\n\ntype oktaUser struct {\n\tId      string      `json:\"id\"`\n\tStatus  string      `json:\"status\"`\n\tProfile oktaProfile `json:\"profile\"`\n}\n\ntype oktaFactor struct {\n\tId         string `json:\"id\"`\n\tFactorType string `json:\"factorType\"`\n\tProvider   string `json:\"provider\"`\n\tStatus     string `json:\"status\"`\n}\n\ntype oktaVerifyParams struct {\n\tPasscode string `json:\"passCode,omitempty\"`\n}\n\ntype oktaLink struct {\n\tHref string `json:\"href\"`\n}\n\ntype oktaLinks struct {\n\tPoll oktaLink `json:\"poll\"`\n}\n\ntype oktaVerify struct {\n\tFactorResult string    `json:\"factorResult\"`\n\tLinks        oktaLinks `json:\"_links\"`\n}\n\nfunc okta(db *database.Database, provider *settings.SecondaryProvider,\n\tr *http.Request, usr *user.User, factor, passcode string) (\n\tresult bool, err error) {\n\n\tif factor != Push && factor != Passcode {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.New(\"secondary: Okta invalid factor\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif factor == Passcode && passcode == \"\" {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Okta passcode empty\"),\n\t\t}\n\t\treturn\n\t}\n\n\tapiUrl := fmt.Sprintf(\n\t\t\"https://%s\",\n\t\tprovider.OktaDomain,\n\t)\n\n\tapiHeader := fmt.Sprintf(\n\t\t\"SSWS %s\",\n\t\tprovider.OktaToken,\n\t)\n\n\treqUrl, _ := url.Parse(apiUrl + \"/api/v1/users/\" + usr.Username)\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: Okta users request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Authorization\", apiHeader)\n\n\tresp, err := oktaClient.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: Okta users request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"secondary: Okta request error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\toktaUsr := &oktaUser{}\n\terr = json.NewDecoder(resp.Body).Decode(oktaUsr)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"secondary: Okta users parse failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tshortUsername := \"\"\n\tif oktaUsr.Id == \"\" && strings.Contains(usr.Username, \"@\") {\n\t\tshortUsername = strings.SplitN(usr.Username, \"@\", 2)[0]\n\n\t\treqUrl, _ = url.Parse(apiUrl + \"/api/v1/users/\" + shortUsername)\n\t\treq, err = http.NewRequest(\n\t\t\t\"GET\",\n\t\t\treqUrl.String(),\n\t\t\tnil,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"secondary: Okta users request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treq.Header.Set(\"Accept\", \"application/json\")\n\t\treq.Header.Set(\"Authorization\", apiHeader)\n\n\t\tresp, err = oktaClient.Do(req)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"secondary: Okta users request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\terr = utils.CheckRequest(resp, \"secondary: Okta request error\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\toktaUsr = &oktaUser{}\n\t\terr = json.NewDecoder(resp.Body).Decode(oktaUsr)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"secondary: Okta users parse failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tif oktaUsr.Id == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"secondary: Okta users not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif usr.Username != oktaUsr.Profile.Login &&\n\t\tusr.Username != oktaUsr.Profile.Email &&\n\t\t(shortUsername != \"\" && shortUsername != oktaUsr.Profile.Login) {\n\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Okta username mismatch\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif strings.ToLower(oktaUsr.Status) != \"active\" {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Okta user is not active\"),\n\t\t}\n\t\treturn\n\t}\n\n\tuserId := oktaUsr.Id\n\n\treqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(\n\t\t\"/api/v1/users/%s/factors\", userId))\n\treq, err = http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: Okta factors request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Authorization\", apiHeader)\n\n\tresp, err = oktaClient.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: Okta factors request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"secondary: Okta request error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfactors := []*oktaFactor{}\n\terr = json.NewDecoder(resp.Body).Decode(&factors)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"secondary: Okta factors parse failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(factors) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"secondary: Okta user has no factors\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfactorId := \"\"\n\tfor _, fctr := range factors {\n\t\tif fctr.Id == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.ToLower(fctr.Status) != \"active\" ||\n\t\t\tstrings.ToLower(fctr.Provider) != \"okta\" {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch factor {\n\t\tcase Push:\n\t\t\tif strings.ToLower(fctr.FactorType) != \"push\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\tcase Passcode:\n\t\t\tif strings.ToLower(fctr.FactorType) != \"token:software:totp\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\tfactorId = fctr.Id\n\t}\n\n\tverifyParams := &oktaVerifyParams{\n\t\tPasscode: passcode,\n\t}\n\tverifyBody, err := json.Marshal(verifyParams)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"secondary: Okta failed to parse verify params\"),\n\t\t}\n\t\treturn\n\t}\n\n\treqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(\n\t\t\"/api/v1/users/%s/factors/%s/verify\", userId, factorId))\n\treq, err = http.NewRequest(\n\t\t\"POST\",\n\t\treqUrl.String(),\n\t\tbytes.NewBuffer(verifyBody),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: Okta verify request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", apiHeader)\n\treq.Header.Set(\"User-Agent\", r.UserAgent())\n\treq.Header.Set(\"X-Forwarded-For\", node.Self.GetRemoteAddr(r))\n\n\tresp, err = oktaClient.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: Okta verify request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequestN(\n\t\tresp, \"secondary: Okta request error\",\n\t\t[]int{200, 201},\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tverify := &oktaVerify{}\n\terr = json.NewDecoder(resp.Body).Decode(verify)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"secondary: Okta verify parse failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif strings.ToLower(verify.FactorResult) == \"waiting\" &&\n\t\tverify.Links.Poll.Href != \"\" {\n\n\t\tstart := time.Now()\n\t\tfor {\n\t\t\tif time.Now().Sub(start) > 45*time.Second {\n\t\t\t\terr = audit.New(\n\t\t\t\t\tdb,\n\t\t\t\t\tr,\n\t\t\t\t\tusr.Id,\n\t\t\t\t\taudit.OktaDeny,\n\t\t\t\t\taudit.Fields{\n\t\t\t\t\t\t\"okta_factor\": factor,\n\t\t\t\t\t\t\"okta_error\":  \"timeout\",\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tresult = false\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treqUrl, _ = url.Parse(verify.Links.Poll.Href)\n\t\t\treq, err = http.NewRequest(\n\t\t\t\t\"GET\",\n\t\t\t\treqUrl.String(),\n\t\t\t\tnil,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(err, \"secondary: Okta verify request failed\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treq.Header.Set(\"Accept\", \"application/json\")\n\t\t\treq.Header.Set(\"Authorization\", apiHeader)\n\t\t\treq.Header.Set(\"User-Agent\", r.UserAgent())\n\t\t\treq.Header.Set(\"X-Forwarded-For\", node.Self.GetRemoteAddr(r))\n\n\t\t\tresp, err = oktaClient.Do(req)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(err, \"secondary: Okta verify request failed\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer resp.Body.Close()\n\n\t\t\terr = utils.CheckRequestN(\n\t\t\t\tresp, \"secondary: Okta request error\",\n\t\t\t\t[]int{200, 201},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tverify = &oktaVerify{}\n\t\t\terr = json.NewDecoder(resp.Body).Decode(verify)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err, \"secondary: Okta verify parse failed\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif strings.ToLower(verify.FactorResult) == \"waiting\" &&\n\t\t\t\tverify.Links.Poll.Href != \"\" {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif strings.ToLower(verify.FactorResult) == \"success\" {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tr,\n\t\t\tusr.Id,\n\t\t\taudit.OktaApprove,\n\t\t\taudit.Fields{\n\t\t\t\t\"okta_factor\": factor,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tresult = true\n\t} else {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tr,\n\t\t\tusr.Id,\n\t\t\taudit.OktaDeny,\n\t\t\taudit.Fields{\n\t\t\t\t\"okta_factor\": factor,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tresult = false\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "secondary/onelogin.go",
    "content": "package secondary\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\toneloginClient = &http.Client{\n\t\tTimeout: 20 * time.Second,\n\t}\n)\n\ntype oneloginAuthParams struct {\n\tGrantType string `json:\"grant_type\"`\n}\n\ntype oneloginAuth struct {\n\tAccessToken  string `json:\"access_token\"`\n\tRefreshToken string `json:\"refresh_token\"`\n\tTokenType    string `json:\"token_type\"`\n}\n\ntype oneloginUsersData struct {\n\tId       int    `json:\"id\"`\n\tUsername string `json:\"username\"`\n\tEmail    string `json:\"email\"`\n\tStatus   int    `json:\"status\"`\n}\n\ntype oneloginUsers struct {\n\tData []oneloginUsersData `json:\"data\"`\n}\n\ntype oneloginOtpDevicesDataDevices struct {\n\tId              int    `json:\"id\"`\n\tTypeDisplayName string `json:\"type_display_name\"`\n\tUserDisplayName string `json:\"user_display_name\"`\n\tAuthFactorName  string `json:\"auth_factor_name\"`\n\tActive          bool   `json:\"boolean\"`\n\tDefault         bool   `json:\"default\"`\n\tNeedsTrigger    bool   `json:\"needs_trigger\"`\n}\n\ntype oneloginOtpDevicesData struct {\n\tOtpDevices []oneloginOtpDevicesDataDevices `json:\"otp_devices\"`\n}\n\ntype oneloginOtpDevices struct {\n\tData oneloginOtpDevicesData `json:\"data\"`\n}\n\ntype oneloginActivateParams struct {\n\tIpAddr string `json:\"ipaddr\"`\n}\n\ntype oneloginActivateData struct {\n\tId         int    `json:\"id\"`\n\tDeviceId   int    `json:\"device_id\"`\n\tStateToken string `json:\"state_token\"`\n}\n\ntype oneloginActivate struct {\n\tData []oneloginActivateData `json:\"data\"`\n}\n\ntype oneloginVerifyParams struct {\n\tOtpToken   string `json:\"otp_token,omitempty\"`\n\tStateToken string `json:\"state_token,omitempty\"`\n}\n\ntype oneloginVerifyStatus struct {\n\tType    string `json:\"type\"`\n\tCode    int    `json:\"code\"`\n\tMessage string `json:\"message\"`\n\tError   bool   `json:\"error\"`\n}\n\ntype oneloginVerify struct {\n\tStatus oneloginVerifyStatus `json:\"status\"`\n}\n\nfunc onelogin(db *database.Database, provider *settings.SecondaryProvider,\n\tr *http.Request, usr *user.User, factor, passcode string) (\n\tresult bool, err error) {\n\n\tif factor != Push && factor != Passcode {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.New(\"secondary: OneLogin invalid factor\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif factor == Passcode && passcode == \"\" {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: OneLogin passcode empty\"),\n\t\t}\n\t\treturn\n\t}\n\n\tapiUrl := fmt.Sprintf(\n\t\t\"https://api.%s.onelogin.com\",\n\t\tprovider.OneLoginRegion,\n\t)\n\n\tauthParams := &oneloginAuthParams{\n\t\tGrantType: \"client_credentials\",\n\t}\n\tauthBody, err := json.Marshal(authParams)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"secondary: OneLogin failed to parse auth params\"),\n\t\t}\n\t\treturn\n\t}\n\n\treqUrl, _ := url.Parse(apiUrl + \"/auth/oauth2/v2/token\")\n\treq, err := http.NewRequest(\n\t\t\"POST\",\n\t\treqUrl.String(),\n\t\tbytes.NewBuffer(authBody),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: OneLogin auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\n\t\t\"Authorization\",\n\t\tfmt.Sprintf(\n\t\t\t\"client_id:%s, client_secret:%s\",\n\t\t\tprovider.OneLoginId,\n\t\t\tprovider.OneLoginSecret,\n\t\t),\n\t)\n\n\tresp, err := oneloginClient.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: OneLogin auth request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"secondary: OneLogin request error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tauth := &oneloginAuth{}\n\terr = json.NewDecoder(resp.Body).Decode(auth)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"secondary: OneLogin auth parse failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tapiHeader := fmt.Sprintf(\n\t\t\"bearer:%s\",\n\t\tauth.AccessToken,\n\t)\n\n\treqVals := url.Values{}\n\treqVals.Set(\"username\", usr.Username)\n\treqVals.Set(\"fields\", \"id,username,email,status\")\n\treqUrl, _ = url.Parse(apiUrl + \"/api/1/users\")\n\treqUrl.RawQuery = reqVals.Encode()\n\n\treq, err = http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: OneLogin users request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Authorization\", apiHeader)\n\n\tresp, err = oneloginClient.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: OneLogin users request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"secondary: OneLogin request error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tusers := &oneloginUsers{}\n\terr = json.NewDecoder(resp.Body).Decode(users)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"secondary: OneLogin users parse failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif users.Data == nil || len(users.Data) == 0 {\n\t\treqVals := url.Values{}\n\t\treqVals.Set(\"email\", usr.Username)\n\t\treqVals.Set(\"fields\", \"id,username,email,status\")\n\t\treqUrl, _ = url.Parse(apiUrl + \"/api/1/users\")\n\t\treqUrl.RawQuery = reqVals.Encode()\n\n\t\treq, err = http.NewRequest(\n\t\t\t\"GET\",\n\t\t\treqUrl.String(),\n\t\t\tnil,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\terr, \"secondary: OneLogin users request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treq.Header.Set(\"Accept\", \"application/json\")\n\t\treq.Header.Set(\"Authorization\", apiHeader)\n\n\t\tresp, err = oneloginClient.Do(req)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\terr, \"secondary: OneLogin users request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tusers = &oneloginUsers{}\n\t\terr = json.NewDecoder(resp.Body).Decode(users)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"secondary: OneLogin users parse failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tshortUsername := \"\"\n\tif (users.Data == nil || len(users.Data) == 0) &&\n\t\tstrings.Contains(usr.Username, \"@\") {\n\n\t\tshortUsername = strings.SplitN(usr.Username, \"@\", 2)[0]\n\n\t\treqVals := url.Values{}\n\t\treqVals.Set(\"username\", shortUsername)\n\t\treqVals.Set(\"fields\", \"id,username,email,status\")\n\t\treqUrl, _ = url.Parse(apiUrl + \"/api/1/users\")\n\t\treqUrl.RawQuery = reqVals.Encode()\n\n\t\treq, err = http.NewRequest(\n\t\t\t\"GET\",\n\t\t\treqUrl.String(),\n\t\t\tnil,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\terr, \"secondary: OneLogin users request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treq.Header.Set(\"Accept\", \"application/json\")\n\t\treq.Header.Set(\"Authorization\", apiHeader)\n\n\t\tresp, err = oneloginClient.Do(req)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\terr, \"secondary: OneLogin users request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tusers = &oneloginUsers{}\n\t\terr = json.NewDecoder(resp.Body).Decode(users)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"secondary: OneLogin users parse failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tif users.Data == nil || len(users.Data) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"secondary: OneLogin user not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif users.Data[0].Id == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"secondary: OneLogin unknown user ID\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif usr.Username != users.Data[0].Username &&\n\t\tusr.Username != users.Data[0].Email &&\n\t\t(shortUsername != \"\" && shortUsername != users.Data[0].Username) {\n\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: OneLogin username mismatch\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif users.Data[0].Status != 1 {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: OneLogin user is not active\"),\n\t\t}\n\t\treturn\n\t}\n\n\tuserId := users.Data[0].Id\n\n\treqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(\n\t\t\"/api/1/users/%d/otp_devices\",\n\t\tuserId,\n\t))\n\n\treq, err = http.NewRequest(\n\t\t\"GET\",\n\t\treqUrl.String(),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: OneLogin devices request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Authorization\", apiHeader)\n\n\tresp, err = oneloginClient.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secondary: OneLogin devices request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\terr = utils.CheckRequest(resp, \"secondary: OneLogin request error\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdevices := &oneloginOtpDevices{}\n\terr = json.NewDecoder(resp.Body).Decode(devices)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"secondary: OneLogin users parse failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif devices.Data.OtpDevices == nil || len(devices.Data.OtpDevices) == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"secondary: OneLogin user has no devices\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdeviceId := 0\n\tneedsTrigger := false\n\tfor _, device := range devices.Data.OtpDevices {\n\t\tif device.AuthFactorName != \"OneLogin Protect\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif device.Default {\n\t\t\tdeviceId = device.Id\n\t\t\tneedsTrigger = device.NeedsTrigger\n\t\t\tbreak\n\t\t} else if deviceId == 0 {\n\t\t\tdeviceId = device.Id\n\t\t\tneedsTrigger = device.NeedsTrigger\n\t\t}\n\n\t}\n\n\tif deviceId == 0 {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"secondary: OneLogin user device type not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tstateToken := \"\"\n\tif needsTrigger || factor == Push {\n\t\treqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(\n\t\t\t\"/api/1/users/%d/otp_devices/%d/trigger\",\n\t\t\tuserId,\n\t\t\tdeviceId,\n\t\t))\n\n\t\tvar activateBuffer *bytes.Buffer\n\t\tif factor == Push {\n\t\t\tactivateParams := &oneloginActivateParams{\n\t\t\t\tIpAddr: node.Self.GetRemoteAddr(r),\n\t\t\t}\n\t\t\tactivateBody, e := json.Marshal(activateParams)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(\n\t\t\t\t\t\te,\n\t\t\t\t\t\t\"secondary: OneLogin failed to parse activate params\",\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactivateBuffer = bytes.NewBuffer(activateBody)\n\t\t}\n\n\t\treq, err = http.NewRequest(\n\t\t\t\"POST\",\n\t\t\treqUrl.String(),\n\t\t\tactivateBuffer,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\terr, \"secondary: OneLogin activate request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treq.Header.Set(\"Accept\", \"application/json\")\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t\treq.Header.Set(\"Authorization\", apiHeader)\n\t\treq.Header.Set(\"User-Agent\", r.UserAgent())\n\t\treq.Header.Set(\"X-Forwarded-For\", node.Self.GetRemoteAddr(r))\n\n\t\tresp, err = oneloginClient.Do(req)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(\n\t\t\t\t\terr, \"secondary: OneLogin activate request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\terr = utils.CheckRequest(resp, \"secondary: OneLogin request error\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tactivate := &oneloginActivate{}\n\t\terr = json.NewDecoder(resp.Body).Decode(activate)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"secondary: OneLogin activate parse failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif activate.Data == nil || len(activate.Data) == 0 {\n\t\t\terr = &errortypes.UnknownError{\n\t\t\t\terrors.New(\"secondary: OneLogin activate empty data\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif activate.Data[0].Id != userId {\n\t\t\terr = &errortypes.AuthenticationError{\n\t\t\t\terrors.New(\"secondary: OneLogin activate user id mismatch\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif activate.Data[0].DeviceId != deviceId {\n\t\t\terr = &errortypes.AuthenticationError{\n\t\t\t\terrors.New(\"secondary: OneLogin activate device id mismatch\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif activate.Data[0].StateToken == \"\" {\n\t\t\terr = &errortypes.AuthenticationError{\n\t\t\t\terrors.New(\"secondary: OneLogin activate state token empty\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tstateToken = activate.Data[0].StateToken\n\t}\n\n\tverifyParams := &oneloginVerifyParams{\n\t\tOtpToken:   passcode,\n\t\tStateToken: stateToken,\n\t}\n\tverifyBody, err := json.Marshal(verifyParams)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr, \"secondary: OneLogin failed to parse verify params\"),\n\t\t}\n\t\treturn\n\t}\n\n\tstart := time.Now()\n\tfor {\n\t\tif time.Now().Sub(start) > 45*time.Second {\n\t\t\terr = audit.New(\n\t\t\t\tdb,\n\t\t\t\tr,\n\t\t\t\tusr.Id,\n\t\t\t\taudit.OneLoginDeny,\n\t\t\t\taudit.Fields{\n\t\t\t\t\t\"one_login_factor\": factor,\n\t\t\t\t\t\"one_login_error\":  \"timeout\",\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresult = false\n\n\t\t\treturn\n\t\t}\n\n\t\treqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(\n\t\t\t\"/api/1/users/%d/otp_devices/%d/verify\",\n\t\t\tuserId,\n\t\t\tdeviceId,\n\t\t))\n\n\t\treq, err = http.NewRequest(\n\t\t\t\"POST\",\n\t\t\treqUrl.String(),\n\t\t\tbytes.NewBuffer(verifyBody),\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"secondary: OneLogin verify request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treq.Header.Set(\"Accept\", \"application/json\")\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t\treq.Header.Set(\"Authorization\", apiHeader)\n\t\treq.Header.Set(\"User-Agent\", r.UserAgent())\n\t\treq.Header.Set(\"X-Forwarded-For\", node.Self.GetRemoteAddr(r))\n\n\t\tresp, err = oneloginClient.Do(req)\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"secondary: OneLogin verify request failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\terr = utils.CheckRequestN(\n\t\t\tresp, \"secondary: OneLogin verify request failed\",\n\t\t\t[]int{200, 401},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tverify := &oneloginVerify{}\n\t\terr = json.NewDecoder(resp.Body).Decode(verify)\n\t\tif err != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"secondary: OneLogin verify parse failed\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif resp.StatusCode == 401 {\n\t\t\tif strings.Contains(\n\t\t\t\tverify.Status.Message, \"Authentication pending\") {\n\t\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\terr = audit.New(\n\t\t\t\tdb,\n\t\t\t\tr,\n\t\t\t\tusr.Id,\n\t\t\t\taudit.OneLoginDeny,\n\t\t\t\taudit.Fields{\n\t\t\t\t\t\"one_login_factor\": factor,\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresult = false\n\n\t\t\treturn\n\t\t}\n\n\t\tif verify.Status.Type != \"success\" || verify.Status.Code != 200 ||\n\t\t\tverify.Status.Error {\n\n\t\t\terr = &errortypes.UnknownError{\n\t\t\t\terrors.New(\"secondary: OneLogin verify request bad data\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tr,\n\t\t\tusr.Id,\n\t\t\taudit.OneLoginApprove,\n\t\t\taudit.Fields{\n\t\t\t\t\"one_login_factor\": factor,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tresult = true\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "secondary/secondary.go",
    "content": "package secondary\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/go-webauthn/webauthn/protocol\"\n\t\"github.com/go-webauthn/webauthn/webauthn\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/device\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype SecondaryData struct {\n\tToken          string `json:\"token\"`\n\tLabel          string `json:\"label\"`\n\tPush           bool   `json:\"push\"`\n\tPhone          bool   `json:\"phone\"`\n\tPasscode       bool   `json:\"passcode\"`\n\tSms            bool   `json:\"sms\"`\n\tDevice         bool   `json:\"device\"`\n\tDeviceRegister bool   `json:\"device_register\"`\n}\n\ntype Secondary struct {\n\tusr        *user.User                  `bson:\"-\"`\n\tprovider   *settings.SecondaryProvider `bson:\"-\"`\n\tId         string                      `bson:\"_id\"`\n\tProviderId bson.ObjectID               `bson:\"provider_id,omitempty\"`\n\tUserId     bson.ObjectID               `bson:\"user_id\"`\n\tType       string                      `bson:\"type\"`\n\tTimestamp  time.Time                   `bson:\"timestamp\"`\n\tPushSent   bool                        `bson:\"push_sent\"`\n\tPhoneSent  bool                        `bson:\"phone_sent\"`\n\tSmsSent    bool                        `bson:\"sms_sent\"`\n\tDisabled   bool                        `bson:\"disabled\"`\n\tWanSession *webauthn.SessionData       `bson:\"wan_session\"`\n}\n\n// TODO Disable secondary after login\n\nfunc (s *Secondary) Push(db *database.Database, r *http.Request) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif s.Disabled {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_disabled\",\n\t\t\tMessage: \"Secondary authentication has already been completed\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.PushSent {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Push already sent\"),\n\t\t}\n\t\treturn\n\t}\n\ts.PushSent = true\n\terr = s.CommitFields(db, set.NewSet(\"push_sent\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tprovider, err := s.GetProvider()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !provider.PushFactor {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Push factor not available\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := s.GetUser(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresult := false\n\tswitch provider.Type {\n\tcase Duo:\n\t\tresult, err = duo(db, provider, r, usr, Push, \"\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase OneLogin:\n\t\tresult, err = onelogin(db, provider, r, usr, Push, \"\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase Okta:\n\t\tresult, err = okta(db, provider, r, usr, Push, \"\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.New(\"secondary: Unknown secondary provider type\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif !result {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_denied\",\n\t\t\tMessage: \"Secondary authentication was denied\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) Phone(db *database.Database, r *http.Request) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif s.Disabled {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_disabled\",\n\t\t\tMessage: \"Secondary authentication has already been completed\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.PhoneSent {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Phone already sent\"),\n\t\t}\n\t\treturn\n\t}\n\ts.PhoneSent = true\n\terr = s.CommitFields(db, set.NewSet(\"phone_sent\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tprovider, err := s.GetProvider()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !provider.PhoneFactor {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Phone factor not available\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := s.GetUser(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresult := false\n\tswitch provider.Type {\n\tcase Duo:\n\t\tresult, err = duo(db, provider, r, usr, Phone, \"\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.New(\"secondary: Unknown secondary provider type\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif !result {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_denied\",\n\t\t\tMessage: \"Secondary authentication was denied\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) Passcode(db *database.Database, r *http.Request,\n\tpasscode string) (errData *errortypes.ErrorData, err error) {\n\n\tif s.Disabled {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_disabled\",\n\t\t\tMessage: \"Secondary authentication has already been completed\",\n\t\t}\n\t\treturn\n\t}\n\n\tprovider, err := s.GetProvider()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !provider.PasscodeFactor {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Passcode factor not available\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := s.GetUser(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresult := false\n\tswitch provider.Type {\n\tcase Duo:\n\t\tresult, err = duo(db, provider, r, usr, Passcode, passcode)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase OneLogin:\n\t\tresult, err = onelogin(db, provider, r, usr, Passcode, passcode)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase Okta:\n\t\tresult, err = okta(db, provider, r, usr, Passcode, passcode)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.New(\"secondary: Unknown secondary provider type\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif !result {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_denied\",\n\t\t\tMessage: \"Secondary authentication was denied\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) Sms(db *database.Database, r *http.Request) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif s.Disabled {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_disabled\",\n\t\t\tMessage: \"Secondary authentication has already been completed\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.SmsSent {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Sms already sent\"),\n\t\t}\n\t\treturn\n\t}\n\n\tprovider, err := s.GetProvider()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !provider.SmsFactor {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Sms factor not available\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := s.GetUser(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tswitch provider.Type {\n\tcase Duo:\n\t\t_, err = duo(db, provider, r, usr, Sms, \"\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.New(\"secondary: Unknown secondary provider type\"),\n\t\t}\n\t\treturn\n\t}\n\n\ts.SmsSent = true\n\terr = s.CommitFields(db, set.NewSet(\"sms_sent\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = &IncompleteError{\n\t\terrors.New(\"secondary: Secondary auth is incomplete\"),\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) DeviceRegisterRequest(db *database.Database,\n\torigin string) (jsonResp interface{}, errData *errortypes.ErrorData,\n\terr error) {\n\n\tif s.Disabled {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_disabled\",\n\t\t\tMessage: \"Secondary registration has already been completed\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.ProviderId != DeviceProvider {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Device register not available\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif s.WanSession != nil {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Device registration already requested\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := s.GetUser(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tweb, err := node.Self.GetWebauthn(origin, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\toptions, sessionData, err := web.BeginRegistration(usr)\n\tif err != nil {\n\t\terr = utils.ParseWebauthnError(err)\n\t\treturn\n\t}\n\n\ts.WanSession = sessionData\n\terr = s.CommitFields(db, set.NewSet(\"wan_session\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tjsonResp = options\n\n\treturn\n}\n\nfunc (s *Secondary) DeviceRegisterResponse(db *database.Database,\n\torigin string, body io.Reader, name string) (\n\tdevc *device.Device, errData *errortypes.ErrorData, err error) {\n\n\tif s.Disabled {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_disabled\",\n\t\t\tMessage: \"Secondary registration has already been completed\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.ProviderId != DeviceProvider {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Device register not available\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif s.WanSession == nil {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Device registration not requested\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := s.GetUser(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata, err := protocol.ParseCredentialCreationResponseBody(body)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Webauthn parse error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tweb, err := node.Self.GetWebauthn(origin, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcredential, err := web.CreateCredential(usr, *s.WanSession, data)\n\tif err != nil {\n\t\terr = utils.ParseWebauthnError(err)\n\t\treturn\n\t}\n\n\tdevc = device.New(usr.Id, device.WebAuthn, device.Secondary)\n\tdevc.User = usr.Id\n\tdevc.Name = name\n\tdevc.WanRpId = web.Config.RPID\n\n\tdevc.MarshalWebauthn(credential)\n\n\terrData, err = devc.Validate(db)\n\tif err != nil || errData != nil {\n\t\treturn\n\t}\n\n\terr = devc.Insert(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) DeviceRequest(db *database.Database, origin string) (\n\tjsonResp interface{}, errData *errortypes.ErrorData, err error) {\n\n\tif s.Disabled {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_disabled\",\n\t\t\tMessage: \"Secondary authentication has already been completed\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.ProviderId != DeviceProvider {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Device sign not available\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif s.WanSession != nil {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Device sign already requested\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := s.GetUser(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tweb, err := node.Self.GetWebauthn(origin, false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, hasU2f, err := usr.LoadWebAuthnDevices(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tloginOpts := []webauthn.LoginOption{\n\t\twebauthn.WithUserVerification(protocol.VerificationPreferred),\n\t}\n\tif hasU2f {\n\t\tloginOpts = append(\n\t\t\tloginOpts,\n\t\t\twebauthn.WithAssertionExtensions(\n\t\t\t\tprotocol.AuthenticationExtensions{\n\t\t\t\t\t\"appid\": settings.Local.AppId,\n\t\t\t\t},\n\t\t\t),\n\t\t)\n\t}\n\n\toptions, sessionData, err := web.BeginLogin(usr, loginOpts...)\n\tif err != nil {\n\t\terr = utils.ParseWebauthnError(err)\n\t\treturn\n\t}\n\n\ts.WanSession = sessionData\n\terr = s.CommitFields(db, set.NewSet(\"wan_session\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tjsonResp = options\n\n\treturn\n}\n\nfunc (s *Secondary) DeviceRespond(db *database.Database, origin string,\n\tbody io.Reader) (errData *errortypes.ErrorData, err error) {\n\n\tif s.Disabled {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_disabled\",\n\t\t\tMessage: \"Secondary authentication has already been completed\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.ProviderId != DeviceProvider {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Device sign not available\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif s.WanSession == nil {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"secondary: Device sign not requested\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := s.GetUser(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata, err := protocol.ParseCredentialRequestResponseBody(body)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Webauthn parse error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tweb, err := node.Self.GetWebauthn(origin, false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdevices, _, err := usr.LoadWebAuthnDevices(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcredential, err := web.ValidateLogin(\n\t\tusr, *s.WanSession, data)\n\tif err != nil {\n\t\terr = utils.ParseWebauthnError(err)\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"user_id\": s.UserId.Hex(),\n\t\t\t\"error\":   err,\n\t\t}).Error(\"secondary: Secondary authentication was denied\")\n\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_denied\",\n\t\t\tMessage: \"Secondary authentication was denied\",\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, devc := range devices {\n\t\tif devc.Type == device.U2f {\n\t\t\tif !bytes.Equal(devc.U2fKeyHandle, credential.ID) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else if devc.Type == device.WebAuthn {\n\t\t\tif !bytes.Equal(devc.WanId, credential.ID) ||\n\t\t\t\t!bytes.Equal(devc.WanPublicKey, credential.PublicKey) {\n\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\n\t\tdevc.LastActive = time.Now()\n\t\tdevc.MarshalWebauthn(credential)\n\n\t\terr = devc.CommitFields(db, set.NewSet(\n\t\t\t\"last_active\", \"u2f_counter\", \"wan_authenticator\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t}\n\n\terrData = &errortypes.ErrorData{\n\t\tError:   \"secondary_denied\",\n\t\tMessage: \"Secondary authentication was denied\",\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) GetData() (data *SecondaryData, err error) {\n\tif s.ProviderId == DeviceProvider {\n\t\tlabel := \"\"\n\t\tregister := false\n\n\t\tif strings.Contains(s.Type, \"register\") {\n\t\t\tlabel = \"Register Device\"\n\t\t\tregister = true\n\t\t} else {\n\t\t\tlabel = \"Device Authentication\"\n\t\t\tregister = false\n\t\t}\n\n\t\tdata = &SecondaryData{\n\t\t\tToken:          s.Id,\n\t\t\tLabel:          label,\n\t\t\tPush:           false,\n\t\t\tPhone:          false,\n\t\t\tPasscode:       false,\n\t\t\tSms:            false,\n\t\t\tDevice:         !register,\n\t\t\tDeviceRegister: register,\n\t\t}\n\t\treturn\n\t}\n\n\tprovider, err := s.GetProvider()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdata = &SecondaryData{\n\t\tToken:    s.Id,\n\t\tLabel:    provider.Label,\n\t\tPush:     provider.PushFactor,\n\t\tPhone:    provider.PhoneFactor,\n\t\tPasscode: provider.PasscodeFactor || provider.SmsFactor,\n\t\tSms:      provider.SmsFactor,\n\t}\n\treturn\n}\n\nfunc (s *Secondary) GetQuery() (query string, err error) {\n\tif s.ProviderId == DeviceProvider {\n\t\tlabel := \"\"\n\t\tfactor := \"\"\n\n\t\tif strings.Contains(s.Type, \"register\") {\n\t\t\tlabel = \"Register Device\"\n\t\t\tfactor = \"device_register\"\n\t\t} else {\n\t\t\tlabel = \"Device Authentication\"\n\t\t\tfactor = \"device\"\n\t\t}\n\n\t\tquery = fmt.Sprintf(\n\t\t\t\"secondary=%s&label=%s&factors=%s\",\n\t\t\ts.Id,\n\t\t\turl.PathEscape(label),\n\t\t\tfactor,\n\t\t)\n\t\treturn\n\t}\n\n\tprovider, err := s.GetProvider()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfactors := []string{}\n\tif provider.PushFactor {\n\t\tfactors = append(factors, \"push\")\n\t}\n\tif provider.PhoneFactor {\n\t\tfactors = append(factors, \"phone\")\n\t}\n\tif provider.PasscodeFactor || provider.SmsFactor {\n\t\tfactors = append(factors, \"passcode\")\n\t}\n\tif provider.SmsFactor {\n\t\tfactors = append(factors, \"sms\")\n\t}\n\n\tquery = fmt.Sprintf(\n\t\t\"secondary=%s&label=%s&factors=%s\",\n\t\ts.Id,\n\t\turl.PathEscape(provider.Label),\n\t\tstrings.Join(factors, \",\"),\n\t)\n\n\treturn\n}\n\nfunc (s *Secondary) Complete(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif s.Disabled {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_disabled\",\n\t\t\tMessage: \"Secondary authentication is already completed\",\n\t\t}\n\t\treturn\n\t}\n\ts.Disabled = true\n\n\tcoll := db.SecondaryTokens()\n\tresp, err := coll.UpdateOne(db, &bson.M{\n\t\t\"_id\":      s.Id,\n\t\t\"disabled\": false,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"disabled\": true,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif resp.ModifiedCount == 0 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"secondary_update_disabled\",\n\t\t\tMessage: \"Secondary authentication update is already completed\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) Handle(db *database.Database, r *http.Request,\n\tfactor, passcode string) (errData *errortypes.ErrorData, err error) {\n\n\tswitch factor {\n\tcase Push:\n\t\terrData, err = s.Push(db, r)\n\t\tbreak\n\tcase Phone:\n\t\terrData, err = s.Phone(db, r)\n\t\tbreak\n\tcase Passcode:\n\t\terrData, err = s.Passcode(db, r, passcode)\n\t\tbreak\n\tcase Sms:\n\t\terrData, err = s.Sms(db, r)\n\t\tbreak\n\tdefault:\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.New(\"secondary: Unknown secondary factor\"),\n\t\t}\n\t}\n\n\tif err == nil && errData == nil && factor != Sms {\n\t\terrData, err = s.Complete(db)\n\t\tif err != nil || errData != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) GetUser(db *database.Database) (\n\tusr *user.User, err error) {\n\n\tif s.usr != nil {\n\t\tusr = s.usr\n\t\treturn\n\t}\n\n\tusr, err = user.Get(db, s.UserId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ts.usr = usr\n\n\treturn\n}\n\nfunc (s *Secondary) GetProvider() (provider *settings.SecondaryProvider,\n\terr error) {\n\n\tprovider = settings.Auth.GetSecondaryProvider(s.ProviderId)\n\tif provider == nil {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"secondary: Secondary provider not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) Commit(db *database.Database) (err error) {\n\tcoll := db.SecondaryTokens()\n\n\terr = coll.Commit(s.Id, s)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.SecondaryTokens()\n\n\terr = coll.CommitFields(s.Id, s, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Secondary) Insert(db *database.Database) (err error) {\n\tcoll := db.SecondaryTokens()\n\n\t_, err = coll.InsertOne(db, s)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "secondary/utils.go",
    "content": "package secondary\n\nimport (\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc New(db *database.Database, userId bson.ObjectID, typ string,\n\tproivderId bson.ObjectID) (secd *Secondary, err error) {\n\n\ttoken, err := utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsecd = &Secondary{\n\t\tId:         token,\n\t\tUserId:     userId,\n\t\tType:       typ,\n\t\tProviderId: proivderId,\n\t\tTimestamp:  time.Now(),\n\t}\n\n\terr = secd.Insert(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Get(db *database.Database, token string, typ string) (\n\tsecd *Secondary, err error) {\n\n\tcoll := db.SecondaryTokens()\n\tsecd = &Secondary{}\n\n\ttimestamp := time.Now().Add(\n\t\t-time.Duration(settings.Auth.SecondaryExpire) * time.Second)\n\n\ttime.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":  token,\n\t\t\"type\": typ,\n\t\t\"timestamp\": &bson.M{\n\t\t\t\"$gte\": timestamp,\n\t\t},\n\t}).Decode(secd)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, token string) (err error) {\n\tcoll := db.SecondaryTokens()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": token,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "secret/constants.go",
    "content": "package secret\n\nimport \"github.com/pritunl/mongo-go-driver/v2/bson\"\n\nconst (\n\tAWS         = \"aws\"\n\tCloudflare  = \"cloudflare\"\n\tOracleCloud = \"oracle_cloud\"\n\tGoogleCloud = \"google_cloud\"\n\tJson        = \"json\"\n)\n\nvar (\n\tGlobal = bson.NilObjectID\n)\n"
  },
  {
    "path": "secret/oracle.go",
    "content": "package secret\n\nimport (\n\t\"crypto/rsa\"\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/oracle/oci-go-sdk/v65/common\"\n\t\"github.com/oracle/oci-go-sdk/v65/core\"\n\t\"github.com/oracle/oci-go-sdk/v65/dns\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype OracleProvider struct {\n\tprivateKey    *rsa.PrivateKey\n\ttenancy       string\n\tuser          string\n\tfingerprint   string\n\tregion        string\n\tcompartment   string\n\tdnsClient     *dns.DnsClient\n\tcomputeClient *core.ComputeClient\n}\n\nfunc (p *OracleProvider) AuthType() (common.AuthConfig, error) {\n\treturn common.AuthConfig{\n\t\tAuthType:         common.UserPrincipal,\n\t\tIsFromConfigFile: false,\n\t\tOboToken:         nil,\n\t}, nil\n}\n\nfunc (p *OracleProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {\n\treturn p.privateKey, nil\n}\n\nfunc (p *OracleProvider) KeyID() (string, error) {\n\treturn fmt.Sprintf(\"%s/%s/%s\", p.tenancy, p.user, p.fingerprint), nil\n}\n\nfunc (p *OracleProvider) TenancyOCID() (string, error) {\n\treturn p.tenancy, nil\n}\n\nfunc (p *OracleProvider) UserOCID() (string, error) {\n\treturn p.user, nil\n}\n\nfunc (p *OracleProvider) KeyFingerprint() (string, error) {\n\treturn p.fingerprint, nil\n}\n\nfunc (p *OracleProvider) Region() (string, error) {\n\treturn p.region, nil\n}\n\nfunc (p *OracleProvider) CompartmentOCID() (string, error) {\n\treturn p.compartment, nil\n}\n\nfunc (p *OracleProvider) GetDnsClient() (\n\tdnsClient *dns.DnsClient, err error) {\n\n\tif p.dnsClient != nil {\n\t\tdnsClient = p.dnsClient\n\t\treturn\n\t}\n\n\tclient, err := dns.NewDnsClientWithConfigurationProvider(p)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"secret: Failed to create oracle client\"),\n\t\t}\n\t\treturn\n\t}\n\n\tp.dnsClient = &client\n\tdnsClient = p.dnsClient\n\n\treturn\n}\n\nfunc NewOracleProvider(secr *Secret) (prov *OracleProvider, err error) {\n\tprivateKey, fingerprint, err := loadPrivateKey(secr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tprov = &OracleProvider{\n\t\tprivateKey:  privateKey,\n\t\ttenancy:     secr.Key,\n\t\tuser:        secr.Value,\n\t\tfingerprint: fingerprint,\n\t\tregion:      secr.Region,\n\t\tcompartment: secr.Key,\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "secret/secret.go",
    "content": "package secret\n\nimport (\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Secret struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tComment      string        `bson:\"comment\" json:\"comment\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tType         string        `bson:\"type\" json:\"type\"`\n\tKey          string        `bson:\"key\" json:\"key\"`\n\tValue        string        `bson:\"value\" json:\"value\"`\n\tRegion       string        `bson:\"region\" json:\"region\"`\n\tPublicKey    string        `bson:\"public_key\" json:\"public_key\"`\n\tData         string        `bson:\"data\" json:\"data\"`\n\tPrivateKey   string        `bson:\"private_key\" json:\"-\"`\n}\n\ntype Completion struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tType         string        `bson:\"type\" json:\"type\"`\n}\n\nfunc (c *Secret) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tc.Name = utils.FilterName(c.Name)\n\n\tswitch c.Type {\n\tcase AWS, \"\":\n\t\tc.Type = AWS\n\n\t\tif c.Region == \"\" {\n\t\t\tc.Region = \"us-east-1\"\n\t\t}\n\n\t\tbreak\n\tcase Cloudflare:\n\t\tc.Value = \"\"\n\t\tc.Region = \"\"\n\n\t\tbreak\n\tcase OracleCloud:\n\t\tbreak\n\tcase GoogleCloud:\n\t\tc.Value = \"\"\n\t\tc.Region = \"\"\n\n\t\tbreak\n\tcase Json:\n\t\tc.Key = \"\"\n\t\tc.Value = \"\"\n\t\tc.Region = \"\"\n\n\t\tif !JsonValid(c.Data) {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_secret_json\",\n\t\t\t\tMessage: \"Secret json data invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_secret_type\",\n\t\t\tMessage: \"Secret type invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif c.PrivateKey == \"\" {\n\t\tprivKey, pubKey, e := utils.GenerateRsaKey()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tc.PublicKey = strings.TrimSpace(string(pubKey))\n\t\tc.PrivateKey = strings.TrimSpace(string(privKey))\n\t}\n\n\treturn\n}\n\nfunc (c *Secret) GetOracleProvider() (prov *OracleProvider, err error) {\n\tprov, err = NewOracleProvider(c)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Secret) Commit(db *database.Database) (err error) {\n\tcoll := db.Secrets()\n\n\terr = coll.Commit(c.Id, c)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Secret) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Secrets()\n\n\terr = coll.CommitFields(c.Id, c, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (c *Secret) Insert(db *database.Database) (err error) {\n\tcoll := db.Secrets()\n\n\tif !c.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"secret: Secret already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = coll.InsertOne(db, c)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "secret/utils.go",
    "content": "package secret\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc JsonValid(data string) bool {\n\tvar dataMap map[string]any\n\n\terr := json.Unmarshal([]byte(data), &dataMap)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tfor _, value := range dataMap {\n\t\tswitch value.(type) {\n\t\tcase string, bool, float64, int, int64, nil:\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc Get(db *database.Database, secrId bson.ObjectID) (\n\tsecr *Secret, err error) {\n\n\tcoll := db.Secrets()\n\tsecr = &Secret{}\n\n\terr = coll.FindOneId(secrId, secr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (secr *Secret, err error) {\n\tcoll := db.Secrets()\n\tsecr = &Secret{}\n\n\terr = coll.FindOne(db, query).Decode(secr)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, secrId bson.ObjectID) (\n\tsecr *Secret, err error) {\n\n\tcoll := db.Secrets()\n\tsecr = &Secret{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          secrId,\n\t\t\"organization\": orgId,\n\t}).Decode(secr)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tsecrs []*Secret, err error) {\n\n\tcoll := db.Secrets()\n\tsecrs = []*Secret{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tsecr := &Secret{}\n\t\terr = cursor.Decode(secr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tsecrs = append(secrs, secr)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllOrg(db *database.Database, orgId bson.ObjectID) (\n\tsecrs []*Secret, err error) {\n\n\tcoll := db.Secrets()\n\tsecrs = []*Secret{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tsecr := &Secret{}\n\t\terr = cursor.Decode(secr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tsecrs = append(secrs, secr)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNames(db *database.Database, query *bson.M) (\n\tsecrs []*database.Named, err error) {\n\n\tcoll := db.Certificates()\n\tsecrs = []*database.Named{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetProjection(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tsecr := &database.Named{}\n\t\terr = cursor.Decode(secr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tsecrs = append(secrs, secr)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (secrs []*Secret, count int64, err error) {\n\n\tcoll := db.Secrets()\n\tsecrs = []*Secret{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tsecr := &Secret{}\n\t\terr = cursor.Decode(secr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tsecrs = append(secrs, secr)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExistsOrg(db *database.Database, orgId, secrId bson.ObjectID) (\n\texists bool, err error) {\n\n\tcoll := db.Secrets()\n\tn, err := coll.CountDocuments(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\":          secrId,\n\t\t\t\"organization\": orgId,\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif n > 0 {\n\t\texists = true\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, secrId bson.ObjectID) (err error) {\n\tcoll := db.Secrets()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": secrId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, secrId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Secrets()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          secrId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, secrIds []bson.ObjectID) (\n\terr error) {\n\tcoll := db.Secrets()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": secrIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tsecrIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Secrets()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": secrIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc loadPrivateKey(secr *Secret) (\n\tkey *rsa.PrivateKey, fingerprint string, err error) {\n\n\tblock, _ := pem.Decode([]byte(secr.PrivateKey))\n\tif block == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"secret: Failed to decode private key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif block.Type != \"RSA PRIVATE KEY\" {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"secret: Invalid private key type\"),\n\t\t}\n\t\treturn\n\t}\n\n\tkey, err = x509.ParsePKCS1PrivateKey(block.Bytes)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"secret: Failed to parse rsa key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tpubKey, err := x509.MarshalPKIXPublicKey(key.Public())\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"secret: Failed to marshal public key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tkeyHash := md5.New()\n\tkeyHash.Write(pubKey)\n\tfingerprint = fmt.Sprintf(\"%x\", keyHash.Sum(nil))\n\tfingerprintBuf := bytes.Buffer{}\n\n\tfor i, run := range fingerprint {\n\t\tfingerprintBuf.WriteRune(run)\n\t\tif i%2 == 1 && i != len(fingerprint)-1 {\n\t\t\tfingerprintBuf.WriteRune(':')\n\t\t}\n\t}\n\tfingerprint = fingerprintBuf.String()\n\n\treturn\n}\n"
  },
  {
    "path": "session/constants.go",
    "content": "package session\n\nconst (\n\tAdmin = \"admin\"\n\tUser  = \"user\"\n)\n"
  },
  {
    "path": "session/session.go",
    "content": "// Stores sessions in cookies.\npackage session\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha512\"\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/rokey\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/useragent\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Session struct {\n\tId         string           `bson:\"_id\" json:\"id\"`\n\tType       string           `bson:\"type\" json:\"type\"`\n\tUser       bson.ObjectID    `bson:\"user\" json:\"user\"`\n\tRokey      bson.ObjectID    `bson:\"rokey\" json:\"-\"`\n\tSecret     string           `bson:\"secret\" json:\"-\"`\n\tTimestamp  time.Time        `bson:\"timestamp\" json:\"timestamp\"`\n\tLastActive time.Time        `bson:\"last_active\" json:\"last_active\"`\n\tRemoved    bool             `bson:\"removed\" json:\"removed\"`\n\tAgent      *useragent.Agent `bson:\"agent\" json:\"agent\"`\n\tuser       *user.User       `bson:\"-\" json:\"-\"`\n}\n\nfunc (s *Session) CheckSignature(db *database.Database, inSig string) (\n\tvalid bool, err error) {\n\n\tif s.Rokey.IsZero() || s.Secret == \"\" {\n\t\treturn\n\t}\n\n\trkey, err := rokey.GetId(db, s.Type, s.Rokey)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif rkey == nil {\n\t\treturn\n\t}\n\n\tif rkey.Secret == \"\" {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"session: Empty secret\"),\n\t\t}\n\t\treturn\n\t}\n\n\thash := hmac.New(sha512.New, []byte(rkey.Secret))\n\thash.Write([]byte(s.Secret))\n\toutSig := base64.RawStdEncoding.EncodeToString(hash.Sum(nil))\n\n\tif subtle.ConstantTimeCompare([]byte(inSig), []byte(outSig)) == 1 {\n\t\tvalid = true\n\t}\n\n\treturn\n}\n\nfunc (s *Session) GenerateSignature(db *database.Database) (\n\tsig string, err error) {\n\n\trkey, err := rokey.Get(db, s.Type)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ts.Rokey = rkey.Id\n\n\ts.Secret, err = utils.RandStr(64)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif rkey.Secret == \"\" {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"session: Empty secret\"),\n\t\t}\n\t\treturn\n\t}\n\n\thash := hmac.New(sha512.New, []byte(rkey.Secret))\n\thash.Write([]byte(s.Secret))\n\tsig = base64.RawStdEncoding.EncodeToString(hash.Sum(nil))\n\n\treturn\n}\n\nfunc (s *Session) Active() bool {\n\tif s.Removed {\n\t\treturn false\n\t}\n\n\texpire := GetExpire(s.Type)\n\tmaxDuration := GetMaxDuration(s.Type)\n\n\tif expire != 0 {\n\t\tif time.Since(s.LastActive) > expire {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif maxDuration != 0 {\n\t\tif time.Since(s.Timestamp) > maxDuration {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (s *Session) Update(db *database.Database) (err error) {\n\tcoll := db.Sessions()\n\n\terr = coll.FindOneId(s.Id, s)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Session) Remove(db *database.Database) (err error) {\n\terr = Remove(db, s.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Session) GetUser(db *database.Database) (usr *user.User, err error) {\n\tif s.user != nil || db == nil {\n\t\tusr = s.user\n\t\treturn\n\t}\n\n\tusr, err = user.GetUpdate(db, s.User)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ts.user = usr\n\n\treturn\n}\n"
  },
  {
    "path": "session/utils.go",
    "content": "package session\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/useragent\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc GetExpire(typ string) time.Duration {\n\tswitch typ {\n\tcase User:\n\t\treturn time.Duration(settings.Auth.UserExpire) * time.Minute\n\tdefault:\n\t\treturn time.Duration(settings.Auth.AdminExpire) * time.Minute\n\t}\n}\n\nfunc GetMaxDuration(typ string) time.Duration {\n\tswitch typ {\n\tcase User:\n\t\treturn time.Duration(settings.Auth.UserMaxDuration) * time.Minute\n\tdefault:\n\t\treturn time.Duration(settings.Auth.AdminMaxDuration) * time.Minute\n\t}\n}\n\nfunc Get(db *database.Database, sessId string) (\n\tsess *Session, err error) {\n\n\tcoll := db.Sessions()\n\tsess = &Session{}\n\n\terr = coll.FindOneId(sessId, sess)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetUpdate(db *database.Database, sessId string, r *http.Request,\n\ttyp, sig string) (sess *Session, err error) {\n\n\tquery := bson.M{\n\t\t\"_id\": sessId,\n\t\t\"removed\": &bson.M{\n\t\t\t\"$ne\": true,\n\t\t},\n\t}\n\n\texpire := GetExpire(typ)\n\tmaxDuration := GetMaxDuration(typ)\n\n\tif expire != 0 {\n\t\tquery[\"last_active\"] = &bson.M{\n\t\t\t\"$gte\": time.Now().Add(-expire),\n\t\t}\n\t}\n\n\tif maxDuration != 0 {\n\t\tquery[\"timestamp\"] = &bson.M{\n\t\t\t\"$gte\": time.Now().Add(-maxDuration),\n\t\t}\n\t}\n\n\tcoll := db.Sessions()\n\tsess = &Session{}\n\ttimestamp := time.Now()\n\n\terr = coll.FindOneAndUpdate(\n\t\tdb,\n\t\tquery,\n\t\t&bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"last_active\": timestamp,\n\t\t\t},\n\t\t},\n\t).Decode(sess)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tsess.LastActive = timestamp\n\n\tvalid, err := sess.CheckSignature(db, sig)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !valid {\n\t\tsess = nil\n\t\treturn\n\t}\n\n\tagnt, err := useragent.Parse(db, r)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif agnt != nil && (sess.Agent == nil || sess.Agent.Diff(agnt)) {\n\t\tsess.Agent = agnt\n\t\terr = coll.UpdateId(sess.Id, &bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"agent\": agnt,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, userId bson.ObjectID,\n\tincludeRemoved bool) (sessions []*Session, err error) {\n\n\tcoll := db.Sessions()\n\tsessions = []*Session{}\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"user\": userId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tsess := &Session{}\n\t\terr = cursor.Decode(sess)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif !sess.Active() {\n\t\t\tif !includeRemoved {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsess.Removed = true\n\t\t}\n\t\tsessions = append(sessions, sess)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc New(db *database.Database, r *http.Request, userId bson.ObjectID,\n\ttyp string) (sess *Session, sig string, err error) {\n\n\tid, err := utils.RandStr(32)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tagnt, err := useragent.Parse(db, r)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcoll := db.Sessions()\n\tsess = &Session{\n\t\tId:         id,\n\t\tType:       typ,\n\t\tUser:       userId,\n\t\tTimestamp:  time.Now(),\n\t\tLastActive: time.Now(),\n\t\tAgent:      agnt,\n\t}\n\n\tsig, err = sess.GenerateSignature(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, err = coll.InsertOne(db, sess)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, id string) (err error) {\n\tcoll := db.Sessions()\n\n\terr = coll.UpdateId(id, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"removed\": true,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveAll(db *database.Database, userId bson.ObjectID) (err error) {\n\tcoll := db.Sessions()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"user\": userId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"removed\": true,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "settings/acme.go",
    "content": "package settings\n\nvar Acme *acme\n\ntype acme struct {\n\tId                string `bson:\"_id\"`\n\tUrl               string `bson:\"url\" default:\"https://acme-v01.api.letsencrypt.org\"`\n\tDnsMaxConcurrent  int    `bson:\"dns_max_concurrent\" default:\"10\"`\n\tDnsRetryRate      int    `bson:\"dns_retry_rate\" default:\"3\"`\n\tDnsTimeout        int    `bson:\"dns_timeout\" default:\"45\"`\n\tDnsDelay          int    `bson:\"dns_delay\" default:\"15\"`\n\tDnsAwsTtl         int    `bson:\"dns_aws_ttl\" default:\"10\"`\n\tDnsCloudflareTtl  int    `bson:\"dns_cloudflare_ttl\" default:\"60\"`\n\tDnsOracleCloudTtl int    `bson:\"dns_oracle_cloud_ttl\" default:\"10\"`\n\tDnsGoogleCloudTtl int    `bson:\"dns_google_cloud_ttl\" default:\"10\"`\n}\n\nfunc newAcme() interface{} {\n\treturn &acme{\n\t\tId: \"acme\",\n\t}\n}\n\nfunc updateAcme(data interface{}) {\n\tAcme = data.(*acme)\n}\n\nfunc init() {\n\tregister(\"acme\", newAcme, updateAcme)\n}\n"
  },
  {
    "path": "settings/auth.go",
    "content": "package settings\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar Auth *auth\n\nconst (\n\tSetOnInsert = \"set_on_insert\"\n\tMerge       = \"merge\"\n\tOverwrite   = \"overwrite\"\n\n\tAzure     = \"azure\"\n\tAuthZero  = \"authzero\"\n\tGoogle    = \"google\"\n\tOneLogin  = \"onelogin\"\n\tOkta      = \"okta\"\n\tJumpCloud = \"jumpcloud\"\n\n\tDuo       = \"duo\"\n\tOneLogin2 = \"one_login\"\n)\n\ntype Provider struct {\n\tId              bson.ObjectID `bson:\"id\" json:\"id\"`\n\tType            string        `bson:\"type\" json:\"type\"`\n\tLabel           string        `bson:\"label\" json:\"label\"`\n\tDefaultRoles    []string      `bson:\"default_roles\" json:\"default_roles\"`\n\tAutoCreate      bool          `bson:\"auto_create\" json:\"auto_create\"`\n\tRoleManagement  string        `bson:\"role_management\" json:\"role_management\"`\n\tRegion          string        `bson:\"region\" json:\"region\"`                     // azure\n\tTenant          string        `bson:\"tenant\" json:\"tenant\"`                     // azure\n\tClientId        string        `bson:\"client_id\" json:\"client_id\"`               // azure + authzero\n\tClientSecret    string        `bson:\"client_secret\" json:\"client_secret\"`       // azure + authzero\n\tDomain          string        `bson:\"domain\" json:\"domain\"`                     // google + authzero\n\tGoogleKey       string        `bson:\"google_key\" json:\"google_key\"`             // google\n\tGoogleEmail     string        `bson:\"google_email\" json:\"google_email\"`         // google\n\tJumpCloudAppId  string        `bson:\"jumpcloud_app_id\" json:\"jumpcloud_app_id\"` // jumpcloud\n\tJumpCloudSecret string        `bson:\"jumpcloud_secret\" json:\"jumpcloud_secret\"` // jumpcloud\n\tIssuerUrl       string        `bson:\"issuer_url\" json:\"issuer_url\"`             // saml\n\tSamlUrl         string        `bson:\"saml_url\" json:\"saml_url\"`                 // saml\n\tSamlCert        string        `bson:\"saml_cert\" json:\"saml_cert\"`               // saml\n}\n\nfunc (p *Provider) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif p.Id.IsZero() {\n\t\tp.Id = bson.NewObjectID()\n\t}\n\n\tp.Label = utils.FilterStr(p.Label, 32)\n\n\tswitch p.Type {\n\tcase AuthZero:\n\t\tp.Region = \"\"\n\t\tp.Tenant = \"\"\n\t\tp.GoogleKey = \"\"\n\t\tp.GoogleEmail = \"\"\n\t\tp.JumpCloudAppId = \"\"\n\t\tp.JumpCloudSecret = \"\"\n\t\tp.IssuerUrl = \"\"\n\t\tp.SamlUrl = \"\"\n\t\tp.SamlCert = \"\"\n\t\tbreak\n\tcase Azure:\n\t\tif p.Region == \"\" {\n\t\t\tp.Region = \"global2\"\n\t\t}\n\t\tp.Domain = \"\"\n\t\tp.GoogleKey = \"\"\n\t\tp.GoogleEmail = \"\"\n\t\tp.JumpCloudAppId = \"\"\n\t\tp.JumpCloudSecret = \"\"\n\t\tp.IssuerUrl = \"\"\n\t\tp.SamlUrl = \"\"\n\t\tp.SamlCert = \"\"\n\t\tbreak\n\tcase Google:\n\t\tp.Region = \"\"\n\t\tp.Tenant = \"\"\n\t\tp.ClientId = \"\"\n\t\tp.ClientSecret = \"\"\n\t\tp.JumpCloudAppId = \"\"\n\t\tp.JumpCloudSecret = \"\"\n\t\tp.IssuerUrl = \"\"\n\t\tp.SamlUrl = \"\"\n\t\tp.SamlCert = \"\"\n\t\tbreak\n\tcase OneLogin:\n\t\tp.Region = \"\"\n\t\tp.Tenant = \"\"\n\t\tp.ClientId = \"\"\n\t\tp.ClientSecret = \"\"\n\t\tp.Domain = \"\"\n\t\tp.GoogleKey = \"\"\n\t\tp.GoogleEmail = \"\"\n\t\tp.JumpCloudAppId = \"\"\n\t\tp.JumpCloudSecret = \"\"\n\t\tbreak\n\tcase Okta:\n\t\tp.Region = \"\"\n\t\tp.Tenant = \"\"\n\t\tp.ClientId = \"\"\n\t\tp.ClientSecret = \"\"\n\t\tp.Domain = \"\"\n\t\tp.GoogleKey = \"\"\n\t\tp.GoogleEmail = \"\"\n\t\tp.JumpCloudAppId = \"\"\n\t\tp.JumpCloudSecret = \"\"\n\t\tbreak\n\tcase JumpCloud:\n\t\tp.Region = \"\"\n\t\tp.Tenant = \"\"\n\t\tp.ClientId = \"\"\n\t\tp.ClientSecret = \"\"\n\t\tp.Domain = \"\"\n\t\tp.GoogleKey = \"\"\n\t\tp.GoogleEmail = \"\"\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unknown_provider_type\",\n\t\t\tMessage: \"Unknown authentication provider type\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch p.RoleManagement {\n\tcase SetOnInsert, \"\":\n\t\tbreak\n\tcase Merge:\n\t\tbreak\n\tcase Overwrite:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unknown_role_management\",\n\t\t\tMessage: \"Unknown role management mode\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\ntype SecondaryProvider struct {\n\tId             bson.ObjectID `bson:\"id\" json:\"id\"`\n\tType           string        `bson:\"type\" json:\"type\"`\n\tName           string        `bson:\"name\" json:\"name\"`\n\tLabel          string        `bson:\"label\" json:\"label\"`\n\tDuoHostname    string        `bson:\"duo_hostname\" json:\"duo_hostname\"`         // duo\n\tDuoKey         string        `bson:\"duo_key\" json:\"duo_key\"`                   // duo\n\tDuoSecret      string        `bson:\"duo_secret\" json:\"duo_secret\"`             // duo\n\tOneLoginRegion string        `bson:\"one_login_region\" json:\"one_login_region\"` // onelogin\n\tOneLoginId     string        `bson:\"one_login_id\" json:\"one_login_id\"`         // onelogin\n\tOneLoginSecret string        `bson:\"one_login_secret\" json:\"one_login_secret\"` // onelogin\n\tOktaDomain     string        `bson:\"okta_domain\" json:\"okta_domain\"`           // okta\n\tOktaToken      string        `bson:\"okta_token\" json:\"okta_token\"`             // okta\n\tPushFactor     bool          `bson:\"push_factor\" json:\"push_factor\"`           // duo + onelogin + okta\n\tPhoneFactor    bool          `bson:\"phone_factor\" json:\"phone_factor\"`         // duo + onelogin + okta\n\tPasscodeFactor bool          `bson:\"passcode_factor\" json:\"passcode_factor\"`   // duo + onelogin + okta\n\tSmsFactor      bool          `bson:\"sms_factor\" json:\"sms_factor\"`             // duo + onelogin + okta\n}\n\nfunc (p *SecondaryProvider) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif p.Id.IsZero() {\n\t\tp.Id = bson.NewObjectID()\n\t}\n\n\tp.Name = utils.FilterStr(p.Name, 32)\n\tp.Label = utils.FilterStr(p.Label, 32)\n\n\tswitch p.Type {\n\tcase Duo:\n\t\tp.OneLoginRegion = \"\"\n\t\tp.OneLoginId = \"\"\n\t\tp.OneLoginSecret = \"\"\n\t\tp.OktaDomain = \"\"\n\t\tp.OktaToken = \"\"\n\t\tbreak\n\tcase OneLogin2:\n\t\tp.DuoHostname = \"\"\n\t\tp.DuoKey = \"\"\n\t\tp.DuoSecret = \"\"\n\t\tp.OktaDomain = \"\"\n\t\tp.OktaToken = \"\"\n\t\tif p.OneLoginRegion == \"\" {\n\t\t\tp.OneLoginRegion = \"us\"\n\t\t}\n\t\tbreak\n\tcase Okta:\n\t\tp.DuoHostname = \"\"\n\t\tp.DuoKey = \"\"\n\t\tp.DuoSecret = \"\"\n\t\tp.OneLoginRegion = \"\"\n\t\tp.OneLoginId = \"\"\n\t\tp.OneLoginSecret = \"\"\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unknown_secondary_provider_type\",\n\t\t\tMessage: \"Unknown secondary authentication provider type\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\ntype auth struct {\n\tId                 string               `bson:\"_id\"`\n\tServer             string               `bson:\"server\" default:\"https://auth.pritunl.com\"`\n\tSync               int                  `bson:\"sync\" json:\"sync\" default:\"1800\"`\n\tCookieAge          int                  `bson:\"cookie_age\" json:\"cookie_age\" default:\"63072000\"`\n\tProviders          []*Provider          `bson:\"providers\"`\n\tSecondaryProviders []*SecondaryProvider `bson:\"secondary_providers\"`\n\tFastLogin          bool                 `bson:\"fast_login\" json:\"fast_login\"`\n\tForceFastUserLogin bool                 `bson:\"force_fast_user_login\" json:\"force_fast_user_login\"`\n\tWindow             int                  `bson:\"window\" json:\"window\" default:\"60\"`\n\tSecondaryExpire    int                  `bson:\"secondary_expire\" json:\"secondary_expire\" default:\"90\"`\n\tAdminExpire        int                  `bson:\"admin_expire\" json:\"admin_expire\" default:\"1440\"`\n\tAdminMaxDuration   int                  `bson:\"admin_max_duration\" json:\"admin_max_duration\" default:\"4320\"`\n\tUserExpire         int                  `bson:\"user_expire\" json:\"user_expire\" default:\"1440\"`\n\tUserMaxDuration    int                  `bson:\"user_max_duration\" json:\"user_max_duration\" default:\"4320\"`\n}\n\nfunc (a *auth) GetProvider(id bson.ObjectID) *Provider {\n\tfor _, provider := range a.Providers {\n\t\tif provider.Id == id {\n\t\t\treturn provider\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *auth) GetSecondaryProvider(id bson.ObjectID) *SecondaryProvider {\n\tfor _, provider := range a.SecondaryProviders {\n\t\tif provider.Id == id {\n\t\t\treturn provider\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc newAuth() interface{} {\n\treturn &auth{\n\t\tId:                 \"auth\",\n\t\tProviders:          []*Provider{},\n\t\tSecondaryProviders: []*SecondaryProvider{},\n\t}\n}\n\nfunc updateAuth(data interface{}) {\n\tAuth = data.(*auth)\n}\n\nfunc init() {\n\tregister(\"auth\", newAuth, updateAuth)\n}\n"
  },
  {
    "path": "settings/hypervisor.go",
    "content": "package settings\n\nvar Hypervisor *hypervisor\n\ntype hypervisor struct {\n\tId                     string `bson:\"_id\"`\n\tSystemdPath            string `bson:\"systemd_path\" default:\"/etc/systemd/system\"`\n\tLibPath                string `bson:\"lib_path\" default:\"/var/lib/pritunl-cloud\"`\n\tRunPath                string `bson:\"run_path\" default:\"/var/run/pritunl-cloud\"`\n\tAgentHostPath          string `bson:\"agent_host_path\" default:\"/usr/bin/pritunl-cloud-agent\"`\n\tAgentBsdHostPath       string `bson:\"agent_bsd_host_path\" default:\"/usr/bin/pritunl-cloud-agent-bsd\"`\n\tAgentGuestPath         string `bson:\"agent_guest_path\" default:\"/usr/bin/pci\"`\n\tInitGuestPath          string `bson:\"init_guest_path\" default:\"/etc/pritunl-cloud-init\"`\n\tHugepagesPath          string `bson:\"hugepages_path\" default:\"/dev/hugepages/pritunl\"`\n\tLockCloudPass          bool   `bson:\"lock_cloud_pass\"`\n\tDesktopEnv             string `bson:\"desktop_env\" default:\"gnome\"`\n\tOvmfCodePath           string `bson:\"ovmf_code_path\"`\n\tOvmfVarsPath           string `bson:\"ovmf_vars_path\"`\n\tOvmfSecureCodePath     string `bson:\"ovmf_secure_code_path\"`\n\tOvmfSecureVarsPath     string `bson:\"ovmf_secure_vars_path\"`\n\tNbdPath                string `bson:\"nbd_path\" default:\"/dev/nbd6\"`\n\tDiskAio                string `bson:\"disk_aio\"`\n\tNoSandbox              bool   `bson:\"no_sandbox\"`\n\tGlHostMem              int    `bson:\"gl_host_mem\" default:\"2048\"`\n\tBridgeIfaceName        string `bson:\"bridge_iface_name\" default:\"br0\"`\n\tImdsIfaceName          string `bson:\"imds_iface_name\" default:\"imds0\"`\n\tNormalMtu              int    `bson:\"normal_mtu\" default:\"1500\"`\n\tJumboMtu               int    `bson:\"jumbo_mtu\" default:\"9000\"`\n\tDiskQueuesMin          int    `bson:\"disk_queues_min\" default:\"1\"`\n\tDiskQueuesMax          int    `bson:\"disk_queues_max\" default:\"4\"`\n\tNetworkQueuesMin       int    `bson:\"network_queues_min\" default:\"1\"`\n\tNetworkQueuesMax       int    `bson:\"network_queues_max\" default:\"8\"`\n\tCloudInitNetVer        int    `bson:\"cloud_init_net_ver\" default:\"1\"`\n\tHostNetwork            string `bson:\"host_network\" default:\"198.18.84.0/22\"`\n\tHostNetworkName        string `bson:\"host_network_name\" default:\"pritunlhost0\"`\n\tVirtRng                bool   `bson:\"virt_rng\"`\n\tVlanRanges             string `bson:\"vlan_ranges\" default:\"1001-3999\"`\n\tVxlanId                int    `bson:\"vxlan_id\" default:\"9417\"`\n\tVxlanDestPort          int    `bson:\"vxlan_dest_port\" default:\"4789\"`\n\tIpTimeout              int    `bson:\"ip_timeout\" default:\"30\"`\n\tIpTimeout6             int    `bson:\"ip_timeout6\" default:\"15\"`\n\tActionRate             int    `bson:\"action_rate\" default:\"3\"`\n\tNodePortNetwork        string `bson:\"node_port_network\" default:\"198.19.96.0/23\"`\n\tNodePortRanges         string `bson:\"node_port_ranges\" default:\"30000-32767\"`\n\tNodePortNetworkName    string `bson:\"node_port_network_name\" default:\"pritunlport0\"`\n\tAddressRefreshTtl      int    `bson:\"address_refresh_ttl\" default:\"1800\"`\n\tStartTimeout           int    `bson:\"start_timeout\" default:\"45\"`\n\tStopTimeout            int    `bson:\"stop_timeout\" default:\"180\"`\n\tRefreshRate            int    `bson:\"refresh_rate\" default:\"90\"`\n\tSplashTime             int    `bson:\"splash_time\" default:\"60\"`\n\tDhcpRenewTtl           int    `bson:\"dhcp_renew_ttl\" default:\"60\"`\n\tNoIpv6PingInit         bool   `bson:\"no_ipv6_ping_init\"`\n\tIpv6PingHost           string `bson:\"ipv6_ping_host\" default:\"2001:4860:4860::8888\"`\n\tImdsAddress            string `bson:\"imds_address\" default:\"169.254.169.254/32\"`\n\tImdsPort               int    `bson:\"imds_port\" default:\"80\"`\n\tImdsSyncLogTimeout     int    `bson:\"imds_sync_log_timeout\" default:\"20\"`\n\tImdsSyncRestartTimeout int    `bson:\"imds_sync_log_timeout\" default:\"30\"`\n\tInfoTtl                int    `bson:\"info_ttl\" default:\"10\"`\n\tNoGuiFullscreen        bool   `bson:\"no_gui_fullscreen\"`\n\tUsbHsPorts             int    `bson:\"usb_hs_ports\" default:\"4\"`\n\tUsbSsPorts             int    `bson:\"usb_ss_ports\" default:\"4\"`\n\tNoVirtioHid            bool   `bson:\"no_virtio_hid\"`\n\tJournalDisplayLimit    int64  `bson:\"journal_display_limit\" default:\"3000\"`\n\tDhcpLifetime           int    `bson:\"dhcp_lifetime\" default:\"3600\"`\n\tNdpRaInterval          int    `bson:\"ndp_ra_interval\" default:\"6\"`\n\tDnsServerPrimary       string `bson:\"dns_server_primary\" default:\"8.8.8.8\"`\n\tDnsServerSecondary     string `bson:\"dns_server_secondary\" default:\"8.8.4.4\"`\n\tDnsServerPrimary6      string `bson:\"dns_server_primary6\" default:\"2001:4860:4860::8888\"`\n\tDnsServerSecondary6    string `bson:\"dns_server_secondary6\" default:\"2001:4860:4860::8844\"`\n\tNodePortMaxAttempts    int    `bson:\"node_port_max_attempts\" default:\"10000\"`\n\tMaxDeploymentFailures  int    `bson:\"max_deployment_failures\" default:\"3\"`\n}\n\nfunc newHypervisor() interface{} {\n\treturn &hypervisor{\n\t\tId: \"hypervisor\",\n\t}\n}\n\nfunc updateHypervisor(data interface{}) {\n\tHypervisor = data.(*hypervisor)\n}\n\nfunc init() {\n\tregister(\"hypervisor\", newHypervisor, updateHypervisor)\n}\n"
  },
  {
    "path": "settings/local.go",
    "content": "package settings\n\nvar Local *local\n\ntype local struct {\n\tAppId       string\n\tFacets      []string\n\tNoLocalAuth bool\n\tDisableWeb  bool\n\tDisableMsg  string\n}\n\nfunc init() {\n\tLocal = &local{}\n}\n"
  },
  {
    "path": "settings/registry.go",
    "content": "package settings\n\nvar (\n\tregistry = map[string]*group{}\n)\n\ntype newFunc func() interface{}\ntype updateFunc func(interface{})\n\ntype group struct {\n\tNew    newFunc\n\tUpdate updateFunc\n}\n\nfunc register(name string, new newFunc, update updateFunc) {\n\tgrp := &group{\n\t\tNew:    new,\n\t\tUpdate: update,\n\t}\n\n\tregistry[name] = grp\n}\n"
  },
  {
    "path": "settings/router.go",
    "content": "package settings\n\nvar Router *router\n\ntype router struct {\n\tId                   string `bson:\"_id\"`\n\tReadTimeout          int    `bson:\"read_timeout\" default:\"300\"`\n\tReadHeaderTimeout    int    `bson:\"read_header_timeout\" default:\"60\"`\n\tWriteTimeout         int    `bson:\"write_timeout\" default:\"300\"`\n\tIdleTimeout          int    `bson:\"idle_timeout\" default:\"60\"`\n\tDialTimeout          int    `bson:\"dial_timeout\" default:\"60\"`\n\tDialKeepAlive        int    `bson:\"dial_keep_alive\" default:\"60\"`\n\tMaxIdleConns         int    `bson:\"max_idle_conns\" default:\"1000\"`\n\tMaxIdleConnsPerHost  int    `bson:\"max_idle_conns_per_host\" default:\"100\"`\n\tIdleConnTimeout      int    `bson:\"idle_conn_timeout\" default:\"90\"`\n\tHandshakeTimeout     int    `bson:\"handshake_timeout\" default:\"10\"`\n\tContinueTimeout      int    `bson:\"continue_timeout\" default:\"10\"`\n\tMaxHeaderBytes       int    `bson:\"max_header_bytes\" default:\"4194304\"`\n\tForceRedirectSystemd bool   `bson:\"force_redirect_systemd\"`\n\tSkipVerify           bool   `bson:\"skip_verify\"`\n\tProxyResolverRefresh int    `bson:\"proxy_resolver_refresh\" default:\"30\"`\n\tProxyResolverTtl     int    `bson:\"proxy_resolver_ttl\" default:\"30\"`\n}\n\nfunc newRouter() interface{} {\n\treturn &router{\n\t\tId: \"router\",\n\t}\n}\n\nfunc updateRouter(data interface{}) {\n\tRouter = data.(*router)\n}\n\nfunc init() {\n\tregister(\"router\", newRouter, updateRouter)\n}\n"
  },
  {
    "path": "settings/settings.go",
    "content": "package settings\n\nimport (\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc Commit(db *database.Database, group interface{}, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Settings()\n\n\tselector := database.SelectFields(group, set.NewSet(\"_id\"))\n\tupdated := database.SelectFields(group, fields)\n\t_, err = coll.UpdateOne(\n\t\tdb,\n\t\tselector,\n\t\t&bson.M{\n\t\t\t\"$set\": updated,\n\t\t},\n\t\toptions.UpdateOne().SetUpsert(true),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Get(db *database.Database, group string, key string) (\n\tval interface{}, err error) {\n\n\tcoll := db.Settings()\n\n\tgrp := map[string]interface{}{}\n\n\terr = coll.FindOne(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": group,\n\t\t},\n\t\toptions.FindOne().SetProjection(&bson.D{\n\t\t\t\t{key, 1},\n\t\t\t}),\n\t).Decode(grp)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\t\treturn\n\t\tdefault:\n\t\t\terr = &errortypes.DatabaseError{\n\t\t\t\terrors.Wrap(err, \"settings: Database error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tval = grp[key]\n\treturn\n}\n\nfunc Set(db *database.Database, group string, key string, val interface{}) (\n\terr error) {\n\n\tcoll := db.Settings()\n\t_, err = coll.UpdateOne(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": group,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\tkey: val,\n\t\t\t},\n\t\t},\n\t\toptions.UpdateOne().SetUpsert(true),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Unset(db *database.Database, group string, key string) (\n\terr error) {\n\n\tcoll := db.Settings()\n\t_, err = coll.UpdateOne(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": group,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$unset\": &bson.M{\n\t\t\t\tkey: 1,\n\t\t\t},\n\t\t},\n\t\toptions.UpdateOne().SetUpsert(true),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc setDefaults(obj interface{}) {\n\tval := reflect.ValueOf(obj)\n\telm := val.Elem()\n\n\tn := elm.NumField()\n\tfor i := 0; i < n; i++ {\n\t\tfld := elm.Field(i)\n\t\ttyp := elm.Type().Field(i)\n\n\t\tif typ.PkgPath != \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\ttag := typ.Tag.Get(\"default\")\n\t\tif tag == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch fld.Kind() {\n\t\tcase reflect.Bool:\n\t\t\tparVal, err := strconv.ParseBool(tag)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tfld.SetBool(parVal)\n\n\t\t\tbreak\n\t\tcase reflect.Int, reflect.Int64:\n\t\t\tif fld.Int() != 0 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tparVal, err := strconv.Atoi(tag)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tfld.SetInt(int64(parVal))\n\n\t\t\tbreak\n\t\tcase reflect.String:\n\t\t\tif fld.String() != \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tfld.SetString(tag)\n\n\t\t\tbreak\n\t\tcase reflect.Slice:\n\t\t\tif fld.Len() != 0 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tsliceType := reflect.TypeOf(fld.Interface()).Elem()\n\t\t\tvals := strings.Split(tag, \",\")\n\t\t\tn := len(vals)\n\t\t\tslice := reflect.MakeSlice(reflect.SliceOf(sliceType), n, n)\n\n\t\t\tswitch sliceType.Kind() {\n\t\t\tcase reflect.Bool:\n\t\t\t\tfor i, val := range vals {\n\t\t\t\t\tparVal, err := strconv.ParseBool(val)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\t\t\t\t\tslice.Index(i).SetBool(parVal)\n\t\t\t\t}\n\t\t\tcase reflect.Int:\n\t\t\t\tfor i, val := range vals {\n\t\t\t\t\tparVal, err := strconv.Atoi(val)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\t\t\t\t\tslice.Index(i).SetInt(int64(parVal))\n\t\t\t\t}\n\t\t\tcase reflect.String:\n\t\t\t\tfor i, val := range vals {\n\t\t\t\t\tslice.Index(i).SetString(val)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfld.Set(slice)\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Update(name string) (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tcoll := db.Settings()\n\tgroup := registry[name]\n\tdata := group.New()\n\n\terr = database.IgnoreNotFoundError(coll.FindOneId(name, data))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsetDefaults(data)\n\n\tgroup.Update(data)\n\n\treturn\n}\n\nfunc update() {\n\tfor {\n\t\ttime.Sleep(10 * time.Second)\n\t\tif constants.Shutdown {\n\t\t\treturn\n\t\t}\n\n\t\tfor name := range registry {\n\t\t\terr := Update(name)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"settings: Update error\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc init() {\n\tmodule := requires.New(\"settings\")\n\tmodule.After(\"database\")\n\n\tmodule.Handler = func() (err error) {\n\t\tfor name := range registry {\n\t\t\terr = Update(name)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif System.DatabaseVersion == 0 {\n\t\t\tSystem.DatabaseVersion = constants.DatabaseVersion\n\t\t\terr = Commit(db, System, set.NewSet(\"database_version\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif System.DatabaseVersion > constants.DatabaseVersion {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"database_version\": System.DatabaseVersion,\n\t\t\t\t\"software_version\": constants.DatabaseVersion,\n\t\t\t}).Error(\"settings: Database version newer then software\")\n\n\t\t\terr = &errortypes.DatabaseError{\n\t\t\t\terrors.New(\n\t\t\t\t\t\"settings: Database version newer then software\"),\n\t\t\t}\n\t\t\treturn\n\t\t} else if System.DatabaseVersion != constants.DatabaseVersion {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"database_version\":     System.DatabaseVersion,\n\t\t\t\t\"new_database_version\": constants.DatabaseVersion,\n\t\t\t}).Info(\"settings: Upgrading database version\")\n\n\t\t\tSystem.DatabaseVersion = constants.DatabaseVersion\n\t\t\terr = Commit(db, System, set.NewSet(\"database_version\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif System.Name == \"\" {\n\t\t\tSystem.Name = utils.RandName()\n\t\t\terr = Commit(db, System, set.NewSet(\"name\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif Auth.Providers == nil {\n\t\t\tAuth.Providers = []*Provider{}\n\t\t\terr = Commit(db, Auth, set.NewSet(\"providers\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif Auth.SecondaryProviders == nil {\n\t\t\tAuth.SecondaryProviders = []*SecondaryProvider{}\n\t\t\terr = Commit(db, Auth, set.NewSet(\"secondary_providers\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tgo update()\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "settings/system.go",
    "content": "package settings\n\nvar System *system\n\ntype system struct {\n\tId                   string `bson:\"_id\"`\n\tName                 string `bson:\"name\"`\n\tDatabaseVersion      int    `bson:\"database_version\"`\n\tDemo                 bool   `bson:\"demo\"`\n\tLicense              string `bson:\"license\"`\n\tAdminCookieAuthKey   []byte `bson:\"admin_cookie_auth_key\"`\n\tAdminCookieCryptoKey []byte `bson:\"admin_cookie_crypto_key\"`\n\tUserCookieAuthKey    []byte `bson:\"user_cookie_auth_key\"`\n\tUserCookieCryptoKey  []byte `bson:\"user_cookie_crypto_key\"`\n\tNodeTimestampTtl     int    `bson:\"node_timestamp_ttl\" default:\"15\"`\n\tInstanceTimestampTtl int    `bson:\"instance_timestamp_ttl\" default:\"20\"`\n\tDomainLockTtl        int    `bson:\"domain_lock_ttl\" default:\"30\"`\n\tDomainDeleteTtl      int    `bson:\"domain_delete_ttl\" default:\"200\"`\n\tDomainRefreshTtl     int    `bson:\"domain_refresh_ttl\" default:\"90\"`\n\tAcmeKeyAlgorithm     string `bson:\"acme_key_algorithm\" default:\"rsa\"`\n\tDiskBackupWindow     int    `bson:\"disk_backup_window\" default:\"6\"`\n\tDiskBackupTime       int    `bson:\"disk_backup_time\" default:\"10\"`\n\tPlannerBatchSize     int    `bson:\"planner_batch_size\" default:\"10\"`\n\tNoMigrateRefresh     bool   `bson:\"no_migrate_refresh\"`\n\tOracleApiRetryRate   int    `bson:\"oracle_api_retry_rate\" default:\"1\"`\n\tOracleApiRetryCount  int    `bson:\"oracle_api_retry_count\" default:\"120\"`\n\tTwilioAccount        string `bson:\"twilio_account\"`\n\tTwilioSecret         string `bson:\"twilio_secret\"`\n\tTwilioNumber         string `bson:\"twilio_number\"`\n}\n\nfunc newSystem() interface{} {\n\treturn &system{\n\t\tId: \"system\",\n\t}\n}\n\nfunc updateSystem(data interface{}) {\n\tSystem = data.(*system)\n}\n\nfunc init() {\n\tregister(\"system\", newSystem, updateSystem)\n}\n"
  },
  {
    "path": "settings/telemetry.go",
    "content": "package settings\n\nvar Telemetry *telemetry\n\ntype telemetry struct {\n\tId              string `bson:\"_id\"`\n\tNvdTtl          int    `bson:\"nvd_ttl\" default:\"21600\"`\n\tNvdFinalTtl     int    `bson:\"nvd_final_ttl\" default:\"604800\"`\n\tNvdApiLimit     int    `bson:\"nvd_api_limit\" default:\"8\"`\n\tNvdApiAuthLimit int    `bson:\"nvd_api_auth_limit\" default:\"1\"`\n\tNvdApiKey       string `bson:\"nvd_api_key\"`\n}\n\nfunc newTelemetry() interface{} {\n\treturn &telemetry{\n\t\tId: \"telemetry\",\n\t}\n}\n\nfunc updateTelemetry(data interface{}) {\n\tTelemetry = data.(*telemetry)\n}\n\nfunc init() {\n\tregister(\"telemetry\", newTelemetry, updateTelemetry)\n}\n"
  },
  {
    "path": "setup/iptables.go",
    "content": "package setup\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/ipset\"\n\t\"github.com/pritunl/pritunl-cloud/iptables\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\nfunc Iptables() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tnamespaces, err := utils.GetNamespaces()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnodeDatacenter, err := node.Self.GetDatacenter(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvpcs := []*vpc.Vpc{}\n\tif !nodeDatacenter.IsZero() {\n\t\tvpcs, err = vpc.GetDatacenter(db, nodeDatacenter)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tinstances, err := instance.GetAllVirt(db, &bson.M{\n\t\t\"node\": node.Self.Id,\n\t}, nil, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tspecRules, nodePortsMap, err := firewall.GetSpecRulesSlow(\n\t\tdb, node.Self.Id, instances)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnodeFirewall, firewalls, firewallMaps, _, err := firewall.GetAllIngress(\n\t\tdb, node.Self, instances, specRules, nodePortsMap)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = ipset.Init(namespaces, instances, nodeFirewall, firewalls)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = iptables.Init(namespaces, vpcs, instances, nodeFirewall,\n\t\tfirewalls, firewallMaps)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = ipset.InitNames(namespaces, instances, nodeFirewall, firewalls)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "shape/constants.go",
    "content": "package shape\n\nconst (\n\tInstance = \"instance\"\n\n\tQcow2 = \"qcow2\"\n\tLvm   = \"lvm\"\n)\n"
  },
  {
    "path": "shape/node.go",
    "content": "package shape\n\nimport (\n\t\"sort\"\n\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\ntype Nodes []*node.Node\n\nfunc (n Nodes) Len() int {\n\treturn len(n)\n}\n\nfunc (n Nodes) Less(i, j int) bool {\n\treturn n[i].Usage() < n[j].Usage()\n}\n\nfunc (n Nodes) Swap(i, j int) {\n\tn[i], n[j] = n[j], n[i]\n}\n\nfunc (n Nodes) Sort() {\n\tsort.Sort(n)\n}\n"
  },
  {
    "path": "shape/shape.go",
    "content": "package shape\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\ntype Shape struct {\n\tId               bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName             string        `bson:\"name\" json:\"name\"`\n\tComment          string        `bson:\"comment\" json:\"comment\"`\n\tType             string        `bson:\"type\" json:\"type\"`\n\tDeleteProtection bool          `bson:\"delete_protection\" json:\"delete_protection\"`\n\tDatacenter       bson.ObjectID `bson:\"datacenter\" json:\"datacenter\"`\n\tRoles            []string      `bson:\"roles\" json:\"roles\"`\n\tFlexible         bool          `bson:\"flexible\" json:\"flexible\"`\n\tDiskType         string        `bson:\"disk_type\" json:\"disk_type\"`\n\tDiskPool         bson.ObjectID `bson:\"disk_pool\" json:\"disk_pool\"`\n\tMemory           int           `bson:\"memory\" json:\"memory\"`\n\tProcessors       int           `bson:\"processors\" json:\"processors\"`\n\tNodeCount        int           `bson:\"-\" json:\"node_count\"`\n}\n\ntype Completion struct {\n\tId         bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName       string        `bson:\"name\" json:\"name\"`\n\tDatacenter bson.ObjectID `bson:\"datacenter\" json:\"datacenter\"`\n\tFlexible   bool          `bson:\"flexible\" json:\"flexible\"`\n\tMemory     int           `bson:\"memory\" json:\"memory\"`\n\tProcessors int           `bson:\"processors\" json:\"processors\"`\n}\n\nfunc (s *Shape) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\ts.Name = utils.FilterName(s.Name)\n\n\tif s.Type == \"\" {\n\t\ts.Type = Instance\n\t}\n\n\tswitch s.Type {\n\tcase Instance:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_shape_type\",\n\t\t\tMessage: \"Shape type invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.Roles == nil {\n\t\ts.Roles = []string{}\n\t}\n\n\tswitch s.DiskType {\n\tcase \"\", Qcow2:\n\t\ts.DiskType = Qcow2\n\t\tbreak\n\tcase Lvm:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_disk_type\",\n\t\t\tMessage: \"Disk type invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.Datacenter.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"missing_datacenter\",\n\t\t\tMessage: \"Shape datacenter required\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Shape) FindNode(db *database.Database, processors, memory int) (\n\tnde *node.Node, err error) {\n\n\tzones, err := zone.GetAllDatacenter(db, s.Datacenter)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzoneIds := []bson.ObjectID{}\n\tfor _, zne := range zones {\n\t\tzoneIds = append(zoneIds, zne.Id)\n\t}\n\n\tndes, err := node.GetAllShape(db, zoneIds, s.Roles)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tNodes(ndes).Sort()\n\n\tfor _, nd := range ndes {\n\t\tnde = nd\n\t\treturn\n\t}\n\n\terr = &errortypes.NotFoundError{\n\t\terrors.New(\"shape: Failed to find available node\"),\n\t}\n\treturn\n}\n\nfunc (s *Shape) Commit(db *database.Database) (err error) {\n\tcoll := db.Shapes()\n\n\terr = coll.Commit(s.Id, s)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Shape) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Shapes()\n\n\terr = coll.CommitFields(s.Id, s, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Shape) Insert(db *database.Database) (err error) {\n\tcoll := db.Shapes()\n\n\t_, err = coll.InsertOne(db, s)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "shape/utils.go",
    "content": "package shape\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, shapeId bson.ObjectID) (\n\tshpe *Shape, err error) {\n\n\tcoll := db.Shapes()\n\tshpe = &Shape{}\n\n\terr = coll.FindOneId(shapeId, shpe)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (shpe *Shape, err error) {\n\tcoll := db.Shapes()\n\tshpe = &Shape{}\n\n\terr = coll.FindOne(db, query).Decode(shpe)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tshapes []*Shape, err error) {\n\n\tcoll := db.Shapes()\n\tshapes = []*Shape{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tnde := &Shape{}\n\t\terr = cursor.Decode(nde)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tshapes = append(shapes, nde)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (shapes []*Shape, count int64, err error) {\n\n\tcoll := db.Shapes()\n\tshapes = []*Shape{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tshpe := &Shape{}\n\t\terr = cursor.Decode(shpe)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tshapes = append(shapes, shpe)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNames(db *database.Database, query *bson.M) (\n\tshapes []*Shape, err error) {\n\n\tcoll := db.Shapes()\n\tshapes = []*Shape{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetProjection(&bson.D{\n\t\t\t\t{\"_id\", 1},\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"type\", 1},\n\t\t\t\t{\"zone\", 1},\n\t\t\t\t{\"flexible\", 1},\n\t\t\t\t{\"disk_type\", 1},\n\t\t\t\t{\"disk_pool\", 1},\n\t\t\t\t{\"memory\", 1},\n\t\t\t\t{\"processors\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tshpe := &Shape{}\n\t\terr = cursor.Decode(shpe)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tshapes = append(shapes, shpe)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, shapeId bson.ObjectID) (err error) {\n\tcoll := db.Shapes()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":               shapeId,\n\t\t\"delete_protection\": false,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, shapeIds []bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Shapes()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": shapeIds,\n\t\t},\n\t\t\"delete_protection\": false,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "signature/signature.go",
    "content": "package signature\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha512\"\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/nonce\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Signature struct {\n\tToken     string\n\tNonce     string\n\tTimestamp time.Time\n\tSignature string\n\tMethod    string\n\tPath      string\n\tuser      *user.User\n}\n\nfunc (s *Signature) GetUser(db *database.Database) (\n\tusr *user.User, err error) {\n\n\tif s.user != nil || db == nil || s.Token == \"\" {\n\t\tusr = s.user\n\t\treturn\n\t}\n\n\tusr, err = user.GetTokenUpdate(db, s.Token)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ts.user = usr\n\n\treturn\n}\n\nfunc (s *Signature) Validate(db *database.Database) (err error) {\n\tif s.Token == \"\" {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"signature: Invalid authentication token\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(s.Nonce) < 16 || len(s.Nonce) > 128 {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"signature: Invalid authentication nonce\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif utils.SinceAbs(s.Timestamp) > time.Duration(\n\t\tsettings.Auth.Window)*time.Second {\n\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"signature: Authentication timestamp outside window\"),\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := s.GetUser(db)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\tusr = nil\n\t\t\terr = nil\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\tif usr == nil || usr.Type != user.Api ||\n\t\tusr.Token == \"\" || usr.Secret == \"\" {\n\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"signature: User not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tauthString := strings.Join([]string{\n\t\tusr.Token,\n\t\tstrconv.FormatInt(s.Timestamp.Unix(), 10),\n\t\ts.Nonce,\n\t\ts.Method,\n\t\ts.Path,\n\t}, \"&\")\n\n\terr = nonce.Validate(db, s.Nonce)\n\tif err != nil {\n\t\treturn\n\t}\n\n\thashFunc := hmac.New(sha512.New, []byte(usr.Secret))\n\thashFunc.Write([]byte(authString))\n\trawSignature := hashFunc.Sum(nil)\n\tsig := base64.StdEncoding.EncodeToString(rawSignature)\n\n\tif subtle.ConstantTimeCompare([]byte(s.Signature), []byte(sig)) != 1 {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"signature: Invalid signature\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "signature/utils.go",
    "content": "package signature\n\nimport (\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc Parse(token, sigStr, timeStr, nonce, method, path string) (\n\tsig *Signature, err error) {\n\n\ttimestampInt, _ := strconv.ParseInt(timeStr, 10, 64)\n\tif timestampInt == 0 {\n\t\terr = &errortypes.AuthenticationError{\n\t\t\terrors.New(\"signature: Invalid authentication timestamp\"),\n\t\t}\n\t\treturn\n\t}\n\n\ttimestamp := time.Unix(timestampInt, 0)\n\n\tsig = &Signature{\n\t\tToken:     token,\n\t\tNonce:     nonce,\n\t\tTimestamp: timestamp,\n\t\tSignature: sigStr,\n\t\tMethod:    method,\n\t\tPath:      path,\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "spec/constants.go",
    "content": "package spec\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n)\n\nvar resourcesRe = regexp.MustCompile(\"(?s)```yaml(.*?)```\")\n\nconst (\n\tAll       = \"all\"\n\tIcmp      = \"icmp\"\n\tTcp       = \"tcp\"\n\tUdp       = \"udp\"\n\tMulticast = \"multicast\"\n\tBroadcast = \"broadcast\"\n\n\tHost         = \"host\"\n\tPrivate      = \"private\"\n\tPrivate6     = \"private6\"\n\tPublic       = \"public\"\n\tPublic6      = \"public6\"\n\tCloudPublic  = \"cloud_public\"\n\tCloudPublic6 = \"cloud_public6\"\n\tCloudPrivate = \"cloud_private\"\n\n\tSystemd = \"systemd\"\n\tFile    = \"file\"\n\n\tTokenPrefix = \"+/\"\n\n\tDisk     = \"disk\"\n\tHostPath = \"host_path\"\n)\n\ntype Base struct {\n\tKind string `yaml:\"kind\"`\n}\n\nconst (\n\tUnit = \"unit\"\n)\n\ntype Refrence struct {\n\tId       bson.ObjectID `bson:\"id\" json:\"id\"`\n\tRealm    bson.ObjectID `bson:\"realm\" json:\"realm\"`\n\tKind     string        `bson:\"kind\" json:\"kind\"`\n\tSelector string        `bson:\"selector\" json:\"selector\"`\n}\n"
  },
  {
    "path": "spec/domain.go",
    "content": "package spec\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Domain struct {\n\tRecords []*Record `bson:\"records\" json:\"records\"`\n}\n\nfunc (d *Domain) Validate() (errData *errortypes.ErrorData, err error) {\n\tfor _, rec := range d.Records {\n\t\trec.Name = utils.FilterDomain(rec.Name)\n\n\t\tswitch rec.Type {\n\t\tcase Host:\n\t\t\tbreak\n\t\tcase Private:\n\t\t\tbreak\n\t\tcase Private6:\n\t\t\tbreak\n\t\tcase Public:\n\t\t\tbreak\n\t\tcase Public6:\n\t\t\tbreak\n\t\tcase CloudPublic:\n\t\t\tbreak\n\t\tcase CloudPrivate:\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"unknown_domain_record_type\",\n\t\t\t\tMessage: \"Unknown domain record type\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\ntype Record struct {\n\tName   string        `bson:\"name\" json:\"name\"`\n\tDomain bson.ObjectID `bson:\"domain\" json:\"domain\"`\n\tType   string        `bson:\"type\" json:\"type\"`\n}\n\ntype DomainYaml struct {\n\tName    string             `yaml:\"name\"`\n\tKind    string             `yaml:\"kind\"`\n\tRecords []DomainYamlRecord `yaml:\"records\"`\n}\n\ntype DomainYamlRecord struct {\n\tName   string `yaml:\"name\"`\n\tDomain string `yaml:\"domain\"`\n\tType   string `yaml:\"type\"`\n}\n"
  },
  {
    "path": "spec/firewall.go",
    "content": "package spec\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype Firewall struct {\n\tIngress []*Rule `bson:\"ingress\" json:\"ingress\"`\n}\n\ntype Rule struct {\n\tProtocol  string      `bson:\"protocol\" json:\"protocol\"`\n\tPort      string      `bson:\"port\" json:\"port\"`\n\tSourceIps []string    `bson:\"source_ips\" json:\"source_ips\"`\n\tSources   []*Refrence `bson:\"sources\" json:\"sources\"`\n}\n\nfunc (f *Firewall) Validate() (errData *errortypes.ErrorData, err error) {\n\tif f.Ingress == nil {\n\t\tf.Ingress = []*Rule{}\n\t}\n\n\tfor _, rule := range f.Ingress {\n\t\tswitch rule.Protocol {\n\t\tcase All:\n\t\t\trule.Port = \"\"\n\t\t\tbreak\n\t\tcase Icmp:\n\t\t\trule.Port = \"\"\n\t\t\tbreak\n\t\tcase Tcp, Udp, Multicast, Broadcast:\n\t\t\tports := strings.Split(rule.Port, \"-\")\n\n\t\t\tportInt, e := strconv.Atoi(ports[0])\n\t\t\tif e != nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_ingress_rule_port\",\n\t\t\t\t\tMessage: \"Invalid ingress rule port\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif portInt < 1 || portInt > 65535 {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_ingress_rule_port\",\n\t\t\t\t\tMessage: \"Invalid ingress rule port\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tparsedPort := strconv.Itoa(portInt)\n\t\t\tif len(ports) > 1 {\n\t\t\t\tportInt2, e := strconv.Atoi(ports[1])\n\t\t\t\tif e != nil {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"invalid_ingress_rule_port\",\n\t\t\t\t\t\tMessage: \"Invalid ingress rule port\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif portInt < 1 || portInt > 65535 || portInt2 <= portInt {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"invalid_ingress_rule_port\",\n\t\t\t\t\t\tMessage: \"Invalid ingress rule port\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tparsedPort += \"-\" + strconv.Itoa(portInt2)\n\t\t\t}\n\n\t\t\trule.Port = parsedPort\n\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_ingress_rule_protocol\",\n\t\t\t\tMessage: \"Invalid ingress rule protocol\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif rule.Sources == nil {\n\t\t\trule.Sources = []*Refrence{}\n\t\t}\n\n\t\tif rule.SourceIps == nil {\n\t\t\trule.SourceIps = []string{}\n\t\t}\n\n\t\tfor i, sourceIp := range rule.SourceIps {\n\t\t\tif sourceIp == \"\" {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_ingress_rule_source_ip\",\n\t\t\t\t\tMessage: \"Empty ingress rule source IP\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !strings.Contains(sourceIp, \"/\") {\n\t\t\t\tif strings.Contains(sourceIp, \":\") {\n\t\t\t\t\tsourceIp += \"/128\"\n\t\t\t\t} else {\n\t\t\t\t\tsourceIp += \"/32\"\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t_, sourceCidr, e := net.ParseCIDR(sourceIp)\n\t\t\tif e != nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"invalid_ingress_rule_source_ip\",\n\t\t\t\t\tMessage: \"Invalid ingress rule source IP\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trule.SourceIps[i] = sourceCidr.String()\n\t\t}\n\n\t\tif rule.Protocol == Multicast || rule.Protocol == Broadcast {\n\t\t\trule.Sources = []*Refrence{}\n\t\t\trule.SourceIps = []string{}\n\t\t}\n\t}\n\n\treturn\n}\n\ntype FirewallYaml struct {\n\tName    string                `yaml:\"name\"`\n\tKind    string                `yaml:\"kind\"`\n\tIngress []FirewallYamlIngress `yaml:\"ingress\"`\n}\n\ntype FirewallYamlIngress struct {\n\tProtocol string   `yaml:\"protocol\"`\n\tPort     string   `yaml:\"port\"`\n\tSource   []string `yaml:\"source\"`\n}\n"
  },
  {
    "path": "spec/instance.go",
    "content": "package spec\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n)\n\ntype Instance struct {\n\tPlan                bson.ObjectID   `bson:\"plan,omitempty\" json:\"plan\"`                           // clear\n\tDatacenter          bson.ObjectID   `bson:\"datacenter\" json:\"datacenter\"`                         // hard\n\tZone                bson.ObjectID   `bson:\"zone\" json:\"zone\"`                                     // hard\n\tNode                bson.ObjectID   `bson:\"node,omitempty\" json:\"node\"`                           // hard\n\tShape               bson.ObjectID   `bson:\"shape,omitempty\" json:\"shape\"`                         // hard\n\tVpc                 bson.ObjectID   `bson:\"vpc\" json:\"vpc\"`                                       // hard\n\tSubnet              bson.ObjectID   `bson:\"subnet\" json:\"subnet\"`                                 // hard\n\tRoles               []string        `bson:\"roles\" json:\"roles\"`                                   // soft\n\tProcessors          int             `bson:\"processors\" json:\"processors\"`                         // soft\n\tMemory              int             `bson:\"memory\" json:\"memory\"`                                 // soft\n\tUefi                *bool           `bson:\"uefi,omitempty\" json:\"uefi\"`                           // soft\n\tSecureBoot          *bool           `bson:\"secure_boot,omitempty\" json:\"secure_boot\"`             // soft\n\tCloudType           string          `bson:\"cloud_type\" json:\"cloud_type\"`                         // soft\n\tTpm                 bool            `bson:\"tpm\" json:\"tpm\"`                                       // soft\n\tVnc                 bool            `bson:\"vnc\" json:\"vnc\"`                                       // soft\n\tDeleteProtection    bool            `bson:\"delete_protection\" json:\"delete_protection\"`           // soft\n\tSkipSourceDestCheck bool            `bson:\"skip_source_dest_check\" json:\"skip_source_dest_check\"` // soft\n\tGui                 bool            `bson:\"gui\" json:\"gui\"`                                       // soft\n\tHostAddress         *bool           `bson:\"host_address,omitempty\" json:\"host_address\"`           // soft\n\tPublicAddress       *bool           `bson:\"public_address,omitempty\" json:\"public_address\"`       // soft\n\tPublicAddress6      *bool           `bson:\"public_address6,omitempty\" json:\"public_address6\"`     // soft\n\tDhcpServer          bool            `bson:\"dhcp_server\" json:\"dhcp_server\"`                       // soft\n\tImage               bson.ObjectID   `bson:\"image\" json:\"image\"`                                   // hard\n\tDiskSize            int             `bson:\"disk_size\" json:\"disk_size\"`                           // hard\n\tMounts              []Mount         `bson:\"mounts\" json:\"mounts\"`                                 // hard\n\tNodePorts           []NodePort      `bson:\"node_ports\" json:\"node_ports\"`                         // soft\n\tCertificates        []bson.ObjectID `bson:\"certificates\" json:\"certificates\"`                     // soft\n\tSecrets             []bson.ObjectID `bson:\"secrets\" json:\"secrets\"`                               // soft\n\tPods                []bson.ObjectID `bson:\"pods\" json:\"pods\"`                                     // soft\n}\n\ntype NodePort struct {\n\tProtocol     string `bson:\"protocol\" json:\"protocol\"`\n\tExternalPort int    `bson:\"external_port\" json:\"external_port\"`\n\tInternalPort int    `bson:\"internal_port\" json:\"internal_port\"`\n}\n\nfunc (m *NodePort) Validate() (\n\terrData *errortypes.ErrorData, err error) {\n\n\tswitch m.Protocol {\n\tcase Tcp, Udp:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_protocol\",\n\t\t\tMessage: \"Invalid node port protocol\",\n\t\t}\n\t\treturn\n\t}\n\n\tportRanges, e := nodeport.GetPortRanges()\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tmatched := false\n\tfor _, ports := range portRanges {\n\t\tif ports.Contains(m.ExternalPort) {\n\t\t\tmatched = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !matched {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_external_port\",\n\t\t\tMessage: \"Invalid external node port\",\n\t\t}\n\t\treturn\n\t}\n\n\tif m.InternalPort <= 0 || m.InternalPort > 65535 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_internal_port\",\n\t\t\tMessage: \"Invalid internal node port\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (i *Instance) DiffNodePorts(newNodePorts []NodePort) bool {\n\tif len(i.NodePorts) != len(newNodePorts) {\n\t\treturn true\n\t}\n\n\tfor x := range i.NodePorts {\n\t\tif i.NodePorts[x].Protocol != newNodePorts[x].Protocol ||\n\t\t\ti.NodePorts[x].ExternalPort != newNodePorts[x].ExternalPort ||\n\t\t\ti.NodePorts[x].InternalPort != newNodePorts[x].InternalPort {\n\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (i *Instance) MemoryUnits() float64 {\n\treturn float64(i.Memory) / float64(1024)\n}\n\ntype Mount struct {\n\tName     string          `bson:\"name\" json:\"name\"`\n\tType     string          `bson:\"type\" json:\"type\"`\n\tPath     string          `bson:\"path\" json:\"path\"`\n\tHostPath string          `bson:\"host_path\" json:\"host_path\"`\n\tDisks    []bson.ObjectID `bson:\"disks\" json:\"disks\"`\n}\n\ntype InstanceYaml struct {\n\tName                string                 `yaml:\"name\"`\n\tKind                string                 `yaml:\"kind\"`\n\tCount               int                    `yaml:\"count\"`\n\tPlan                string                 `yaml:\"plan\"`\n\tZone                string                 `yaml:\"zone\"`\n\tNode                string                 `yaml:\"node,omitempty\"`\n\tShape               string                 `yaml:\"shape,omitempty\"`\n\tVpc                 string                 `yaml:\"vpc\"`\n\tSubnet              string                 `yaml:\"subnet\"`\n\tRoles               []string               `yaml:\"roles\"`\n\tProcessors          int                    `yaml:\"processors\"`\n\tMemory              int                    `yaml:\"memory\"`\n\tUefi                *bool                  `yaml:\"uefi\"`\n\tSecureBoot          *bool                  `yaml:\"secureBoot\"`\n\tCloudType           string                 `yaml:\"cloudType\"`\n\tTpm                 bool                   `yaml:\"tpm\"`\n\tVnc                 bool                   `yaml:\"vnc\"`\n\tDeleteProtection    bool                   `yaml:\"deleteProtection\"`\n\tSkipSourceDestCheck bool                   `yaml:\"skipSourceDestCheck\"`\n\tGui                 bool                   `yaml:\"gui\"`\n\tHostAddress         *bool                  `yaml:\"hostAddress\"`\n\tPublicAddress       *bool                  `yaml:\"publicAddress\"`\n\tPublicAddress6      *bool                  `yaml:\"publicAddress6\"`\n\tDhcpServer          bool                   `yaml:\"dhcpServer\"`\n\tImage               string                 `yaml:\"image\"`\n\tMounts              []InstanceMountYaml    `yaml:\"mounts\"`\n\tNodePorts           []InstanceNodePortYaml `yaml:\"nodePorts\"`\n\tCertificates        []string               `yaml:\"certificates\"`\n\tSecrets             []string               `yaml:\"secrets\"`\n\tPods                []string               `yaml:\"pods\"`\n\tDiskSize            int                    `yaml:\"diskSize\"`\n}\n\ntype InstanceMountYaml struct {\n\tName     string   `yaml:\"name\"`\n\tType     string   `yaml:\"type\"`\n\tPath     string   `yaml:\"path\"`\n\tHostPath string   `yaml:\"hostPath\"`\n\tDisks    []string `yaml:\"disks\"`\n}\n\ntype InstanceNodePortYaml struct {\n\tProtocol     string `yaml:\"protocol\"`\n\tExternalPort int    `yaml:\"externalPort\"`\n\tInternalPort int    `yaml:\"internalPort\"`\n}\n"
  },
  {
    "path": "spec/journal.go",
    "content": "package spec\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Journal struct {\n\tInputs []*Input `bson:\"inputs\" json:\"inputs\"`\n}\n\ntype Input struct {\n\tIndex int32  `bson:\"index\" json:\"index\"`\n\tKey   string `bson:\"key\" json:\"key\"`\n\tType  string `bson:\"type\" json:\"type\"`\n\tUnit  string `bson:\"unit\" json:\"unit\"`\n\tPath  string `bson:\"path\" json:\"path\"`\n}\n\nfunc (j *Journal) Validate() (errData *errortypes.ErrorData, err error) {\n\tfor _, input := range j.Inputs {\n\t\tif input.Key == \"\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"journal_key_missing\",\n\t\t\t\tMessage: \"Missing journal key\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tkey := utils.FilterName(input.Key)\n\t\tif input.Key != key {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"journal_key_invalid\",\n\t\t\t\tMessage: \"Journal key invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tinput.Key = key\n\n\t\tswitch input.Type {\n\t\tcase Systemd:\n\t\t\tinput.Path = \"\"\n\t\t\tif input.Unit == \"\" {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"systemd_unit_missing\",\n\t\t\t\t\tMessage: \"Missing systemd unit\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinputUnit := utils.FilterUnit(input.Unit)\n\t\t\tif input.Unit != inputUnit {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"systemd_unit_invalid\",\n\t\t\t\t\tMessage: \"Invalid systemd unit\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinput.Unit = inputUnit\n\t\t\tbreak\n\t\tcase File:\n\t\t\tinput.Unit = \"\"\n\t\t\tif input.Path == \"\" {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"log_path_missing\",\n\t\t\t\t\tMessage: \"Missing log path\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinput.Path = utils.FilterPath(input.Path)\n\t\t\tif input.Path == \"\" {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"log_path_invalid\",\n\t\t\t\t\tMessage: \"Invalid log path\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"unknown_input_type\",\n\t\t\t\tMessage: \"Unknown input type\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\ntype JournalYaml struct {\n\tName   string             `yaml:\"name\"`\n\tKind   string             `yaml:\"kind\"`\n\tInputs []JournalYamlInput `yaml:\"inputs\"`\n}\n\ntype JournalYamlInput struct {\n\tKey  string `yaml:\"key\"`\n\tType string `yaml:\"type\"`\n\tUnit string `yaml:\"unit,omitempty\"`\n\tPath string `yaml:\"path,omitempty\"`\n}\n"
  },
  {
    "path": "spec/node.go",
    "content": "package spec\n\nimport (\n\t\"sort\"\n\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\ntype Nodes []*node.Node\n\nfunc (n Nodes) Len() int {\n\treturn len(n)\n}\n\nfunc (n Nodes) Less(i, j int) bool {\n\treturn n[i].Usage() < n[j].Usage()\n}\n\nfunc (n Nodes) Swap(i, j int) {\n\tn[i], n[j] = n[j], n[i]\n}\n\nfunc (n Nodes) Sort() {\n\tsort.Sort(n)\n}\n"
  },
  {
    "path": "spec/spec.go",
    "content": "package spec\n\nimport (\n\t\"crypto/sha1\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/finder\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/journal\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/organization\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n\t\"gopkg.in/yaml.v2\"\n)\n\ntype Spec struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tPod          bson.ObjectID `bson:\"pod\" json:\"pod\"`\n\tUnit         bson.ObjectID `bson:\"unit\" json:\"unit\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tIndex        int           `bson:\"index\" json:\"index\"`\n\tTimestamp    time.Time     `bson:\"timestamp\" json:\"timestamp\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tKind         string        `bson:\"kind\" json:\"kind\"`\n\tCount        int           `bson:\"count\" json:\"count\"`\n\tHash         string        `bson:\"hash\" json:\"hash\"`\n\tData         string        `bson:\"data\" json:\"data\"`\n\tInstance     *Instance     `bson:\"instance,omitempty\" json:\"-\"`\n\tFirewall     *Firewall     `bson:\"firewall,omitempty\" json:\"-\"`\n\tDomain       *Domain       `bson:\"domain,omitempty\" json:\"-\"`\n\tJournal      *Journal      `bson:\"journal,omitempty\" json:\"-\"`\n}\n\nfunc (s *Spec) GetAllNodes(db *database.Database) (ndes Nodes,\n\tofflineCount, noMountCount int, err error) {\n\n\torg, err := organization.Get(db, s.Organization)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tshpe, err := shape.Get(db, s.Instance.Shape)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzones, err := zone.GetAllDatacenter(db, shpe.Datacenter)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tzoneIds := []bson.ObjectID{}\n\tfor _, zne := range zones {\n\t\tzoneIds = append(zoneIds, zne.Id)\n\t}\n\n\tallNdes, err := node.GetAllShape(db, zoneIds, shpe.Roles)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvar mountNodes []set.Set\n\tif len(s.Instance.Mounts) > 0 {\n\t\tdiskIds := []bson.ObjectID{}\n\t\tfor _, mount := range s.Instance.Mounts {\n\t\t\tif mount.Type != Disk {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdiskIds = append(diskIds, mount.Disks...)\n\t\t}\n\n\t\tdisksMap := map[bson.ObjectID]*disk.Disk{}\n\t\tif len(diskIds) > 0 {\n\t\t\tdisksMap, err = disk.GetAllMap(db, &bson.M{\n\t\t\t\t\"_id\": &bson.M{\n\t\t\t\t\t\"$in\": diskIds,\n\t\t\t\t},\n\t\t\t\t\"organization\": s.Organization,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tfor _, mount := range s.Instance.Mounts {\n\t\t\tmountSet := set.NewSet()\n\n\t\t\tif mount.Type == Disk {\n\t\t\t\tfor _, dskId := range mount.Disks {\n\t\t\t\t\tdsk := disksMap[dskId]\n\t\t\t\t\tif dsk == nil || !dsk.IsAvailable() {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tmountSet.Add(dsk.Node)\n\t\t\t\t}\n\t\t\t} else if mount.Type == HostPath {\n\t\t\t\tfor _, nde := range allNdes {\n\t\t\t\t\tfor _, share := range nde.Shares {\n\t\t\t\t\t\tif share.MatchPath(mount.HostPath) &&\n\t\t\t\t\t\t\tutils.HasMatchingItem(share.Roles, org.Roles) {\n\n\t\t\t\t\t\t\tmountSet.Add(nde.Id)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmountNodes = append(mountNodes, mountSet)\n\t\t}\n\t}\n\n\tndes = Nodes{}\n\tfor _, nde := range allNdes {\n\t\tif !nde.IsOnline() {\n\t\t\tofflineCount += 1\n\t\t\tcontinue\n\t\t}\n\n\t\tif mountNodes != nil {\n\t\t\tmatch := true\n\t\t\tfor _, mountSet := range mountNodes {\n\t\t\t\tif !mountSet.Contains(nde.Id) {\n\t\t\t\t\tmatch = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !match {\n\t\t\t\tnoMountCount += 1\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tndes = append(ndes, nde)\n\t}\n\n\treturn\n}\n\nfunc (s *Spec) ExtractResources() (resources string, err error) {\n\tmatches := resourcesRe.FindStringSubmatch(s.Data)\n\tif len(matches) > 1 {\n\t\tresources = matches[1]\n\t\tresources = strings.TrimSpace(resources)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Spec) parseFirewall(db *database.Database,\n\torgId bson.ObjectID, dataYaml *FirewallYaml) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tdata := &Firewall{\n\t\tIngress: []*Rule{},\n\t}\n\n\tif dataYaml.Kind != finder.FirewallKind {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_kind_mismatch\",\n\t\t\tMessage: \"Unit kind unexpected\",\n\t\t}\n\t\treturn\n\t}\n\n\tresources := &finder.Resources{\n\t\tOrganization: orgId,\n\t}\n\n\tfor _, ruleYaml := range dataYaml.Ingress {\n\t\tif ruleYaml.Source == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\trule := &Rule{\n\t\t\tProtocol: ruleYaml.Protocol,\n\t\t\tPort:     ruleYaml.Port,\n\t\t}\n\n\t\trefs := set.NewSet()\n\t\tfor _, source := range ruleYaml.Source {\n\t\t\tif strings.HasPrefix(source, TokenPrefix) {\n\t\t\t\tkind, e := resources.Find(db, source)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif kind == finder.UnitKind && resources.Unit != nil {\n\t\t\t\t\tselector := resources.Selector\n\t\t\t\t\tif selector == \"\" {\n\t\t\t\t\t\tselector = \"private_ips\"\n\t\t\t\t\t}\n\n\t\t\t\t\trefs.Add(Refrence{\n\t\t\t\t\t\tId:       resources.Unit.Id,\n\t\t\t\t\t\tRealm:    resources.Unit.Pod,\n\t\t\t\t\t\tKind:     Unit,\n\t\t\t\t\t\tSelector: selector,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trule.SourceIps = append(rule.SourceIps, source)\n\t\t\t}\n\t\t}\n\n\t\tfor refInf := range refs.Iter() {\n\t\t\tref := refInf.(Refrence)\n\t\t\trule.Sources = append(rule.Sources, &ref)\n\t\t}\n\n\t\tdata.Ingress = append(data.Ingress, rule)\n\t}\n\n\terrData, err = data.Validate()\n\tif err != nil || errData != nil {\n\t\treturn\n\t}\n\n\ts.Firewall = data\n\n\treturn\n}\n\nfunc (s *Spec) parseInstance(db *database.Database,\n\torgId bson.ObjectID, dataYaml *InstanceYaml) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tdata := &Instance{}\n\tvar shpe *shape.Shape\n\n\tif dataYaml.Name == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_name_missing\",\n\t\t\tMessage: \"Unit name is missing\",\n\t\t}\n\t\treturn\n\t}\n\n\tswitch dataYaml.Kind {\n\tcase finder.InstanceKind:\n\t\tbreak\n\tcase finder.ImageKind:\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_kind_mismatch\",\n\t\t\tMessage: \"Unit kind unexpected\",\n\t\t}\n\t\treturn\n\t}\n\n\tresources := &finder.Resources{\n\t\tOrganization: orgId,\n\t}\n\n\tif dataYaml.Plan != \"\" {\n\t\tkind, e := resources.Find(db, dataYaml.Plan)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif kind == finder.PlanKind && resources.Plan != nil {\n\t\t\tdata.Plan = resources.Plan.Id\n\t\t}\n\t}\n\n\tif dataYaml.Zone != \"\" {\n\t\tkind, e := resources.Find(db, dataYaml.Zone)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif kind == finder.ZoneKind && resources.Zone != nil {\n\t\t\tdata.Datacenter = resources.Datacenter.Id\n\t\t\tdata.Zone = resources.Zone.Id\n\t\t}\n\t}\n\n\tif data.Zone.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_zone_missing\",\n\t\t\tMessage: \"Unit zone is missing\",\n\t\t}\n\t\treturn\n\t}\n\n\tif dataYaml.Node != \"\" {\n\t\tkind, e := resources.Find(db, dataYaml.Node)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif kind == finder.NodeKind && resources.Node != nil {\n\t\t\tdata.Node = resources.Node.Id\n\t\t}\n\t}\n\tif dataYaml.Shape != \"\" {\n\t\tkind, e := resources.Find(db, dataYaml.Shape)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif kind == finder.ShapeKind && resources.Shape != nil {\n\t\t\tshpe = resources.Shape\n\t\t\tdata.Shape = resources.Shape.Id\n\t\t}\n\t}\n\n\tif data.Node.IsZero() && data.Shape.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_node_missing\",\n\t\t\tMessage: \"Unit node or shape is missing\",\n\t\t}\n\t\treturn\n\t}\n\n\tif dataYaml.Vpc != \"\" {\n\t\tkind, e := resources.Find(db, dataYaml.Vpc)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif kind == finder.VpcKind && resources.Vpc != nil {\n\t\t\tdata.Vpc = resources.Vpc.Id\n\t\t}\n\t}\n\n\tif data.Vpc.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_vpc_missing\",\n\t\t\tMessage: \"Unit VPC is missing\",\n\t\t}\n\t\treturn\n\t}\n\n\tif dataYaml.Subnet != \"\" {\n\t\tkind, e := resources.Find(db, dataYaml.Subnet)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif kind == finder.SubnetKind && resources.Subnet != nil {\n\t\t\tdata.Subnet = resources.Subnet.Id\n\t\t}\n\t}\n\n\tif data.Subnet.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_subnet_missing\",\n\t\t\tMessage: \"Unit subnet is missing\",\n\t\t}\n\t\treturn\n\t}\n\n\tif dataYaml.Image != \"\" {\n\t\tkind, e := resources.Find(db, dataYaml.Image)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif kind == finder.ImageKind && resources.Image != nil {\n\t\t\tdata.Image = resources.Image.Id\n\t\t}\n\n\t\tif kind == finder.BuildKind && resources.Deployment != nil &&\n\t\t\tresources.Deployment.ImageReady() {\n\n\t\t\tdata.Image = resources.Deployment.Image\n\t\t}\n\t}\n\n\tif dataYaml.Mounts != nil {\n\t\tfor _, mount := range dataYaml.Mounts {\n\t\t\tmnt := Mount{\n\t\t\t\tPath:  utils.FilterPath(mount.Path),\n\t\t\t\tDisks: []bson.ObjectID{},\n\t\t\t}\n\n\t\t\tif mnt.Path == \"\" {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"mount_path_missing\",\n\t\t\t\t\tMessage: \"Unit mount path is missing\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif mount.Type == HostPath {\n\t\t\t\tmnt.Name = mount.Name\n\t\t\t\tmnt.Type = HostPath\n\t\t\t\tmnt.HostPath = utils.FilterPath(mount.HostPath)\n\n\t\t\t\tif mnt.Name == \"\" {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"mount_name_missing\",\n\t\t\t\t\t\tMessage: \"Unit mount name is missing\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif mnt.HostPath == \"\" {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"mount_host_path_missing\",\n\t\t\t\t\t\tMessage: \"Unit mount hostPath is missing\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else if mount.Type == Disk || mount.Type == \"\" {\n\t\t\t\tmnt.Type = Disk\n\n\t\t\t\tif mnt.Path == \"\" {\n\t\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\t\tError:   \"mount_path_missing\",\n\t\t\t\t\t\tMessage: \"Unit mount path is missing\",\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tfor _, dsk := range mount.Disks {\n\t\t\t\t\tkind, e := resources.Find(db, dsk)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif kind == finder.DiskKind && resources.Disks != nil {\n\t\t\t\t\t\tfor _, dskRes := range resources.Disks {\n\t\t\t\t\t\t\tmnt.Disks = append(mnt.Disks, dskRes.Id)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"mount_type_invalid\",\n\t\t\t\t\tMessage: \"Unit mount type is invalid\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdata.Mounts = append(data.Mounts, mnt)\n\t\t}\n\t}\n\n\tif dataYaml.NodePorts != nil {\n\t\texternalNodePorts := set.NewSet()\n\t\tfor _, nodePrt := range dataYaml.NodePorts {\n\t\t\tmapping := NodePort{\n\t\t\t\tProtocol:     nodePrt.Protocol,\n\t\t\t\tExternalPort: nodePrt.ExternalPort,\n\t\t\t\tInternalPort: nodePrt.InternalPort,\n\t\t\t}\n\n\t\t\textPortKey := fmt.Sprintf(\"%s:%d\",\n\t\t\t\tmapping.Protocol, mapping.ExternalPort)\n\n\t\t\tif externalNodePorts.Contains(extPortKey) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"node_port_external_duplicate\",\n\t\t\t\t\tMessage: \"Duplicate external node port\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\texternalNodePorts.Add(extPortKey)\n\n\t\t\terrData, err = mapping.Validate()\n\t\t\tif err != nil || errData != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdata.NodePorts = append(data.NodePorts, mapping)\n\t\t}\n\t}\n\n\tif dataYaml.Certificates != nil {\n\t\tfor _, cert := range dataYaml.Certificates {\n\t\t\tkind, e := resources.Find(db, cert)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif kind == finder.CertificateKind && resources.Certificate != nil {\n\t\t\t\tdata.Certificates = append(\n\t\t\t\t\tdata.Certificates,\n\t\t\t\t\tresources.Certificate.Id,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tif dataYaml.Secrets != nil {\n\t\tfor _, cert := range dataYaml.Secrets {\n\t\t\tkind, e := resources.Find(db, cert)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif kind == finder.SecretKind && resources.Secret != nil {\n\t\t\t\tdata.Secrets = append(\n\t\t\t\t\tdata.Secrets,\n\t\t\t\t\tresources.Secret.Id,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tif dataYaml.Pods != nil {\n\t\tfor _, cert := range dataYaml.Pods {\n\t\t\tkind, e := resources.Find(db, cert)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif kind == finder.PodKind && resources.Pod != nil {\n\t\t\t\tdata.Pods = append(\n\t\t\t\t\tdata.Pods,\n\t\t\t\t\tresources.Pod.Id,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tif data.Node.IsZero() && data.Shape.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_image_missing\",\n\t\t\tMessage: \"Unit image is missing\",\n\t\t}\n\t\treturn\n\t}\n\n\tif shpe != nil {\n\t\tdata.Processors = shpe.Processors\n\t\tdata.Memory = shpe.Memory\n\t\tif shpe.Flexible {\n\t\t\tif dataYaml.Processors != 0 {\n\t\t\t\tdata.Processors = dataYaml.Processors\n\t\t\t}\n\t\t\tif dataYaml.Memory != 0 {\n\t\t\t\tdata.Memory = dataYaml.Memory\n\t\t\t}\n\t\t}\n\t} else {\n\t\tdata.Processors = dataYaml.Processors\n\t\tdata.Memory = dataYaml.Memory\n\t}\n\n\tdata.Uefi = dataYaml.Uefi\n\tdata.SecureBoot = dataYaml.SecureBoot\n\n\tswitch dataYaml.CloudType {\n\tcase instance.Linux:\n\t\tdata.CloudType = instance.Linux\n\t\tbreak\n\tcase instance.LinuxLegacy:\n\t\tdata.CloudType = instance.LinuxLegacy\n\t\tbreak\n\tcase instance.BSD:\n\t\tdata.CloudType = instance.BSD\n\t\tbreak\n\tcase \"\":\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_unit_cloud_type\",\n\t\t\tMessage: \"Unit instance cloud type is invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tdata.Tpm = dataYaml.Tpm\n\tdata.Vnc = dataYaml.Vnc\n\tdata.DeleteProtection = dataYaml.DeleteProtection\n\tdata.SkipSourceDestCheck = dataYaml.SkipSourceDestCheck\n\tdata.Gui = dataYaml.Gui\n\tdata.HostAddress = dataYaml.HostAddress\n\tdata.PublicAddress = dataYaml.PublicAddress\n\tdata.PublicAddress6 = dataYaml.PublicAddress6\n\tdata.DhcpServer = dataYaml.DhcpServer\n\n\tdata.Roles = dataYaml.Roles\n\tdata.DiskSize = dataYaml.DiskSize\n\n\ts.Name = dataYaml.Name\n\ts.Kind = dataYaml.Kind\n\ts.Count = dataYaml.Count\n\ts.Instance = data\n\n\ts.Count = dataYaml.Count\n\tif s.Kind == finder.ImageKind && s.Count != 0 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"count_invalid\",\n\t\t\tMessage: \"Count not valid for image kind\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Spec) parseDomain(db *database.Database,\n\torgId bson.ObjectID, dataYaml *DomainYaml) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tdata := &Domain{\n\t\tRecords: []*Record{},\n\t}\n\n\tif dataYaml.Kind != finder.DomainKind {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_kind_mismatch\",\n\t\t\tMessage: \"Unit kind unexpected\",\n\t\t}\n\t\treturn\n\t}\n\n\tresources := &finder.Resources{\n\t\tOrganization: orgId,\n\t}\n\n\tfor _, recordYaml := range dataYaml.Records {\n\t\tif recordYaml.Name == \"\" || recordYaml.Type == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\trecord := &Record{\n\t\t\tName: utils.FilterName(recordYaml.Name),\n\t\t\tType: recordYaml.Type,\n\t\t}\n\n\t\tkind, e := resources.Find(db, recordYaml.Domain)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif kind == finder.DomainKind && resources.Domain != nil {\n\t\t\trecord.Domain = resources.Domain.Id\n\t\t}\n\n\t\tdata.Records = append(data.Records, record)\n\t}\n\n\terrData, err = data.Validate()\n\tif err != nil || errData != nil {\n\t\treturn\n\t}\n\n\ts.Domain = data\n\n\treturn\n}\n\nfunc (s *Spec) parseJournal(db *database.Database,\n\tjrnlKindGen journal.KindGenerator, dataYaml *JournalYaml) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tdata := &Journal{\n\t\tInputs: []*Input{},\n\t}\n\n\tif dataYaml.Kind != finder.JournalKind {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_kind_mismatch\",\n\t\t\tMessage: \"Unit kind unexpected\",\n\t\t}\n\t\treturn\n\t}\n\n\tcurIndexes := map[string]int32{}\n\tif s.Journal != nil {\n\t\tfor _, jrnl := range s.Journal.Inputs {\n\t\t\tcurIndexes[jrnl.Key] = jrnl.Index\n\t\t}\n\t}\n\n\tfor _, input := range dataYaml.Inputs {\n\t\tdata.Inputs = append(data.Inputs, &Input{\n\t\t\tKey:  input.Key,\n\t\t\tType: input.Type,\n\t\t\tUnit: input.Unit,\n\t\t\tPath: input.Path,\n\t\t})\n\t}\n\n\terrData, err = data.Validate()\n\tif err != nil || errData != nil {\n\t\treturn\n\t}\n\n\tfor _, input := range data.Inputs {\n\t\tinput.Index = curIndexes[input.Key]\n\t\tif input.Index == 0 {\n\t\t\tif jrnlKindGen == nil {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"journal_missing_index\",\n\t\t\t\t\tMessage: \"Journal missing index\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tindex, e := jrnlKindGen.GetKind(db, input.Key)\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tinput.Index = index\n\t\t}\n\t}\n\n\ts.Journal = data\n\n\treturn\n}\n\nfunc (s *Spec) Refresh(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\terrData, err = s.Parse(db, nil)\n\tif err != nil || errData != nil {\n\t\treturn\n\t}\n\n\terr = s.CommitData(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Spec) Parse(db *database.Database,\n\tjrnlKindGen journal.KindGenerator) (\n\terrData *errortypes.ErrorData, err error) {\n\n\thash := sha1.New()\n\thash.Write([]byte(filterSpecHash(s.Data)))\n\thashBytes := hash.Sum(nil)\n\ts.Hash = fmt.Sprintf(\"%x\", hashBytes)\n\n\tresourcesSpec, err := s.ExtractResources()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif resourcesSpec == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_resources_block_missing\",\n\t\t\tMessage: \"Unit missing yaml resources block\",\n\t\t}\n\t\treturn\n\t}\n\n\tbaseDecode := yaml.NewDecoder(strings.NewReader(resourcesSpec))\n\tdecoder := yaml.NewDecoder(strings.NewReader(resourcesSpec))\n\tfor {\n\t\tbaseDoc := &Base{}\n\n\t\terr = baseDecode.Decode(baseDoc)\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\terr = nil\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(err, \"spec: Failed to decode yaml doc\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tswitch baseDoc.Kind {\n\t\tcase finder.InstanceKind, finder.ImageKind:\n\t\t\tinstYaml := &InstanceYaml{}\n\n\t\t\terr = decoder.Decode(instYaml)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"spec: Failed to decode instance yaml doc\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terrData, err = s.parseInstance(db, s.Organization, instYaml)\n\t\t\tif err != nil || errData != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\tcase finder.FirewallKind:\n\t\t\tfireYaml := &FirewallYaml{}\n\n\t\t\terr = decoder.Decode(fireYaml)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"spec: Failed to decode firewall yaml doc\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terrData, err = s.parseFirewall(db, s.Organization, fireYaml)\n\t\t\tif err != nil || errData != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\tcase finder.DomainKind:\n\t\t\tdomnYaml := &DomainYaml{}\n\n\t\t\terr = decoder.Decode(domnYaml)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"spec: Failed to decode domain yaml doc\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terrData, err = s.parseDomain(db, s.Organization, domnYaml)\n\t\t\tif err != nil || errData != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\tcase finder.JournalKind:\n\t\t\tjrnlYaml := &JournalYaml{}\n\n\t\t\terr = decoder.Decode(jrnlYaml)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"spec: Failed to decode domain yaml doc\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terrData, err = s.parseJournal(db, jrnlKindGen, jrnlYaml)\n\t\t\tif err != nil || errData != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\tdefault:\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"unit_kind_invalid\",\n\t\t\t\tMessage: \"Unit kind is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (s *Spec) CanMigrate(db *database.Database,\n\tdeply *deployment.Deployment, spc *Spec) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tvar inst *instance.Instance\n\tif !deply.Instance.IsZero() {\n\t\tinst, err = instance.Get(db, deply.Instance)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif !settings.System.NoMigrateRefresh {\n\t\terrData, err = s.Parse(db, nil)\n\t\tif err != nil || errData != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terrData, err = spc.Parse(db, nil)\n\tif err != nil || errData != nil {\n\t\treturn\n\t}\n\n\tif s.Pod != spc.Pod || s.Unit != spc.Unit {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"spec: Invalid unit\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif s.Kind != spc.Kind {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unit_kind_conflict\",\n\t\t\tMessage: \"Cannot migrate to different kind\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.Instance == nil || spc.Instance == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"spec: Instance not found\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif s.Instance.Datacenter != spc.Instance.Datacenter {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"instance_datacenter_conflict\",\n\t\t\tMessage: \"Cannot migrate to different instance datacenter\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.Instance.Zone != spc.Instance.Zone {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"instance_zone_conflict\",\n\t\t\tMessage: \"Cannot migrate to different instance zone\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.Instance.Node != spc.Instance.Node &&\n\t\t!spc.Instance.Node.IsZero() &&\n\t\tinst.Node != spc.Instance.Node {\n\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"instance_node_coflict\",\n\t\t\tMessage: \"Cannot migrate to different instance node\",\n\t\t}\n\t\treturn\n\t}\n\n\tif s.Instance.Subnet != spc.Instance.Subnet {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"instance_subnet_coflict\",\n\t\t\tMessage: \"Cannot migrate to different instance subnet\",\n\t\t}\n\t\treturn\n\t}\n\n\tcurMountPaths := set.NewSet()\n\tfor _, mnt := range s.Instance.Mounts {\n\t\tif mnt.Type == HostPath {\n\t\t\tcontinue\n\t\t}\n\t\tcurMountPaths.Add(mnt.Path)\n\t}\n\n\tnewMountPaths := set.NewSet()\n\tfor _, mnt := range spc.Instance.Mounts {\n\t\tif mnt.Type == HostPath {\n\t\t\tcontinue\n\t\t}\n\t\tnewMountPaths.Add(mnt.Path)\n\t}\n\n\tif !curMountPaths.IsEqual(newMountPaths) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"instance_mount_coflict\",\n\t\t\tMessage: \"Cannot migrate to different instance mounts\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Spec) Commit(db *database.Database) (err error) {\n\tcoll := db.Specs()\n\n\terr = coll.Commit(s.Id, s)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Spec) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Specs()\n\n\terr = coll.CommitFields(s.Id, s, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Spec) CommitData(db *database.Database) (err error) {\n\tcoll := db.Specs()\n\n\terr = coll.CommitFields(s.Id, s, set.NewSet(\n\t\t\"name\", \"count\", \"data\", \"instance\", \"firewall\", \"domain\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Spec) Insert(db *database.Database) (err error) {\n\tcoll := db.Specs()\n\n\tresp, err := coll.InsertOne(db, s)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\ts.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  },
  {
    "path": "spec/utils.go",
    "content": "package spec\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Named struct {\n\tId        bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tUnit      bson.ObjectID `bson:\"unit\" json:\"unit\"`\n\tIndex     int           `bson:\"index\" json:\"index\"`\n\tTimestamp time.Time     `bson:\"timestamp\" json:\"timestamp\"`\n}\n\nvar (\n\tyamlBlockRe = regexp.MustCompile(\"(?s)^```yaml\\\\n(.*?)```\")\n)\n\nfunc filterSpecHash(input string) string {\n\treturn yamlBlockRe.ReplaceAllStringFunc(input, func(block string) string {\n\t\tlines := strings.Split(block, \"\\n\")\n\t\tresult := []string{}\n\n\t\tfor _, line := range lines {\n\t\t\tline = strings.TrimSpace(line)\n\t\t\tif strings.HasPrefix(line, \"name:\") ||\n\t\t\t\tstrings.HasPrefix(line, \"count:\") {\n\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresult = append(result, line)\n\t\t}\n\n\t\treturn strings.Join(result, \"\\n\")\n\t})\n}\n\nfunc New(podId, unitId, orgId bson.ObjectID, data string) (spc *Spec) {\n\tspc = &Spec{\n\t\tId:           bson.NewObjectID(),\n\t\tUnit:         unitId,\n\t\tPod:          podId,\n\t\tOrganization: orgId,\n\t\tData:         data,\n\t}\n\n\treturn\n}\n\nfunc Get(db *database.Database, commitId bson.ObjectID) (\n\tspc *Spec, err error) {\n\n\tcoll := db.Specs()\n\tspc = &Spec{}\n\n\terr = coll.FindOneId(commitId, spc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (\n\tspc *Spec, err error) {\n\n\tcoll := db.Specs()\n\tspc = &Spec{}\n\n\terr = coll.FindOne(db, query).Decode(spc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (spcs []*Named, count int64, err error) {\n\n\tcoll := db.Specs()\n\tspcs = []*Named{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetProjection(bson.M{\n\t\t\t\t\"_id\":       1,\n\t\t\t\t\"unit\":      1,\n\t\t\t\t\"index\":     1,\n\t\t\t\t\"timestamp\": 1,\n\t\t\t}).\n\t\t\tSetSort(bson.D{{\"timestamp\", -1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tspc := &Named{}\n\t\terr = cursor.Decode(spc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tspcs = append(spcs, spc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tspcs []*Spec, err error) {\n\n\tcoll := db.Specs()\n\tspcs = []*Spec{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tspc := &Spec{}\n\t\terr = cursor.Decode(spc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tspcs = append(spcs, spc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllIndexes(db *database.Database, query *bson.M) (\n\tspcs []*Spec, err error) {\n\n\tcoll := db.Specs()\n\tspcs = []*Spec{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetProjection(bson.M{\n\t\t\t\t\"_id\":       1,\n\t\t\t\t\"unit\":      1,\n\t\t\t\t\"index\":     1,\n\t\t\t\t\"timestamp\": 1,\n\t\t\t}).\n\t\t\tSetSort(bson.D{{\"timestamp\", 1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tspc := &Spec{}\n\t\terr = cursor.Decode(spc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tspcs = append(spcs, spc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllProjectSorted(db *database.Database, query *bson.M) (\n\tspcs []*Spec, err error) {\n\n\tcoll := db.Specs()\n\tspcs = []*Spec{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetProjection(bson.M{\n\t\t\t\t\"_id\":       1,\n\t\t\t\t\"unit\":      1,\n\t\t\t\t\"index\":     1,\n\t\t\t\t\"timestamp\": 1,\n\t\t\t\t\"hash\":      1,\n\t\t\t\t\"data\":      1,\n\t\t\t}).\n\t\t\tSetSort(bson.D{{\"timestamp\", -1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tspc := &Spec{}\n\t\terr = cursor.Decode(spc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tspcs = append(spcs, spc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllIds(db *database.Database) (specIds set.Set, err error) {\n\tcoll := db.Specs()\n\tspecIds = set.NewSet()\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tbson.M{},\n\t\toptions.Find().\n\t\t\tSetProjection(bson.M{\n\t\t\t\t\"_id\": 1,\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tspc := &Spec{}\n\t\terr = cursor.Decode(spc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tspecIds.Add(spc.Id)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, commitId bson.ObjectID) (err error) {\n\tcoll := db.Specs()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": commitId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveAll(db *database.Database, query *bson.M) (err error) {\n\tcoll := db.Specs()\n\n\t_, err = coll.DeleteMany(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "state/arps.go",
    "content": "package state\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/arp\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nvar (\n\tArps    = &ArpsState{}\n\tArpsPkg = NewPackage(Arps)\n)\n\ntype ArpsState struct {\n\tarpRecords map[string]set.Set\n}\n\nfunc (p *ArpsState) ArpRecords(namespace string) set.Set {\n\treturn p.arpRecords[namespace]\n}\n\nfunc (p *ArpsState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tp.arpRecords = arp.BuildState(Instances.Instances(),\n\t\tVpcs.VpcsMap(), Vpcs.VpcIpsMap())\n\n\treturn\n}\n\nfunc (p *ArpsState) Apply(st *State) {\n\tst.ArpRecords = p.ArpRecords\n}\n\nfunc init() {\n\tArpsPkg.\n\t\tAfter(Instances).\n\t\tAfter(Vpcs)\n}\n"
  },
  {
    "path": "state/authorities.go",
    "content": "package state\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nvar (\n\tAuthorities    = &AuthoritiesState{}\n\tAuthoritiesPkg = NewPackage(Authorities)\n)\n\ntype AuthoritiesState struct {\n\tauthoritiesMap map[string][]*authority.Authority\n}\n\nfunc (p *AuthoritiesState) GetInstaceAuthorities(\n\torgId bson.ObjectID, roles []string) []*authority.Authority {\n\n\tauthrSet := set.NewSet()\n\tauthrs := []*authority.Authority{}\n\n\tfor _, role := range roles {\n\t\tfor _, authr := range p.authoritiesMap[role] {\n\t\t\tif authrSet.Contains(authr.Id) || authr.Organization != orgId {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tauthrSet.Add(authr.Id)\n\t\t\tauthrs = append(authrs, authr)\n\t\t}\n\t}\n\n\treturn authrs\n}\n\nfunc (p *AuthoritiesState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\t_, rolesSet := InstancesPreload.GetRoles()\n\tauthorities := AuthoritiesPreload.Authorities()\n\tpreloadRolesSet := AuthoritiesPreload.RolesSet()\n\troles := rolesSet.Copy()\n\troles.Subtract(preloadRolesSet)\n\n\tmissRoles := []string{}\n\tfor roleInf := range roles.Iter() {\n\t\tmissRoles = append(missRoles, roleInf.(string))\n\t}\n\n\tif len(missRoles) > 0 {\n\t\tmissAuthorities, e := authority.GetMapRoles(db, &bson.M{\n\t\t\t\"roles\": &bson.M{\n\t\t\t\t\"$in\": missRoles,\n\t\t\t},\n\t\t})\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor role, authrs := range missAuthorities {\n\t\t\tauthorities[role] = authrs\n\t\t}\n\t}\n\n\tp.authoritiesMap = authorities\n\n\treturn\n}\n\nfunc (p *AuthoritiesState) Apply(st *State) {\n\tst.GetInstaceAuthorities = p.GetInstaceAuthorities\n}\n\nfunc init() {\n\tAuthoritiesPkg.\n\t\tAfter(AuthoritiesPreload).\n\t\tAfter(InstancesPreload)\n}\n"
  },
  {
    "path": "state/authorities_preload.go",
    "content": "package state\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nvar (\n\tAuthoritiesPreload    = &AuthoritiesPreloadState{}\n\tAuthoritiesPreloadPkg = NewPackage(AuthoritiesPreload)\n)\n\ntype AuthoritiesPreloadState struct {\n\tauthoritiesMap      map[string][]*authority.Authority\n\tauthoritiesRolesSet set.Set\n}\n\nfunc (p *AuthoritiesPreloadState) Authorities() map[string][]*authority.Authority {\n\treturn p.authoritiesMap\n}\n\nfunc (p *AuthoritiesPreloadState) RolesSet() set.Set {\n\treturn p.authoritiesRolesSet\n}\n\nfunc (p *AuthoritiesPreloadState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\troles, rolesSet := InstancesPreload.GetRoles()\n\tif len(roles) == 0 {\n\t\tp.authoritiesMap = map[string][]*authority.Authority{}\n\t\tp.authoritiesRolesSet = set.NewSet()\n\t\treturn\n\t}\n\n\tauthrsMap, err := authority.GetMapRoles(db, &bson.M{\n\t\t\"roles\": &bson.M{\n\t\t\t\"$in\": roles,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\tp.authoritiesMap = authrsMap\n\tp.authoritiesRolesSet = rolesSet\n\n\treturn\n}\n\nfunc (p *AuthoritiesPreloadState) Apply(st *State) {\n}\n"
  },
  {
    "path": "state/datacenter.go",
    "content": "package state\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\nvar (\n\tDatacenter    = &DatacenterState{}\n\tDatacenterPkg = NewPackage(Datacenter)\n)\n\ntype DatacenterState struct {\n\tnodeDatacenter *datacenter.Datacenter\n}\n\nfunc (p *DatacenterState) NodeDatacenter() *datacenter.Datacenter {\n\treturn p.nodeDatacenter\n}\n\nfunc (p *DatacenterState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tdcId := node.Self.Datacenter\n\tif dcId.IsZero() {\n\t\tp.nodeDatacenter = nil\n\t\tpkg.Evict()\n\t\treturn\n\t}\n\n\tdc, e := datacenter.Get(db, dcId)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tp.nodeDatacenter = dc\n\tpkg.Cache(15 * time.Second)\n\n\treturn\n}\n\nfunc (p *DatacenterState) Apply(st *State) {\n\tst.NodeDatacenter = p.NodeDatacenter\n}\n"
  },
  {
    "path": "state/deployments.go",
    "content": "package state\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n)\n\nvar (\n\tDeployments    = &DeploymentsState{}\n\tDeploymentsPkg = NewPackage(Deployments)\n)\n\ntype DeploymentsResult struct {\n\tDeploymentIds []bson.ObjectID `bson:\"deployment_ids\"`\n\tPodIds        []bson.ObjectID `bson:\"pod_ids\"`\n\tUnitIds       []bson.ObjectID `bson:\"unit_ids\"`\n\tSpecIds       []bson.ObjectID `bson:\"spec_ids\"`\n\n\tDeployments []*deployment.Deployment `bson:\"deployments\"`\n\tPods        []*pod.Pod               `bson:\"pods\"`\n\tUnits       []*unit.Unit             `bson:\"units\"`\n\tSpecs       []*spec.Spec             `bson:\"specs\"`\n\n\tSpecPodIds    []bson.ObjectID `bson:\"spec_pod_ids\"`\n\tSpecUnitIds   []bson.ObjectID `bson:\"spec_unit_ids\"`\n\tSpecSecretIds []bson.ObjectID `bson:\"spec_secret_ids\"`\n\tSpecCertIds   []bson.ObjectID `bson:\"spec_cert_ids\"`\n\tSpecDomainIds []bson.ObjectID `bson:\"spec_domain_ids\"`\n\n\tSpecIdUnits        []*unit.Unit               `bson:\"spec_id_units\"`\n\tSpecPodUnits       []*unit.Unit               `bson:\"spec_pod_units\"`\n\tSpecPods           []*pod.Pod                 `bson:\"spec_pods\"`\n\tSpecSecrets        []*secret.Secret           `bson:\"spec_secrets\"`\n\tSpecCerts          []*certificate.Certificate `bson:\"spec_certs\"`\n\tSpecDomains        []*domain.Domain           `bson:\"spec_domains\"`\n\tSpecDomainsRecords []*domain.Record           `bson:\"spec_domains_records\"`\n\n\tLinkedDeployments []*deployment.Deployment `bson:\"linked_deployments\"`\n}\n\ntype DeploymentsState struct {\n\tpodsMap                map[bson.ObjectID]*pod.Pod\n\tunitsMap               map[bson.ObjectID]*unit.Unit\n\tspecsMap               map[bson.ObjectID]*spec.Spec\n\tspecsPodsMap           map[bson.ObjectID]*pod.Pod\n\tspecsPodUnitsMap       map[bson.ObjectID][]*unit.Unit\n\tspecsUnitsMap          map[bson.ObjectID]*unit.Unit\n\tspecsDeploymentsMap    map[bson.ObjectID]*deployment.Deployment\n\tspecsDomainsMap        map[bson.ObjectID]*domain.Domain\n\tspecsSecretsMap        map[bson.ObjectID]*secret.Secret\n\tspecsCertsMap          map[bson.ObjectID]*certificate.Certificate\n\tdeploymentsNode        map[bson.ObjectID]*deployment.Deployment\n\tdeploymentsReservedMap map[bson.ObjectID]*deployment.Deployment\n\tdeploymentsDeployedMap map[bson.ObjectID]*deployment.Deployment\n\tdeploymentsInactiveMap map[bson.ObjectID]*deployment.Deployment\n}\n\nfunc (p *DeploymentsState) Pod(pdId bson.ObjectID) *pod.Pod {\n\treturn p.podsMap[pdId]\n}\n\nfunc (p *DeploymentsState) PodsMap() map[bson.ObjectID]*pod.Pod {\n\treturn p.podsMap\n}\n\nfunc (p *DeploymentsState) Unit(untId bson.ObjectID) *unit.Unit {\n\treturn p.unitsMap[untId]\n}\n\nfunc (p *DeploymentsState) UnitsMap() map[bson.ObjectID]*unit.Unit {\n\treturn p.unitsMap\n}\n\nfunc (p *DeploymentsState) Spec(commitId bson.ObjectID) *spec.Spec {\n\treturn p.specsMap[commitId]\n}\n\nfunc (p *DeploymentsState) SpecsMap() map[bson.ObjectID]*spec.Spec {\n\treturn p.specsMap\n}\n\nfunc (p *DeploymentsState) SpecPod(pdId bson.ObjectID) *pod.Pod {\n\treturn p.specsPodsMap[pdId]\n}\n\nfunc (p *DeploymentsState) SpecPodUnits(pdId bson.ObjectID) []*unit.Unit {\n\treturn p.specsPodUnitsMap[pdId]\n}\n\nfunc (p *DeploymentsState) SpecUnit(unitId bson.ObjectID) *unit.Unit {\n\treturn p.specsUnitsMap[unitId]\n}\n\nfunc (p *DeploymentsState) SpecsUnitsMap() map[bson.ObjectID]*unit.Unit {\n\treturn p.specsUnitsMap\n}\n\nfunc (p *DeploymentsState) SpecDomain(domnId bson.ObjectID) *domain.Domain {\n\treturn p.specsDomainsMap[domnId]\n}\n\nfunc (p *DeploymentsState) SpecSecret(secrID bson.ObjectID) *secret.Secret {\n\treturn p.specsSecretsMap[secrID]\n}\n\nfunc (p *DeploymentsState) SpecCert(\n\tcertId bson.ObjectID) *certificate.Certificate {\n\n\treturn p.specsCertsMap[certId]\n}\n\nfunc (p *DeploymentsState) SpecCertMap() map[bson.ObjectID]*certificate.Certificate {\n\treturn p.specsCertsMap\n}\n\nfunc (p *DeploymentsState) DeploymentsNode() map[bson.ObjectID]*deployment.Deployment {\n\treturn p.deploymentsNode\n}\n\nfunc (p *DeploymentsState) DeploymentReserved(deplyId bson.ObjectID) *deployment.Deployment {\n\treturn p.deploymentsReservedMap[deplyId]\n}\n\nfunc (p *DeploymentsState) DeploymentsReserved() (\n\tdeplys map[bson.ObjectID]*deployment.Deployment) {\n\n\tdeplys = p.deploymentsReservedMap\n\treturn\n}\n\nfunc (p *DeploymentsState) DeploymentDeployed(deplyId bson.ObjectID) *deployment.Deployment {\n\treturn p.deploymentsDeployedMap[deplyId]\n}\n\nfunc (p *DeploymentsState) DeploymentsDeployed() (\n\tdeplys map[bson.ObjectID]*deployment.Deployment) {\n\n\tdeplys = p.deploymentsDeployedMap\n\treturn\n}\n\nfunc (p *DeploymentsState) DeploymentsDestroy() (\n\tdeplys map[bson.ObjectID]*deployment.Deployment) {\n\n\tdeplys = p.deploymentsInactiveMap\n\treturn\n}\n\nfunc (p *DeploymentsState) DeploymentInactive(deplyId bson.ObjectID) *deployment.Deployment {\n\treturn p.deploymentsInactiveMap[deplyId]\n}\n\nfunc (p *DeploymentsState) DeploymentsInactive() (\n\tdeplys map[bson.ObjectID]*deployment.Deployment) {\n\n\tdeplys = p.deploymentsInactiveMap\n\treturn\n}\n\nfunc (p *DeploymentsState) Deployment(deplyId bson.ObjectID) (\n\tdeply *deployment.Deployment) {\n\n\tdeply = p.deploymentsDeployedMap[deplyId]\n\tif deply != nil {\n\t\treturn\n\t}\n\n\tdeply = p.deploymentsReservedMap[deplyId]\n\tif deply != nil {\n\t\treturn\n\t}\n\n\tdeply = p.deploymentsInactiveMap[deplyId]\n\tif deply != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (p *DeploymentsState) Refresh(pkg *Package, db *database.Database) (err error) {\n\tpipeline := bson.A{\n\t\tbson.M{\n\t\t\t\"$match\": bson.M{\n\t\t\t\t\"node\": node.Self.Id,\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$group\": bson.M{\n\t\t\t\t\"_id\":            nil,\n\t\t\t\t\"deployments\":    bson.M{\"$push\": \"$$ROOT\"},\n\t\t\t\t\"deployment_ids\": bson.M{\"$addToSet\": \"$_id\"},\n\t\t\t\t\"pod_ids\":        bson.M{\"$addToSet\": \"$pod\"},\n\t\t\t\t\"unit_ids\":       bson.M{\"$addToSet\": \"$unit\"},\n\t\t\t\t\"spec_ids\":       bson.M{\"$addToSet\": \"$spec\"},\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"specs\",\n\t\t\t\t\"localField\":   \"spec_ids\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"specs\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"units\",\n\t\t\t\t\"localField\":   \"unit_ids\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"units\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"pods\",\n\t\t\t\t\"localField\":   \"pod_ids\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"pods\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$addFields\": bson.M{\n\t\t\t\t\"specs_data\": bson.M{\n\t\t\t\t\t\"$reduce\": bson.M{\n\t\t\t\t\t\t\"input\": \"$specs\",\n\t\t\t\t\t\t\"initialValue\": bson.M{\n\t\t\t\t\t\t\t\"pod_ids\":    bson.A{},\n\t\t\t\t\t\t\t\"secret_ids\": bson.A{},\n\t\t\t\t\t\t\t\"cert_ids\":   bson.A{},\n\t\t\t\t\t\t\t\"domain_ids\": bson.A{},\n\t\t\t\t\t\t\t\"unit_ids\":   bson.A{},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"in\": bson.M{\n\t\t\t\t\t\t\t\"pod_ids\": bson.M{\n\t\t\t\t\t\t\t\t\"$concatArrays\": bson.A{\n\t\t\t\t\t\t\t\t\t\"$$value.pod_ids\",\n\t\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\t\"$$this.instance.pods\",\n\t\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"secret_ids\": bson.M{\n\t\t\t\t\t\t\t\t\"$concatArrays\": bson.A{\n\t\t\t\t\t\t\t\t\t\"$$value.secret_ids\",\n\t\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\t\"$$this.instance.secrets\",\n\t\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"cert_ids\": bson.M{\n\t\t\t\t\t\t\t\t\"$concatArrays\": bson.A{\n\t\t\t\t\t\t\t\t\t\"$$value.cert_ids\",\n\t\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\t\"$$this.instance.certificates\",\n\t\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"domain_ids\": bson.M{\n\t\t\t\t\t\t\t\t\"$concatArrays\": bson.A{\n\t\t\t\t\t\t\t\t\t\"$$value.domain_ids\",\n\t\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\t\"$map\": bson.M{\n\t\t\t\t\t\t\t\t\t\t\t\"input\": bson.M{\n\t\t\t\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"$$this.domain.records\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\"as\": \"record\",\n\t\t\t\t\t\t\t\t\t\t\t\"in\": \"$$record.domain\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"unit_ids\": bson.M{\n\t\t\t\t\t\t\t\t\"$concatArrays\": bson.A{\n\t\t\t\t\t\t\t\t\t\"$$value.unit_ids\",\n\t\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\t\"$reduce\": bson.M{\n\t\t\t\t\t\t\t\t\t\t\t\"input\": bson.M{\n\t\t\t\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"$$this.firewall.ingress\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\"initialValue\": bson.A{},\n\t\t\t\t\t\t\t\t\t\t\t\"in\": bson.M{\n\t\t\t\t\t\t\t\t\t\t\t\t\"$concatArrays\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"$$value\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"$map\": bson.M{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"input\": bson.M{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"$$this.sources\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"as\": \"source\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"in\": \"$$source.id\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$addFields\": bson.M{\n\t\t\t\t\"spec_pod_ids\":    \"$specs_data.pod_ids\",\n\t\t\t\t\"spec_secret_ids\": \"$specs_data.secret_ids\",\n\t\t\t\t\"spec_cert_ids\":   \"$specs_data.cert_ids\",\n\t\t\t\t\"spec_domain_ids\": \"$specs_data.domain_ids\",\n\t\t\t\t\"spec_unit_ids\":   \"$specs_data.unit_ids\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"units\",\n\t\t\t\t\"localField\":   \"spec_unit_ids\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"spec_id_units\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"units\",\n\t\t\t\t\"localField\":   \"spec_pod_ids\",\n\t\t\t\t\"foreignField\": \"pod\",\n\t\t\t\t\"as\":           \"spec_pod_units\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"pods\",\n\t\t\t\t\"localField\":   \"spec_pod_ids\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"spec_pods\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"secrets\",\n\t\t\t\t\"localField\":   \"spec_secret_ids\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"spec_secrets\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"certificates\",\n\t\t\t\t\"localField\":   \"spec_cert_ids\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"spec_certs\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"domains\",\n\t\t\t\t\"localField\":   \"spec_domain_ids\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"spec_domains\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"domains_records\",\n\t\t\t\t\"localField\":   \"spec_domain_ids\",\n\t\t\t\t\"foreignField\": \"domain\",\n\t\t\t\t\"as\":           \"spec_domains_records\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$addFields\": bson.M{\n\t\t\t\t\"spec_deployment_id_ids\": bson.M{\n\t\t\t\t\t\"$reduce\": bson.M{\n\t\t\t\t\t\t\"input\":        \"$spec_id_units\",\n\t\t\t\t\t\t\"initialValue\": bson.A{},\n\t\t\t\t\t\t\"in\": bson.M{\n\t\t\t\t\t\t\t\"$concatArrays\": bson.A{\n\t\t\t\t\t\t\t\t\"$$value\",\n\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\"$$this.deployments\",\n\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"spec_deployment_pod_ids\": bson.M{\n\t\t\t\t\t\"$reduce\": bson.M{\n\t\t\t\t\t\t\"input\":        \"$spec_pod_units\",\n\t\t\t\t\t\t\"initialValue\": bson.A{},\n\t\t\t\t\t\t\"in\": bson.M{\n\t\t\t\t\t\t\t\"$concatArrays\": bson.A{\n\t\t\t\t\t\t\t\t\"$$value\",\n\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\"$$this.deployments\",\n\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"pod_deployment_ids\": bson.M{\n\t\t\t\t\t\"$reduce\": bson.M{\n\t\t\t\t\t\t\"input\":        \"$units\",\n\t\t\t\t\t\t\"initialValue\": bson.A{},\n\t\t\t\t\t\t\"in\": bson.M{\n\t\t\t\t\t\t\t\"$setUnion\": bson.A{\n\t\t\t\t\t\t\t\t\"$$value\",\n\t\t\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\t\t\"$ifNull\": bson.A{\n\t\t\t\t\t\t\t\t\t\t\"$$this.deployments\",\n\t\t\t\t\t\t\t\t\t\tbson.A{},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$addFields\": bson.M{\n\t\t\t\t\"linked_deployment_ids\": bson.M{\n\t\t\t\t\t\"$setDifference\": bson.A{\n\t\t\t\t\t\tbson.M{\n\t\t\t\t\t\t\t\"$setUnion\": bson.A{\n\t\t\t\t\t\t\t\t\"$spec_deployment_id_ids\",\n\t\t\t\t\t\t\t\t\"$spec_deployment_pod_ids\",\n\t\t\t\t\t\t\t\t\"$pod_deployment_ids\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"$deployment_ids\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$lookup\": bson.M{\n\t\t\t\t\"from\":         \"deployments\",\n\t\t\t\t\"localField\":   \"linked_deployment_ids\",\n\t\t\t\t\"foreignField\": \"_id\",\n\t\t\t\t\"as\":           \"linked_deployments\",\n\t\t\t},\n\t\t},\n\n\t\tbson.M{\n\t\t\t\"$project\": bson.M{\n\t\t\t\t\"deployment_ids\":       1,\n\t\t\t\t\"pod_ids\":              1,\n\t\t\t\t\"unit_ids\":             1,\n\t\t\t\t\"spec_ids\":             1,\n\t\t\t\t\"deployments\":          1,\n\t\t\t\t\"pods\":                 1,\n\t\t\t\t\"units\":                1,\n\t\t\t\t\"specs\":                1,\n\t\t\t\t\"spec_pod_ids\":         1,\n\t\t\t\t\"spec_unit_ids\":        1,\n\t\t\t\t\"spec_secret_ids\":      1,\n\t\t\t\t\"spec_cert_ids\":        1,\n\t\t\t\t\"spec_domain_ids\":      1,\n\t\t\t\t\"spec_id_units\":        1,\n\t\t\t\t\"spec_pod_units\":       1,\n\t\t\t\t\"spec_pods\":            1,\n\t\t\t\t\"spec_secrets\":         1,\n\t\t\t\t\"spec_certs\":           1,\n\t\t\t\t\"spec_domains\":         1,\n\t\t\t\t\"spec_domains_records\": 1,\n\t\t\t\t\"linked_deployments\":   1,\n\t\t\t},\n\t\t},\n\t}\n\n\tcursor, err := db.Deployments().Aggregate(db, pipeline)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tresult := &DeploymentsResult{}\n\tif cursor.Next(db) {\n\t\terr = cursor.Decode(result)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tdeploymentsNode := map[bson.ObjectID]*deployment.Deployment{}\n\tdeploymentsReservedMap := map[bson.ObjectID]*deployment.Deployment{}\n\tdeploymentsDeployedMap := map[bson.ObjectID]*deployment.Deployment{}\n\tdeploymentsInactiveMap := map[bson.ObjectID]*deployment.Deployment{}\n\n\tfor _, deply := range result.Deployments {\n\t\tdeploymentsNode[deply.Id] = deply\n\n\t\tswitch deply.State {\n\t\tcase deployment.Reserved:\n\t\t\tdeploymentsReservedMap[deply.Id] = deply\n\t\tcase deployment.Deployed:\n\t\t\tswitch deply.Action {\n\t\t\tcase deployment.Destroy, deployment.Archive, deployment.Restore:\n\t\t\t\tdeploymentsInactiveMap[deply.Id] = deply\n\t\t\tdefault:\n\t\t\t\tdeploymentsDeployedMap[deply.Id] = deply\n\t\t\t}\n\t\tcase deployment.Archived:\n\t\t\tdeploymentsInactiveMap[deply.Id] = deply\n\t\t}\n\t}\n\tp.deploymentsNode = deploymentsNode\n\n\tspecsMap := map[bson.ObjectID]*spec.Spec{}\n\tfor _, spec := range result.Specs {\n\t\tspecsMap[spec.Id] = spec\n\t}\n\tp.specsMap = specsMap\n\n\tspecsCertsMap := map[bson.ObjectID]*certificate.Certificate{}\n\tfor _, specCert := range result.SpecCerts {\n\t\tspecsCertsMap[specCert.Id] = specCert\n\t}\n\tp.specsCertsMap = specsCertsMap\n\n\tspecsSecretsMap := map[bson.ObjectID]*secret.Secret{}\n\tfor _, specSecret := range result.SpecSecrets {\n\t\tspecsSecretsMap[specSecret.Id] = specSecret\n\t}\n\tp.specsSecretsMap = specsSecretsMap\n\n\tspecsPodsMap := map[bson.ObjectID]*pod.Pod{}\n\tfor _, specPod := range result.SpecPods {\n\t\tspecsPodsMap[specPod.Id] = specPod\n\t}\n\tp.specsPodsMap = specsPodsMap\n\n\tspecDomains := domain.PreloadedRecords(\n\t\tresult.SpecDomains, result.SpecDomainsRecords)\n\tspecsDomainsMap := map[bson.ObjectID]*domain.Domain{}\n\tfor _, specDomain := range specDomains {\n\t\tspecsDomainsMap[specDomain.Id] = specDomain\n\t}\n\tp.specsDomainsMap = specsDomainsMap\n\n\tspecUnitsIds := set.NewSet()\n\tspecsUnitsMap := map[bson.ObjectID]*unit.Unit{}\n\tspecsPodUnitsMap := map[bson.ObjectID][]*unit.Unit{}\n\tfor _, specUnit := range result.SpecIdUnits {\n\t\tif specUnitsIds.Contains(specUnit.Id) {\n\t\t\tcontinue\n\t\t}\n\t\tspecUnitsIds.Add(specUnit.Id)\n\n\t\tspecsUnitsMap[specUnit.Id] = specUnit\n\t\tspecsPodUnitsMap[specUnit.Pod] = append(\n\t\t\tspecsPodUnitsMap[specUnit.Pod], specUnit)\n\t}\n\tfor _, specUnit := range result.SpecPodUnits {\n\t\tif specUnitsIds.Contains(specUnit.Id) {\n\t\t\tcontinue\n\t\t}\n\t\tspecUnitsIds.Add(specUnit.Id)\n\n\t\tspecsUnitsMap[specUnit.Id] = specUnit\n\t\tspecsPodUnitsMap[specUnit.Pod] = append(\n\t\t\tspecsPodUnitsMap[specUnit.Pod], specUnit)\n\t}\n\tp.specsUnitsMap = specsUnitsMap\n\tp.specsPodUnitsMap = specsPodUnitsMap\n\n\tfor _, deply := range result.LinkedDeployments {\n\t\tswitch deply.State {\n\t\tcase deployment.Reserved:\n\t\t\tdeploymentsReservedMap[deply.Id] = deply\n\t\tcase deployment.Deployed:\n\t\t\tswitch deply.Action {\n\t\t\tcase deployment.Destroy, deployment.Archive, deployment.Restore:\n\t\t\t\tdeploymentsInactiveMap[deply.Id] = deply\n\t\t\tdefault:\n\t\t\t\tdeploymentsDeployedMap[deply.Id] = deply\n\t\t\t}\n\t\tcase deployment.Archived:\n\t\t\tdeploymentsInactiveMap[deply.Id] = deply\n\t\t}\n\t}\n\tp.deploymentsReservedMap = deploymentsReservedMap\n\tp.deploymentsDeployedMap = deploymentsDeployedMap\n\tp.deploymentsInactiveMap = deploymentsInactiveMap\n\n\tpodsMap := map[bson.ObjectID]*pod.Pod{}\n\tfor _, pd := range result.Pods {\n\t\tpodsMap[pd.Id] = pd\n\t}\n\tp.podsMap = podsMap\n\n\tunitsMap := map[bson.ObjectID]*unit.Unit{}\n\tfor _, unt := range result.Units {\n\t\tunitsMap[unt.Id] = unt\n\t}\n\tp.unitsMap = unitsMap\n\n\treturn\n}\n\nfunc (p *DeploymentsState) Apply(st *State) {\n\tst.Pod = p.Pod\n\tst.PodsMap = p.PodsMap\n\tst.Unit = p.Unit\n\tst.UnitsMap = p.UnitsMap\n\tst.Spec = p.Spec\n\tst.SpecPod = p.SpecPod\n\tst.SpecPodUnits = p.SpecPodUnits\n\tst.SpecUnit = p.SpecUnit\n\tst.SpecsUnitsMap = p.SpecsUnitsMap\n\tst.SpecDomain = p.SpecDomain\n\tst.SpecSecret = p.SpecSecret\n\tst.SpecCert = p.SpecCert\n\tst.DeploymentsNode = p.DeploymentsNode\n\tst.DeploymentReserved = p.DeploymentReserved\n\tst.DeploymentsReserved = p.DeploymentsReserved\n\tst.DeploymentDeployed = p.DeploymentDeployed\n\tst.DeploymentsDeployed = p.DeploymentsDeployed\n\tst.DeploymentsDestroy = p.DeploymentsDestroy\n\tst.DeploymentInactive = p.DeploymentInactive\n\tst.DeploymentsInactive = p.DeploymentsInactive\n\tst.Deployment = p.Deployment\n}\n"
  },
  {
    "path": "state/disks.go",
    "content": "package state\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\nvar (\n\tDisks    = &DisksState{}\n\tDisksPkg = NewPackage(Disks)\n)\n\ntype DisksState struct {\n\tdisks           []*disk.Disk\n\tinstanceDisks   map[bson.ObjectID][]*disk.Disk\n\tdeploymentDisks map[bson.ObjectID][]*disk.Disk\n}\n\nfunc (p *DisksState) Disks() []*disk.Disk {\n\treturn p.disks\n}\n\nfunc (p *DisksState) GetInstaceDisks(instId bson.ObjectID) []*disk.Disk {\n\treturn p.instanceDisks[instId]\n}\n\nfunc (p *DisksState) GetDeploymentDisks(\n\tdeplyId bson.ObjectID) []*disk.Disk {\n\n\treturn p.deploymentDisks[deplyId]\n}\n\nfunc (p *DisksState) InstaceDisksMap() map[bson.ObjectID][]*disk.Disk {\n\treturn p.instanceDisks\n}\n\nfunc (p *DisksState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tndeId := node.Self.Id\n\tndePools := node.Self.Pools\n\n\tdisks, err := disk.GetNode(db, ndeId, ndePools)\n\tif err != nil {\n\t\treturn\n\t}\n\tp.disks = disks\n\n\tinstanceDisks := map[bson.ObjectID][]*disk.Disk{}\n\tdeploymentDisks := map[bson.ObjectID][]*disk.Disk{}\n\tfor _, dsk := range disks {\n\t\tif !dsk.Instance.IsZero() {\n\t\t\tinstanceDisks[dsk.Instance] = append(\n\t\t\t\tinstanceDisks[dsk.Instance], dsk)\n\t\t}\n\t\tif !dsk.Deployment.IsZero() {\n\t\t\tdeploymentDisks[dsk.Deployment] = append(\n\t\t\t\tdeploymentDisks[dsk.Deployment], dsk)\n\t\t}\n\t}\n\tp.instanceDisks = instanceDisks\n\tp.deploymentDisks = deploymentDisks\n\n\treturn\n}\n\nfunc (p *DisksState) Apply(st *State) {\n\tst.Disks = p.Disks\n\tst.GetInstaceDisks = p.GetInstaceDisks\n\tst.GetDeploymentDisks = p.GetDeploymentDisks\n\tst.InstaceDisksMap = p.InstaceDisksMap\n}\n"
  },
  {
    "path": "state/domains.go",
    "content": "package state\n\nimport (\n\t\"net\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n)\n\nvar (\n\tDomains    = &DomainsState{}\n\tDomainsPkg = NewPackage(Domains)\n)\n\ntype DomainsState struct {\n\tdomains map[bson.ObjectID][]*types.Domain\n}\n\nfunc (p *DomainsState) GetDomains(orgId bson.ObjectID) []*types.Domain {\n\treturn p.domains[orgId]\n}\n\nfunc (p *DomainsState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tcoll := db.Domains()\n\trootDomains := map[bson.ObjectID]*domain.Domain{}\n\trecords := map[bson.ObjectID][]*types.Domain{}\n\n\tcursor, err := coll.Find(db, bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdmn := &domain.Domain{}\n\t\terr = cursor.Decode(dmn)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\trootDomains[dmn.Id] = dmn\n\t}\n\n\tcoll = db.DomainsRecords()\n\n\tcursor, err = coll.Find(\n\t\tdb,\n\t\tbson.M{},\n\t\toptions.Find().SetSort(bson.D{{\"_id\", 1}}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\trec := &domain.Record{}\n\t\terr = cursor.Decode(rec)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif rec.IsDeleted() {\n\t\t\tcontinue\n\t\t}\n\n\t\tdmn := rootDomains[rec.Domain]\n\t\tif dmn == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tdmnRec := &types.Domain{\n\t\t\tDomain: rec.SubDomain + \".\" + dmn.RootDomain + \".\",\n\t\t\tType:   rec.Type,\n\t\t}\n\n\t\tswitch rec.Type {\n\t\tcase domain.A:\n\t\t\tdmnRec.Ip = net.ParseIP(rec.Value)\n\t\tcase domain.AAAA:\n\t\t\tdmnRec.Ip = net.ParseIP(rec.Value)\n\t\tcase domain.CNAME:\n\t\t\tdmnRec.Target = rec.Value + \".\"\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\trecords[dmn.Organization] = append(records[dmn.Organization], dmnRec)\n\t}\n\n\tp.domains = records\n\n\treturn\n}\n\nfunc (p *DomainsState) Apply(st *State) {\n\tst.GetDomains = p.GetDomains\n}\n"
  },
  {
    "path": "state/firewalls.go",
    "content": "package state\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\nvar (\n\tFirewalls    = &FirewallsState{}\n\tFirewallsPkg = NewPackage(Firewalls)\n)\n\ntype FirewallsState struct {\n\tnodeFirewall       []*firewall.Rule\n\tfirewalls          map[string][]*firewall.Rule\n\tfirewallMaps       map[string][]*firewall.Mapping\n\tinstanceNamespaces map[bson.ObjectID][]string\n}\n\nfunc (p *FirewallsState) NodeFirewall() []*firewall.Rule {\n\treturn p.nodeFirewall\n}\n\nfunc (p *FirewallsState) Firewalls() map[string][]*firewall.Rule {\n\treturn p.firewalls\n}\n\nfunc (p *FirewallsState) FirewallMaps() map[string][]*firewall.Mapping {\n\treturn p.firewallMaps\n}\n\nfunc (p *FirewallsState) GetInstanceNamespaces(\n\tinstId bson.ObjectID) []string {\n\n\treturn p.instanceNamespaces[instId]\n}\n\nfunc (p *FirewallsState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tspecRules, err := firewall.GetSpecRules(Instances.Instances(),\n\t\tDeployments.DeploymentsNode(), Deployments.SpecsMap(),\n\t\tDeployments.SpecsUnitsMap(), Deployments.DeploymentsDeployed())\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_, rolesSet := InstancesPreload.GetRoles()\n\tfiresMap := FirewallsPreload.Firewalls()\n\tfirewallRolesSet := FirewallsPreload.RolesSet()\n\troles := rolesSet.Copy()\n\troles.Subtract(firewallRolesSet)\n\n\tmissRoles := []string{}\n\tfor roleInf := range roles.Iter() {\n\t\tmissRoles = append(missRoles, roleInf.(string))\n\t}\n\n\tif len(missRoles) > 0 {\n\t\tmissFiresMap, e := firewall.GetMapRoles(db, missRoles)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor role, fires := range missFiresMap {\n\t\t\tfiresMap[role] = fires\n\t\t}\n\t}\n\n\tnodeFirewall, firewalls, firewallMaps, instNamespaces, err :=\n\t\tfirewall.GetAllIngressPreloaded(node.Self, Instances.Instances(),\n\t\t\tspecRules, Instances.NodePortsMap(), firesMap)\n\tif err != nil {\n\t\treturn\n\t}\n\tp.nodeFirewall = nodeFirewall\n\tp.firewalls = firewalls\n\tp.firewallMaps = firewallMaps\n\tp.instanceNamespaces = instNamespaces\n\n\treturn\n}\n\nfunc (p *FirewallsState) Apply(st *State) {\n\tst.NodeFirewall = p.NodeFirewall\n\tst.Firewalls = p.Firewalls\n\tst.FirewallMaps = p.FirewallMaps\n\tst.GetInstanceNamespaces = p.GetInstanceNamespaces\n}\n\nfunc init() {\n\tFirewallsPkg.\n\t\tAfter(FirewallsPreload).\n\t\tAfter(Instances).\n\t\tAfter(Vpcs).\n\t\tAfter(Deployments)\n}\n"
  },
  {
    "path": "state/firewalls_preload.go",
    "content": "package state\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n)\n\nvar (\n\tFirewallsPreload    = &FirewallsPreloadState{}\n\tFirewallsPreloadPkg = NewPackage(FirewallsPreload)\n)\n\ntype FirewallsPreloadState struct {\n\tfirewalls         map[string][]*firewall.Firewall\n\tfirewallsRolesSet set.Set\n}\n\nfunc (p *FirewallsPreloadState) Firewalls() map[string][]*firewall.Firewall {\n\treturn p.firewalls\n}\n\nfunc (p *FirewallsPreloadState) RolesSet() set.Set {\n\treturn p.firewallsRolesSet\n}\n\nfunc (p *FirewallsPreloadState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\troles, rolesSet := InstancesPreload.GetRoles()\n\tif len(roles) == 0 {\n\t\tp.firewalls = map[string][]*firewall.Firewall{}\n\t\tp.firewallsRolesSet = set.NewSet()\n\t\treturn\n\t}\n\n\tfiresMap, err := firewall.GetMapRoles(db, roles)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tp.firewalls = firesMap\n\tp.firewallsRolesSet = rolesSet\n\n\treturn\n}\n\nfunc (p *FirewallsPreloadState) Apply(st *State) {\n}\n"
  },
  {
    "path": "state/instances.go",
    "content": "package state\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n)\n\nvar (\n\tInstances    = &InstancesState{}\n\tInstancesPkg = NewPackage(Instances)\n)\n\ntype InstancesState struct {\n\tinstances    []*instance.Instance\n\tinstancesMap map[bson.ObjectID]*instance.Instance\n\tnodePortsMap map[string][]*nodeport.Mapping\n}\n\nfunc (p *InstancesState) GetInstace(\n\tinstId bson.ObjectID) *instance.Instance {\n\n\tif instId.IsZero() {\n\t\treturn nil\n\t}\n\treturn p.instancesMap[instId]\n}\n\nfunc (p *InstancesState) Instances() []*instance.Instance {\n\treturn p.instances\n}\n\nfunc (p *InstancesState) NodePortsMap() map[string][]*nodeport.Mapping {\n\treturn p.nodePortsMap\n}\n\nfunc (p *InstancesState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tinstances := InstancesPreload.GetInstances()\n\tinstances = instance.LoadAllVirt(instances,\n\t\tPools.NodePools(), Disks.InstaceDisksMap())\n\n\tp.instances = instances\n\n\tinstId := set.NewSet()\n\tinstancesMap := map[bson.ObjectID]*instance.Instance{}\n\tnodePortsMap := map[string][]*nodeport.Mapping{}\n\tfor _, inst := range instances {\n\t\tinstId.Add(inst.Id)\n\t\tinstancesMap[inst.Id] = inst\n\n\t\tnodePortsMap[inst.NetworkNamespace] = append(\n\t\t\tnodePortsMap[inst.NetworkNamespace], inst.NodePorts...)\n\t}\n\tp.instancesMap = instancesMap\n\tp.nodePortsMap = nodePortsMap\n\n\treturn\n}\n\nfunc (p *InstancesState) Apply(st *State) {\n\tst.GetInstace = p.GetInstace\n\tst.Instances = p.Instances\n\tst.NodePortsMap = p.NodePortsMap\n}\n\nfunc init() {\n\tInstancesPkg.\n\t\tAfter(InstancesPreload).\n\t\tAfter(Disks).\n\t\tAfter(Pools)\n}\n"
  },
  {
    "path": "state/instances_preload.go",
    "content": "package state\n\nimport (\n\t\"sync\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n)\n\nvar (\n\tInstancesPreload    = &InstancesPreloadState{}\n\tInstancesPreloadPkg = NewPackage(InstancesPreload)\n)\n\ntype InstancesPreloadState struct {\n\troles     []string\n\trolesSet  set.Set\n\trolesLock sync.Mutex\n\tinstances []*instance.Instance\n}\n\nfunc (p *InstancesPreloadState) GetInstances() []*instance.Instance {\n\treturn p.instances\n}\n\nfunc (p *InstancesPreloadState) GetRoles() (roles []string, rolesSet set.Set) {\n\tp.rolesLock.Lock()\n\troles = p.roles\n\trolesSet = p.rolesSet\n\tp.rolesLock.Unlock()\n\treturn\n}\n\nfunc (p *InstancesPreloadState) setRoles(roles []string, rolesSet set.Set) {\n\tp.rolesLock.Lock()\n\tp.roles = roles\n\tp.rolesSet = rolesSet\n\tp.rolesLock.Unlock()\n\treturn\n}\n\nfunc (p *InstancesPreloadState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tndeId := node.Self.Id\n\n\tinstances, rolesSet, err := instance.GetAllRoles(db, &bson.M{\n\t\t\"node\": ndeId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tp.instances = instances\n\n\tnde := node.Self\n\tif nde.Firewall {\n\t\troles := nde.Roles\n\t\tfor _, role := range roles {\n\t\t\trolesSet.Add(role)\n\t\t}\n\t}\n\n\troles := []string{}\n\tfor instRoleInf := range rolesSet.Iter() {\n\t\troles = append(roles, instRoleInf.(string))\n\t}\n\n\tp.setRoles(roles, rolesSet)\n\n\treturn\n}\n\nfunc (p *InstancesPreloadState) Apply(st *State) {\n}\n"
  },
  {
    "path": "state/network.go",
    "content": "package state\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tNetwork    = &NetworkState{}\n\tNetworkPkg = NewPackage(Network)\n)\n\ntype NetworkState struct {\n\tnamespaces    []string\n\tinterfaces    []string\n\tinterfacesSet set.Set\n}\n\nfunc (p *NetworkState) Namespaces() []string {\n\treturn p.namespaces\n}\n\nfunc (p *NetworkState) Interfaces() []string {\n\treturn p.interfaces\n}\n\nfunc (p *NetworkState) HasInterfaces(iface string) bool {\n\treturn p.interfacesSet.Contains(iface)\n}\n\nfunc (p *NetworkState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tnamespaces, err := utils.GetNamespaces()\n\tif err != nil {\n\t\treturn\n\t}\n\tp.namespaces = namespaces\n\n\tinterfaces, interfacesSet, err := utils.GetInterfacesSet()\n\tif err != nil {\n\t\treturn\n\t}\n\tp.interfaces = interfaces\n\tp.interfacesSet = interfacesSet\n\n\treturn\n}\n\nfunc (p *NetworkState) Apply(st *State) {\n\tst.Namespaces = p.Namespaces\n\tst.Interfaces = p.Interfaces\n\tst.HasInterfaces = p.HasInterfaces\n}\n"
  },
  {
    "path": "state/package.go",
    "content": "package state\n\nimport (\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nvar (\n\trefCounter = 0\n\tregistry   = map[PackageHandler]*Package{}\n)\n\ntype PackageHandler interface {\n\tRefresh(pkg *Package, db *database.Database) (err error)\n\tApply(st *State)\n}\n\ntype Package struct {\n\tname    string\n\tref     int\n\tafter   set.Set\n\tttl     time.Duration\n\thandler PackageHandler\n}\n\nfunc (p *Package) Cache(d time.Duration) *Package {\n\tp.ttl = d\n\treturn p\n}\n\nfunc (p *Package) Evict() *Package {\n\tp.ttl = 0\n\treturn p\n}\n\nfunc (p *Package) After(handler PackageHandler) *Package {\n\tp.after.Add(registry[handler].ref)\n\treturn p\n}\n\nfunc NewPackage(handler PackageHandler) *Package {\n\trefCounter += 1\n\n\tpkg := &Package{\n\t\tname:    reflect.TypeOf(handler).Elem().Name(),\n\t\tref:     refCounter,\n\t\thandler: handler,\n\t\tafter:   set.NewSet(),\n\t}\n\tregistry[handler] = pkg\n\n\treturn pkg\n}\n\nfunc RefreshAll(db *database.Database, runtimes *Runtimes) (err error) {\n\tinDegree := make(map[int]int)\n\tdependents := make(map[int][]*Package)\n\trefToPackage := make(map[int]*Package)\n\n\tfor _, pkg := range registry {\n\t\tinDegree[pkg.ref] = 0\n\t\tdependents[pkg.ref] = []*Package{}\n\t\trefToPackage[pkg.ref] = pkg\n\t}\n\n\tfor _, pkg := range registry {\n\t\tfor afterRef := range pkg.after.Iter() {\n\t\t\tafterRefInt := afterRef.(int)\n\t\t\tinDegree[pkg.ref]++\n\t\t\tdependents[afterRefInt] = append(dependents[afterRefInt], pkg)\n\t\t}\n\t}\n\n\tready := make(chan *Package, len(registry))\n\tfor ref, degree := range inDegree {\n\t\tif degree == 0 {\n\t\t\tready <- refToPackage[ref]\n\t\t}\n\t}\n\n\tdone := make(chan *Package, len(registry))\n\terrors := make(chan error, len(registry))\n\tcompleted := make(map[int]bool)\n\n\tprocessPackage := func(pkg *Package) {\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\tdone <- pkg\n\t\t\t}()\n\n\t\t\tstart := time.Now()\n\t\t\trefreshErr := pkg.handler.Refresh(pkg, db)\n\t\t\tdur := time.Since(start)\n\t\t\tif refreshErr != nil {\n\t\t\t\terrors <- refreshErr\n\t\t\t}\n\n\t\t\truntimes.SetState(pkg.name, dur)\n\t\t}()\n\t}\n\n\tprocessed := 0\n\ttotalPackages := len(registry)\n\n\tfor processed < totalPackages {\n\t\tselect {\n\t\tcase pkg := <-ready:\n\t\t\tprocessPackage(pkg)\n\n\t\tcase completedPkg := <-done:\n\t\t\tprocessed++\n\t\t\tcompleted[completedPkg.ref] = true\n\n\t\t\tfor _, dependent := range dependents[completedPkg.ref] {\n\t\t\t\tif !completed[dependent.ref] {\n\t\t\t\t\tallDepsCompleted := true\n\n\t\t\t\t\tfor afterRef := range dependent.after.Iter() {\n\t\t\t\t\t\tif !completed[afterRef.(int)] {\n\t\t\t\t\t\t\tallDepsCompleted = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif allDepsCompleted {\n\t\t\t\t\t\tready <- dependent\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase refreshErr := <-errors:\n\t\t\tif err == nil {\n\t\t\t\terr = refreshErr\n\t\t\t}\n\t\t}\n\t}\n\n\tselect {\n\tcase refreshErr := <-errors:\n\t\tif err == nil {\n\t\t\terr = refreshErr\n\t\t}\n\tdefault:\n\t}\n\n\treturn\n}\n\nfunc ApplyAll(st *State) {\n\tfor _, pkg := range registry {\n\t\tpkg.handler.Apply(st)\n\t}\n}\n"
  },
  {
    "path": "state/pools.go",
    "content": "package state\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n)\n\nvar (\n\tPools    = &PoolsState{}\n\tPoolsPkg = NewPackage(Pools)\n)\n\ntype PoolsState struct {\n\tnodePools []*pool.Pool\n}\n\nfunc (p *PoolsState) NodePools() []*pool.Pool {\n\treturn p.nodePools\n}\n\nfunc (p *PoolsState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tzneId := node.Self.Zone\n\tif zneId.IsZero() {\n\t\tp.nodePools = nil\n\t\treturn\n\t}\n\n\tpools, err := pool.GetAll(db, &bson.M{\n\t\t\"zone\": zneId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\tp.nodePools = pools\n\n\treturn\n}\n\nfunc (p *PoolsState) Apply(st *State) {\n\tst.NodePools = p.NodePools\n}\n"
  },
  {
    "path": "state/runtimes.go",
    "content": "package state\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Runtimes struct {\n\tState       map[string]time.Duration\n\tNetwork     time.Duration\n\tIpset       time.Duration\n\tIptables    time.Duration\n\tDisks       time.Duration\n\tInstances   time.Duration\n\tNamespaces  time.Duration\n\tPods        time.Duration\n\tDeployments time.Duration\n\tImds        time.Duration\n\tWait        time.Duration\n\tTotal       time.Duration\n\tlock        sync.Mutex\n}\n\nfunc (r *Runtimes) Init() {\n\tr.State = map[string]time.Duration{}\n}\n\nfunc (r *Runtimes) SetState(key string, dur time.Duration) {\n\tr.lock.Lock()\n\tr.State[key] = dur\n\tr.lock.Unlock()\n}\n\nfunc (r *Runtimes) Log() {\n\tfields := logrus.Fields{\n\t\t\"network\":     fmt.Sprintf(\"%v\", r.Network),\n\t\t\"ipset\":       fmt.Sprintf(\"%v\", r.Ipset),\n\t\t\"iptables\":    fmt.Sprintf(\"%v\", r.Iptables),\n\t\t\"disks\":       fmt.Sprintf(\"%v\", r.Disks),\n\t\t\"namespaces\":  fmt.Sprintf(\"%v\", r.Namespaces),\n\t\t\"pods\":        fmt.Sprintf(\"%v\", r.Pods),\n\t\t\"deployments\": fmt.Sprintf(\"%v\", r.Deployments),\n\t\t\"imds\":        fmt.Sprintf(\"%v\", r.Imds),\n\t\t\"wait\":        fmt.Sprintf(\"%v\", r.Wait),\n\t\t\"total\":       fmt.Sprintf(\"%v\", r.Total),\n\t}\n\n\tfor key, dur := range r.State {\n\t\tkey = utils.ToSnakeCase(key)\n\t\tfields[key] = fmt.Sprintf(\"%v\", dur)\n\t}\n\n\tlogrus.WithFields(fields).Warn(\"sync: High state sync runtime\")\n}\n"
  },
  {
    "path": "state/schedulers.go",
    "content": "package state\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/scheduler\"\n)\n\nvar (\n\tSchedulers    = &SchedulersState{}\n\tSchedulersPkg = NewPackage(Schedulers)\n)\n\ntype SchedulersState struct {\n\tschedulers []*scheduler.Scheduler\n}\n\nfunc (p *SchedulersState) Schedulers() []*scheduler.Scheduler {\n\treturn p.schedulers\n}\n\nfunc (p *SchedulersState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tschedulers, err := scheduler.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\tp.schedulers = schedulers\n\n\treturn\n}\n\nfunc (p *SchedulersState) Apply(st *State) {\n\tst.Schedulers = p.Schedulers\n}\n"
  },
  {
    "path": "state/state.go",
    "content": "package state\n\nimport (\n\t\"sync\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/scheduler\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\ntype State struct {\n\twaiter *sync.WaitGroup\n\n\t// Datacenter\n\tNodeDatacenter func() *datacenter.Datacenter\n\n\t// Zone\n\tNodeZone func() *zone.Zone\n\n\t// Zones\n\tVxLan   func() bool\n\tGetZone func(zneId bson.ObjectID) *zone.Zone\n\tNodes   func() []*node.Node\n\n\t// Network\n\tNamespaces    func() []string\n\tInterfaces    func() []string\n\tHasInterfaces func(iface string) bool\n\n\t// Domains\n\tGetDomains func(orgId bson.ObjectID) []*types.Domain\n\n\t// Pools\n\tNodePools func() []*pool.Pool\n\n\t// Disks\n\tDisks              func() []*disk.Disk\n\tGetInstaceDisks    func(instId bson.ObjectID) []*disk.Disk\n\tGetDeploymentDisks func(deplyId bson.ObjectID) []*disk.Disk\n\tInstaceDisksMap    func() map[bson.ObjectID][]*disk.Disk\n\n\t// Vpcs\n\tVpc       func(vpcId bson.ObjectID) *vpc.Vpc\n\tVpcsMap   func() map[bson.ObjectID]*vpc.Vpc\n\tVpcIps    func(vpcId bson.ObjectID) []*vpc.VpcIp\n\tVpcIpsMap func() map[bson.ObjectID][]*vpc.VpcIp\n\tVpcs      func() []*vpc.Vpc\n\n\t// Deployments\n\tPod                 func(pdId bson.ObjectID) *pod.Pod\n\tPodsMap             func() map[bson.ObjectID]*pod.Pod\n\tUnit                func(unitId bson.ObjectID) *unit.Unit\n\tUnitsMap            func() map[bson.ObjectID]*unit.Unit\n\tSpec                func(commitId bson.ObjectID) *spec.Spec\n\tSpecsMap            func() map[bson.ObjectID]*spec.Spec\n\tSpecPod             func(pdId bson.ObjectID) *pod.Pod\n\tSpecPodUnits        func(pdId bson.ObjectID) []*unit.Unit\n\tSpecUnit            func(unitId bson.ObjectID) *unit.Unit\n\tSpecsUnitsMap       func() map[bson.ObjectID]*unit.Unit\n\tSpecDomain          func(domnId bson.ObjectID) *domain.Domain\n\tSpecSecret          func(secrID bson.ObjectID) *secret.Secret\n\tSpecCert            func(certId bson.ObjectID) *certificate.Certificate\n\tDeploymentsNode     func() map[bson.ObjectID]*deployment.Deployment\n\tDeploymentReserved  func(deplyId bson.ObjectID) *deployment.Deployment\n\tDeploymentsReserved func() map[bson.ObjectID]*deployment.Deployment\n\tDeploymentDeployed  func(deplyId bson.ObjectID) *deployment.Deployment\n\tDeploymentsDeployed func() map[bson.ObjectID]*deployment.Deployment\n\tDeploymentsDestroy  func() map[bson.ObjectID]*deployment.Deployment\n\tDeploymentInactive  func(deplyId bson.ObjectID) *deployment.Deployment\n\tDeploymentsInactive func() map[bson.ObjectID]*deployment.Deployment\n\tDeployment          func(deplyId bson.ObjectID) *deployment.Deployment\n\n\t// Instances\n\tGetInstace            func(instId bson.ObjectID) *instance.Instance\n\tInstances             func() []*instance.Instance\n\tNodePortsMap          func() map[string][]*nodeport.Mapping\n\tGetInstaceAuthorities func(orgId bson.ObjectID,\n\t\troles []string) []*authority.Authority\n\n\t// Virtuals\n\tDiskInUse func(instId, dskId bson.ObjectID) bool\n\tGetVirt   func(instId bson.ObjectID) *vm.VirtualMachine\n\tVirtsMap  func() map[bson.ObjectID]*vm.VirtualMachine\n\n\t// Schedulers\n\tSchedulers func() []*scheduler.Scheduler\n\n\t// Firewalls\n\tNodeFirewall          func() []*firewall.Rule\n\tFirewalls             func() map[string][]*firewall.Rule\n\tFirewallMaps          func() map[string][]*firewall.Mapping\n\tArpRecords            func(namespace string) set.Set\n\tGetInstanceNamespaces func(instId bson.ObjectID) []string\n}\n\nfunc (s *State) Node() *node.Node {\n\treturn node.Self\n}\n\nfunc (s *State) WaitAdd() {\n\ts.waiter.Add(1)\n}\n\nfunc (s *State) WaitDone() {\n\ts.waiter.Done()\n}\n\nfunc (s *State) Wait() {\n\ts.waiter.Wait()\n}\n\nfunc GetState(runtimes *Runtimes) (stat *State, err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\terr = RefreshAll(db, runtimes)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstat = &State{\n\t\twaiter: &sync.WaitGroup{},\n\t}\n\tApplyAll(stat)\n\n\treturn\n}\n"
  },
  {
    "path": "state/state_old.go",
    "content": "package state\n\nimport (\n\t\"io/ioutil\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/arp\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/qemu\"\n\t\"github.com/pritunl/pritunl-cloud/scheduler\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype StateOld struct {\n\tnodeSelf               *node.Node\n\tnodes                  []*node.Node\n\tnodeDatacenter         *datacenter.Datacenter\n\tnodeZone               *zone.Zone\n\tvxlan                  bool\n\tzoneMap                map[bson.ObjectID]*zone.Zone\n\tnamespaces             []string\n\tinterfaces             []string\n\tinterfacesSet          set.Set\n\tnodeFirewall           []*firewall.Rule\n\tfirewalls              map[string][]*firewall.Rule\n\tfirewallMaps           map[string][]*firewall.Mapping\n\tpools                  []*pool.Pool\n\tdisks                  []*disk.Disk\n\tschedulers             []*scheduler.Scheduler\n\tdeploymentsReservedMap map[bson.ObjectID]*deployment.Deployment\n\tdeploymentsDeployedMap map[bson.ObjectID]*deployment.Deployment\n\tdeploymentsInactiveMap map[bson.ObjectID]*deployment.Deployment\n\tpodsMap                map[bson.ObjectID]*pod.Pod\n\tunitsMap               map[bson.ObjectID]*unit.Unit\n\n\tspecsMap            map[bson.ObjectID]*spec.Spec\n\tspecsPodsMap        map[bson.ObjectID]*pod.Pod\n\tspecsPodUnitsMap    map[bson.ObjectID][]*unit.Unit\n\tspecsUnitsMap       map[bson.ObjectID]*unit.Unit\n\tspecsDeploymentsMap map[bson.ObjectID]*deployment.Deployment\n\tspecsDomainsMap     map[bson.ObjectID]*domain.Domain\n\tspecsSecretsMap     map[bson.ObjectID]*secret.Secret\n\tspecsCertsMap       map[bson.ObjectID]*certificate.Certificate\n\n\tvirtsMap           map[bson.ObjectID]*vm.VirtualMachine\n\tinstances          []*instance.Instance\n\tinstancesMap       map[bson.ObjectID]*instance.Instance\n\tinstanceDisks      map[bson.ObjectID][]*disk.Disk\n\tinstanceNamespaces map[bson.ObjectID][]string\n\tauthoritiesMap     map[string][]*authority.Authority\n\tvpcs               []*vpc.Vpc\n\tvpcsMap            map[bson.ObjectID]*vpc.Vpc\n\tvpcIpsMap          map[bson.ObjectID][]*vpc.VpcIp\n\tarpRecords         map[string]set.Set\n\taddInstances       set.Set\n\tremInstances       set.Set\n\trunning            []string\n}\n\nfunc (s *StateOld) Node() *node.Node {\n\treturn s.nodeSelf\n}\n\nfunc (s *StateOld) Nodes() []*node.Node {\n\treturn s.nodes\n}\n\nfunc (s *StateOld) VxLan() bool {\n\treturn s.vxlan\n}\n\nfunc (s *StateOld) NodeDatacenter() *datacenter.Datacenter {\n\treturn s.nodeDatacenter\n}\n\nfunc (s *StateOld) NodeZone() *zone.Zone {\n\treturn s.nodeZone\n}\n\nfunc (s *StateOld) GetZone(zneId bson.ObjectID) *zone.Zone {\n\treturn s.zoneMap[zneId]\n}\n\nfunc (s *StateOld) Namespaces() []string {\n\treturn s.namespaces\n}\n\nfunc (s *StateOld) Interfaces() []string {\n\treturn s.interfaces\n}\n\nfunc (s *StateOld) HasInterfaces(iface string) bool {\n\treturn s.interfacesSet.Contains(iface)\n}\n\nfunc (s *StateOld) Instances() []*instance.Instance {\n\treturn s.instances\n}\n\nfunc (s *StateOld) Schedulers() []*scheduler.Scheduler {\n\treturn s.schedulers\n}\n\nfunc (s *StateOld) NodeFirewall() []*firewall.Rule {\n\treturn s.nodeFirewall\n}\n\nfunc (s *StateOld) Firewalls() map[string][]*firewall.Rule {\n\treturn s.firewalls\n}\n\nfunc (s *StateOld) FirewallMaps() map[string][]*firewall.Mapping {\n\treturn s.firewallMaps\n}\n\nfunc (s *StateOld) Running() []string {\n\treturn s.running\n}\n\nfunc (s *StateOld) Disks() []*disk.Disk {\n\treturn s.disks\n}\n\nfunc (s *StateOld) GetInstaceDisks(instId bson.ObjectID) []*disk.Disk {\n\treturn s.instanceDisks[instId]\n}\n\nfunc (s *StateOld) GetInstanceNamespaces(instId bson.ObjectID) []string {\n\treturn s.instanceNamespaces[instId]\n}\n\nfunc (s *StateOld) GetInstaceAuthorities(roles []string) []*authority.Authority {\n\tauthrSet := set.NewSet()\n\tauthrs := []*authority.Authority{}\n\n\tfor _, role := range roles {\n\t\tfor _, authr := range s.authoritiesMap[role] {\n\t\t\tif authrSet.Contains(authr.Id) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tauthrSet.Add(authr.Id)\n\t\t\tauthrs = append(authrs, authr)\n\t\t}\n\t}\n\n\treturn authrs\n}\n\nfunc (s *StateOld) DeploymentReserved(deplyId bson.ObjectID) *deployment.Deployment {\n\treturn s.deploymentsReservedMap[deplyId]\n}\n\nfunc (s *StateOld) DeploymentsReserved() (\n\tdeplys map[bson.ObjectID]*deployment.Deployment) {\n\n\tdeplys = s.deploymentsReservedMap\n\treturn\n}\n\nfunc (s *StateOld) DeploymentDeployed(deplyId bson.ObjectID) *deployment.Deployment {\n\treturn s.deploymentsDeployedMap[deplyId]\n}\n\nfunc (s *StateOld) DeploymentsDeployed() (\n\tdeplys map[bson.ObjectID]*deployment.Deployment) {\n\n\tdeplys = s.deploymentsDeployedMap\n\treturn\n}\n\nfunc (s *StateOld) DeploymentsDestroy() (\n\tdeplys map[bson.ObjectID]*deployment.Deployment) {\n\n\tdeplys = s.deploymentsInactiveMap\n\treturn\n}\n\nfunc (s *StateOld) DeploymentInactive(deplyId bson.ObjectID) *deployment.Deployment {\n\treturn s.deploymentsInactiveMap[deplyId]\n}\n\nfunc (s *StateOld) DeploymentsInactive() (\n\tdeplys map[bson.ObjectID]*deployment.Deployment) {\n\n\tdeplys = s.deploymentsInactiveMap\n\treturn\n}\n\nfunc (s *StateOld) Deployment(deplyId bson.ObjectID) (\n\tdeply *deployment.Deployment) {\n\n\tdeply = s.deploymentsDeployedMap[deplyId]\n\tif deply != nil {\n\t\treturn\n\t}\n\n\tdeply = s.deploymentsReservedMap[deplyId]\n\tif deply != nil {\n\t\treturn\n\t}\n\n\tdeply = s.deploymentsInactiveMap[deplyId]\n\tif deply != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *StateOld) Pod(pdId bson.ObjectID) *pod.Pod {\n\treturn s.podsMap[pdId]\n}\n\nfunc (s *StateOld) Unit(unitId bson.ObjectID) *unit.Unit {\n\treturn s.unitsMap[unitId]\n}\n\nfunc (s *StateOld) Spec(commitId bson.ObjectID) *spec.Spec {\n\treturn s.specsMap[commitId]\n}\n\nfunc (s *StateOld) SpecPod(pdId bson.ObjectID) *pod.Pod {\n\treturn s.specsPodsMap[pdId]\n}\n\nfunc (s *StateOld) SpecPodUnits(pdId bson.ObjectID) []*unit.Unit {\n\treturn s.specsPodUnitsMap[pdId]\n}\n\nfunc (s *StateOld) SpecUnit(unitId bson.ObjectID) *unit.Unit {\n\treturn s.specsUnitsMap[unitId]\n}\n\nfunc (s *StateOld) SpecDomain(domnId bson.ObjectID) *domain.Domain {\n\treturn s.specsDomainsMap[domnId]\n}\n\nfunc (s *StateOld) SpecSecret(secrID bson.ObjectID) *secret.Secret {\n\treturn s.specsSecretsMap[secrID]\n}\n\nfunc (s *StateOld) SpecCert(certId bson.ObjectID) *certificate.Certificate {\n\treturn s.specsCertsMap[certId]\n}\n\nfunc (s *StateOld) Vpc(vpcId bson.ObjectID) *vpc.Vpc {\n\treturn s.vpcsMap[vpcId]\n}\n\nfunc (s *StateOld) VpcIps(vpcId bson.ObjectID) []*vpc.VpcIp {\n\treturn s.vpcIpsMap[vpcId]\n}\n\nfunc (s *StateOld) VpcIpsMap() map[bson.ObjectID][]*vpc.VpcIp {\n\treturn s.vpcIpsMap\n}\n\nfunc (s *StateOld) ArpRecords(namespace string) set.Set {\n\treturn s.arpRecords[namespace]\n}\n\nfunc (s *StateOld) Vpcs() []*vpc.Vpc {\n\treturn s.vpcs\n}\n\nfunc (s *StateOld) DiskInUse(instId, dskId bson.ObjectID) bool {\n\tcurVirt := s.virtsMap[instId]\n\n\tif curVirt != nil {\n\t\tif curVirt.State != vm.Stopped && curVirt.State != vm.Failed {\n\t\t\tfor _, vmDsk := range curVirt.Disks {\n\t\t\t\tif vmDsk.GetId() == dskId {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (s *StateOld) GetVirt(instId bson.ObjectID) *vm.VirtualMachine {\n\tif instId.IsZero() {\n\t\treturn nil\n\t}\n\treturn s.virtsMap[instId]\n}\n\nfunc (s *StateOld) GetInstace(instId bson.ObjectID) *instance.Instance {\n\tif instId.IsZero() {\n\t\treturn nil\n\t}\n\treturn s.instancesMap[instId]\n}\n\nfunc (s *StateOld) init() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\ts.nodeSelf = node.Self.Copy()\n\n\t// Datacenter\n\tdcId := s.nodeSelf.Datacenter\n\tif !dcId.IsZero() {\n\t\tdc, e := datacenter.Get(db, dcId)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\ts.nodeDatacenter = dc\n\t}\n\t// Datacenter\n\n\t// Zone\n\tzneId := s.nodeSelf.Zone\n\tif !zneId.IsZero() {\n\t\tzne, e := zone.Get(db, zneId)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\ts.nodeZone = zne\n\t}\n\t// Zone\n\n\t// Zones\n\tif s.nodeDatacenter != nil && s.nodeDatacenter.Vxlan() {\n\t\ts.vxlan = true\n\n\t\tznes, e := zone.GetAllDatacenter(db, s.nodeDatacenter.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tzonesMap := map[bson.ObjectID]*zone.Zone{}\n\t\tfor _, zne := range znes {\n\t\t\tzonesMap[zne.Id] = zne\n\t\t}\n\t\ts.zoneMap = zonesMap\n\n\t\tndes, e := node.GetAllNet(db)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\ts.nodes = ndes\n\t}\n\t// Zones\n\n\t// Network\n\tnamespaces, err := utils.GetNamespaces()\n\tif err != nil {\n\t\treturn\n\t}\n\ts.namespaces = namespaces\n\n\tinterfaces, interfacesSet, err := utils.GetInterfacesSet()\n\tif err != nil {\n\t\treturn\n\t}\n\ts.interfaces = interfaces\n\ts.interfacesSet = interfacesSet\n\t// Network\n\n\t// Pools\n\tpools, err := pool.GetAll(db, &bson.M{\n\t\t\"zone\": s.nodeSelf.Zone,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\ts.pools = pools\n\t// Pools\n\n\t// Disks\n\tdisks, err := disk.GetNode(db, s.nodeSelf.Id, s.nodeSelf.Pools)\n\tif err != nil {\n\t\treturn\n\t}\n\ts.disks = disks\n\n\tinstanceDisks := map[bson.ObjectID][]*disk.Disk{}\n\tfor _, dsk := range disks {\n\t\tdsks := instanceDisks[dsk.Instance]\n\t\tif dsks == nil {\n\t\t\tdsks = []*disk.Disk{}\n\t\t}\n\t\tinstanceDisks[dsk.Instance] = append(dsks, dsk)\n\t}\n\ts.instanceDisks = instanceDisks\n\t// Disks\n\n\t// Vpcs\n\tvpcs := []*vpc.Vpc{}\n\tvpcsId := []bson.ObjectID{}\n\tvpcsMap := map[bson.ObjectID]*vpc.Vpc{}\n\tif s.nodeDatacenter != nil {\n\t\tvpcs, err = vpc.GetDatacenter(db, s.nodeDatacenter.Id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tfor _, vc := range vpcs {\n\t\t\tvpcsId = append(vpcsId, vc.Id)\n\t\t\tvpcsMap[vc.Id] = vc\n\t\t}\n\t}\n\ts.vpcs = vpcs\n\ts.vpcsMap = vpcsMap\n\n\tvpcIpsMap := map[bson.ObjectID][]*vpc.VpcIp{}\n\tif s.nodeDatacenter != nil {\n\t\tvpcIpsMap, err = vpc.GetIpsMapped(db, vpcsId)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\ts.vpcIpsMap = vpcIpsMap\n\t// Vpcs\n\n\t// Deployments\n\tdeployments, err := deployment.GetAll(db, &bson.M{\n\t\t\"node\": node.Self.Id,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdeploymentsNode := map[bson.ObjectID]*deployment.Deployment{}\n\tdeploymentsReservedMap := map[bson.ObjectID]*deployment.Deployment{}\n\tdeploymentsDeployedMap := map[bson.ObjectID]*deployment.Deployment{}\n\tdeploymentsInactiveMap := map[bson.ObjectID]*deployment.Deployment{}\n\tdeploymentsIdSet := set.NewSet()\n\tpodIdsSet := set.NewSet()\n\tunitIdsSet := set.NewSet()\n\tspecIdsSet := set.NewSet()\n\tfor _, deply := range deployments {\n\t\tdeploymentsNode[deply.Id] = deply\n\n\t\tdeploymentsIdSet.Add(deply.Id)\n\t\tswitch deply.State {\n\t\tcase deployment.Reserved:\n\t\t\tdeploymentsReservedMap[deply.Id] = deply\n\t\t\tbreak\n\t\tcase deployment.Deployed:\n\t\t\tswitch deply.Action {\n\t\t\tcase deployment.Destroy, deployment.Archive,\n\t\t\t\tdeployment.Restore:\n\n\t\t\t\tdeploymentsInactiveMap[deply.Id] = deply\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tdeploymentsDeployedMap[deply.Id] = deply\n\t\t\t}\n\t\t\tbreak\n\t\tcase deployment.Archived:\n\t\t\tdeploymentsInactiveMap[deply.Id] = deply\n\t\t\tbreak\n\t\t}\n\n\t\tpodIdsSet.Add(deply.Pod)\n\t\tunitIdsSet.Add(deply.Unit)\n\t\tspecIdsSet.Add(deply.Spec)\n\t}\n\n\tspecIds := []bson.ObjectID{}\n\tfor specId := range specIdsSet.Iter() {\n\t\tspecIds = append(specIds, specId.(bson.ObjectID))\n\t}\n\n\tspecs := []*spec.Spec{}\n\tif len(specIds) > 0 {\n\t\tspecs, err = spec.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": specIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tspecSecretsSet := set.NewSet()\n\tspecCertsSet := set.NewSet()\n\tspecPodsSet := set.NewSet()\n\tspecUnitsSet := set.NewSet()\n\tspecDomainsSet := set.NewSet()\n\tspecsMap := map[bson.ObjectID]*spec.Spec{}\n\tfor _, spc := range specs {\n\t\tspecsMap[spc.Id] = spc\n\n\t\tif spc.Instance != nil {\n\t\t\tif spc.Instance.Pods != nil {\n\t\t\t\tfor _, pdId := range spc.Instance.Pods {\n\t\t\t\t\tspecPodsSet.Add(pdId)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif spc.Instance.Secrets != nil {\n\t\t\t\tfor _, secrId := range spc.Instance.Secrets {\n\t\t\t\t\tspecSecretsSet.Add(secrId)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif spc.Instance.Certificates != nil {\n\t\t\t\tfor _, certId := range spc.Instance.Certificates {\n\t\t\t\t\tspecCertsSet.Add(certId)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif spc.Firewall != nil {\n\t\t\tfor _, rule := range spc.Firewall.Ingress {\n\t\t\t\tfor _, ref := range rule.Sources {\n\t\t\t\t\tspecUnitsSet.Add(ref.Id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif spc.Domain != nil {\n\t\t\tfor _, record := range spc.Domain.Records {\n\t\t\t\tspecDomainsSet.Add(record.Domain)\n\t\t\t}\n\t\t}\n\t}\n\ts.specsMap = specsMap\n\n\tspecCertIds := []bson.ObjectID{}\n\tfor certId := range specCertsSet.Iter() {\n\t\tspecCertIds = append(specCertIds, certId.(bson.ObjectID))\n\t}\n\n\tspecsCertsMap := map[bson.ObjectID]*certificate.Certificate{}\n\tspecCerts := []*certificate.Certificate{}\n\tif len(specCertIds) > 0 {\n\t\tspecCerts, err = certificate.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": specCertIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, specCert := range specCerts {\n\t\tspecsCertsMap[specCert.Id] = specCert\n\t}\n\ts.specsCertsMap = specsCertsMap\n\n\tspecSecretIds := []bson.ObjectID{}\n\tfor secrId := range specSecretsSet.Iter() {\n\t\tspecSecretIds = append(specSecretIds, secrId.(bson.ObjectID))\n\t}\n\n\tspecsSecretsMap := map[bson.ObjectID]*secret.Secret{}\n\n\tspecSecrets := []*secret.Secret{}\n\tif len(specSecretIds) > 0 {\n\t\tspecSecrets, err = secret.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": specSecretIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, specSecret := range specSecrets {\n\t\tspecsSecretsMap[specSecret.Id] = specSecret\n\t}\n\ts.specsSecretsMap = specsSecretsMap\n\n\tspecPodIds := []bson.ObjectID{}\n\tfor pdId := range specPodsSet.Iter() {\n\t\tspecPodIds = append(specPodIds, pdId.(bson.ObjectID))\n\t}\n\n\tspecPods := []*pod.Pod{}\n\tif len(specPodIds) > 0 {\n\t\tspecPods, err = pod.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": specPodIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tspecsPodsMap := map[bson.ObjectID]*pod.Pod{}\n\tfor _, specPod := range specPods {\n\t\tspecsPodsMap[specPod.Id] = specPod\n\t}\n\ts.specsPodsMap = specsPodsMap\n\n\tspecUnitIds := []bson.ObjectID{}\n\tfor unitId := range specUnitsSet.Iter() {\n\t\tspecUnitIds = append(specUnitIds, unitId.(bson.ObjectID))\n\t}\n\n\tspecUnits := []*unit.Unit{}\n\tif len(specUnitIds) > 0 || len(specPodIds) > 0 {\n\t\tspecUnits, err = unit.GetAll(db, &bson.M{\n\t\t\t\"$or\": []*bson.M{\n\t\t\t\t&bson.M{\n\t\t\t\t\t\"_id\": &bson.M{\n\t\t\t\t\t\t\"$in\": specUnitIds,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&bson.M{\n\t\t\t\t\t\"pod\": &bson.M{\n\t\t\t\t\t\t\"$in\": specPodIds,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tspecDeploymentsSet := set.NewSet()\n\tspecsUnitsMap := map[bson.ObjectID]*unit.Unit{}\n\tspecsPodUnitsMap := map[bson.ObjectID][]*unit.Unit{}\n\tfor _, specUnit := range specUnits {\n\t\tspecsUnitsMap[specUnit.Id] = specUnit\n\n\t\tspecsPodUnitsMap[specUnit.Pod] = append(\n\t\t\tspecsPodUnitsMap[specUnit.Pod], specUnit)\n\n\t\tfor _, deplyId := range specUnit.Deployments {\n\t\t\tspecDeploymentsSet.Add(deplyId)\n\t\t}\n\t}\n\ts.specsUnitsMap = specsUnitsMap\n\ts.specsPodUnitsMap = specsPodUnitsMap\n\n\tspecDomainIds := []bson.ObjectID{}\n\tfor pdId := range specDomainsSet.Iter() {\n\t\tspecDomainIds = append(specDomainIds, pdId.(bson.ObjectID))\n\t}\n\n\tspecsDomainsMap := map[bson.ObjectID]*domain.Domain{}\n\tspecDomains, err := domain.GetLoadedAllIds(db, specDomainIds)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, specDomain := range specDomains {\n\t\tspecsDomainsMap[specDomain.Id] = specDomain\n\t}\n\ts.specsDomainsMap = specsDomainsMap\n\n\tspecDeploymentIds := []bson.ObjectID{}\n\tfor deplyIdInf := range specDeploymentsSet.Iter() {\n\t\tdeplyId := deplyIdInf.(bson.ObjectID)\n\t\tif !deploymentsIdSet.Contains(deplyId) {\n\t\t\tspecDeploymentIds = append(specDeploymentIds, deplyId)\n\t\t}\n\t}\n\n\tspecDeployments := []*deployment.Deployment{}\n\tif len(specDeploymentIds) > 0 {\n\t\tspecDeployments, err = deployment.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": specDeploymentIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, specDeployment := range specDeployments {\n\t\tdeploymentsIdSet.Add(specDeployment.Id)\n\n\t\tswitch specDeployment.State {\n\t\tcase deployment.Reserved:\n\t\t\tdeploymentsReservedMap[specDeployment.Id] = specDeployment\n\t\t\tbreak\n\t\tcase deployment.Deployed:\n\t\t\tswitch specDeployment.Action {\n\t\t\tcase deployment.Destroy, deployment.Archive,\n\t\t\t\tdeployment.Restore:\n\n\t\t\t\tdeploymentsInactiveMap[specDeployment.Id] = specDeployment\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tdeploymentsDeployedMap[specDeployment.Id] = specDeployment\n\t\t\t}\n\t\t\tbreak\n\t\tcase deployment.Archived:\n\t\t\tdeploymentsInactiveMap[specDeployment.Id] = specDeployment\n\t\t\tbreak\n\t\t}\n\t}\n\n\tpodIds := []bson.ObjectID{}\n\tfor podId := range podIdsSet.Iter() {\n\t\tpodIds = append(podIds, podId.(bson.ObjectID))\n\t}\n\n\tpods := []*pod.Pod{}\n\tif len(podIds) > 0 {\n\t\tpods, err = pod.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": podIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tpodsMap := map[bson.ObjectID]*pod.Pod{}\n\tfor _, pd := range pods {\n\t\tpodsMap[pd.Id] = pd\n\t}\n\ts.podsMap = podsMap\n\n\tunitIds := []bson.ObjectID{}\n\tfor unitId := range unitIdsSet.Iter() {\n\t\tunitIds = append(unitIds, unitId.(bson.ObjectID))\n\t}\n\n\tunits := []*unit.Unit{}\n\tif len(unitIds) > 0 {\n\t\tunits, err = unit.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": unitIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tunitsMap := map[bson.ObjectID]*unit.Unit{}\n\tpodDeploymentsSet := set.NewSet()\n\tfor _, unt := range units {\n\t\tunitsMap[unt.Id] = unt\n\n\t\tfor _, deplyId := range unt.Deployments {\n\t\t\tpodDeploymentsSet.Add(deplyId)\n\t\t}\n\n\t}\n\ts.unitsMap = unitsMap\n\n\tpodDeploymentIds := []bson.ObjectID{}\n\tfor deplyIdInf := range podDeploymentsSet.Iter() {\n\t\tdeplyId := deplyIdInf.(bson.ObjectID)\n\t\tif !deploymentsIdSet.Contains(deplyId) {\n\t\t\tpodDeploymentIds = append(podDeploymentIds, deplyId)\n\t\t}\n\t}\n\n\tpodDeployments := []*deployment.Deployment{}\n\tif len(podDeploymentIds) > 0 {\n\t\tpodDeployments, err = deployment.GetAll(db, &bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": podDeploymentIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, podDeployment := range podDeployments {\n\t\tdeploymentsIdSet.Add(podDeployment.Id)\n\n\t\tswitch podDeployment.State {\n\t\tcase deployment.Reserved:\n\t\t\tdeploymentsReservedMap[podDeployment.Id] = podDeployment\n\t\t\tbreak\n\t\tcase deployment.Deployed:\n\t\t\tswitch podDeployment.Action {\n\t\t\tcase deployment.Destroy, deployment.Archive,\n\t\t\t\tdeployment.Restore:\n\n\t\t\t\tdeploymentsInactiveMap[podDeployment.Id] = podDeployment\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tdeploymentsDeployedMap[podDeployment.Id] = podDeployment\n\t\t\t}\n\t\t\tbreak\n\t\tcase deployment.Archived:\n\t\t\tdeploymentsInactiveMap[podDeployment.Id] = podDeployment\n\t\t\tbreak\n\t\t}\n\t}\n\ts.deploymentsReservedMap = deploymentsReservedMap\n\ts.deploymentsDeployedMap = deploymentsDeployedMap\n\ts.deploymentsInactiveMap = deploymentsInactiveMap\n\t// Deployments\n\n\t// Instances\n\tinstances, err := instance.GetAllVirtMapped(db, &bson.M{\n\t\t\"node\": s.nodeSelf.Id,\n\t}, s.pools, instanceDisks)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ts.instances = instances\n\n\tnodePortsMap := map[string][]*nodeport.Mapping{}\n\n\tinstId := set.NewSet()\n\tinstancesMap := map[bson.ObjectID]*instance.Instance{}\n\tinstancesRolesSet := set.NewSet()\n\tfor _, inst := range instances {\n\t\tinstId.Add(inst.Id)\n\t\tinstancesMap[inst.Id] = inst\n\n\t\tnodePortsMap[inst.NetworkNamespace] = append(\n\t\t\tnodePortsMap[inst.NetworkNamespace], inst.NodePorts...)\n\n\t\tfor _, role := range inst.Roles {\n\t\t\tinstancesRolesSet.Add(role)\n\t\t}\n\t}\n\ts.instancesMap = instancesMap\n\n\tinstancesRoles := []string{}\n\tfor instRoleInf := range instancesRolesSet.Iter() {\n\t\tinstancesRoles = append(instancesRoles, instRoleInf.(string))\n\t}\n\n\tauthrsMap, err := authority.GetMapRoles(db, &bson.M{\n\t\t\"roles\": &bson.M{\n\t\t\t\"$in\": instancesRoles,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\ts.authoritiesMap = authrsMap\n\t// Instances\n\n\t// Virtuals\n\tcurVirts, err := qemu.GetVms(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvirtsMap := map[bson.ObjectID]*vm.VirtualMachine{}\n\tfor _, virt := range curVirts {\n\t\tif !instId.Contains(virt.Id) {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"id\": virt.Id.Hex(),\n\t\t\t}).Info(\"sync: Unknown instance\")\n\t\t}\n\t\tvirtsMap[virt.Id] = virt\n\t}\n\ts.virtsMap = virtsMap\n\t// Virtuals\n\n\t// Firewalls\n\ts.arpRecords = arp.BuildState(s.instances, s.vpcsMap, s.vpcIpsMap)\n\t// Firewalls\n\n\t// Firewalls\n\tspecRules, err := firewall.GetSpecRules(instances, deploymentsNode,\n\t\tspecsMap, specsUnitsMap, deploymentsDeployedMap)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnodeFirewall, firewalls, firewallMaps, instNamespaces, err :=\n\t\tfirewall.GetAllIngress(db, s.nodeSelf, instances,\n\t\t\tspecRules, nodePortsMap)\n\tif err != nil {\n\t\treturn\n\t}\n\ts.nodeFirewall = nodeFirewall\n\ts.firewalls = firewalls\n\ts.firewallMaps = firewallMaps\n\ts.instanceNamespaces = instNamespaces\n\t// Firewalls\n\n\t// Schedulers\n\tschedulers, err := scheduler.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\ts.schedulers = schedulers\n\t// Schedulers\n\n\t// Running\n\titems, err := ioutil.ReadDir(\"/var/run\")\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"state: Failed to read run directory\"),\n\t\t}\n\t\treturn\n\t}\n\n\trunning := []string{}\n\tfor _, item := range items {\n\t\tif !item.IsDir() {\n\t\t\trunning = append(running, item.Name())\n\t\t}\n\t}\n\ts.running = running\n\t// Running\n\n\treturn\n}\n"
  },
  {
    "path": "state/virtuals.go",
    "content": "package state\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/qemu\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar (\n\tVirtuals    = &VirtualsState{}\n\tVirtualsPkg = NewPackage(Virtuals)\n)\n\ntype VirtualsState struct {\n\tvirtsMap map[bson.ObjectID]*vm.VirtualMachine\n}\n\nfunc (p *VirtualsState) DiskInUse(instId, dskId bson.ObjectID) bool {\n\tcurVirt := p.virtsMap[instId]\n\n\tif curVirt != nil {\n\t\tif curVirt.State != vm.Stopped && curVirt.State != vm.Failed {\n\t\t\tfor _, vmDsk := range curVirt.Disks {\n\t\t\t\tif vmDsk.GetId() == dskId {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (p *VirtualsState) GetVirt(instId bson.ObjectID) *vm.VirtualMachine {\n\tif instId.IsZero() {\n\t\treturn nil\n\t}\n\treturn p.virtsMap[instId]\n}\n\nfunc (p *VirtualsState) VirtsMap() map[bson.ObjectID]*vm.VirtualMachine {\n\treturn p.virtsMap\n}\n\nfunc (p *VirtualsState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tcurVirts, err := qemu.GetVms(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvirtsMap := map[bson.ObjectID]*vm.VirtualMachine{}\n\tfor _, virt := range curVirts {\n\t\tvirtsMap[virt.Id] = virt\n\t}\n\tp.virtsMap = virtsMap\n\n\treturn\n}\n\nfunc (p *VirtualsState) Apply(st *State) {\n\tst.DiskInUse = p.DiskInUse\n\tst.GetVirt = p.GetVirt\n\tst.VirtsMap = p.VirtsMap\n}\n"
  },
  {
    "path": "state/vpcs.go",
    "content": "package state\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\nvar (\n\tVpcs    = &VpcsState{}\n\tVpcsPkg = NewPackage(Vpcs)\n)\n\ntype VpcsState struct {\n\tvpcs      []*vpc.Vpc\n\tvpcsMap   map[bson.ObjectID]*vpc.Vpc\n\tvpcIpsMap map[bson.ObjectID][]*vpc.VpcIp\n}\n\nfunc (p *VpcsState) Vpc(vpcId bson.ObjectID) *vpc.Vpc {\n\treturn p.vpcsMap[vpcId]\n}\n\nfunc (p *VpcsState) VpcsMap() map[bson.ObjectID]*vpc.Vpc {\n\treturn p.vpcsMap\n}\n\nfunc (p *VpcsState) VpcIps(vpcId bson.ObjectID) []*vpc.VpcIp {\n\treturn p.vpcIpsMap[vpcId]\n}\n\nfunc (p *VpcsState) VpcIpsMap() map[bson.ObjectID][]*vpc.VpcIp {\n\treturn p.vpcIpsMap\n}\n\nfunc (p *VpcsState) Vpcs() []*vpc.Vpc {\n\treturn p.vpcs\n}\n\nfunc (p *VpcsState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tdcId := node.Self.Datacenter\n\tvpcsId := []bson.ObjectID{}\n\tvpcsMap := map[bson.ObjectID]*vpc.Vpc{}\n\tif dcId.IsZero() {\n\t\tp.vpcs = nil\n\t\tp.vpcsMap = map[bson.ObjectID]*vpc.Vpc{}\n\t\tp.vpcIpsMap = map[bson.ObjectID][]*vpc.VpcIp{}\n\t\treturn\n\t}\n\n\tvpcs, err := vpc.GetDatacenter(db, dcId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, vc := range vpcs {\n\t\tvpcsId = append(vpcsId, vc.Id)\n\t\tvpcsMap[vc.Id] = vc\n\t}\n\n\tp.vpcs = vpcs\n\tp.vpcsMap = vpcsMap\n\n\tvpcIpsMap, err := vpc.GetIpsMapped(db, vpcsId)\n\tif err != nil {\n\t\treturn\n\t}\n\tp.vpcIpsMap = vpcIpsMap\n\n\treturn\n}\n\nfunc (p *VpcsState) Apply(st *State) {\n\tst.Vpc = p.Vpc\n\tst.VpcsMap = p.VpcsMap\n\tst.VpcIps = p.VpcIps\n\tst.VpcIpsMap = p.VpcIpsMap\n\tst.Vpcs = p.Vpcs\n}\n"
  },
  {
    "path": "state/zone.go",
    "content": "package state\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\nvar (\n\tZone    = &ZoneState{}\n\tZonePkg = NewPackage(Zone)\n)\n\ntype ZoneState struct {\n\tnodeZone *zone.Zone\n}\n\nfunc (p *ZoneState) NodeZone() *zone.Zone {\n\treturn p.nodeZone\n}\n\nfunc (p *ZoneState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tzneId := node.Self.Zone\n\tif zneId.IsZero() {\n\t\tp.nodeZone = nil\n\t\tpkg.Evict()\n\t\treturn\n\t}\n\n\tzne, e := zone.Get(db, zneId)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tp.nodeZone = zne\n\n\tpkg.Cache(15 * time.Second)\n\n\treturn\n}\n\nfunc (p *ZoneState) Apply(st *State) {\n\tst.NodeZone = p.NodeZone\n}\n"
  },
  {
    "path": "state/zones.go",
    "content": "package state\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\nvar (\n\tZones    = &ZonesState{}\n\tZonesPkg = NewPackage(Zones)\n)\n\ntype ZonesState struct {\n\tvxlan   bool\n\tzoneMap map[bson.ObjectID]*zone.Zone\n\tnodes   []*node.Node\n}\n\nfunc (p *ZonesState) VxLan() bool {\n\treturn p.vxlan\n}\n\nfunc (p *ZonesState) GetZone(zneId bson.ObjectID) *zone.Zone {\n\treturn p.zoneMap[zneId]\n}\n\nfunc (p *ZonesState) Nodes() []*node.Node {\n\treturn p.nodes\n}\n\nfunc (p *ZonesState) Refresh(pkg *Package,\n\tdb *database.Database) (err error) {\n\n\tnodeDc := Datacenter.NodeDatacenter()\n\tif nodeDc == nil || !nodeDc.Vxlan() {\n\t\tp.vxlan = false\n\t\tp.zoneMap = nil\n\t\tp.nodes = nil\n\t\tpkg.Evict()\n\t\treturn\n\t}\n\n\tp.vxlan = true\n\n\tznes, e := zone.GetAllDatacenter(db, nodeDc.Id)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tzonesMap := map[bson.ObjectID]*zone.Zone{}\n\tfor _, zne := range znes {\n\t\tzonesMap[zne.Id] = zne\n\t}\n\tp.zoneMap = zonesMap\n\n\tndes, e := node.GetAllNet(db)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tp.nodes = ndes\n\tpkg.Cache(10 * time.Second)\n\n\treturn\n}\n\nfunc (p *ZonesState) Apply(st *State) {\n\tst.VxLan = p.VxLan\n\tst.GetZone = p.GetZone\n\tst.Nodes = p.Nodes\n}\n\nfunc init() {\n\tZonesPkg.\n\t\tAfter(Datacenter)\n}\n"
  },
  {
    "path": "static/file.go",
    "content": "package static\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"crypto/md5\"\n\t\"encoding/base32\"\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nvar (\n\tmimeTypes = map[string]string{\n\t\t\".js\":    \"application/javascript\",\n\t\t\".json\":  \"application/json\",\n\t\t\".css\":   \"text/css\",\n\t\t\".html\":  \"text/html\",\n\t\t\".jpg\":   \"image/jpeg\",\n\t\t\".png\":   \"image/png\",\n\t\t\".svg\":   \"image/svg+xml\",\n\t\t\".ico\":   \"image/vnd.microsoft.icon\",\n\t\t\".otf\":   \"application/font-sfnt\",\n\t\t\".ttf\":   \"application/font-sfnt\",\n\t\t\".woff\":  \"application/font-woff\",\n\t\t\".woff2\": \"font/woff2\",\n\t\t\".ijmap\": \"text/plain\",\n\t\t\".eot\":   \"application/vnd.ms-fontobject\",\n\t\t\".map\":   \"application/json\",\n\t}\n)\n\ntype File struct {\n\tType     string\n\tHash     string\n\tData     []byte\n\tGzipData []byte\n}\n\nfunc NewFile(path string) (file *File, err error) {\n\text := filepath.Ext(path)\n\tif len(ext) == 0 {\n\t\treturn\n\t}\n\n\ttyp, ok := mimeTypes[ext]\n\tif !ok {\n\t\treturn\n\t}\n\n\tdata, e := ioutil.ReadFile(path)\n\tif e != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(e, \"static: Read error\"),\n\t\t}\n\t\treturn\n\t}\n\n\thash := md5.Sum(data)\n\thashStr := base32.StdEncoding.EncodeToString(hash[:])\n\thashStr = strings.Replace(hashStr, \"=\", \"\", -1)\n\thashStr = strings.ToLower(hashStr)\n\n\tfile = &File{\n\t\tType: typ,\n\t\tHash: hashStr,\n\t\tData: data,\n\t}\n\n\tgzipData := &bytes.Buffer{}\n\n\twriter, err := gzip.NewWriterLevel(gzipData, gzip.BestCompression)\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"static: Gzip error\"),\n\t\t}\n\t\treturn\n\t}\n\n\twriter.Write(file.Data)\n\twriter.Close()\n\tfile.GzipData = gzipData.Bytes()\n\n\treturn\n}\n"
  },
  {
    "path": "static/static.go",
    "content": "// Versions static files with hash, replaces references and stores in memory.\npackage static\n\nimport (\n\t\"io/ioutil\"\n\t\"path\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype Store struct {\n\tFiles map[string]*File\n\troot  string\n}\n\nfunc (s *Store) addDir(dir string) (err error) {\n\tfiles, err := ioutil.ReadDir(dir)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"static: Read directory error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, info := range files {\n\t\tname := info.Name()\n\t\tfullPath := path.Join(dir, name)\n\n\t\tif info.IsDir() {\n\t\t\ts.addDir(fullPath)\n\t\t\tcontinue\n\t\t}\n\n\t\tfile, e := NewFile(fullPath)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif file != nil {\n\t\t\ts.Files[fullPath] = file\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc NewStore(root string) (store *Store, err error) {\n\tstore = &Store{\n\t\tFiles: map[string]*File{},\n\t\troot:  root,\n\t}\n\n\terr = store.addDir(root)\n\tif err != nil {\n\t\ttime.Sleep(3 * time.Second)\n\n\t\terr = store.addDir(root)\n\t\tif err != nil {\n\t\t\terr = &errortypes.UnknownError{\n\t\t\t\terrors.Wrap(err, \"static: Init error\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GetMimeType(name string) string {\n\treturn mimeTypes[path.Ext(name)]\n}\n"
  },
  {
    "path": "storage/constants.go",
    "content": "package storage\n\nconst (\n\tPublic  = \"public\"\n\tPrivate = \"private\"\n\tWeb     = \"web\"\n\n\tAwsStandard         = \"aws_standard\"\n\tAwsInfrequentAccess = \"aws_infrequent_access\"\n\tAwsGlacier          = \"aws_glacier\"\n\tOracleStandard      = \"oracle_standard\"\n\tOracleArchive       = \"oracle_archive\"\n)\n"
  },
  {
    "path": "storage/storage.go",
    "content": "package storage\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Storage struct {\n\tId        bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName      string        `bson:\"name\" json:\"name\"`\n\tComment   string        `bson:\"comment\" json:\"comment\"`\n\tType      string        `bson:\"type\" json:\"type\"`\n\tEndpoint  string        `bson:\"endpoint\" json:\"endpoint\"`\n\tBucket    string        `bson:\"bucket\" json:\"bucket\"`\n\tAccessKey string        `bson:\"access_key\" json:\"access_key\"`\n\tSecretKey string        `bson:\"secret_key\" json:\"secret_key\"`\n\tInsecure  bool          `bson:\"insecure\" json:\"insecure\"`\n}\n\ntype Completion struct {\n\tId   bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName string        `bson:\"name\" json:\"name\"`\n\tType string        `bson:\"type\" json:\"type\"`\n}\n\nfunc (s *Storage) IsOracle() bool {\n\treturn strings.Contains(strings.ToLower(s.Endpoint), \"oracle\")\n}\n\nfunc (s *Storage) GetWebUrl() (u *url.URL) {\n\tu = &url.URL{}\n\n\tif s.Insecure {\n\t\tu.Scheme = \"http\"\n\t} else {\n\t\tu.Scheme = \"https\"\n\t}\n\tu.Host = s.Endpoint\n\tu.Path = \"/\" + s.Bucket\n\n\treturn\n}\n\nfunc (s *Storage) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\ts.Name = utils.FilterName(s.Name)\n\n\tswitch s.Type {\n\tcase Public:\n\t\tbreak\n\tcase Private:\n\t\tbreak\n\tcase Web:\n\t\tbreak\n\tcase \"\":\n\t\ts.Type = Public\n\t\tbreak\n\tdefault:\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_type\",\n\t\t\tMessage: \"Storage type is invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Storage) Commit(db *database.Database) (err error) {\n\tcoll := db.Storages()\n\n\terr = coll.Commit(s.Id, s)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Storage) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Storages()\n\n\terr = coll.CommitFields(s.Id, s, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (s *Storage) Insert(db *database.Database) (err error) {\n\tcoll := db.Storages()\n\n\tif !s.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"storage: Storage already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := coll.InsertOne(db, s)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\ts.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  },
  {
    "path": "storage/utils.go",
    "content": "package storage\n\nimport (\n\t\"strings\"\n\n\tminio \"github.com/minio/minio-go/v7\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, storeId bson.ObjectID) (\n\tstore *Storage, err error) {\n\n\tcoll := db.Storages()\n\tstore = &Storage{}\n\n\terr = coll.FindOneId(storeId, store)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database) (stores []*Storage, err error) {\n\tcoll := db.Storages()\n\tstores = []*Storage{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tstore := &Storage{}\n\t\terr = cursor.Decode(store)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tstores = append(stores, store)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (stores []*Storage, count int64, err error) {\n\n\tcoll := db.Storages()\n\tstores = []*Storage{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tstore := &Storage{}\n\t\terr = cursor.Decode(store)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tstores = append(stores, store)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, storeId bson.ObjectID) (err error) {\n\tcoll := db.Images()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"storage\": storeId,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcoll = db.Storages()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": storeId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, storeIds []bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Images()\n\n\tfor _, storeId := range storeIds {\n\t\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\t\"storage\": storeId,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tcoll = db.Storages()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": storeIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc FormatStorageClass(class string) string {\n\tswitch class {\n\tcase AwsStandard:\n\t\treturn \"STANDARD\"\n\tcase AwsInfrequentAccess:\n\t\treturn \"STANDARD_IA\"\n\tcase AwsGlacier:\n\t\treturn \"GLACIER\"\n\t}\n\n\treturn \"\"\n}\n\nfunc ParseStorageClass(obj minio.ObjectInfo) string {\n\topcRequestId := obj.Metadata.Get(\"Opc-Request-Id\")\n\tarchivalState := strings.ToLower(obj.Metadata.Get(\"Archival-State\"))\n\tif archivalState != \"\" {\n\t\treturn OracleArchive\n\t} else if opcRequestId != \"\" {\n\t\treturn OracleStandard\n\t}\n\n\tswitch obj.StorageClass {\n\tcase \"STANDARD\":\n\t\treturn AwsStandard\n\tcase \"STANDARD_IA\":\n\t\treturn AwsInfrequentAccess\n\tcase \"GLACIER\":\n\t\treturn AwsGlacier\n\t}\n\n\treturn \"\"\n}\n"
  },
  {
    "path": "store/address.go",
    "content": "package store\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nvar (\n\taddressStores     = map[bson.ObjectID]*AddressStore{}\n\taddressStoresLock = sync.Mutex{}\n)\n\ntype AddressStore struct {\n\tAddr      string\n\tAddr6     string\n\tTimestamp time.Time\n\tRefresh   time.Duration\n}\n\nfunc GetAddress(virtId bson.ObjectID) (\n\taddressStore *AddressStore, ok bool) {\n\n\taddressStoresLock.Lock()\n\taddressStore, ok = addressStores[virtId]\n\taddressStoresLock.Unlock()\n\n\tttl := settings.Hypervisor.AddressRefreshTtl\n\tif ok && ttl != 0 && time.Since(addressStore.Timestamp) > time.Duration(\n\t\tttl)*time.Second && node.Self.IsDhcp6() {\n\n\t\tok = false\n\t}\n\n\treturn\n}\n\nfunc SetAddress(virtId bson.ObjectID, addr, addr6 string) {\n\taddressStoresLock.Lock()\n\tnow := time.Now()\n\n\taddressStore := addressStores[virtId]\n\tif addressStore != nil && addressStore.Refresh != 0 {\n\t\trefreshTtl := time.Duration(\n\t\t\tsettings.Hypervisor.AddressRefreshTtl) * time.Second\n\t\tnow = now.Add(-refreshTtl).Add(addressStore.Refresh)\n\t}\n\n\taddressStores[virtId] = &AddressStore{\n\t\tAddr:      addr,\n\t\tAddr6:     addr6,\n\t\tTimestamp: now,\n\t}\n\n\taddressStoresLock.Unlock()\n}\n\nfunc SetAddressExpire(virtId bson.ObjectID, ttl time.Duration) {\n\taddressStoresLock.Lock()\n\taddressStore, ok := addressStores[virtId]\n\tif ok {\n\t\trefreshTtl := time.Duration(\n\t\t\tsettings.Hypervisor.AddressRefreshTtl) * time.Second\n\t\taddressStore.Timestamp = time.Now().Add(-refreshTtl).Add(ttl)\n\t}\n\taddressStoresLock.Unlock()\n}\n\nfunc SetAddressExpireMulti(virtId bson.ObjectID,\n\tttl, ttl2 time.Duration) {\n\n\taddressStoresLock.Lock()\n\taddressStore, ok := addressStores[virtId]\n\tif ok {\n\t\trefreshTtl := time.Duration(\n\t\t\tsettings.Hypervisor.AddressRefreshTtl) * time.Second\n\t\taddressStore.Timestamp = time.Now().Add(-refreshTtl).Add(ttl)\n\t\taddressStore.Refresh = ttl2\n\t}\n\taddressStoresLock.Unlock()\n}\n\nfunc RemAddress(addressId bson.ObjectID) {\n\taddressStoresLock.Lock()\n\tdelete(addressStores, addressId)\n\taddressStoresLock.Unlock()\n}\n"
  },
  {
    "path": "store/arp.go",
    "content": "package store\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n)\n\nvar (\n\tarpStores     = map[bson.ObjectID]ArpStore{}\n\tarpStoresLock = sync.Mutex{}\n)\n\ntype ArpStore struct {\n\tRecords   set.Set\n\tTimestamp time.Time\n}\n\nfunc GetArp(instId bson.ObjectID) (arpStore ArpStore, ok bool) {\n\tarpStoresLock.Lock()\n\tarpStore, ok = arpStores[instId]\n\tarpStoresLock.Unlock()\n\n\tif ok {\n\t\tarpStore.Records = arpStore.Records.Copy()\n\t}\n\n\treturn\n}\n\nfunc SetArp(instId bson.ObjectID, records set.Set) {\n\tarpStoresLock.Lock()\n\tarpStores[instId] = ArpStore{\n\t\tRecords:   records.Copy(),\n\t\tTimestamp: time.Now(),\n\t}\n\tarpStoresLock.Unlock()\n}\n\nfunc RemArp(instId bson.ObjectID) {\n\tarpStoresLock.Lock()\n\tdelete(arpStores, instId)\n\tarpStoresLock.Unlock()\n}\n"
  },
  {
    "path": "store/disks.go",
    "content": "package store\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar (\n\tdisksStores     = map[bson.ObjectID]DisksStore{}\n\tdisksStoresLock = sync.Mutex{}\n)\n\ntype DisksStore struct {\n\tDisks     []vm.Disk\n\tTimestamp time.Time\n}\n\nfunc GetDisks(virtId bson.ObjectID) (disksStore DisksStore, ok bool) {\n\tdisksStoresLock.Lock()\n\tdisksStore, ok = disksStores[virtId]\n\tdisksStoresLock.Unlock()\n\n\tif ok {\n\t\tdisksStore.Disks = append([]vm.Disk{}, disksStore.Disks...)\n\t}\n\n\treturn\n}\n\nfunc SetDisks(virtId bson.ObjectID, disks []*vm.Disk) {\n\tdisksRef := []vm.Disk{}\n\tfor _, dsk := range disks {\n\t\tdisksRef = append(disksRef, *dsk)\n\t}\n\n\tdisksStoresLock.Lock()\n\tdisksStores[virtId] = DisksStore{\n\t\tDisks:     disksRef,\n\t\tTimestamp: time.Now(),\n\t}\n\tdisksStoresLock.Unlock()\n}\n\nfunc RemDisks(virtId bson.ObjectID) {\n\tdisksStoresLock.Lock()\n\tdelete(disksStores, virtId)\n\tdisksStoresLock.Unlock()\n}\n"
  },
  {
    "path": "store/routes.go",
    "content": "package store\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\nvar (\n\troutesStores     = map[bson.ObjectID]RoutesStore{}\n\troutesStoresLock = sync.Mutex{}\n)\n\ntype RoutesStore struct {\n\tIcmpRedirects bool\n\tRoutes        []vpc.Route\n\tRoutes6       []vpc.Route\n\tTimestamp     time.Time\n}\n\nfunc GetRoutes(instId bson.ObjectID) (routesStore RoutesStore, ok bool) {\n\troutesStoresLock.Lock()\n\troutesStore, ok = routesStores[instId]\n\troutesStoresLock.Unlock()\n\n\tif ok {\n\t\troutesStore.Routes = append([]vpc.Route{}, routesStore.Routes...)\n\t}\n\n\treturn\n}\n\nfunc SetRoutes(instId bson.ObjectID, icmpRedirects bool,\n\troutes, routes6 []vpc.Route) {\n\n\troutesStoresLock.Lock()\n\troutesStores[instId] = RoutesStore{\n\t\tIcmpRedirects: icmpRedirects,\n\t\tRoutes:        append([]vpc.Route{}, routes...),\n\t\tRoutes6:       append([]vpc.Route{}, routes6...),\n\t\tTimestamp:     time.Now(),\n\t}\n\troutesStoresLock.Unlock()\n}\n\nfunc RemRoutes(instId bson.ObjectID) {\n\troutesStoresLock.Lock()\n\tdelete(routesStores, instId)\n\troutesStoresLock.Unlock()\n}\n"
  },
  {
    "path": "store/usb.go",
    "content": "package store\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar (\n\tusbsStores     = map[bson.ObjectID]UsbsStore{}\n\tusbsStoresLock = sync.Mutex{}\n)\n\ntype UsbsStore struct {\n\tUsbs      []vm.UsbDevice\n\tTimestamp time.Time\n}\n\nfunc GetUsbs(virtId bson.ObjectID) (usbsStore UsbsStore, ok bool) {\n\tusbsStoresLock.Lock()\n\tusbsStore, ok = usbsStores[virtId]\n\tusbsStoresLock.Unlock()\n\n\tif ok {\n\t\tusbsStore.Usbs = append([]vm.UsbDevice{}, usbsStore.Usbs...)\n\t}\n\n\treturn\n}\n\nfunc SetUsbs(virtId bson.ObjectID, usbs []*vm.UsbDevice) {\n\tusbsRef := []vm.UsbDevice{}\n\tfor _, dsk := range usbs {\n\t\tusbsRef = append(usbsRef, *dsk)\n\t}\n\n\tusbsStoresLock.Lock()\n\tusbsStores[virtId] = UsbsStore{\n\t\tUsbs:      usbsRef,\n\t\tTimestamp: time.Now(),\n\t}\n\tusbsStoresLock.Unlock()\n}\n\nfunc RemUsbs(virtId bson.ObjectID) {\n\tusbsStoresLock.Lock()\n\tdelete(usbsStores, virtId)\n\tusbsStoresLock.Unlock()\n}\n"
  },
  {
    "path": "store/virt.go",
    "content": "package store\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nvar (\n\tvirtStores     = map[bson.ObjectID]VirtStore{}\n\tvirtStoresLock = sync.Mutex{}\n)\n\ntype VirtStore struct {\n\tVirt      vm.VirtualMachine\n\tTimestamp time.Time\n}\n\nfunc GetVirt(virtId bson.ObjectID) (virtStore VirtStore, ok bool) {\n\tvirtStoresLock.Lock()\n\tvirtStore, ok = virtStores[virtId]\n\tvirtStoresLock.Unlock()\n\n\treturn\n}\n\nfunc SetVirt(virtId bson.ObjectID, virt *vm.VirtualMachine) {\n\tvirtRef := *virt\n\tvirtRef.Disks = nil\n\n\tvirtStoresLock.Lock()\n\tvirtStores[virtId] = VirtStore{\n\t\tVirt:      virtRef,\n\t\tTimestamp: time.Now(),\n\t}\n\tvirtStoresLock.Unlock()\n}\n\nfunc RemVirt(virtId bson.ObjectID) {\n\tvirtStoresLock.Lock()\n\tdelete(virtStores, virtId)\n\tvirtStoresLock.Unlock()\n}\n"
  },
  {
    "path": "subscription/subscription.go",
    "content": "package subscription\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tSub    = &Subscription{}\n\tclient = &http.Client{\n\t\tTimeout: 30 * time.Second,\n\t}\n)\n\ntype Subscription struct {\n\tActive            bool      `json:\"active\"`\n\tStatus            string    `json:\"status\"`\n\tPlan              string    `json:\"plan\"`\n\tQuantity          int       `json:\"quantity\"`\n\tAmount            int       `json:\"amount\"`\n\tPeriodEnd         time.Time `json:\"period_end\"`\n\tTrialEnd          time.Time `json:\"trial_end\"`\n\tCancelAtPeriodEnd bool      `json:\"cancel_at_period_end\"`\n\tBalance           int64     `json:\"balance\"`\n\tUrlKey            string    `json:\"url_key\"`\n}\n\ntype subscriptionData struct {\n\tActive            bool   `json:\"active\"`\n\tStatus            string `json:\"status\"`\n\tPlan              string `json:\"plan\"`\n\tQuantity          int    `json:\"quantity\"`\n\tAmount            int    `json:\"amount\"`\n\tPeriodEnd         int64  `json:\"period_end\"`\n\tTrialEnd          int64  `json:\"trial_end\"`\n\tCancelAtPeriodEnd bool   `json:\"cancel_at_period_end\"`\n\tBalance           int64  `json:\"balance\"`\n\tUrlKey            string `json:\"url_key\"`\n}\n\nfunc Update() (errData *errortypes.ErrorData, err error) {\n\tsub := &Subscription{}\n\n\tif settings.System.License == \"\" {\n\t\tSub = sub\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(struct {\n\t\tId      string `json:\"id\"`\n\t\tLicense string `json:\"license\"`\n\t}{\n\t\tId:      settings.System.Name,\n\t\tLicense: settings.System.License,\n\t})\n\n\treq, err := http.NewRequest(\n\t\t\"GET\",\n\t\t\"https://app.pritunl.com/subscription\",\n\t\tbytes.NewBuffer(data),\n\t)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"subscription: Subscription request failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"subscription: Subscription request failed\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\terrData = &errortypes.ErrorData{}\n\t\terr = json.NewDecoder(resp.Body).Decode(errData)\n\t\tif err != nil {\n\t\t\terrData = nil\n\t\t} else {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\":     errData.Error,\n\t\t\t\t\"error_msg\": errData.Message,\n\t\t\t}).Error(\"subscription: Subscription error\")\n\t\t}\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"subscription: Subscription server error\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsubData := &subscriptionData{}\n\terr = json.NewDecoder(resp.Body).Decode(subData)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(\n\t\t\t\terr,\n\t\t\t\t\"subscription: Failed to parse subscription response\",\n\t\t\t),\n\t\t}\n\t\treturn\n\t}\n\n\tif !strings.Contains(subData.Plan, \"zero\") &&\n\t\t!strings.Contains(subData.Plan, \"cloud\") {\n\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"invalid_plan\",\n\t\t\tMessage: \"Invalid subscription plan\",\n\t\t}\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"subscription: Invalid plan\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsub.Active = subData.Active\n\tsub.Status = subData.Status\n\tsub.Plan = subData.Plan\n\tsub.Quantity = subData.Quantity\n\tsub.Amount = subData.Amount\n\tsub.CancelAtPeriodEnd = subData.CancelAtPeriodEnd\n\tsub.Balance = subData.Balance\n\tsub.UrlKey = subData.UrlKey\n\n\tif subData.PeriodEnd != 0 {\n\t\tsub.PeriodEnd = time.Unix(subData.PeriodEnd, 0)\n\t}\n\tif subData.TrialEnd != 0 {\n\t\tsub.TrialEnd = time.Unix(subData.TrialEnd, 0)\n\t}\n\n\tSub = sub\n\n\treturn\n}\n\nfunc update() {\n\tfor {\n\t\ttime.Sleep(30 * time.Minute)\n\t\tif constants.Shutdown {\n\t\t\treturn\n\t\t}\n\n\t\terr, _ := Update()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"subscription: Update error\")\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc init() {\n\tmodule := requires.New(\"subscription\")\n\tmodule.After(\"settings\")\n\n\tmodule.Handler = func() (err error) {\n\t\tUpdate()\n\t\tgo update()\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "sync/auth.go",
    "content": "package sync\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc authSync() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tcoll := db.Users()\n\tcount, err := coll.CountDocuments(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"type\": user.Local,\n\t\t},\n\t\toptions.Count().SetLimit(1),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tsettings.Local.NoLocalAuth = count == 0\n\n\treturn\n}\n\nfunc authRunner() {\n\ttime.Sleep(1 * time.Second)\n\n\tfor {\n\t\ttime.Sleep(10 * time.Second)\n\n\t\tif constants.Shutdown {\n\t\t\treturn\n\t\t}\n\n\t\terr := authSync()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"sync: Failed to sync authentication status\")\n\t\t}\n\t}\n}\n\nfunc initAuth() {\n\tgo authRunner()\n}\n"
  },
  {
    "path": "sync/nodes.go",
    "content": "package sync\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc nodeSync() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tappId := \"\"\n\tfacets := []string{}\n\n\tif node.Self.UserDomain != \"\" {\n\t\tdomain := node.Self.UserDomain\n\t\tport := node.Self.Port\n\t\tif node.Self.Protocol == \"https\" && port != 443 {\n\t\t\tdomain += \":\" + strconv.Itoa(port)\n\t\t}\n\t\tappId = fmt.Sprintf(\"https://%s/auth/u2f/app.json\", domain)\n\t}\n\n\tnodes, err := node.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdomains := set.NewSet()\n\tfor _, nde := range nodes {\n\t\tif appId == \"\" {\n\t\t\tappId = fmt.Sprintf(\"https://%s/auth/u2f/app.json\",\n\t\t\t\tnde.UserDomain)\n\t\t}\n\n\t\tdomain := nde.UserDomain\n\t\tport := nde.Port\n\t\tif domain != \"\" {\n\t\t\tif nde.Protocol == \"https\" && port != 443 {\n\t\t\t\tdomain += \":\" + strconv.Itoa(port)\n\t\t\t}\n\n\t\t\tif !domains.Contains(domain) {\n\t\t\t\tdomains.Add(domain)\n\t\t\t\tfacets = append(facets, fmt.Sprintf(\"https://%s\", domain))\n\t\t\t}\n\t\t}\n\n\t\tdomain = nde.AdminDomain\n\t\tport = nde.Port\n\t\tif domain != \"\" {\n\t\t\tif nde.Protocol == \"https\" && port != 443 {\n\t\t\t\tdomain += \":\" + strconv.Itoa(port)\n\t\t\t}\n\n\t\t\tif !domains.Contains(domain) {\n\t\t\t\tdomains.Add(domain)\n\t\t\t\tfacets = append(facets, fmt.Sprintf(\"https://%s\", domain))\n\t\t\t}\n\t\t}\n\t}\n\n\tsettings.Local.AppId = appId\n\tsettings.Local.Facets = facets\n\n\treturn\n}\n\nfunc nodeRunner() {\n\ttime.Sleep(1 * time.Second)\n\n\tfor {\n\t\tif constants.Shutdown {\n\t\t\treturn\n\t\t}\n\n\t\terr := nodeSync()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"sync: Failed to sync node status\")\n\t\t}\n\n\t\ttime.Sleep(10 * time.Second)\n\t}\n}\n\nfunc initNode() {\n\tgo nodeRunner()\n}\n"
  },
  {
    "path": "sync/sync.go",
    "content": "package sync\n\nfunc Init() {\n\tinitAuth()\n\tinitNode()\n\tinitVm()\n}\n"
  },
  {
    "path": "sync/vm.go",
    "content": "package sync\n\nimport (\n\t\"runtime/debug\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deploy\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/iptables\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc deployState(runtimes *state.Runtimes) (err error) {\n\tdefer func() {\n\t\tpanc := recover()\n\t\tif panc != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\"panic\": panc,\n\t\t\t}).Error(\"sync: Panic in state deploy\")\n\t\t}\n\t}()\n\n\tstart := time.Now()\n\n\tstat, err := state.GetState(runtimes)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = deploy.Deploy(stat, runtimes)\n\tif err != nil {\n\t\treturn\n\t}\n\n\truntimes.Total = time.Since(start)\n\n\treturn\n}\n\nfunc syncNodeFirewall() {\n\tdefer func() {\n\t\tpanc := recover()\n\t\tif panc != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\"panic\": panc,\n\t\t\t}).Error(\"sync: Panic in node firewall\")\n\t\t}\n\t}()\n\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\tif !node.Self.Firewall {\n\t\tiptables.UpdateState(node.Self, []*vpc.Vpc{}, []*instance.Instance{},\n\t\t\t[]string{}, nil, map[string][]*firewall.Rule{},\n\t\t\tmap[string][]*firewall.Mapping{})\n\t\treturn\n\t}\n\n\tfor i := 0; i < 2; i++ {\n\t\tfires, err := firewall.GetRoles(db, node.Self.Roles)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"sync: Failed to get node firewall rules\")\n\t\t\treturn\n\t\t}\n\n\t\tingress := firewall.MergeIngress(fires)\n\n\t\tiptables.UpdateStateRecover(node.Self, []*vpc.Vpc{},\n\t\t\t[]*instance.Instance{}, []string{}, ingress,\n\t\t\tmap[string][]*firewall.Rule{}, map[string][]*firewall.Mapping{})\n\n\t\tbreak\n\t}\n}\n\nfunc vmRunner() {\n\ttime.Sleep(1 * time.Second)\n\n\tfor {\n\t\ttime.Sleep(1 * time.Second)\n\t\tif constants.Shutdown {\n\t\t\treturn\n\t\t}\n\n\t\tif !node.Self.IsHypervisor() {\n\t\t\tsyncNodeFirewall()\n\t\t\tcontinue\n\t\t}\n\n\t\tbreak\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"production\": constants.Production,\n\t}).Info(\"sync: Starting hypervisor\")\n\n\truntimes := &state.Runtimes{}\n\truntimes.Init()\n\tfor {\n\t\tif runtimes.Total > 1500*time.Millisecond {\n\t\t\truntimes.Log()\n\t\t}\n\n\t\tdelay := (3000 * time.Millisecond) - runtimes.Total\n\t\tif delay < 50*time.Millisecond {\n\t\t\tdelay = 50 * time.Millisecond\n\t\t}\n\t\ttime.Sleep(delay)\n\t\truntimes = &state.Runtimes{}\n\t\truntimes.Init()\n\n\t\tif constants.Shutdown {\n\t\t\treturn\n\t\t}\n\n\t\tif !node.Self.IsHypervisor() {\n\t\t\tsyncNodeFirewall()\n\t\t\tcontinue\n\t\t}\n\n\t\terr := deployState(runtimes)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"error\": err,\n\t\t\t}).Error(\"sync: Failed to deploy state\")\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc initVm() {\n\tgo vmRunner()\n}\n"
  },
  {
    "path": "systemd/systemd.go",
    "content": "package systemd\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nvar (\n\tsystemdLock = sync.Mutex{}\n)\n\nfunc Reload() (err error) {\n\tsystemdLock.Lock()\n\tdefer systemdLock.Unlock()\n\n\t_, err = utils.ExecCombinedOutput(\"\", \"systemctl\", \"daemon-reload\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(100 * time.Millisecond)\n\n\treturn\n}\n\nfunc Start(unit string) (err error) {\n\tsystemdLock.Lock()\n\tdefer systemdLock.Unlock()\n\n\t_, err = utils.ExecCombinedOutputLogged(nil, \"systemctl\", \"start\", unit)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(300 * time.Millisecond)\n\n\treturn\n}\n\nfunc Restart(unit string) (err error) {\n\tsystemdLock.Lock()\n\tdefer systemdLock.Unlock()\n\n\t_, err = utils.ExecCombinedOutputLogged(nil, \"systemctl\", \"restart\", unit)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(300 * time.Millisecond)\n\n\treturn\n}\n\nfunc Stop(unit string) (err error) {\n\tsystemdLock.Lock()\n\tdefer systemdLock.Unlock()\n\n\t_, err = utils.ExecCombinedOutput(\"\", \"systemctl\", \"stop\", unit)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttime.Sleep(300 * time.Millisecond)\n\n\treturn\n}\n\nfunc GetState(unit string) (state string, timestamp time.Time, err error) {\n\tsystemdLock.Lock()\n\tdefer systemdLock.Unlock()\n\n\toutput, _ := utils.ExecOutput(\"\", \"systemctl\", \"show\",\n\t\t\"--no-page\", unit)\n\n\ttimestampStr := \"\"\n\texitCode := \"\"\n\texitStatus := \"\"\n\n\tfor _, line := range strings.Split(output, \"\\n\") {\n\t\tn := len(line)\n\t\tif state == \"\" && n > 13 && line[:12] == \"ActiveState=\" {\n\t\t\tstate = line[12:]\n\t\t} else if exitCode == \"\" && n > 13 && line[:13] == \"ExecMainCode=\" {\n\t\t\texitCode = line[13:]\n\t\t} else if exitStatus == \"\" && n > 15 &&\n\t\t\tline[:15] == \"ExecMainStatus=\" {\n\n\t\t\texitStatus = line[15:]\n\t\t} else if timestampStr == \"\" && n > 24 &&\n\t\t\tline[:23] == \"ExecMainStartTimestamp=\" {\n\n\t\t\ttimestampStr = line[23:]\n\t\t}\n\t}\n\n\tif (state == \"failed\" && exitCode == \"2\" && exitStatus == \"31\") ||\n\t\t(state == \"failed\" && exitCode == \"3\" && exitStatus == \"31\") {\n\n\t\tstate = \"inactive\"\n\t}\n\n\tif timestampStr != \"\" && timestampStr != \"0\" && timestampStr != \"n/a\" {\n\t\ttimestamp, _ = time.Parse(\"Mon 2006-01-02 15:04:05 MST\", timestampStr)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "systemd/utils.go",
    "content": "package systemd\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc FormatUptime(timestamp time.Time) (uptime string) {\n\tsince := time.Since(timestamp)\n\tminutes := int64(since.Minutes())\n\tdays := minutes / 1440\n\thours := (minutes % 1440) / 60\n\tminutes = (minutes % 1440) % 60\n\n\tif days > 0 {\n\t\tuptime = fmt.Sprintf(\"%d days\", days)\n\t}\n\tif hours > 0 || uptime != \"\" {\n\t\tif uptime != \"\" {\n\t\t\tuptime += \" \"\n\t\t}\n\t\tuptime += fmt.Sprintf(\"%d hours\", hours)\n\t}\n\tif uptime != \"\" {\n\t\tuptime += \" \"\n\t}\n\tuptime += fmt.Sprintf(\"%d mins\", minutes)\n\n\treturn\n}\n\nfunc FormatUptimeShort(timestamp time.Time) (uptime string) {\n\tsince := time.Since(timestamp)\n\tminutes := int64(since.Minutes())\n\tdays := minutes / 1440\n\thours := (minutes % 1440) / 60\n\tminutes = (minutes % 1440) % 60\n\n\tif days > 3 {\n\t\tuptime = fmt.Sprintf(\"%d days\", days)\n\t} else {\n\t\tif days > 0 {\n\t\t\thours += days * 24\n\t\t}\n\n\t\tif hours > 0 {\n\t\t\tuptime += fmt.Sprintf(\"%d hr\", hours)\n\t\t}\n\n\t\tif minutes > 0 {\n\t\t\tif uptime != \"\" {\n\t\t\t\tuptime += \" \"\n\t\t\t}\n\t\t\tuptime += fmt.Sprintf(\"%d mn\", minutes)\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "task/acme.go",
    "content": "package task\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/acme\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar acmeRenew = &Task{\n\tName:    \"acme_renew\",\n\tVersion: 1,\n\tHours:   []int{7},\n\tMinutes: []int{45},\n\tHandler: acmeRenewHandler,\n}\n\nfunc acmeRenewHandler(db *database.Database) (err error) {\n\tcerts, err := certificate.GetAll(db, &bson.M{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, cert := range certs {\n\t\tif cert.Type != certificate.LetsEncrypt {\n\t\t\tcontinue\n\t\t}\n\n\t\terr = acme.Renew(db, cert, false)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"certificate_id\":   cert.Id.Hex(),\n\t\t\t\t\"certificate_name\": cert.Name,\n\t\t\t\t\"error\":            err,\n\t\t\t}).Error(\"task: Failed to renew certificate\")\n\t\t\tcontinue\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(acmeRenew)\n}\n"
  },
  {
    "path": "task/advisory.go",
    "content": "package task\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/advisory\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar advisoryData = &Task{\n\tName:       \"advisory\",\n\tVersion:    1,\n\tHours:      []int{0, 3, 6, 9, 12, 15, 18, 21},\n\tMinutes:    []int{22},\n\tHandler:    advisoryDataHandler,\n\tRunOnStart: true,\n}\n\nfunc advisoryDataHandler(db *database.Database) (err error) {\n\tadvisories := map[string]*advisory.Advisory{}\n\n\tcoll := db.Instances()\n\n\tcursor, err := coll.Find(db, &bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tinst := &instance.Instance{}\n\t\terr = cursor.Decode(inst)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif inst.Guest == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, updt := range inst.Guest.Updates {\n\t\t\tdetails := []*advisory.Advisory{}\n\n\t\t\tfor _, cve := range updt.Cves {\n\t\t\t\tadv, ok := advisories[cve]\n\t\t\t\tif !ok {\n\t\t\t\t\tadv, err = advisory.GetOneLimit(db, cve)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"cve_id\": cve,\n\t\t\t\t\t\t\t\"error\":  err,\n\t\t\t\t\t\t}).Error(\"task: Failed to query CVE\")\n\t\t\t\t\t\terr = nil\n\t\t\t\t\t\tadv = nil\n\t\t\t\t\t}\n\t\t\t\t\tadvisories[cve] = adv\n\t\t\t\t}\n\n\t\t\t\tif adv != nil {\n\t\t\t\t\tdetails = append(details, adv)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tupdt.Details = details\n\t\t}\n\n\t\terr = inst.CommitFields(db, set.NewSet(\"guest\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(advisoryData)\n}\n"
  },
  {
    "path": "task/backing.go",
    "content": "package task\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar backingClean = &Task{\n\tName:    \"backing_clean\",\n\tVersion: 1,\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes: []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55},\n\tHandler: backingCleanHandler,\n}\n\nfunc backingCleanHandler(db *database.Database) (err error) {\n\tbackingDir := paths.GetBackingPath()\n\n\tdiskKeys, err := disk.GetAllKeys(db, node.Self.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\texists, err := utils.ExistsDir(backingDir)\n\tif !exists {\n\t\treturn\n\t}\n\n\titems, err := ioutil.ReadDir(backingDir)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"task: Failed to read backing directory\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, item := range items {\n\t\tname := item.Name()\n\t\tpth := filepath.Join(backingDir, name)\n\n\t\tif strings.HasPrefix(name, \"image-\") {\n\t\t\tkeys := strings.Split(name, \"-\")\n\t\t\tif len(keys) != 3 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkey := fmt.Sprintf(\"%s-%s\", keys[1], keys[2])\n\n\t\t\tif !diskKeys.Contains(key) {\n\t\t\t\tif time.Since(item.ModTime()) > 5*time.Minute {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"key\":  key,\n\t\t\t\t\t\t\"path\": pth,\n\t\t\t\t\t}).Info(\"task: Removing unused backing image\")\n\t\t\t\t\tos.Remove(pth)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(backingClean)\n}\n"
  },
  {
    "path": "task/balancer.go",
    "content": "package task\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/balancer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nvar balancerClean = &Task{\n\tName:    \"balancer_clean\",\n\tVersion: 1,\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes: []int{35},\n\tHandler: balancerCleanHandler,\n}\n\nfunc balancerCleanHandler(db *database.Database) (err error) {\n\tbalcns, err := balancer.GetAll(db, &bson.M{})\n\n\tfor _, balnc := range balcns {\n\t\terr = balnc.Clean(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(balancerClean)\n}\n"
  },
  {
    "path": "task/blocks.go",
    "content": "package task\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar blocksCheck = &Task{\n\tName:    \"blocks_check\",\n\tVersion: 1,\n\tHours:   []int{7},\n\tMinutes: []int{30},\n\tHandler: blocksCheckHandler,\n}\n\nfunc blocksCheckHandler(db *database.Database) (err error) {\n\tcoll := db.Blocks()\n\tipColl := db.BlocksIp()\n\tinstColl := db.Instances()\n\tipBlocks := []bson.ObjectID{}\n\n\terr = ipColl.Distinct(db, \"block\", &bson.M{\n\t\t\"type\": &bson.M{\n\t\t\t\"$in\": []string{\n\t\t\t\tblock.External,\n\t\t\t\tblock.IPv4,\n\t\t\t\tblock.IPv6,\n\t\t\t},\n\t\t},\n\t}).Decode(&ipBlocks)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tblocks := set.NewSet()\n\tipBlocksSet := set.NewSet()\n\n\tfor _, ipBlock := range ipBlocks {\n\t\tipBlocksSet.Add(ipBlock)\n\t}\n\n\tcursor, err := coll.Find(db, &bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tblck := &block.Block{}\n\t\terr = cursor.Decode(blck)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tblocks.Add(blck.Id)\n\n\t\terr = blck.ValidateAddresses(db, nil)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tipBlocksSet.Subtract(blocks)\n\tfor blckIdInf := range ipBlocksSet.Iter() {\n\t\tblckId := blckIdInf.(bson.ObjectID)\n\n\t\tcursor2, e := ipColl.Find(db, &bson.M{\n\t\t\t\"block\": blckId,\n\t\t})\n\t\tif e != nil {\n\t\t\terr = database.ParseError(e)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor2.Close(db)\n\n\t\tfor cursor2.Next(db) {\n\t\t\tblckIp := &block.BlockIp{}\n\t\t\terr = cursor2.Decode(blckIp)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"ip_address\":  utils.Int2IpAddress(blckIp.Ip).String(),\n\t\t\t\t\"block_id\":    blckIp.Id.Hex(),\n\t\t\t\t\"instance_id\": blckIp.Instance.Hex(),\n\t\t\t}).Warn(\"task: Removing lost block IP\")\n\n\t\t\t_, _ = instColl.UpdateOne(db, &bson.M{\n\t\t\t\t\"_id\": blckIp.Instance,\n\t\t\t}, &bson.M{\n\t\t\t\t\"$set\": &bson.M{\n\t\t\t\t\t\"restart_block_ip\": true,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\t_, err = ipColl.DeleteOne(db, &bson.M{\n\t\t\t\t\"_id\": blckIp.Id,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\terr = cursor2.Err()\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(blocksCheck)\n}\n"
  },
  {
    "path": "task/cache.go",
    "content": "package task\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar cacheClean = &Task{\n\tName:    \"cache_clean\",\n\tVersion: 1,\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes: []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55},\n\tHandler: cacheCleanHandler,\n}\n\nfunc cacheCleanHandler(db *database.Database) (err error) {\n\tcacheDir := node.Self.GetCachePath()\n\n\timageKeys, err := image.GetAllKeys(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\texists, err := utils.ExistsDir(cacheDir)\n\tif !exists {\n\t\treturn\n\t}\n\n\titems, err := ioutil.ReadDir(cacheDir)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"task: Failed to read cache directory\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, item := range items {\n\t\tname := item.Name()\n\t\tpth := filepath.Join(cacheDir, name)\n\n\t\tif strings.HasPrefix(name, \"image-\") {\n\t\t\tkeys := strings.Split(name, \"-\")\n\t\t\tif len(keys) != 3 {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"path\": pth,\n\t\t\t\t}).Warning(\"task: Removing unknown image cache\")\n\t\t\t\tos.Remove(pth)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkey := fmt.Sprintf(\"%s-%s\", keys[1], keys[2])\n\n\t\t\tif !imageKeys.Contains(key) {\n\t\t\t\tif time.Since(item.ModTime()) > 5*time.Minute {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"key\":  key,\n\t\t\t\t\t\t\"path\": pth,\n\t\t\t\t\t}).Info(\"task: Removing old image cache\")\n\t\t\t\t\tos.Remove(pth)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(cacheClean)\n}\n"
  },
  {
    "path": "task/constants.go",
    "content": "package task\n\nconst (\n\tRunning  = \"running\"\n\tFailed   = \"failed\"\n\tFinished = \"finished\"\n)\n\nvar (\n\tAllHours = []int{\n\t\t0,\n\t\t1,\n\t\t2,\n\t\t3,\n\t\t4,\n\t\t5,\n\t\t6,\n\t\t7,\n\t\t8,\n\t\t9,\n\t\t10,\n\t\t11,\n\t\t12,\n\t\t13,\n\t\t14,\n\t\t15,\n\t\t16,\n\t\t17,\n\t\t18,\n\t\t19,\n\t\t20,\n\t\t21,\n\t\t22,\n\t\t23,\n\t}\n\tTwoHours = []int{\n\t\t0,\n\t\t2,\n\t\t4,\n\t\t6,\n\t\t8,\n\t\t10,\n\t\t12,\n\t\t14,\n\t\t16,\n\t\t18,\n\t\t20,\n\t\t22,\n\t}\n\tSixHours = []int{\n\t\t0,\n\t\t6,\n\t\t12,\n\t\t18,\n\t}\n\tAllMins = []int{\n\t\t0,\n\t\t1,\n\t\t2,\n\t\t3,\n\t\t4,\n\t\t5,\n\t\t6,\n\t\t7,\n\t\t8,\n\t\t9,\n\t\t10,\n\t\t11,\n\t\t12,\n\t\t13,\n\t\t14,\n\t\t15,\n\t\t16,\n\t\t17,\n\t\t18,\n\t\t19,\n\t\t20,\n\t\t21,\n\t\t22,\n\t\t23,\n\t\t24,\n\t\t25,\n\t\t26,\n\t\t27,\n\t\t28,\n\t\t29,\n\t\t30,\n\t\t31,\n\t\t32,\n\t\t33,\n\t\t34,\n\t\t35,\n\t\t36,\n\t\t37,\n\t\t38,\n\t\t39,\n\t\t40,\n\t\t41,\n\t\t42,\n\t\t43,\n\t\t44,\n\t\t45,\n\t\t46,\n\t\t47,\n\t\t48,\n\t\t49,\n\t\t50,\n\t\t51,\n\t\t52,\n\t\t53,\n\t\t54,\n\t\t55,\n\t\t56,\n\t\t57,\n\t\t58,\n\t\t59,\n\t}\n\tTwoMins = []int{\n\t\t0,\n\t\t2,\n\t\t4,\n\t\t6,\n\t\t8,\n\t\t10,\n\t\t12,\n\t\t14,\n\t\t16,\n\t\t18,\n\t\t20,\n\t\t22,\n\t\t24,\n\t\t26,\n\t\t28,\n\t\t30,\n\t\t32,\n\t\t34,\n\t\t36,\n\t\t38,\n\t\t40,\n\t\t42,\n\t\t44,\n\t\t46,\n\t\t48,\n\t\t50,\n\t\t52,\n\t\t54,\n\t\t56,\n\t\t58,\n\t}\n\tFiveMins = []int{\n\t\t0,\n\t\t5,\n\t\t10,\n\t\t15,\n\t\t20,\n\t\t25,\n\t\t30,\n\t\t35,\n\t\t40,\n\t\t45,\n\t\t50,\n\t\t55,\n\t}\n\tTenMins = []int{\n\t\t0,\n\t\t10,\n\t\t20,\n\t\t30,\n\t\t40,\n\t\t50,\n\t}\n\tFifteenMins = []int{\n\t\t0,\n\t\t15,\n\t\t30,\n\t\t45,\n\t}\n\tThirtyMins = []int{\n\t\t0,\n\t\t30,\n\t}\n)\n"
  },
  {
    "path": "task/deployments.go",
    "content": "package task\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/planner\"\n)\n\nvar deployments = &Task{\n\tName:    \"deployments\",\n\tVersion: 1,\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,\n\t\t26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,\n\t\t39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n\t\t52, 53, 54, 55, 56, 57, 58, 59},\n\tSeconds: 3 * time.Second,\n\tHandler: deploymentsHandler,\n}\n\nfunc deploymentsHandler(db *database.Database) (err error) {\n\tplnr := &planner.Planner{}\n\n\terr = plnr.ApplyPlans(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(deployments)\n}\n"
  },
  {
    "path": "task/domains.go",
    "content": "package task\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n)\n\nvar domains = &Task{\n\tName:    \"domains\",\n\tVersion: 1,\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,\n\t\t26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,\n\t\t39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n\t\t52, 53, 54, 55, 56, 57, 58, 59},\n\tHandler: domainsHandler,\n}\n\nfunc domainsHandler(db *database.Database) (err error) {\n\trefreshTtl := time.Duration(\n\t\tsettings.System.DomainRefreshTtl) * time.Second\n\n\tdomns, err := domain.GetAll(db, &bson.M{\n\t\t\"last_update\": &bson.M{\n\t\t\t\"$gte\": time.Now().Add(-refreshTtl),\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, domn := range domns {\n\t\tdomain.Refresh(db, domn.Id)\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(domains)\n}\n"
  },
  {
    "path": "task/imds.go",
    "content": "package task\n\nimport (\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/imds\"\n\t\"github.com/pritunl/pritunl-cloud/imds/types\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar imdsSync = &Task{\n\tName: \"imds_sync\",\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,\n\t\t26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,\n\t\t39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n\t\t52, 53, 54, 55, 56, 57, 58, 59},\n\tSeconds: 3 * time.Second,\n\tLocal:   true,\n\tHandler: imdsSyncHandler,\n}\n\nvar (\n\tfailTime = map[bson.ObjectID]failTimeData{}\n)\n\ntype failTimeData struct {\n\ttimestamp time.Time\n\tlogged    bool\n}\n\nfunc test() {\n\ttest := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}\n\n\tfor _, val := range test {\n\t\tgo func() {\n\t\t\ttime.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)\n\t\t\tprint(val)\n\t\t}()\n\t}\n}\n\nfunc imdsSyncHandler(db *database.Database) (err error) {\n\tconfs := imds.GetConfigs()\n\tlogTtl := time.Duration(\n\t\tsettings.Hypervisor.ImdsSyncLogTimeout) * time.Second\n\trestartTtl := time.Duration(\n\t\tsettings.Hypervisor.ImdsSyncRestartTimeout) * time.Second\n\n\tnewFailTime := map[bson.ObjectID]failTimeData{}\n\tnewFailTimeLock := sync.Mutex{}\n\twaiter := &sync.WaitGroup{}\n\tfor _, conf := range confs {\n\t\tif conf.Instance == nil || conf.Instance.NetworkNamespace == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\twaiter.Add(1)\n\t\tgo func(conf *types.Config) {\n\t\t\tdefer waiter.Done()\n\n\t\t\terr := imds.Sync(db, conf.Instance.NetworkNamespace,\n\t\t\t\tconf.Instance.Id, conf.Instance.Deployment, conf)\n\t\t\tif err != nil {\n\t\t\t\tnewFailTimeLock.Lock()\n\t\t\t\tttlData := failTime[conf.Instance.Id]\n\n\t\t\t\tif ttlData.timestamp.IsZero() {\n\t\t\t\t\tnewFailTime[conf.Instance.Id] = failTimeData{\n\t\t\t\t\t\ttimestamp: time.Now(),\n\t\t\t\t\t}\n\t\t\t\t} else if time.Since(ttlData.timestamp) > logTtl &&\n\t\t\t\t\t!ttlData.logged {\n\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"action\":   conf.Instance.Action,\n\t\t\t\t\t\t\"instance\": conf.Instance.Id.Hex(),\n\t\t\t\t\t\t\"error\":    err,\n\t\t\t\t\t}).Error(\"task: Failed to sync imds\")\n\n\t\t\t\t\tnewFailTime[conf.Instance.Id] = failTimeData{\n\t\t\t\t\t\ttimestamp: ttlData.timestamp,\n\t\t\t\t\t\tlogged:    true,\n\t\t\t\t\t}\n\t\t\t\t} else if time.Since(ttlData.timestamp) > restartTtl {\n\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"action\":   conf.Instance.Action,\n\t\t\t\t\t\t\"instance\": conf.Instance.Id.Hex(),\n\t\t\t\t\t\t\"error\":    err,\n\t\t\t\t\t}).Error(\"task: Failed to sync imds, restarting...\")\n\n\t\t\t\t\te := imds.Restart(conf.Instance.Id)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"action\":   conf.Instance.Action,\n\t\t\t\t\t\t\t\"instance\": conf.Instance.Id.Hex(),\n\t\t\t\t\t\t\t\"error\":    e,\n\t\t\t\t\t\t}).Error(\"task: Failed to restart imds\")\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tnewFailTime[conf.Instance.Id] = failTime[conf.Instance.Id]\n\t\t\t\t}\n\t\t\t\tnewFailTimeLock.Unlock()\n\t\t\t}\n\t\t}(conf)\n\t}\n\n\twaiter.Wait()\n\n\tfailTime = newFailTime\n\n\treturn\n}\n\nfunc init() {\n\tregister(imdsSync)\n}\n"
  },
  {
    "path": "task/job.go",
    "content": "package task\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\ntype Job struct {\n\tId        string        `bson:\"_id\"`\n\tName      string        `bson:\"name\"`\n\tState     string        `bson:\"state\"`\n\tRetry     bool          `bson:\"retry\"`\n\tNode      bson.ObjectID `bson:\"node\"`\n\tTimestamp time.Time     `bson:\"timestamp\"`\n}\n\nfunc (j *Job) Reserve(db *database.Database) (reserved bool, err error) {\n\tcoll := db.Tasks()\n\n\t_, err = coll.InsertOne(db, j)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\n\t\tswitch err.(type) {\n\t\tcase *database.DuplicateKeyError:\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\n\t\treturn\n\t}\n\n\treserved = true\n\treturn\n}\n\nfunc (j *Job) Failed(db *database.Database) (err error) {\n\tcoll := db.Tasks()\n\n\terr = coll.UpdateId(j.Id, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"state\": Failed,\n\t\t},\n\t})\n\n\treturn\n}\n\nfunc (j *Job) Finished(db *database.Database) (err error) {\n\tcoll := db.Tasks()\n\n\terr = coll.UpdateId(j.Id, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"state\": Finished,\n\t\t},\n\t})\n\n\treturn\n}\n"
  },
  {
    "path": "task/notification.go",
    "content": "package task\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/notification\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar notificationCheck = &Task{\n\tName:    \"notification_check\",\n\tVersion: 1,\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes:    []int{15},\n\tHandler:    notificationCheckHandler,\n\tRunOnStart: true,\n}\n\nfunc notificationCheckHandler(db *database.Database) (err error) {\n\terr = notification.Check()\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"task: Failed to check vulnerability alerts\")\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(notificationCheck)\n}\n"
  },
  {
    "path": "task/scheduler.go",
    "content": "package task\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/scheduler\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar schedule = &Task{\n\tName:    \"schedule\",\n\tVersion: 1,\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,\n\t\t26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,\n\t\t39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n\t\t52, 53, 54, 55, 56, 57, 58, 59},\n\tSeconds: 5 * time.Second,\n\tHandler: scheduleHandler,\n}\n\nfunc scheduleUnits(db *database.Database) (err error) {\n\tunits, err := unit.GetAll(db, &bson.M{\n\t\t\"kind\": bson.M{\n\t\t\t\"$in\": []string{deployment.Instance, deployment.Image},\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdeploymentIds, err := deployment.GetAllActiveIds(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, unt := range units {\n\t\tfor _, deplyId := range unt.Deployments {\n\t\t\tif !deploymentIds.Contains(deplyId) {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"pod\":        unt.Pod.Hex(),\n\t\t\t\t\t\"unit\":       unt.Id.Hex(),\n\t\t\t\t\t\"deployment\": deplyId.Hex(),\n\t\t\t\t}).Info(\"deploy: Removing deployment\")\n\n\t\t\t\terr = unt.RemoveDeployement(db, deplyId)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, unt := range units {\n\t\tif len(unt.Deployments) >= unt.Count {\n\t\t\tcontinue\n\t\t}\n\n\t\terr = scheduler.Schedule(db, unt)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc scheduleHandler(db *database.Database) (err error) {\n\terr = scheduleUnits(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tschds, err := scheduler.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, schd := range schds {\n\t\tif schd.Consumed >= schd.Count {\n\t\t\t_, err = scheduler.Remove(db, schd.Id)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(schedule)\n}\n"
  },
  {
    "path": "task/spec.go",
    "content": "package task\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar specs = &Task{\n\tName:    \"specs\",\n\tVersion: 1,\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n\t\t13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes: []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55},\n\tHandler: specsHandler,\n}\n\nfunc specsHandler(db *database.Database) (err error) {\n\tdeplys, err := deployment.GetAll(db, &bson.M{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tspecIdsSet := set.NewSet()\n\tfor _, deply := range deplys {\n\t\tif deply.Kind != deployment.Instance {\n\t\t\tcontinue\n\t\t}\n\t\tspecIdsSet.Add(deply.Spec)\n\t}\n\n\tspecIds := []bson.ObjectID{}\n\tfor specId := range specIdsSet.Iter() {\n\t\tspecIds = append(specIds, specId.(bson.ObjectID))\n\t}\n\n\tspecs, err := spec.GetAll(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": specIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, spec := range specs {\n\t\terrData, e := spec.Refresh(db)\n\t\tif e != nil || errData != nil {\n\t\t\terr = e\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"spec_id\":    spec.Id.Hex(),\n\t\t\t\t\"error\":      err,\n\t\t\t\t\"error_data\": errData,\n\t\t\t}).Error(\"deploy: Failed to refresh active spec\")\n\n\t\t\terr = nil\n\t\t\terrData = nil\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(specs)\n}\n"
  },
  {
    "path": "task/specindex.go",
    "content": "package task\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n)\n\nvar specIndex = &Task{\n\tName:    \"spec_index\",\n\tVersion: 1,\n\tHours:   []int{6},\n\tMinutes: []int{32},\n\tHandler: specIndexHandler,\n}\n\nfunc specIndexSyncUnit(db *database.Database, unt *unit.Unit) (err error) {\n\tspecs, err := spec.GetAllIndexes(db, &bson.M{\n\t\t\"unit\": unt.Id,\n\t})\n\n\tindex := 0\n\tfor i, spc := range specs {\n\t\tindex = i + 1\n\n\t\tif spc.Index != index {\n\t\t\tspc.Index = index\n\t\t\terr = spc.CommitFields(db, set.NewSet(\"index\"))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif unt.SpecIndex != index {\n\t\tunt.SpecIndex = index\n\t\terr = unt.CommitFields(db, set.NewSet(\"spec_index\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc specIndexHandler(db *database.Database) (err error) {\n\tunits, err := unit.GetAll(db, &bson.M{})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, unt := range units {\n\t\terr = specIndexSyncUnit(db, unt)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc init() {\n\tregister(specIndex)\n}\n"
  },
  {
    "path": "task/storage.go",
    "content": "package task\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar storageSync = &Task{\n\tName:    \"storage_renew\",\n\tVersion: 1,\n\tHours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n\t\t12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},\n\tMinutes:    []int{0, 14, 29, 44, 59},\n\tHandler:    storageSyncHandler,\n\tRunOnStart: true,\n}\n\nfunc storageSyncHandler(db *database.Database) (err error) {\n\tcoll := db.Images()\n\n\timgStoreIdsList := []bson.ObjectID{}\n\n\terr = coll.Distinct(\n\t\tdb,\n\t\t\"storage\",\n\t\t&bson.M{},\n\t).Decode(&imgStoreIdsList)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\timgStoreIds := set.NewSet()\n\tfor _, storeId := range imgStoreIdsList {\n\t\timgStoreIds.Add(storeId)\n\t}\n\n\tstoreIds := set.NewSet()\n\tstores, err := storage.GetAll(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, store := range stores {\n\t\tstoreIds.Add(store.Id)\n\n\t\terr = data.Sync(db, store)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"storage_id\":   store.Id.Hex(),\n\t\t\t\t\"storage_name\": store.Name,\n\t\t\t\t\"error\":        err,\n\t\t\t}).Error(\"task: Failed to sync storage\")\n\t\t}\n\t}\n\n\timgStoreIds.Subtract(storeIds)\n\n\tremStoreIds := []bson.ObjectID{}\n\tfor storeIdInf := range imgStoreIds.Iter() {\n\t\tstoreId := storeIdInf.(bson.ObjectID)\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"storage_id\": storeId.Hex(),\n\t\t}).Warning(\"task: Cleaning unknown images\")\n\n\t\tremStoreIds = append(remStoreIds, storeId)\n\t}\n\n\tif len(remStoreIds) > 0 {\n\t\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\t\"storage\": &bson.M{\n\t\t\t\t\"$in\": remStoreIds,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tevent.PublishDispatch(db, \"image.change\")\n\n\treturn\n}\n\nfunc init() {\n\tregister(storageSync)\n}\n"
  },
  {
    "path": "task/task.go",
    "content": "package task\n\nimport (\n\t\"fmt\"\n\t\"runtime/debug\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/version\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tregistry = []*Task{}\n)\n\ntype Task struct {\n\tName       string\n\tVersion    int\n\tHours      []int\n\tMinutes    []int\n\tSeconds    time.Duration\n\tRetry      bool\n\tHandler    func(*database.Database) error\n\tRunOnStart bool\n\tLocal      bool\n\tDebugNodes []string\n\ttimestamp  time.Time\n}\n\nfunc (t *Task) scheduled(hour, min int) bool {\n\tfor _, h := range t.Hours {\n\t\tif h == hour {\n\t\t\tfor _, m := range t.Minutes {\n\t\t\t\tif m == min {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (t *Task) runShared(db *database.Database, now time.Time) {\n\tdefer func() {\n\t\tpanc := recover()\n\t\tif panc != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\"panic\": panc,\n\t\t\t}).Error(\"sync: Panic in run task\")\n\t\t}\n\t}()\n\n\tif t.Seconds == 0 {\n\t\ttime.Sleep(time.Duration(utils.RandInt(0, 1000)) * time.Millisecond)\n\t} else {\n\t\ttime.Sleep(time.Duration(utils.RandInt(0, 300)) * time.Millisecond)\n\t}\n\n\tif t.DebugNodes != nil {\n\t\tmatched := false\n\t\tfor _, ndeName := range t.DebugNodes {\n\t\t\tif node.Self.Name == ndeName {\n\t\t\t\tmatched = true\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn\n\t\t}\n\t}\n\n\tid := fmt.Sprintf(\"%s-%d\", t.Name, now.Unix()-int64(now.Second()))\n\tif t.Seconds != 0 {\n\t\tid += fmt.Sprintf(\"-%d\", GetBlock(now, t.Seconds))\n\t}\n\n\tjob := &Job{\n\t\tId:        id,\n\t\tName:      t.Name,\n\t\tState:     Running,\n\t\tRetry:     t.Retry,\n\t\tNode:      node.Self.Id,\n\t\tTimestamp: time.Now(),\n\t}\n\n\treserved, err := job.Reserve(db)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"task\":  t.Name,\n\t\t\t\"error\": err,\n\t\t}).Error(\"task: Task reserve failed\")\n\t\treturn\n\t}\n\n\tif !reserved {\n\t\treturn\n\t}\n\n\terr = t.Handler(db)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"task\":  t.Name,\n\t\t\t\"error\": err,\n\t\t}).Error(\"task: Task failed\")\n\t\t_ = job.Failed(db)\n\t\treturn\n\t}\n\n\t_ = job.Finished(db)\n}\n\nfunc (t *Task) runLocal(db *database.Database, now time.Time) {\n\tdefer func() {\n\t\tpanc := recover()\n\t\tif panc != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\t\"panic\": panc,\n\t\t\t}).Error(\"sync: Panic in run local task\")\n\t\t}\n\t}()\n\n\tif t.DebugNodes != nil {\n\t\tmatched := false\n\t\tfor _, ndeName := range t.DebugNodes {\n\t\t\tif node.Self.Name == ndeName {\n\t\t\t\tmatched = true\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn\n\t\t}\n\t}\n\n\tid := fmt.Sprintf(\"%s-%d\", t.Name, now.Unix()-int64(now.Second()))\n\tif t.Seconds != 0 {\n\t\tid += fmt.Sprintf(\"-%d\", GetBlock(now, t.Seconds))\n\t}\n\n\terr := t.Handler(db)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"task\":  t.Name,\n\t\t\t\"error\": err,\n\t\t}).Error(\"task: Local task failed\")\n\t\treturn\n\t}\n}\n\nfunc (t *Task) run(now time.Time) {\n\tgo func() {\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tif t.Version != 0 {\n\t\t\tsupported, err := version.Check(db, t.Name, t.Version)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"task\":  t.Name,\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"task: Version check failed\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !supported {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"task\":    t.Name,\n\t\t\t\t\t\"version\": t.Version,\n\t\t\t\t}).Info(\"task: Skipping incompatible task\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tcurTimestamp := t.timestamp\n\t\tif !curTimestamp.IsZero() {\n\t\t\tif time.Since(curTimestamp) > 10*time.Minute {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"task_name\": t.Name,\n\t\t\t\t\t\"runtime\":   time.Since(curTimestamp),\n\t\t\t\t}).Error(\"task: Task stuck running\")\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tt.timestamp = time.Now()\n\t\tdefer func() {\n\t\t\tt.timestamp = time.Time{}\n\t\t}()\n\n\t\tif t.Local {\n\t\t\tt.runLocal(db, now)\n\t\t} else {\n\t\t\tt.runShared(db, now)\n\t\t}\n\t}()\n}\n\nfunc runScheduler() {\n\tnow := time.Now()\n\tcurHour := now.Hour()\n\tcurMin := now.Minute()\n\tcurSecBlocks := map[time.Duration]int{}\n\n\tfor _, task := range registry {\n\t\tif task.Seconds != 0 {\n\t\t\tcurSecBlocks[task.Seconds] = GetBlock(now, task.Seconds)\n\t\t}\n\n\t\tif task.RunOnStart {\n\t\t\tgo task.run(now)\n\t\t}\n\t}\n\n\tfor {\n\t\ttime.Sleep(1 * time.Second)\n\n\t\tnow = time.Now()\n\t\thour := now.Hour()\n\t\tmin := now.Minute()\n\n\t\tfor block, curSecBlock := range curSecBlocks {\n\t\t\tsecBlock := GetBlock(now, block)\n\n\t\t\tif curSecBlock != secBlock {\n\t\t\t\tfor _, task := range registry {\n\t\t\t\t\tif task.Seconds != 0 && task.Seconds == block &&\n\t\t\t\t\t\ttask.scheduled(hour, min) {\n\n\t\t\t\t\t\ttask.run(now)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurSecBlocks[block] = secBlock\n\t\t}\n\n\t\tif curHour == hour && curMin == min {\n\t\t\tcontinue\n\t\t}\n\t\tcurHour = hour\n\t\tcurMin = min\n\n\t\tfor _, task := range registry {\n\t\t\tif task.Seconds == 0 && task.scheduled(hour, min) {\n\t\t\t\ttask.run(now)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc register(task *Task) {\n\tregistry = append(registry, task)\n}\n\nfunc Init() (err error) {\n\tfor _, task := range registry {\n\t\tif task.Version == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\terr = version.Set(database.GetDatabase(), task.Name, task.Version)\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"task\":    task.Name,\n\t\t\t\t\"version\": task.Version,\n\t\t\t\t\"error\":   err,\n\t\t\t}).Error(\"task: Failed to set task version\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tgo runScheduler()\n\n\treturn\n}\n\nfunc GetBlock(n time.Time, d time.Duration) int {\n\ts := int(d.Seconds())\n\treturn (n.Second() / s) * s\n}\n"
  },
  {
    "path": "telemetry/constants.go",
    "content": "package telemetry\n\nconst (\n\tModerate  = \"moderate\"\n\tImportant = \"important\"\n\tCritical  = \"critical\"\n)\n"
  },
  {
    "path": "telemetry/telemetry.go",
    "content": "package telemetry\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tregistry []handler\n)\n\ntype handler interface {\n\tgetName() string\n\tRefresh() error\n}\n\ntype Telemetry[Data any] struct {\n\tname         string\n\tDefault      Data\n\tlock         sync.Mutex\n\tlastTransmit time.Time\n\tTransmitRate time.Duration\n\tlastRefresh  time.Time\n\tRefreshRate  time.Duration\n\tRefresher    func() (Data, error)\n\tValidate     func(Data) Data\n\tdata         Data\n}\n\nfunc (r *Telemetry[Data]) getName() string {\n\treturn r.name\n}\n\nfunc (r *Telemetry[Data]) Refresh() (err error) {\n\tr.lock.Lock()\n\tlastRefresh := r.lastRefresh\n\tr.lock.Unlock()\n\tif time.Since(lastRefresh) < r.RefreshRate {\n\t\treturn\n\t}\n\n\tvar data Data\n\tfunc() {\n\t\tdefer utils.RecoverLog(\"telemetry: Panic in refresh\")\n\n\t\tdata, err = r.Refresher()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\tr.Set(data)\n\n\treturn\n}\n\nfunc (r *Telemetry[Data]) Set(data Data) {\n\tr.lock.Lock()\n\tr.data = data\n\tr.lastRefresh = time.Now()\n\tr.lock.Unlock()\n}\n\nfunc (r *Telemetry[Data]) Get() (Data, bool) {\n\tr.lock.Lock()\n\tlastRefresh := r.lastRefresh\n\tlastTransmit := r.lastTransmit\n\tr.lock.Unlock()\n\tif lastRefresh.IsZero() || time.Since(lastTransmit) < r.TransmitRate {\n\t\tvar x Data\n\t\treturn x, false\n\t}\n\tr.lock.Lock()\n\tr.lastTransmit = time.Now()\n\tr.lock.Unlock()\n\n\tif r.Validate != nil {\n\t\treturn r.Validate(r.data), true\n\t} else {\n\t\treturn r.data, true\n\t}\n}\n\nfunc Register[Data any](telm *Telemetry[Data]) {\n\ttelm.name = fmt.Sprintf(\"%T\", telm)\n\tregistry = append(registry, telm)\n}\n\nfunc Refresh() {\n\tfor _, telm := range registry {\n\t\terr := telm.Refresh()\n\t\tif err != nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"kind\": telm.getName(),\n\t\t\t}).Error(\"telemetry: Telemetry refresh failed\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "telemetry/updates.go",
    "content": "package telemetry\n\nimport (\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pritunl/pritunl-cloud/advisory\"\n\t\"github.com/pritunl/tools/commander\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tcveReg = regexp.MustCompile(`CVE-\\d{4}-\\d+`)\n)\n\nvar Updates = &Telemetry[[]*Update]{\n\tTransmitRate: 6 * time.Minute,\n\tRefreshRate:  6 * time.Hour,\n\tRefresher:    UpdatesRefresh,\n\tValidate: func(data []*Update) []*Update {\n\t\tif len(data) > 50 {\n\t\t\treturn data[:50]\n\t\t}\n\t\treturn data\n\t},\n}\n\ntype Update struct {\n\tAdvisory    string               `bson:\"advisory\" json:\"advisory\"`\n\tCves        []string             `bson:\"cves\" json:\"cves\"`\n\tSeverity    string               `bson:\"severity\" json:\"severity\"`\n\tDescription string               `bson:\"description\" json:\"description\"`\n\tPackages    []string             `bson:\"packages\" json:\"packages\"`\n\tDetails     []*advisory.Advisory `bson:\"details\" json:\"details\"`\n}\n\nfunc parseRecord(lines []string) (update *Update) {\n\tupdt := &Update{}\n\tdescLines := []string{}\n\tcurrentField := \"\"\n\n\tfor _, line := range lines {\n\t\tcolonIdx := strings.Index(line, \":\")\n\t\tif colonIdx < 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tprefix := line[:colonIdx]\n\t\tvalue := strings.TrimSpace(line[colonIdx+1:])\n\n\t\tif strings.TrimSpace(prefix) == \"\" {\n\t\t\tswitch currentField {\n\t\t\tcase \"Description\":\n\t\t\t\tdescLines = append(descLines, value)\n\t\t\tcase \"CVEs\":\n\t\t\t\tif value != \"\" {\n\t\t\t\t\tupdt.Cves = append(updt.Cves,\n\t\t\t\t\t\tcveReg.FindAllString(value, -1)...)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tfield := strings.TrimSpace(prefix)\n\t\tcurrentField = field\n\n\t\tswitch field {\n\t\tcase \"Update ID\", \"Name\":\n\t\t\tupdt.Advisory = value\n\t\tcase \"Severity\":\n\t\t\tupdt.Severity = parseSeverity(value)\n\t\tcase \"Description\":\n\t\t\tif value != \"\" {\n\t\t\t\tdescLines = append(descLines, value)\n\t\t\t}\n\t\tcase \"CVEs\":\n\t\t\tif value != \"\" {\n\t\t\t\tupdt.Cves = append(updt.Cves,\n\t\t\t\t\tcveReg.FindAllString(value, -1)...)\n\t\t\t}\n\t\t}\n\t}\n\n\tif !matchAdvisory(updt.Advisory) {\n\t\treturn\n\t}\n\tif updt.Severity == \"\" {\n\t\treturn\n\t}\n\n\tfor len(descLines) > 0 && descLines[len(descLines)-1] == \"\" {\n\t\tdescLines = descLines[:len(descLines)-1]\n\t}\n\tupdt.Description = strings.Join(descLines, \"\\n\")\n\n\tfullText := strings.Join(lines, \"\\n\")\n\tcveSet := map[string]bool{}\n\tdeduped := []string{}\n\tfor _, c := range updt.Cves {\n\t\tif !cveSet[c] {\n\t\t\tcveSet[c] = true\n\t\t\tdeduped = append(deduped, c)\n\t\t}\n\t}\n\tfor _, c := range cveReg.FindAllString(fullText, -1) {\n\t\tif !cveSet[c] {\n\t\t\tcveSet[c] = true\n\t\t\tdeduped = append(deduped, c)\n\t\t}\n\t}\n\tsort.Strings(deduped)\n\tupdt.Cves = deduped\n\n\tupdate = updt\n\treturn\n}\n\nfunc updatesList() (advisories map[string][]string, err error) {\n\tif !IsDnf() {\n\t\treturn\n\t}\n\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName: \"dnf\",\n\t\tArgs: []string{\n\t\t\t\"updateinfo\",\n\t\t\t\"list\",\n\t\t},\n\t\tTimeout: 90 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tif resp != nil {\n\t\t\tlogrus.WithFields(\n\t\t\t\tresp.Map(),\n\t\t\t).Error(\"telemetry: Failed to get dnf security update list\")\n\t\t}\n\t\treturn\n\t}\n\n\tadvisories = map[string][]string{}\n\tseen := map[string]map[string]bool{}\n\n\tfor _, line := range strings.Split(string(resp.Output), \"\\n\") {\n\t\tline = strings.TrimSpace(line)\n\t\tif line == \"\" || strings.HasPrefix(line, \"Last metadata\") ||\n\t\t\tstrings.HasPrefix(line, \"Updating\") ||\n\t\t\tstrings.HasPrefix(line, \"Repositories\") {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tparts := strings.Fields(line)\n\t\tif len(parts) < 3 {\n\t\t\tcontinue\n\t\t}\n\n\t\tadv := parts[0]\n\t\tif !matchAdvisory(adv) {\n\t\t\tcontinue\n\t\t}\n\n\t\tpkg := \"\"\n\t\tpart1 := strings.ToLower(parts[1])\n\t\tpart2 := strings.ToLower(parts[2])\n\n\t\tif strings.Contains(part1, Moderate) ||\n\t\t\tstrings.Contains(part1, Important) ||\n\t\t\tstrings.Contains(part1, Critical) {\n\n\t\t\tpkg = parts[2]\n\t\t} else if len(parts) >= 4 && (strings.Contains(part2, Moderate) ||\n\t\t\tstrings.Contains(part2, Important) ||\n\t\t\tstrings.Contains(part2, Critical)) {\n\n\t\t\tpkg = parts[3]\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\n\t\tif pkg == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tpkgSet, ok := seen[adv]\n\t\tif !ok {\n\t\t\tpkgSet = map[string]bool{}\n\t\t\tseen[adv] = pkgSet\n\t\t}\n\t\tif !pkgSet[pkg] {\n\t\t\tpkgSet[pkg] = true\n\t\t\tadvisories[adv] = append(advisories[adv], pkg)\n\t\t}\n\t}\n\n\tfor adv := range advisories {\n\t\tsort.Strings(advisories[adv])\n\t}\n\n\treturn\n}\n\nfunc UpdatesRefresh() (updates []*Update, err error) {\n\tif !IsDnf() {\n\t\treturn\n\t}\n\n\tpkgMap, err := updatesList()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName: \"dnf\",\n\t\tArgs: []string{\n\t\t\t\"updateinfo\",\n\t\t\t\"info\",\n\t\t},\n\t\tTimeout: 120 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tif resp != nil {\n\t\t\tlogrus.WithFields(\n\t\t\t\tresp.Map(),\n\t\t\t).Error(\"telemetry: Failed to get dnf updateinfo report\")\n\t\t}\n\t\treturn\n\t}\n\n\tupdates = []*Update{}\n\tseen := map[string]bool{}\n\tvar current []string\n\n\tflush := func() {\n\t\tif len(current) == 0 {\n\t\t\treturn\n\t\t}\n\t\trecord := current\n\t\tcurrent = nil\n\n\t\tupd := parseRecord(record)\n\t\tif upd == nil {\n\t\t\treturn\n\t\t}\n\t\tif seen[upd.Advisory] {\n\t\t\treturn\n\t\t}\n\t\tpkgs, ok := pkgMap[upd.Advisory]\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tseen[upd.Advisory] = true\n\t\tupd.Packages = pkgs\n\t\tupdates = append(updates, upd)\n\t}\n\n\tfor _, line := range strings.Split(string(resp.Output), \"\\n\") {\n\t\tif isSeparatorLine(line) {\n\t\t\tflush()\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(\n\t\t\tstrings.ReplaceAll(line, \" \", \"\"), \"Name:\") {\n\n\t\t\tflush()\n\t\t}\n\t\tcurrent = append(current, line)\n\t}\n\tflush()\n\n\treturn\n}\n\nfunc init() {\n\tRegister(Updates)\n}\n"
  },
  {
    "path": "telemetry/utils.go",
    "content": "package telemetry\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pritunl/tools/commander\"\n)\n\nvar (\n\thasSevs = 0\n)\n\nfunc IsDnf() bool {\n\t_, err := os.Stat(\"/usr/bin/dnf\")\n\treturn err == nil\n}\n\nfunc HasSevs() bool {\n\tif hasSevs == 1 {\n\t\treturn false\n\t} else if hasSevs == 2 {\n\t\treturn true\n\t}\n\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName: \"dnf\",\n\t\tArgs: []string{\n\t\t\t\"updateinfo\",\n\t\t\t\"list\",\n\t\t\t\"--help\",\n\t\t},\n\t\tTimeout: 8 * time.Second,\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\thasSevs = 1\n\t\treturn false\n\t}\n\n\tif bytes.Contains(resp.Output, []byte(\"--advisory-severities\")) {\n\t\thasSevs = 2\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc matchAdvisory(id string) bool {\n\treturn strings.HasPrefix(id, \"RHSA-\") ||\n\t\tstrings.HasPrefix(id, \"ALSA-\") ||\n\t\tstrings.HasPrefix(id, \"RLSA-\") ||\n\t\tstrings.HasPrefix(id, \"ELSA-\") ||\n\t\tstrings.HasPrefix(id, \"FEDORA-\")\n}\n\nfunc parseSeverity(value string) string {\n\tswitch strings.ToLower(strings.TrimSpace(value)) {\n\tcase \"critical\":\n\t\treturn Critical\n\tcase \"important\":\n\t\treturn Important\n\tcase \"moderate\":\n\t\treturn Moderate\n\t}\n\treturn \"\"\n}\n\nfunc isSeparatorLine(line string) bool {\n\ttrimmed := strings.TrimSpace(line)\n\tif len(trimmed) == 0 {\n\t\treturn false\n\t}\n\tfor _, r := range trimmed {\n\t\tif r != '=' {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "tools/autoindex.py",
    "content": "import os\nimport sys\nfrom datetime import datetime, timezone\n\nROOT_DIR = \"/mnt/images\"\nPREFIX = \"\"\nif len(sys.argv) > 1:\n    PREFIX = sys.argv[1] + \"/\"\nHTML_HEADER = \"\"\"<html><head><title>Index of /{relative_path}</title></head>\n<body bgcolor=\"white\">\n<h1>Index of /{relative_path}</h1><hr><pre><a href=\"../\">../</a>\n\"\"\"\nHTML_FOOTER = \"\"\"</pre><hr>\n</body></html>\n\"\"\"\n\ndef limit_filename_length(filename, max_length=50):\n    if len(filename) > max_length:\n        return filename[:max_length - 3] + '..>'\n    return filename\n\ndef generate_directory_listing_html(current_dir, relative_path, dirs, files):\n    html = HTML_HEADER.format(relative_path=PREFIX + relative_path)\n\n    for directory in sorted(dirs):\n        dir_path = os.path.join(current_dir, directory)\n        stat_info = os.stat(dir_path)\n        modified_time = datetime.fromtimestamp(stat_info.st_mtime,\n            tz=timezone.utc).strftime(\"%d-%b-%Y %H:%M\")\n        label = limit_filename_length(directory + '/')\n        html += f\"<a href=\\\"{directory}/\\\">{label}</a>\" + \\\n            f\"{' ' * (51 - len(label))}{modified_time}{' ' * 20}-\\n\"\n\n    for file_name in sorted(files):\n        if file_name == \"index.html\":\n            continue\n        file_path = os.path.join(current_dir, file_name)\n        stat_info = os.stat(file_path)\n        modified_time = datetime.fromtimestamp(stat_info.st_mtime,\n            tz=timezone.utc).strftime(\"%d-%b-%Y %H:%M\")\n        file_size = f\"{stat_info.st_size:21}\"\n        label = limit_filename_length(file_name)\n        html += f\"<a href=\\\"{file_name}\\\">{label}</a>\" + \\\n            f\"{' ' * (51 - len(label))}{modified_time}{file_size}\\n\"\n\n    html += HTML_FOOTER\n\n    return html\n\ndef generate_index_files(root_dir):\n    for current_dir, subdirs, files in os.walk(root_dir):\n        relative_path = os.path.relpath(current_dir, root_dir)\n        if relative_path == \".\":\n            relative_path = \"\"\n        else:\n            relative_path += \"/\"\n\n        html_content = generate_directory_listing_html(current_dir,\n            relative_path, sorted(subdirs), sorted(files))\n        index_path = os.path.join(current_dir, \"index.html\")\n\n        with open(index_path, \"w\") as f:\n            f.write(html_content)\n\n        print(f\"Generated index.html for: {current_dir}\")\n\ngenerate_index_files(ROOT_DIR)\n"
  },
  {
    "path": "tools/build_run.sh",
    "content": "#!/bin/bash\nset -e\n\nNO_AGENT=false\nSTABLE=false\nARGS=()\n\nwhile [[ $# -gt 0 ]]; do\n    case $1 in\n        --no-agent)\n            NO_AGENT=true\n            shift\n            ;;\n        --stable)\n            STABLE=true\n            shift\n            ;;\n        *)\n            ARGS+=(\"$1\")\n            shift\n            ;;\n    esac\ndone\n\nbuild_pritunl_agent() {\n    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-extldflags \"-static\"' -v -o agent-static\n    sudo cp -f ./agent-static /usr/bin/pritunl-cloud-agent\n    rm -f ./agent-static\n\n    CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -v -o agent-bsd\n    sudo cp -f ./agent-bsd /usr/bin/pritunl-cloud-agent-bsd\n    rm -f ./agent-bsd\n}\n\nif [ \"$NO_AGENT\" = false ]; then\n    cd agent\n    output=$(go install -v 2>&1 | tee /dev/tty) || exit 1\n    if [ -n \"$output\" ]; then\n        build_pritunl_agent\n    fi\n    cd ..\nfi\n\ncd redirect\ngo install -v\nsudo cp -f ~/go/bin/redirect /usr/bin/pritunl-cloud-redirect\ncd ..\n\ngo install -v\nsudo cp -f ~/go/bin/pritunl-cloud /usr/bin/pritunl-cloud\n\nif [ \"$STABLE\" = true ]; then\n    sudo /usr/bin/pritunl-cloud start --fast-exit\nelif [ ${#ARGS[@]} -eq 0 ]; then\n    sudo /usr/bin/pritunl-cloud start --debug\nelse\n    sudo /usr/bin/pritunl-cloud \"${ARGS[@]}\"\nfi\n"
  },
  {
    "path": "tools/builder.py",
    "content": "import optparse\nimport datetime\nimport re\nimport sys\nimport subprocess\nimport time\nimport math\nimport json\nimport requests\nimport os\nimport getpass\nimport base64\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives.ciphers import (\n    Cipher, algorithms, modes\n)\nfrom cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC\n\nUSAGE = \"\"\"Usage: builder [command] [options]\nCommand Help: builder [command] --help\n\nCommands:\n  version               Print the version and exit\n  set-version           Set current version\n  build                 Build and release\"\"\"\n\nCONSTANTS_PATH = 'constants/constants.go'\nCHANGES_PATH = 'CHANGES'\nSTABLE_PACUR_PATH = '../pritunl-pacur'\nTEST_PACUR_PATH = '../pritunl-pacur-test'\nBUILD_KEYS_PATH = os.path.expanduser('~/data/build/pritunl_build.json')\nBUILD_TARGETS = ('pritunl-cloud',)\nREPO_NAME = 'pritunl-cloud'\n\ncur_date = datetime.datetime.utcnow()\npacur_path = None\n\ndef wget(url, cwd=None, output=None):\n    if output:\n        args = ['wget', '-O', output, url]\n    else:\n        args = ['wget', url]\n    subprocess.check_call(args, cwd=cwd)\n\ndef post_git_asset(release_id, file_name, file_path):\n    file_size = os.path.getsize(file_path)\n    response = requests.post(\n        'https://uploads.github.com/repos/%s/%s/releases/%s/assets' % (\n            github_owner, REPO_NAME, release_id),\n        headers={\n            'Authorization': 'token %s' % github_token,\n            'Content-Type': 'application/octet-stream',\n            'Content-Size': str(file_size),\n        },\n        params={\n            'name': file_name,\n        },\n        data=open(file_path, 'rb').read(),\n    )\n\n    if response.status_code != 201:\n        print('Failed to create asset on github')\n        print(response.json())\n        sys.exit(1)\n\ndef get_ver(version):\n    day_num = (cur_date - datetime.datetime(2015, 11, 24)).days\n    min_num = int(math.floor(((cur_date.hour * 60) + cur_date.minute) / 14.4))\n    ver = re.findall(r'\\d+', version)\n    ver_str = '.'.join((ver[0], ver[1], str(day_num), str(min_num)))\n    ver_str += ''.join(re.findall('[a-z]+', version))\n\n    return ver_str\n\ndef get_int_ver(version):\n    ver = re.findall(r'\\d+', version)\n\n    if 'snapshot' in version:\n        pass\n    elif 'alpha' in version:\n        ver[-1] = str(int(ver[-1]) + 1000)\n    elif 'beta' in version:\n        ver[-1] = str(int(ver[-1]) + 2000)\n    elif 'rc' in version:\n        ver[-1] = str(int(ver[-1]) + 3000)\n    else:\n        ver[-1] = str(int(ver[-1]) + 4000)\n\n    return int(''.join([x.zfill(4) for x in ver]))\n\ndef iter_packages():\n    for target in BUILD_TARGETS:\n        target_path = os.path.join(pacur_path, target)\n        for name in os.listdir(target_path):\n            if cur_version not in name:\n                continue\n            elif name.endswith(\".pkg.tar.zst\"):\n                pass\n            elif name.endswith(\".rpm\"):\n                pass\n            elif name.endswith(\".deb\"):\n                pass\n            else:\n                continue\n\n            path = os.path.join(target_path, name)\n\n            yield name, path\n\n# Parse args\nif len(sys.argv) > 1:\n    cmd = sys.argv[1]\nelse:\n    cmd = 'version'\n\ndef aes_encrypt(passphrase, data):\n    enc_salt = os.urandom(32)\n    enc_iv = os.urandom(12)\n\n    kdf = PBKDF2HMAC(\n        algorithm=hashes.SHA512(),\n        length=32,\n        salt=enc_salt,\n        iterations=10000000,\n        backend=default_backend(),\n    )\n    enc_key = kdf.derive(passphrase.encode())\n\n    cipher = Cipher(\n        algorithms.AES(enc_key),\n        modes.GCM(enc_iv),\n        backend=default_backend()\n    ).encryptor()\n\n    enc_data = cipher.update(data.encode('utf-8')) + cipher.finalize()\n    auth_tag = cipher.tag\n\n    return '\\n'.join([\n        base64.b64encode(enc_salt).decode('utf-8'),\n        base64.b64encode(enc_iv).decode('utf-8'),\n        base64.b64encode(enc_data).decode('utf-8'),\n        base64.b64encode(auth_tag).decode('utf-8'),\n    ])\n\ndef aes_decrypt(passphrase, data):\n    data = data.split('\\n')\n    if len(data) < 4:\n        raise ValueError('Invalid encryption data')\n\n    enc_salt = base64.b64decode(data[0])\n    enc_iv = base64.b64decode(data[1])\n    enc_data = base64.b64decode(data[2])\n    auth_tag = base64.b64decode(data[3])\n\n    kdf = PBKDF2HMAC(\n        algorithm=hashes.SHA512(),\n        length=32,\n        salt=enc_salt,\n        iterations=10000000,\n        backend=default_backend(),\n    )\n    enc_key = kdf.derive(passphrase.encode())\n\n    cipher = Cipher(\n        algorithms.AES(enc_key),\n        modes.GCM(enc_iv, auth_tag),\n        backend=default_backend()\n    ).decryptor()\n\n    decrypted_data = cipher.update(enc_data) + cipher.finalize()\n\n    return decrypted_data.decode('utf-8')\n\npassphrase = getpass.getpass('Enter passphrase: ')\n\nif cmd == 'encrypt':\n    passphrase2 = getpass.getpass('Enter passphrase: ')\n\n    if passphrase != passphrase2:\n        print('ERROR: Passphrase mismatch')\n        sys.exit(1)\n\n    with open(BUILD_KEYS_PATH, 'r') as build_keys_file:\n        data = build_keys_file.read().strip()\n\n    enc_data = aes_encrypt(passphrase, data)\n\n    with open(BUILD_KEYS_PATH, 'w') as build_keys_file:\n        build_keys_file.write(enc_data)\n\n    sys.exit(0)\n\nif cmd == 'decrypt':\n    with open(BUILD_KEYS_PATH, 'r') as build_keys_file:\n        enc_data = build_keys_file.read().strip()\n\n    data = aes_decrypt(passphrase, enc_data)\n\n    with open(BUILD_KEYS_PATH, 'w') as build_keys_file:\n        build_keys_file.write(data)\n\n    sys.exit(0)\n\n# Load build keys\nwith open(BUILD_KEYS_PATH, 'r') as build_keys_file:\n    enc_data = build_keys_file.read()\n    data = aes_decrypt(passphrase, enc_data)\n    build_keys = json.loads(data.strip())\n    github_owner = build_keys['github_owner']\n    github_token = build_keys['github_token']\n    gitlab_token = build_keys['gitlab_token']\n    gitlab_host = build_keys['gitlab_host']\n\n\n# Get package info\nwith open(CONSTANTS_PATH, 'r') as constants_file:\n    cur_version = re.findall('= \"(.*?)\"', constants_file.read())[0]\n\n\nparser = optparse.OptionParser(usage=USAGE)\n(options, args) = parser.parse_args()\n\nbuild_num = 0\n\n\n# Run cmd\nif cmd == 'version':\n    print('%s v%s' % (REPO_NAME, cur_version))\n    sys.exit(0)\n\n\nif cmd == 'sync-releases':\n    next_url = 'https://api.github.com/repos/%s/%s/releases' % (\n        github_owner, REPO_NAME)\n\n    while True:\n        # Get github release\n        response = requests.get(\n            next_url,\n            headers={\n                'Authorization': 'token %s' % github_token,\n                'Content-type': 'application/json',\n            },\n        )\n\n        if response.status_code != 200:\n            print('Failed to get repo releases on github')\n            print(response.json())\n            sys.exit(1)\n\n        for release in response.json():\n            print(release['tag_name'])\n\n            # Create gitlab release\n            resp = requests.post(\n                ('https://%s/api/v4/projects' +\n                 '/%s%%2F%s/repository/tags/%s/release') % (\n                    gitlab_host, github_owner, REPO_NAME,\n                    release['tag_name']),\n                headers={\n                    'Private-Token': gitlab_token,\n                    'Content-type': 'application/json',\n                },\n                data=json.dumps({\n                    'tag_name': release['tag_name'],\n                    'description': release['body'],\n                }),\n            )\n\n            if resp.status_code not in (201, 409):\n                print('Failed to create releases on gitlab')\n                print(resp.json())\n                sys.exit(1)\n\n        if 'Link' not in response.headers or \\\n                'rel=\"next\"' not in response.headers['Link']:\n            break\n        next_url = response.headers['Link'].split(';')[0][1:-1]\n\nif cmd == 'get-version':\n    new_version_orig = args[1]\n    new_version = get_ver(new_version_orig)\n    print(new_version)\n\nif cmd == 'set-version':\n    new_version_orig = args[1]\n    new_version = get_ver(new_version_orig)\n    is_snapshot = 'snapshot' in new_version\n    pacur_path = TEST_PACUR_PATH if is_snapshot else STABLE_PACUR_PATH\n\n\n    # Update changes\n    if not is_snapshot:\n        with open(CHANGES_PATH, 'r') as changes_file:\n            changes_data = changes_file.read()\n\n        with open(CHANGES_PATH, 'w') as changes_file:\n            ver_date_str = 'Version ' + new_version.replace(\n                'v', '') + cur_date.strftime(' %Y-%m-%d')\n            changes_file.write(changes_data.replace(\n                '<%= version %>',\n                '%s\\n%s' % (ver_date_str, '-' * len(ver_date_str)),\n            ))\n\n\n    # Check for duplicate version\n    response = requests.get(\n        'https://api.github.com/repos/%s/%s/releases' % (\n            github_owner, REPO_NAME),\n        headers={\n            'Authorization': 'token %s' % github_token,\n            'Content-type': 'application/json',\n        },\n    )\n\n    if response.status_code != 200:\n        print('Failed to get repo releases on github')\n        print(response.json())\n        sys.exit(1)\n\n    for release in response.json():\n        if release['tag_name'] == new_version:\n            print('Version already exists in github')\n            sys.exit(1)\n\n\n    # Generate changelog\n    version = None\n    release_body = ''\n    if not is_snapshot:\n        with open(CHANGES_PATH, 'r') as changelog_file:\n            for line in changelog_file.readlines()[2:]:\n                line = line.strip()\n\n                if not line or line[0] == '-':\n                    continue\n\n                if line[:7] == 'Version':\n                    if version:\n                        break\n                    version = line.split(' ')[1]\n                elif version:\n                    release_body += '* %s\\n' % line\n\n    if not is_snapshot and version != new_version:\n        print('New version does not exist in changes')\n        sys.exit(1)\n\n    if is_snapshot:\n        release_body = '* Snapshot release'\n    elif not release_body:\n        print('Failed to generate github release body')\n        sys.exit(1)\n    release_body = release_body.rstrip('\\n')\n\n\n    # Update constants\n    with open(CONSTANTS_PATH, 'r') as constants_file:\n        constants_data = constants_file.read()\n\n    with open(CONSTANTS_PATH, 'w') as constants_file:\n        constants_file.write(re.sub(\n            '(= \".*?\")',\n            '= \"%s\"' % new_version,\n            constants_data,\n            count=1,\n            ))\n\n    # Git commit\n    subprocess.check_call(['git', 'reset', 'HEAD', '.'])\n    subprocess.check_call(['git', 'add', CHANGES_PATH])\n    subprocess.check_call(['git', 'add', CONSTANTS_PATH])\n    subprocess.check_call(['git', 'commit', '-S', '-m', 'Create new release'])\n    subprocess.check_call(['git', 'push'])\n\n    # Create branch\n    if not is_snapshot:\n        subprocess.check_call(['git', 'branch', new_version])\n        subprocess.check_call(['git', 'push', '-u', 'origin', new_version])\n    time.sleep(6)\n\n    # Create tag\n    subprocess.check_call(['git', 'tag', new_version])\n    subprocess.check_call(['git', 'push', '--tags'])\n    time.sleep(1)\n\n\n    # Create release\n    response = requests.post(\n        'https://api.github.com/repos/%s/%s/releases' % (\n            github_owner, REPO_NAME),\n        headers={\n            'Authorization': 'token %s' % github_token,\n            'Content-type': 'application/json',\n        },\n        data=json.dumps({\n            'tag_name': new_version,\n            'name': '%s v%s' % (REPO_NAME, new_version),\n            'body': release_body,\n            'prerelease': is_snapshot,\n            'target_commitish': 'master' if is_snapshot else new_version,\n        }),\n    )\n\n    if response.status_code != 201:\n        print('Failed to create release on github')\n        print(response.json())\n        sys.exit(1)\n\n    subprocess.check_call(['git', 'pull'])\n    subprocess.check_call(['git', 'push', '--tags'])\n    time.sleep(6)\n\n\n    # Create gitlab release\n    response = requests.post(\n        ('https://%s/api/v4/projects' +\n         '/%s%%2F%s/releases') % (\n            gitlab_host, github_owner, REPO_NAME),\n        headers={\n            'Private-Token': gitlab_token,\n            'Content-type': 'application/json',\n        },\n        data=json.dumps({\n            'tag_name': new_version,\n            'name': '%s v%s' % (REPO_NAME, new_version),\n            'description': release_body,\n        }),\n    )\n\n    if response.status_code != 201:\n        print('Failed to create release on gitlab')\n        print(response.json())\n        sys.exit(1)\n\n\nif cmd == 'build' or cmd == 'build-upload':\n    is_snapshot = 'snapshot' in cur_version\n    pacur_path = TEST_PACUR_PATH if is_snapshot else STABLE_PACUR_PATH\n\n\n    # Get sha256 sum\n    archive_name = '%s.tar.gz' % cur_version\n    archive_path = os.path.join(os.path.sep, 'tmp', archive_name)\n    if os.path.isfile(archive_path):\n        os.remove(archive_path)\n    wget('https://github.com/%s/%s/archive/refs/tags/%s' % (\n        github_owner, REPO_NAME, archive_name),\n        output=archive_name,\n        cwd=os.path.join(os.path.sep, 'tmp'),\n    )\n    archive_sha256_sum = subprocess.check_output(\n        ['sha256sum', archive_path]).split()[0]\n    os.remove(archive_path)\n\n\n    # Update sha256 sum and pkgver in PKGBUILD\n    for target in BUILD_TARGETS:\n        pkgbuild_path = os.path.join(pacur_path, target, 'PKGBUILD')\n\n        with open(pkgbuild_path, 'r') as pkgbuild_file:\n            pkgbuild_data = re.sub(\n                'pkgver=\"(.*)\"',\n                'pkgver=\"%s\"' % cur_version,\n                pkgbuild_file.read(),\n                count=1,\n            )\n            pkgbuild_data = re.sub(\n                '\"[a-f0-9]{64}\"',\n                '\"%s\"' % archive_sha256_sum.decode('utf-8'),\n                pkgbuild_data,\n                count=1,\n            )\n\n        with open(pkgbuild_path, 'w') as pkgbuild_file:\n            pkgbuild_file.write(pkgbuild_data)\n\n\n    # Run pacur project build\n    for build_target in BUILD_TARGETS:\n        subprocess.check_call(\n            ['sudo', 'pacur', 'project', 'build', build_target],\n            cwd=pacur_path,\n        )\n\n\nif cmd == 'upload' or cmd == 'build-upload':\n    is_snapshot = 'snapshot' in cur_version\n    pacur_path = TEST_PACUR_PATH if is_snapshot else STABLE_PACUR_PATH\n\n\n    # Get release id\n    release_id = None\n    response = requests.get(\n        'https://api.github.com/repos/%s/%s/releases' % (\n            github_owner, REPO_NAME),\n        headers={\n            'Authorization': 'token %s' % github_token,\n            'Content-type': 'application/json',\n        },\n    )\n\n    for release in response.json():\n        if release['tag_name'] == cur_version:\n            release_id = release['id']\n\n    if not release_id:\n        print('Version does not exists in github')\n        sys.exit(1)\n\n\n    # Run pacur project build\n    subprocess.check_call(\n        ['sudo', 'pacur', 'project', 'repo'],\n        cwd=pacur_path,\n    )\n\n\n    # Add to github\n    for name, path in iter_packages():\n        post_git_asset(release_id, name, path)\n\n\n    # Sync mirror\n    subprocess.check_call([\n        'sh',\n        'upload-unstable.sh',\n    ], cwd=pacur_path)\n\n\nif cmd == 'upload-github':\n    is_snapshot = 'snapshot' in cur_version\n\n\n    # Get release id\n    release_id = None\n    response = requests.get(\n        'https://api.github.com/repos/%s/%s/releases' % (\n            github_owner, REPO_NAME),\n        headers={\n            'Authorization': 'token %s' % github_token,\n            'Content-type': 'application/json',\n        },\n    )\n\n    for release in response.json():\n        if release['tag_name'] == cur_version:\n            release_id = release['id']\n\n    if not release_id:\n        print('Version does not exists in github')\n        sys.exit(1)\n\n\n    # Add to github\n    for name, path in iter_packages():\n        post_git_asset(release_id, name, path)\n"
  },
  {
    "path": "tools/generate_demo_data.py",
    "content": "#!/usr/bin/env python3\nimport random\nimport string\nimport hashlib\n\nORGANIZATION_ID = \"5a3245a50accad1a8a53bc82\"\nDATACENTER_ID = \"689733b7a7a35eae0dbaea1b\"\nZONE_ID = \"689733b7a7a35eae0dbaea1e\"\nVPC_ID = \"689733b7a7a35eae0dbaea23\"\n\nNODE_IDS = [\n    \"689733b2a7a35eae0dbaea0a\",\n    \"689733b2a7a35eae0dbaea0b\",\n    \"689733b2a7a35eae0dbaea0c\",\n    \"689733b2a7a35eae0dbaea0d\",\n    \"689733b2a7a35eae0dbaea0e\",\n    \"689733b2a7a35eae0dbaea0f\",\n]\nNODE_NAMES = [\n    \"pritunl-east0\",\n    \"pritunl-east1\",\n    \"pritunl-east2\",\n    \"pritunl-east3\",\n    \"pritunl-east4\",\n    \"pritunl-east5\",\n]\n\nSHAPE_IDS = {\n    \"small\": \"65e6e303ceeebbb3dabaec96\",\n    \"medium\": \"65e6e2ecceeebbb3dabaec79\",\n    \"large\": \"66f63282aac06d53e8c9c435\",\n}\n\nIMAGE_IDS = [\n    \"650a2c36aed15f1f1f5e96e1\",\n    \"650a2c36aed15f1f1f5e96e2\",\n]\n\nPOD_ID = \"688bf358d978631566998ffc\"\nUNIT_IDS = {\n    \"web\": \"688c716d9da165ffad4b3682\",\n    \"database\": \"68b67d1aee12c08a1f39f88b\",\n}\nSPEC_IDS = {\n    \"web\": \"688c7cde9da165ffad4b52e4\",\n    \"database\": \"688c7cde9da165ffad4b34f2\",\n}\n\ndef generate_ip(subnet_base=\"10.196\"):\n    third_octet = random.randint(1, 8)\n    fourth_octet = random.randint(2, 254)\n    return f\"{subnet_base}.{third_octet}.{fourth_octet}\"\n\ndef generate_pub_ip(subnet_base=\"1.253.67\"):\n    fourth_octet = random.randint(2, 254)\n    return f\"{subnet_base}.{fourth_octet}\"\n\ndef generate_priv_ip6(id):\n    hash_obj = hashlib.sha256(str(id).encode())\n    hash_hex = hash_obj.hexdigest()\n    return f\"fd97:30bf:d456:a3bc:{hash_hex[0:4]}:{hash_hex[4:8]}:{hash_hex[8:12]}:{hash_hex[12:16]}\"\n\ndef generate_pub_ip6(id):\n    hash_obj = hashlib.sha256(str(id).encode() + str(id).encode())\n    hash_hex = hash_obj.hexdigest()\n    return f\"2001:db8:85a3:4d2f:{hash_hex[0:4]}:{hash_hex[4:8]}:{hash_hex[8:12]}:{hash_hex[12:16]}\"\n\ndef generate_network_namespace():\n    characters = string.ascii_lowercase + string.digits\n    return ''.join(random.choice(characters) for _ in range(14))\n\ndef get_instance_spec(instance_type):\n    specs = {\n        \"web\": {\"name\": \"web-app\", \"shape\": \"small\", \"memory\": 2048, \"processors\": 2, \"disk\": 20},\n        \"database\": {\"name\": \"database\", \"shape\": \"large\", \"memory\": 8192, \"processors\": 4, \"disk\": 100},\n        \"search\": {\"name\": \"search\", \"shape\": \"large\", \"memory\": 8192, \"processors\": 4, \"disk\": 200},\n        \"vpn\": {\"name\": \"vpn\", \"shape\": \"small\", \"memory\": 2048, \"processors\": 2, \"disk\": 20},\n    }\n    return specs.get(instance_type)\n\ndef generate_instances(count=20):\n    instances = []\n    used_priv_ips = set()\n    used_host_ips = set()\n    used_pub_ips = set()\n\n    instance_types = (\n        [\"web\"] * 10 +\n        [\"database\"] * 6 +\n        [\"search\"] * 2 +\n        [\"vpn\"] * 2\n    )[:count]\n\n    for i in range(count):\n        instance_id = f\"651d8e7c4cf9e2e3e4d56a{i:02x}\"\n\n        priv_ip = generate_ip()\n        while priv_ip in used_priv_ips:\n            priv_ip = generate_ip()\n        used_priv_ips.add(priv_ip)\n\n        pub_ip = generate_pub_ip()\n        while pub_ip in used_pub_ips:\n            pub_ip = generate_pub_ip()\n        used_pub_ips.add(pub_ip)\n\n        host_ip = generate_pub_ip(\"198.18.84\")\n        while host_ip in used_host_ips:\n            host_ip = generate_pub_ip(\"198.18.84\")\n        used_host_ips.add(host_ip)\n\n        instance_type = instance_types[i]\n        spec = get_instance_spec(instance_type)\n\n        node_id = NODE_IDS[i % len(NODE_IDS)]\n        node_name = NODE_NAMES[i % len(NODE_NAMES)]\n        image_id = IMAGE_IDS[i % len(IMAGE_IDS)]\n\n        load1 = round(random.uniform(10, 60), 2)\n        load5 = round(load1 + random.uniform(1, 10), 2)\n        load15 = round(load5 + random.uniform(1, 10), 2)\n\n        instance = {\n            \"id\": instance_id,\n            \"type\": instance_type,\n            \"organization\": ORGANIZATION_ID,\n            \"datacenter\": DATACENTER_ID,\n            \"zone\": ZONE_ID,\n            \"vpc\": VPC_ID,\n            \"image\": image_id,\n            \"image_backing\": False,\n            \"status\": \"Running\",\n            \"state\": \"running\",\n            \"action\": \"start\",\n            \"public_ips\": [pub_ip],\n            \"public_ips6\": [generate_pub_ip6(instance_id)],\n            \"private_ips\": [priv_ip],\n            \"private_ips6\": [generate_priv_ip6(instance_id)],\n            \"host_ips\": [host_ip],\n            \"node\": node_id,\n            \"node_name\": node_name,\n            \"shape\": SHAPE_IDS[spec[\"shape\"]],\n            \"name\": spec[\"name\"],\n            \"comment\": \"\",\n            \"init_disk_size\": spec[\"disk\"],\n            \"memory\": spec[\"memory\"],\n            \"processors\": spec[\"processors\"],\n            \"network_namespace\": generate_network_namespace(),\n            \"mem\": round(random.uniform(30, 80), 2),\n            \"load1\": load1,\n            \"load5\": load5,\n            \"load15\": load15,\n        }\n\n        instances.append(instance)\n\n    return instances\n\ndef generate_disks(instances):\n    disks = []\n\n    for i, instance in enumerate(instances):\n        disk_id = f\"651d8e7c4cf9e2e3e4d34f{i:02x}\"\n\n        disk = {\n            \"id\": disk_id,\n            \"name\": instance['name'],\n            \"comment\": \"\",\n            \"state\": \"attached\",\n            \"type\": \"qcow2\",\n            \"datacenter\": instance[\"datacenter\"],\n            \"zone\": instance[\"zone\"],\n            \"node\": instance[\"node\"],\n            \"organization\": instance[\"organization\"],\n            \"instance\": instance[\"id\"],\n            \"image\": instance[\"image\"],\n            \"index\": \"0\",\n            \"size\": instance[\"init_disk_size\"],\n        }\n\n        disks.append(disk)\n\n    return disks\n\ndef generate_deployments(instances):\n    deployments = []\n\n    for i, instance in enumerate(instances):\n        if instance[\"type\"] != \"web\" and instance[\"type\"] != \"database\":\n            continue\n\n        deployment_id = f\"651d8e7c4cf91e3b53d62d{i:02x}\"\n\n        deployment = {\n            \"id\": deployment_id,\n            \"name\": instance['name'],\n            \"type\": instance['type'],\n            \"datacenter\": instance[\"datacenter\"],\n            \"zone\": instance[\"zone\"],\n            \"node\": instance[\"node\"],\n            \"node_name\": instance[\"node_name\"],\n            \"organization\": instance[\"organization\"],\n            \"instance\": instance[\"id\"],\n            \"public_ips\": instance[\"public_ips\"],\n            \"public_ips6\": instance[\"public_ips6\"],\n            \"private_ips\": instance[\"private_ips\"],\n            \"private_ips6\": instance[\"private_ips6\"],\n            \"host_ips\": instance[\"host_ips\"],\n            \"memory\": instance[\"memory\"],\n            \"processors\": instance[\"processors\"],\n            \"mem\": instance[\"mem\"],\n            \"load1\": instance[\"load1\"],\n            \"load5\": instance[\"load5\"],\n            \"load15\": instance[\"load15\"],\n        }\n\n        deployments.append(deployment)\n\n    return deployments\n\ndef format_go_instance(instance):\n    go_code = f\"\"\"\t{{\n\t\tId:               utils.ObjectIdHex(\"{instance['id']}\"),\n\t\tOrganization:     utils.ObjectIdHex(\"{instance['organization']}\"),\n\t\tDatacenter:       utils.ObjectIdHex(\"{instance['datacenter']}\"),\n\t\tZone:             utils.ObjectIdHex(\"{instance['zone']}\"),\n\t\tVpc:              utils.ObjectIdHex(\"{instance['vpc']}\"),\n\t\tImage:            utils.ObjectIdHex(\"{instance['image']}\"),\n\t\tImageBacking:     {str(instance['image_backing']).lower()},\n\t\tStatus:           \"{instance['status']}\",\n\t\tState:            \"{instance['state']}\",\n\t\tAction:           \"{instance['action']}\",\n\t\tUptime:           \"5 days 11 hours 34 mins\",\n\t\tPublicIps:        []string{{\"{instance['public_ips'][0]}\"}},\n\t\tPublicIps6:       []string{{\"{instance['public_ips6'][0]}\"}},\n\t\tPrivateIps:       []string{{\"{instance['private_ips'][0]}\"}},\n\t\tPrivateIps6:      []string{{\"{instance['private_ips6'][0]}\"}},\n\t\tHostIps:          []string{{\"{instance['host_ips'][0]}\"}},\n\t\tNode:             utils.ObjectIdHex(\"{instance['node']}\"),\n\t\tShape:            utils.ObjectIdHex(\"{instance['shape']}\"),\n\t\tName:             \"{instance['name']}\",\n\t\tComment:          \"\",\n\t\tInitDiskSize:     {instance['init_disk_size']},\n\t\tMemory:           {instance['memory']},\n\t\tProcessors:       {instance['processors']},\n\t\tNetworkNamespace: \"{instance['network_namespace']}\",\n\t\tCreated:          time.Now(),\n\t\tTimestamp:        time.Now(),\n\t\tGuest:            &instance.GuestData{{\n\t\t\tStatus:     \"running\",\n\t\t\tTimestamp:  time.Now(),\n\t\t\tHeartbeat:  time.Now(),\n\t\t\tMemory:     {instance['mem']},\n\t\t\tLoad1:      {instance['load1']},\n\t\t\tLoad5:      {instance['load5']},\n\t\t\tLoad15:     {instance['load15']},\n\t\t}},\n\t}},\"\"\"\n    return go_code\n\ndef format_go_disk(disk):\n    go_code = f\"\"\"\t{{\n\t\tDisk: disk.Disk{{\n\t\t\tId:           utils.ObjectIdHex(\"{disk['id']}\"),\n\t\t\tName:         \"{disk['name']}\",\n\t\t\tComment:      \"\",\n\t\t\tState:        \"{disk['state']}\",\n\t\t\tType:         \"{disk['type']}\",\n\t\t\tDatacenter:   utils.ObjectIdHex(\"{disk['datacenter']}\"),\n\t\t\tZone:         utils.ObjectIdHex(\"{disk['zone']}\"),\n\t\t\tNode:         utils.ObjectIdHex(\"{disk['node']}\"),\n\t\t\tOrganization: utils.ObjectIdHex(\"{disk['organization']}\"),\n\t\t\tInstance:     utils.ObjectIdHex(\"{disk['instance']}\"),\n\t\t\tImage:        utils.ObjectIdHex(\"{disk['image']}\"),\n\t\t\tIndex:        \"{disk['index']}\",\n\t\t\tSize:         {disk['size']},\n\t\t\tCreated:      time.Now(),\n\t\t}},\n\t}},\"\"\"\n    return go_code\n\ndef format_go_deployment(deployment):\n    go_code = f\"\"\"\t{{\n\t\tId:            utils.ObjectIdHex(\"{deployment['id']}\"),\n\t\tPod:           utils.ObjectIdHex(\"{POD_ID}\"),\n\t\tUnit:          utils.ObjectIdHex(\"{UNIT_IDS[deployment['type']]}\"),\n\t\tSpec:          utils.ObjectIdHex(\"{SPEC_IDS[deployment['type']]}\"),\n\t\tSpecOffset:    0,\n\t\tSpecIndex:     2,\n\t\tSpecTimestamp: time.Now(),\n\t\tTimestamp:     time.Now(),\n\t\tTags:          []string{{}},\n\t\tKind:          \"instance\",\n\t\tState:         \"deployed\",\n\t\tAction:        \"\",\n\t\tStatus:        \"healthy\",\n\t\tNode:          utils.ObjectIdHex(\"{deployment['node']}\"),\n\t\tInstance:      utils.ObjectIdHex(\"{deployment['instance']}\"),\n\t\tInstanceData: &deployment.InstanceData{{\n\t\t\tHostIps:     []string{{\"{deployment['host_ips'][0]}\"}},\n\t\t\tPublicIps:   []string{{\"{deployment['public_ips'][0]}\"}},\n\t\t\tPublicIps6:  []string{{\"{deployment['public_ips6'][0]}\"}},\n\t\t\tPrivateIps:  []string{{\"{deployment['private_ips'][0]}\"}},\n\t\t\tPrivateIps6: []string{{\"{deployment['private_ips6'][0]}\"}},\n\t\t}},\n\t\tZoneName:            \"us-west-1a\",\n\t\tNodeName:            \"{deployment['node_name']}\",\n\t\tInstanceName:        \"{deployment['name']}\",\n\t\tInstanceRoles:       []string{{\"instance\"}},\n\t\tInstanceMemory:      {deployment['memory']},\n\t\tInstanceProcessors:  {deployment['processors']},\n\t\tInstanceStatus:      \"Running\",\n\t\tInstanceUptime:      \"5 days\",\n\t\tInstanceState:       \"running\",\n\t\tInstanceAction:      \"start\",\n\t\tInstanceGuestStatus: \"running\",\n\t\tInstanceTimestamp:   time.Now(),\n\t\tInstanceHeartbeat:   time.Now(),\n\t\tInstanceMemoryUsage: {deployment['mem']},\n\t\tInstanceHugePages:   0,\n\t\tInstanceLoad1:       {deployment['load1']},\n\t\tInstanceLoad5:       {deployment['load5']},\n\t\tInstanceLoad15:      {deployment['load15']},\n\t}},\"\"\"\n    return go_code\n\ndef main():\n    instances = generate_instances(20)\n    disks = generate_disks(instances)\n    deployments = generate_deployments(instances)\n\n    print(\"// Instances\")\n    print(\"var Instances = []*instance.Instance{\")\n    for i, instance in enumerate(instances):\n        print(format_go_instance(instance))\n    print(\"}\")\n    print(\"\")\n    print(\"// Disks\")\n    print(\"var Disks = []*aggregate.DiskAggregate{\")\n    for i, disk in enumerate(disks):\n        print(format_go_disk(disk))\n    print(\"}\")\n    print(\"\")\n    print(\"// Deployments\")\n    print(\"var Deployments = []*aggregate.Deployment{\")\n    for i, deployment in enumerate(deployments):\n        print(format_go_deployment(deployment))\n    print(\"}\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tools/generate_files.py",
    "content": "import os\nimport shutil\nimport subprocess\nimport json\nfrom datetime import datetime, timezone\n\ndef md5_hash(filepath):\n    result = subprocess.run([\"md5sum\", filepath], stdout=subprocess.PIPE)\n    return result.stdout.split()[0].decode(\"utf-8\")\n\ndef last_modified_time(filepath):\n    timestamp = os.path.getmtime(filepath)\n    return datetime.fromtimestamp(timestamp,\n        tz=timezone.utc).strftime(\"%Y-%m-%dT%H:%M:%S\") + \"Z\"\n\ndef create_files_json(directory, output_file):\n    existing_entries = {}\n    if os.path.exists(output_file):\n        backup_file = \"{}.{}.bak\".format(\n            output_file,\n            datetime.now(timezone.utc).strftime(\"%Y%m%dT%H%M%SZ\"),\n        )\n        shutil.copy2(output_file, backup_file)\n        print(\"Backed up {} to {}\".format(output_file, backup_file))\n\n        with open(output_file, \"r\") as f:\n            existing_data = json.load(f)\n        for entry in existing_data.get(\"files\", []):\n            existing_entries[entry[\"name\"]] = entry\n\n    files_data = {\n        \"version\": 1,\n        \"files\": [],\n    }\n\n    seen_names = set()\n    for root, _, filenames in os.walk(directory):\n        for filename in sorted(filenames):\n            if not filename.endswith(\".qcow2\"):\n                continue\n            if filename in seen_names:\n                continue\n            seen_names.add(filename)\n\n            if filename in existing_entries:\n                files_data[\"files\"].append(existing_entries[filename])\n                continue\n\n            filepath = os.path.join(root, filename)\n            print(\"Hashing new file {}\".format(filename))\n            file_data = {\n                \"name\": filename,\n                \"signed\": True,\n                \"hash\": md5_hash(filepath),\n                \"last_modified\": last_modified_time(filepath),\n            }\n\n            files_data[\"files\"].append(file_data)\n\n    for name, entry in existing_entries.items():\n        if name not in seen_names:\n            files_data[\"files\"].append(entry)\n\n    files_data[\"files\"].sort(key=lambda e: e[\"name\"])\n\n    with open(output_file, \"w\") as f:\n        json.dump(files_data, f, indent=4)\n\ncreate_files_json(os.getcwd(), \"files.json\")\n"
  },
  {
    "path": "tools/package/PKGBUILD",
    "content": "targets=(\n    \"oraclelinux-10\"\n)\npkgname=\"pritunl-cloud\"\npkgver=\"2.0.3665.99\"\npkgrel=\"2\"\npkgdesc=\"Pritunl Cloud\"\npkgdesclong=(\n    \"Pritunl Cloud\"\n)\nmaintainer=\"Pritunl <contact@pritunl.com>\"\narch=\"amd64\"\nlicense=(\"custom\")\nsection=\"utils\"\npriority=\"optional\"\nurl=\"https://github.com/pritunl/${pkgname}\"\ndepends:yum=(\n    \"iptables\"\n    \"net-tools\"\n    \"ipset\"\n    \"ipvsadm\"\n    \"xorriso\"\n)\ndepends:apt=(\n    \"iptables\"\n    \"net-tools\"\n    \"ipset\"\n    \"ipvsadm\"\n    \"xorriso\"\n)\noptdepends:yum=(\n    \"qemu-kvm\"\n    \"qemu-img\"\n    \"swtpm\"\n    \"swtpm-tools\"\n)\n\nmakedepends:yum=(\n    \"git\"\n)\nmakedepends:apt=(\n    \"git\"\n)\nprovides=(\"${pkgname}\")\nconflicts=(\n    \"${pkgname}\"\n)\nsources=(\n    \"pritunl-cloud.tar\"\n)\nhashsums=(\n    \"690c2aab9a0ea6a940b4390b6e95ec55c10951e3634e51115a36051402d686bc\"\n)\nbackup=(\n    \"etc/${pkgname}.json\"\n    \"var/log/${pkgname}.log\"\n    \"var/log/${pkgname}.log.1\"\n)\n\nbuild() {\n    mkdir -p /go/src/github.com/pritunl/${pkgname}/\n    mv \"${srcdir}\"/* /go/src/github.com/pritunl/${pkgname}/\n    cd /go/src/github.com/pritunl/${pkgname}\n    go get\n    go install\n\n    cd /go/src/github.com/pritunl/${pkgname}/agent\n    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-extldflags \"-static\"' -v -o agent-static\n    mv ./agent-static /go/bin/${pkgname}-agent\n    CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -v -o agent-bsd\n    mv ./agent-bsd /go/bin/${pkgname}-agent-bsd\n\n    cd /go/src/github.com/pritunl/${pkgname}/redirect\n    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o redirect\n    mv ./redirect /go/bin/${pkgname}-redirect\n}\n\npackage() {\n    mkdir -p ${pkgdir}/usr/bin\n    cp /go/bin/${pkgname} ${pkgdir}/usr/bin/${pkgname}\n    chmod 755 ${pkgdir}/usr/bin/${pkgname}\n    cp /go/bin/${pkgname}-redirect ${pkgdir}/usr/bin/${pkgname}-redirect\n    chmod 755 ${pkgdir}/usr/bin/${pkgname}-redirect\n    cp /go/bin/${pkgname}-agent ${pkgdir}/usr/bin/${pkgname}-agent\n    chmod 755 ${pkgdir}/usr/bin/${pkgname}-agent\n    cp /go/bin/${pkgname}-agent-bsd ${pkgdir}/usr/bin/${pkgname}-agent-bsd\n    chmod 755 ${pkgdir}/usr/bin/${pkgname}-agent-bsd\n\n    mkdir -p ${pkgdir}/usr/share/${pkgname}/www\n    cp -r /go/src/github.com/pritunl/${pkgname}/www/dist/. ${pkgdir}/usr/share/${pkgname}/www/\n\n    mkdir -p ${pkgdir}/etc\n    echo \"{}\" > ${pkgdir}/etc/${pkgname}.json\n    chmod 600 ${pkgdir}/etc/${pkgname}.json\n\n    mkdir -p ${pkgdir}/etc/systemd/system\n    cp /go/src/github.com/pritunl/${pkgname}/tools/pritunl-cloud.service ${pkgdir}/etc/systemd/system\n    cp /go/src/github.com/pritunl/${pkgname}/tools/pritunl-cloud-redirect.socket ${pkgdir}/etc/systemd/system\n    cp /go/src/github.com/pritunl/${pkgname}/tools/pritunl-cloud-redirect.service ${pkgdir}/etc/systemd/system\n\n    mkdir -p ${pkgdir}/var/log\n    touch ${pkgdir}/var/log/${pkgname}.log\n    touch ${pkgdir}/var/log/${pkgname}.log.1\n}\n\npostinst() {\n    useradd -r -s /sbin/nologin -c 'Pritunl Cloud web server' pritunl-cloud-web &> /dev/null || true\n    systemctl daemon-reload &> /dev/null || true\n    systemctl is-active --quiet pritunl-cloud && systemctl restart pritunl-cloud || true\n}\n\npostrm() {\n    systemctl daemon-reload &> /dev/null || true\n}\n"
  },
  {
    "path": "tools/package/README.md",
    "content": "# pritunl-cloud: package\n\nBuild test package\n\n## install pacur\n\n```bash\nsudo dnf -y install git-core podman\n\nsudo rm -rf /usr/local/go\nwget https://go.dev/dl/go1.25.4.linux-amd64.tar.gz\necho \"9fa5ffeda4170de60f67f3aa0f824e426421ba724c21e133c1e35d6159ca1bec go1.25.4.linux-amd64.tar.gz\" | sha256sum -c - && sudo tar -C /usr/local -xf go1.25.4.linux-amd64.tar.gz\nrm -f go1.25.4.linux-amd64.tar.gz\n\ntee -a ~/.bashrc << EOF\nexport GO111MODULE=on\nexport GOPATH=\\$HOME/go\nexport GOROOT=/usr/local/go\nexport PATH=/usr/local/go/bin:\\$PATH:\\$HOME/go/bin\nEOF\nchown cloud:cloud ~/.bashrc\nsource ~/.bashrc\n\ngo install github.com/pacur/pacur@latest\ncd \"$(ls -d ~/go/pkg/mod/github.com/pacur/pacur@*/docker/ | sort -V | tail -n 1)\"\nsudo find . -maxdepth 1 -type d -name \"*\" ! -name \".\" ! -name \"..\" ! -name \"oraclelinux-10\" -exec rm -rf {} +\nsh clean.sh\nsh build.sh\ncd\n```\n\n## build package\n\n```bash\ngit clone https://github.com/pritunl/pritunl-cloud.git\ncd pritunl-cloud/tools/package\n\nsudo podman run --rm -t -v `pwd`:/pacur:Z localhost/pacur/oraclelinux-10\n```\n"
  },
  {
    "path": "tools/pritunl-cloud-redirect.service",
    "content": "[Unit]\nDescription=Pritunl Cloud Redirect Server Daemon\nRequires=pritunl-cloud-redirect.socket\nAfter=pritunl-cloud-redirect.socket\n\n[Service]\nExecStart=/usr/bin/pritunl-cloud-redirect\nEnvironmentFile=/var/lib/pritunl-cloud/redirect.conf\nUser=pritunl-cloud-web\nGroup=pritunl-cloud-web\nPrivateTmp=true\nPrivateDevices=true\nProtectSystem=strict\nProtectHome=true\nProtectKernelTunables=true\nProtectKernelModules=true\nProtectControlGroups=true\nPrivateNetwork=true\nRestrictAddressFamilies=AF_INET AF_INET6\nRestrictNamespaces=true\nRestrictRealtime=true\nMemoryDenyWriteExecute=true\nLockPersonality=true\nSystemCallFilter=@system-service\nSystemCallArchitectures=native\nRestrictSUIDSGID=true\nDevicePolicy=closed\nCapabilityBoundingSet=\nAmbientCapabilities=\nNoNewPrivileges=true\nIPAddressDeny=any\nSocketBindDeny=any\nReadOnlyPaths=/\nInaccessiblePaths=/home /root /boot /opt /mnt /media\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "tools/pritunl-cloud-redirect.socket",
    "content": "[Unit]\nDescription=Pritunl Cloud Redirect Server Socket\n\n[Socket]\nListenStream=80\nAccept=no\n\n[Install]\nWantedBy=sockets.target\n"
  },
  {
    "path": "tools/pritunl-cloud.service",
    "content": "[Unit]\nDescription=Pritunl Cloud Daemon\n\n[Service]\nLimitNOFILE=50000\nExecStart=/usr/bin/pritunl-cloud start\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "tools/tsc_run.sh",
    "content": "#!/bin/bash\nset -e\n\nnpx tsc --watch\n"
  },
  {
    "path": "tools/virt-install/README.md",
    "content": "# pritunl-cloud: virt-install scripts\n\nScripts used to build base images for pritunl-cloud\n\n```bash\nsudo tee /etc/security/limits.conf << EOF\n* soft memlock 2048000000\n* hard memlock 2048000000\nroot soft memlock 2048000000\nroot hard memlock 2048000000\n* hard nofile 500000\n* soft nofile 500000\nroot hard nofile 500000\nroot soft nofile 500000\nEOF\n\nsudo tee /etc/systemd/system/disable-thp.service << EOF\n[Unit]\nDescription=Disable Transparent Huge Pages\n\n[Service]\nType=simple\nExecStart=/bin/sh -c \"echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled && echo 'never' > /sys/kernel/mm/transparent_hugepage/defrag\"\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsudo systemctl daemon-reload\nsudo systemctl start disable-thp\nsudo systemctl enable disable-thp\n\nsudo sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config\nsudo setenforce 0\n\nsudo dnf -y install qemu-kvm qemu-img libguestfs-tools xorriso edk2-ovmf libvirt virt-install\nsudo systemctl enable --now libvirtd\n\ncd ./setup\nsudo firewall-cmd --zone=libvirt --add-port=8000/tcp --permanent\npython3 -m http.server\n\n# alpine linux\nsetup-alpine\ncurl -o /root/setup.sh http://192.168.122.1:8000/alpine.sh\necho \"c502a8b650d2b60f61414ea2f286577732ab7fc96bac487ebf024cd2120244ca /root/setup.sh\" | sha256sum -c && sudo sh /root/setup.sh\n\n# arch linux\nmkdir /mnt/config\nmount /dev/sr1 /mnt/config\ncp /mnt/config/archinstall.json /root\numount /mnt/config\nrmdir /mnt/config\narchinstall --silent --config /root/archinstall.json\nreboot\ncurl -o /root/setup.sh http://192.168.122.1:8000/arch.sh\necho \"412aacb35f882d09ad7390124f2e3f52a7ae8deb6aaf2825a8775912dfb058fd /root/setup.sh\" | sha256sum -c && bash /root/setup.sh\n\n# debian\nsudo curl -o /root/setup.sh http://192.168.122.1:8000/debian.sh\necho \"5fe9beb585bc434a8ebc8a32fbca347d8180ebc2cf6aef014b06b8c82a1f802a /root/setup.sh\" | sudo sha256sum -c && sudo bash /root/setup.sh\n\n# fedora\ncurl -o /root/setup.sh http://192.168.122.1:8000/fedora.sh\necho \"18032b049b0410f6c78ad5cfcd5f9b65fa3955e58268dc86ca41d7dbfb66a465 /root/setup.sh\" | sha256sum -c && bash /root/setup.sh\n\n# freebsd\nfetch -o /root/setup.sh http://192.168.122.1:8000/freebsd.sh\n[ \"$(sha256sum /root/setup.sh)\" = \"6aab203e3ba7c8aa31ad9dc7da38f701f3871a1ae339904e1f1e6f774ec58238  /root/setup.sh\" ] && sh /root/setup.sh\n\n# rhel7\ncurl -o /root/setup.sh http://192.168.122.1:8000/rhel7.sh\necho \"da5f9518e45a71f1348b7fffd14e496a64cf2bb4a73fc763ec8e97d8f4c2e6d6 /root/setup.sh\" | sha256sum -c && bash /root/setup.sh\n\n# rhel8\ncurl -o /root/setup.sh http://192.168.122.1:8000/rhel8.sh\necho \"dd277240c6d5b573f34e98241c67caec8c0b3c855d13fbb8ccfdfda7f7e726fa /root/setup.sh\" | sha256sum -c && bash /root/setup.sh\n\n# rhel9\ncurl -o /root/setup.sh http://192.168.122.1:8000/rhel9.sh\necho \"23e0b0191270db7e09ade9afce206ac6a455aa7e91bf9eda6b6c677dfb78d994 /root/setup.sh\" | sha256sum -c && bash /root/setup.sh\n\n# rhel10\ncurl -o /root/setup.sh http://192.168.122.1:8000/rhel10.sh\necho \"49cd8fd80e0a3badfbfa1b62de270e62e8f33e831e7f4780fa2f38bd89b9ffe5 /root/setup.sh\" | sha256sum -c && bash /root/setup.sh\n\nfind /var/lib/virt/images/ -name \"*_$(date +%y%m%d).qcow2\" -type f -exec sudo GPG_TTY=$(tty) gpg --default-key 055C08A4 --armor --output {}.sig --detach-sig {} \\;\n\nsudo mkdir -p /mnt/images\nsudo chown cloud:cloud /mnt/images\nmkdir -p /mnt/images/stable\nmkdir -p /mnt/images/unstable\nrsync --human-readable --archive --xattrs --progress 127.0.0.1:/var/lib/virt/images/ /mnt/images/unstable/\n\nsudo wget -P /tmp https://raw.githubusercontent.com/pritunl/toolbox/73aacb9e22b09a34f87d389b3dc301d6c450b0e8/s3c/s3c.py\necho \"7d14fa361e47ff328bbadac302a06a995f6ab65abbe4efce7d8cde6657ba8dde  /tmp/s3c.py\" | sha256sum -c - && sudo cp /tmp/s3c.py /usr/local/bin/s3c && sudo chmod +x /usr/local/bin/s3c\nsudo rm /tmp/s3c.py\n\ncd /mnt/images/unstable\npython3 ~/git/pritunl-cloud/tools/generate_files.py\npython3 ~/git/pritunl-cloud/tools/autoindex.py\n\ns3c cp almalinux8_$(date +%y%m%d).qcow2 pritunl-images:/unstable/almalinux8_$(date +%y%m%d).qcow2\ns3c cp almalinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/almalinux8_$(date +%y%m%d).qcow2.sig\ns3c cp almalinux9_$(date +%y%m%d).qcow2 pritunl-images:/unstable/almalinux9_$(date +%y%m%d).qcow2\ns3c cp almalinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/almalinux9_$(date +%y%m%d).qcow2.sig\ns3c cp almalinux10_$(date +%y%m%d).qcow2 pritunl-images:/unstable/almalinux10_$(date +%y%m%d).qcow2\ns3c cp almalinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/almalinux10_$(date +%y%m%d).qcow2.sig\ns3c cp alpinelinux_$(date +%y%m%d).qcow2 pritunl-images:/unstable/alpinelinux_$(date +%y%m%d).qcow2\ns3c cp alpinelinux_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/alpinelinux_$(date +%y%m%d).qcow2.sig\ns3c cp archlinux_$(date +%y%m%d).qcow2 pritunl-images:/unstable/archlinux_$(date +%y%m%d).qcow2\ns3c cp archlinux_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/archlinux_$(date +%y%m%d).qcow2.sig\ns3c cp fedora43_$(date +%y%m%d).qcow2 pritunl-images:/unstable/fedora43_$(date +%y%m%d).qcow2\ns3c cp fedora43_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/fedora43_$(date +%y%m%d).qcow2.sig\ns3c cp fedora44_$(date +%y%m%d).qcow2 pritunl-images:/unstable/fedora44_$(date +%y%m%d).qcow2\ns3c cp fedora44_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/fedora44_$(date +%y%m%d).qcow2.sig\ns3c cp freebsd_$(date +%y%m%d).qcow2 pritunl-images:/unstable/freebsd_$(date +%y%m%d).qcow2\ns3c cp freebsd_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/freebsd_$(date +%y%m%d).qcow2.sig\ns3c cp oraclelinux7_$(date +%y%m%d).qcow2 pritunl-images:/unstable/oraclelinux7_$(date +%y%m%d).qcow2\ns3c cp oraclelinux7_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/oraclelinux7_$(date +%y%m%d).qcow2.sig\ns3c cp oraclelinux8_$(date +%y%m%d).qcow2 pritunl-images:/unstable/oraclelinux8_$(date +%y%m%d).qcow2\ns3c cp oraclelinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/oraclelinux8_$(date +%y%m%d).qcow2.sig\ns3c cp oraclelinux9_$(date +%y%m%d).qcow2 pritunl-images:/unstable/oraclelinux9_$(date +%y%m%d).qcow2\ns3c cp oraclelinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/oraclelinux9_$(date +%y%m%d).qcow2.sig\ns3c cp oraclelinux10_$(date +%y%m%d).qcow2 pritunl-images:/unstable/oraclelinux10_$(date +%y%m%d).qcow2\ns3c cp oraclelinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/oraclelinux10_$(date +%y%m%d).qcow2.sig\ns3c cp rockylinux8_$(date +%y%m%d).qcow2 pritunl-images:/unstable/rockylinux8_$(date +%y%m%d).qcow2\ns3c cp rockylinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/rockylinux8_$(date +%y%m%d).qcow2.sig\ns3c cp rockylinux9_$(date +%y%m%d).qcow2 pritunl-images:/unstable/rockylinux9_$(date +%y%m%d).qcow2\ns3c cp rockylinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/rockylinux9_$(date +%y%m%d).qcow2.sig\ns3c cp rockylinux10_$(date +%y%m%d).qcow2 pritunl-images:/unstable/rockylinux10_$(date +%y%m%d).qcow2\ns3c cp rockylinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/rockylinux10_$(date +%y%m%d).qcow2.sig\ns3c cp ubuntu2404_$(date +%y%m%d).qcow2 pritunl-images:/unstable/ubuntu2404_$(date +%y%m%d).qcow2\ns3c cp ubuntu2404_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/ubuntu2404_$(date +%y%m%d).qcow2.sig\ns3c cp ubuntu2604_$(date +%y%m%d).qcow2 pritunl-images:/unstable/ubuntu2604_$(date +%y%m%d).qcow2\ns3c cp ubuntu2604_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/ubuntu2604_$(date +%y%m%d).qcow2.sig\ns3c cp files.json pritunl-images:/unstable/files.json\ns3c cp index.html pritunl-images:/unstable/index.html\n\nrsync --human-readable --archive --progress --delete /mnt/images/unstable/ /mnt/images/stable/\ncd /mnt/images/stable\npython3 ~/git/pritunl-cloud/tools/generate_files.py\npython3 ~/git/pritunl-cloud/tools/autoindex.py\n\ns3c cp almalinux8_$(date +%y%m%d).qcow2 pritunl-images:/stable/almalinux8_$(date +%y%m%d).qcow2\ns3c cp almalinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/almalinux8_$(date +%y%m%d).qcow2.sig\ns3c cp almalinux9_$(date +%y%m%d).qcow2 pritunl-images:/stable/almalinux9_$(date +%y%m%d).qcow2\ns3c cp almalinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/almalinux9_$(date +%y%m%d).qcow2.sig\ns3c cp almalinux10_$(date +%y%m%d).qcow2 pritunl-images:/stable/almalinux10_$(date +%y%m%d).qcow2\ns3c cp almalinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/almalinux10_$(date +%y%m%d).qcow2.sig\ns3c cp alpinelinux_$(date +%y%m%d).qcow2 pritunl-images:/stable/alpinelinux_$(date +%y%m%d).qcow2\ns3c cp alpinelinux_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/alpinelinux_$(date +%y%m%d).qcow2.sig\ns3c cp archlinux_$(date +%y%m%d).qcow2 pritunl-images:/stable/archlinux_$(date +%y%m%d).qcow2\ns3c cp archlinux_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/archlinux_$(date +%y%m%d).qcow2.sig\ns3c cp fedora43_$(date +%y%m%d).qcow2 pritunl-images:/stable/fedora43_$(date +%y%m%d).qcow2\ns3c cp fedora43_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/fedora43_$(date +%y%m%d).qcow2.sig\ns3c cp fedora44_$(date +%y%m%d).qcow2 pritunl-images:/stable/fedora44_$(date +%y%m%d).qcow2\ns3c cp fedora44_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/fedora44_$(date +%y%m%d).qcow2.sig\ns3c cp freebsd_$(date +%y%m%d).qcow2 pritunl-images:/stable/freebsd_$(date +%y%m%d).qcow2\ns3c cp freebsd_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/freebsd_$(date +%y%m%d).qcow2.sig\ns3c cp oraclelinux7_$(date +%y%m%d).qcow2 pritunl-images:/stable/oraclelinux7_$(date +%y%m%d).qcow2\ns3c cp oraclelinux7_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/oraclelinux7_$(date +%y%m%d).qcow2.sig\ns3c cp oraclelinux8_$(date +%y%m%d).qcow2 pritunl-images:/stable/oraclelinux8_$(date +%y%m%d).qcow2\ns3c cp oraclelinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/oraclelinux8_$(date +%y%m%d).qcow2.sig\ns3c cp oraclelinux9_$(date +%y%m%d).qcow2 pritunl-images:/stable/oraclelinux9_$(date +%y%m%d).qcow2\ns3c cp oraclelinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/oraclelinux9_$(date +%y%m%d).qcow2.sig\ns3c cp oraclelinux10_$(date +%y%m%d).qcow2 pritunl-images:/stable/oraclelinux10_$(date +%y%m%d).qcow2\ns3c cp oraclelinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/oraclelinux10_$(date +%y%m%d).qcow2.sig\ns3c cp rockylinux8_$(date +%y%m%d).qcow2 pritunl-images:/stable/rockylinux8_$(date +%y%m%d).qcow2\ns3c cp rockylinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/rockylinux8_$(date +%y%m%d).qcow2.sig\ns3c cp rockylinux9_$(date +%y%m%d).qcow2 pritunl-images:/stable/rockylinux9_$(date +%y%m%d).qcow2\ns3c cp rockylinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/rockylinux9_$(date +%y%m%d).qcow2.sig\ns3c cp rockylinux10_$(date +%y%m%d).qcow2 pritunl-images:/stable/rockylinux10_$(date +%y%m%d).qcow2\ns3c cp rockylinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/rockylinux10_$(date +%y%m%d).qcow2.sig\ns3c cp ubuntu2404_$(date +%y%m%d).qcow2 pritunl-images:/stable/ubuntu2404_$(date +%y%m%d).qcow2\ns3c cp ubuntu2404_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/ubuntu2404_$(date +%y%m%d).qcow2.sig\ns3c cp ubuntu2604_$(date +%y%m%d).qcow2 pritunl-images:/stable/ubuntu2604_$(date +%y%m%d).qcow2\ns3c cp ubuntu2604_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/ubuntu2604_$(date +%y%m%d).qcow2.sig\ns3c cp files.json pritunl-images:/stable/files.json\ns3c cp index.html pritunl-images:/stable/index.html\n```\n"
  },
  {
    "path": "tools/virt-install/download.sh",
    "content": "#!/bin/bash\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nINSTALL_DIR=\"$SCRIPT_DIR/install\"\n\nif [ ! -d \"$INSTALL_DIR\" ]; then\n    echo \"Error: install directory not found at: $INSTALL_DIR\"\n    exit 1\nfi\n\nif ! command -v wget &> /dev/null; then\n    echo \"Error: wget is not installed\"\n    exit 1\nfi\n\nprocessed=0\ndownloaded=0\n\nfor script in \"$INSTALL_DIR\"/*.sh; do\n    if [ ! -f \"$script\" ]; then\n        echo \"No .sh files found in $INSTALL_DIR\"\n        exit 0\n    fi\n\n    echo \"Processing: $script\"\n    ((processed++))\n\n    iso_url=$(grep -E \"^[[:space:]]*ISO_URL=\" \"$script\" | tail -1 | cut -d'=' -f2- | sed 's/^[\"'\\'']//' | sed 's/[\"'\\'']$//')\n\n    if [ -z \"$iso_url\" ]; then\n        echo \"  No ISO_URL found in $script\"\n        continue\n    fi\n\n    iso_url=$(echo \"$iso_url\" | sed 's/\\${\\([^}]*\\)}/\\1/g' | sed 's/\\$\\([A-Za-z_][A-Za-z0-9_]*\\)/\\1/g')\n\n    if [ -f \"/var/lib/virt/iso/$(basename ${iso_url})\" ]; then\n        echo \"  File already exists, skipping\"\n        continue\n    fi\n\n    echo \"  Downloading: $iso_url\"\n\n    if sudo wget -P /var/lib/virt/iso \"$iso_url\"; then\n        echo \"  Successfully downloaded: $iso_url\"\n        ((downloaded++))\n    else\n        echo \"  Failed to download from: $iso_url\"\n    fi\n\n    echo \"\"\ndone\n\necho \"Summary:\"\necho \"  Processed $processed .sh files\"\necho \"  Successfully downloaded $downloaded files\"\n"
  },
  {
    "path": "tools/virt-install/install/almalinux10.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"almalinux10\"\nISO_URL=\"https://den.aws.repo.almalinux.org/10.1/isos/x86_64/AlmaLinux-10.1-x86_64-dvd.iso\"\nISO_HASH=\"4597a7483fd7b49bbb7a46958fe2574b7c575531e5ee64ad4d6c1d2779494400\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal-environment\n@standard\n-kexec-tools\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\n\n%post\ngrubby --update-kernel=ALL --remove-args=crashkernel\n%end\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant ${NAME} \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/almalinux8.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"almalinux8\"\nISO_URL=\"https://den.aws.repo.almalinux.org/8.10/isos/x86_64/AlmaLinux-8.10-x86_64-dvd.iso\"\nISO_HASH=\"463fa92155b886e31627f6713e1c2824343762245a914715ffd6f2efc300b7a1\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal-environment\n@standard\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant ${NAME} \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/almalinux9.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"almalinux9\"\nISO_URL=\"https://den.aws.repo.almalinux.org/9.7/isos/x86_64/AlmaLinux-9.7-x86_64-dvd.iso\"\nISO_HASH=\"56f8bf5e44d293a040203b73b70f08bb7dc52f27654b047c20be8598f63ec1f8\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal-environment\n@standard\n-kexec-tools\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\n\n%post\ngrubby --update-kernel=ALL --remove-args=crashkernel\n%end\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant ${NAME} \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/alpinelinux.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"alpinelinux\"\nISO_URL=\"https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/x86_64/alpine-virt-3.22.2-x86_64.iso\"\nISO_HASH=\"b6c45d69829b1b0416ada798353805099d57b8bef9093b85a8319fe5373595d5\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\nsudo mkdir -p /var/lib/virt/init\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo mkdir /var/lib/virt/init/${NAME}\n\nsudo tee /var/lib/virt/init/${NAME}/user-data << EOF\n#alpine-config\napk:\n  repositories:\n    - base_url: https://dl-cdn.alpinelinux.org/alpine\n      repos:\n        - main\n        - community\npackages:\n  - curl\n  - dosfstools\n  - grub-efi\n  - xfsprogs\n  - xfsprogs-extra\n  - sudo\n  - chrony\n  - openssh\n  - qemu-guest-agent\n  - cloud-init\n  - cloud-utils-growpart\nruncmd:\n  - rm /etc/runlevels/*/tiny-cloud*\n  - lbu include /root/.ssh /home/alpine/.ssh\n  - ERASE_DISKS=/dev/vda USE_EFI=1 DISKLABEL=gpt ROOTFS=xfs BOOT_SIZE=100 SWAP_SIZE=0 setup-disk -m sys /dev/vda\n  - reboot\nEOF\n\nsudo tee /var/lib/virt/init/${NAME}/meta-data << EOF\nhostname: cloud\nEOF\n\nsudo rm -f /var/lib/virt/init/${NAME}.iso\nsudo xorriso -as mkisofs \\\n  -output /var/lib/virt/init/${NAME}.iso \\\n  -volid cidata \\\n  -joliet \\\n  -rock \\\n  -input-charset utf-8 \\\n  /var/lib/virt/init/${NAME}/user-data \\\n  /var/lib/virt/init/${NAME}/meta-data\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant alpinelinux3.20 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}),kernel=boot/vmlinuz-virt,initrd=boot/initramfs-virt \\\n  --cloud-init meta-data=/var/lib/virt/init/${NAME}/meta-data,user-data=/var/lib/virt/init/${NAME}/user-data \\\n  --extra-args=\"console=ttyS0 autoinstall\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\nsudo rm -f /var/lib/virt/init/${NAME}.iso\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/archlinux.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"archlinux\"\nISO_URL=\"https://cofractal-sea.mm.fcix.net/archlinux/iso/latest/archlinux-2026.04.01-x86_64.iso\"\nISO_HASH=\"f14bf46afbe782d28835aed99bfa2fe447903872cb9f4b21153196d6ed1d48ae\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/images\nsudo mkdir -p /var/lib/virt/init\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo mkdir /var/lib/virt/init/${NAME}\n\nsudo tee /var/lib/virt/init/${NAME}/archinstall.json << EOF\n{\n    \"additional-repositories\": null,\n    \"archinstall-language\": \"English\",\n    \"audio_config\": null,\n    \"bootloader\": \"Systemd-boot\",\n    \"debug\": false,\n    \"disk_config\": {\n        \"config_type\": \"manual_partitioning\",\n        \"device_modifications\": [\n            {\n                \"device\": \"/dev/vda\",\n                \"partitions\": [\n                    {\n                        \"btrfs\": [],\n                        \"dev_path\": null,\n                        \"flags\": [\"boot\", \"esp\"],\n                        \"fs_type\": \"fat32\",\n                        \"size\": {\n                            \"sector_size\": {\n                                \"unit\": \"B\",\n                                \"value\": 512\n                            },\n                            \"unit\": \"MiB\",\n                            \"value\": 512\n                        },\n                        \"mount_options\": [],\n                        \"mountpoint\": \"/boot\",\n                        \"obj_id\": \"$(uuidgen)\",\n                        \"start\": {\n                            \"sector_size\": {\n                                \"unit\": \"B\",\n                                \"value\": 512\n                            },\n                            \"unit\": \"MiB\",\n                            \"value\": 1\n                        },\n                        \"status\": \"create\",\n                        \"type\": \"primary\"\n                    },\n                    {\n                        \"btrfs\": [],\n                        \"dev_path\": null,\n                        \"flags\": [],\n                        \"fs_type\": \"xfs\",\n                        \"size\": {\n                            \"sector_size\": {\n                                \"unit\": \"B\",\n                                \"value\": 512\n                            },\n                            \"unit\": \"MiB\",\n                            \"value\": 7678\n                        },\n                        \"mount_options\": [],\n                        \"mountpoint\": \"/\",\n                        \"obj_id\": \"$(uuidgen)\",\n                        \"start\": {\n                            \"sector_size\": {\n                                \"unit\": \"B\",\n                                \"value\": 512\n                            },\n                            \"unit\": \"MiB\",\n                            \"value\": 513\n                        },\n                        \"status\": \"create\",\n                        \"type\": \"primary\"\n                    }\n                ],\n                \"wipe\": true\n            }\n        ]\n    },\n    \"hostname\": \"cloud\",\n    \"kernels\": [\"linux\"],\n    \"locale_config\": {\n        \"kb_layout\": \"us\",\n        \"sys_enc\": \"UTF-8\",\n        \"sys_lang\": \"en_US\"\n    },\n    \"network_config\": {\n        \"type\": \"nm\"\n    },\n    \"no_pkg_lookups\": false,\n    \"ntp\": true,\n    \"offline\": false,\n    \"packages\": [\n        \"base\",\n        \"base-devel\",\n        \"linux\",\n        \"linux-firmware\",\n        \"networkmanager\",\n        \"openssh\",\n        \"efibootmgr\",\n        \"vi\"\n    ],\n    \"parallel downloads\": 0,\n    \"profile_config\": {\n        \"gfx_driver\": null,\n        \"greeter\": null,\n        \"profile\": {\n            \"custom_settings\": {},\n            \"details\": [],\n            \"main\": \"Server\"\n        }\n    },\n    \"mirror_config\": {\n        \"custom_mirrors\": [],\n        \"mirror_regions\": {\n            \"Worldwide\": [\n                \"https://geo.mirror.pkgbuild.com/\\$repo/os/\\$arch\",\n                \"https://mirror.rackspace.com/archlinux/\\$repo/os/\\$arch\"\n            ]\n        }\n    },\n    \"custom_commands\": [\n        \"sed -i 's/rootfstype=xfs\\$/rootfstype=xfs console=ttyS0/' /boot/loader/entries/*.conf\",\n        \"systemctl enable serial-getty@ttyS0.service\",\n        \"systemctl set-default multi-user.target\",\n        \"mkinitcpio -P\"\n    ],\n    \"save_config\": null,\n    \"script\": \"guided\",\n    \"silent\": true,\n    \"swap\": false,\n    \"timezone\": \"UTC\",\n    \"version\": \"2.8.6\",\n    \"root_enc_password\": \"\\$y\\$j9T\\$KsJ3WRqoGvcjGsQNis/oG0\\$0zE1DqJ4NJn6pEN3VhnaUIA/nIBSeIYNR8yShbphLW1\"\n}\nEOF\n\nsudo rm -f /var/lib/virt/init/${NAME}/archinstall.iso\nsudo xorriso -as mkisofs \\\n  -output /var/lib/virt/init/${NAME}/archinstall.iso \\\n  -volid CONFIGDATA \\\n  -joliet \\\n  -rock \\\n  -input-charset utf-8 \\\n  /var/lib/virt/init/${NAME}/archinstall.json\n\nsudo tee /var/lib/virt/init/${NAME}/archinstall-auto.service << 'EOF'\n[Unit]\nDescription=Automated Arch Installation\nAfter=multi-user.target\n\n[Service]\nType=oneshot\nExecStart=/usr/bin/archinstall --silent --config /archinstall-config.json\nExecStartPost=/usr/bin/systemctl poweroff\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsudo tee /var/lib/virt/init/${NAME}/archinstall-auto.service << 'EOF'\n[Unit]\nDescription=Automated Arch Installation\nAfter=multi-user.target\n\n[Service]\nType=oneshot\nExecStart=/usr/bin/archinstall --config /archinstall-config.json --silent\nExecStartPost=/usr/bin/systemctl poweroff\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsudo tee /var/lib/virt/init/${NAME}/autoinstall.sh << 'EOF'\n#!/bin/bash\nset -ex\n\nmkdir /mnt/config\nmount /dev/sr1 /mnt/config\ncp /mnt/config/archinstall.json /root/\numount /mnt/config\nrmdir /mnt/config\n\narchinstall --silent --config /root/archinstall.json\n\nreboot\nEOF\nsudo chmod +x /var/lib/virt/init/${NAME}/autoinstall.sh\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --disk path=/var/lib/virt/init/${NAME}/archinstall.iso,device=cdrom,bus=sata \\\n  --os-variant archlinux \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}),kernel=arch/boot/x86_64/vmlinuz-linux,initrd=arch/boot/x86_64/initramfs-linux.img \\\n  --extra-args=\"console=ttyS0 archisobasedir=arch archisosearchuuid=$(blkid -s UUID -o value /var/lib/virt/iso/$(basename ${ISO_URL})) cow_spacesize=1G\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/fedora43.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"fedora43\"\nISO_URL=\"https://cofractal-sea.mm.fcix.net/fedora/linux/releases/43/Server/x86_64/iso/Fedora-Server-dvd-x86_64-43-1.6.iso\"\nISO_HASH=\"aca06983bef83da9b43144c1a2ff4c8483e4745167c17f53725c16a16742e643\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^server-product-environment\n%end\n\nfirstboot --enable\nskipx\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant fedora40 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/fedora44.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"fedora44\"\nISO_URL=\"https://cofractal-sea.mm.fcix.net/fedora/linux/releases/44/Server/x86_64/iso/Fedora-Server-dvd-x86_64-44-1.7.iso\"\nISO_HASH=\"85837793bfa36db6bc709b4cecd2ec116951b87d9c53c3d95eb2fac8dcf7cf1f\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^server-product-environment\n%end\n\nfirstboot --enable\nskipx\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant fedora40 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/freebsd.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"freebsd\"\nISO_URL=\"https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/15.0/FreeBSD-15.0-RELEASE-amd64-dvd1.iso\"\nISO_HASH=\"8cf8e03d8df16401fd5a507480a3270091aa30b59ecf79a9989f102338e359aa\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\n# Console type [vt100]: Enter\n# Welcome: Enter\n# Keymap Selection: Enter\n# Set Hostname: cloud\n# Select Installation Type: Distribution Sets\n# Distribution Select: Enter\n# Partitioning: Auto (UFS)\n# Partition: Enter\n# Partition Scheme: Enter\n# Partition Editor: Delete freebsd-swap\n# Partition Editor: Enter\n# New Password: cloud\n# Retype New Password: cloud\n# Network Configuration: Manual\n# Network Configuration: Enter\n# Network Configuration: Enter\n# Network Configuration: Enter\n# Network Configuration: No\n# IPv4 DNS #1: 8.8.8.8\n# IPv4 DNS #2: 8.8.4.4\n# Time Zone Selector: Enter\n# Time Zone Confirmation: Enter\n# Time & Date: Skip\n# Time & Date: Skip\n# System Configuration: +ntpd +ntpd_sync_on_start\n# System Hardening: Enter\n# Add User Accounts: Enter\n# Username: cloud\n# Full name: Cloud\n# Uid: 1000\n# Login group: Enter\n# Invite cloud into other groups: wheel\n# Login class: Enter\n# Shell: tcsh\n# Home directory: Enter\n# Home directory permissions: Enter\n# Use password-based authentication: no\n# Lock out the account after creation: Enter\n# OK: yes\n# Add another user: Enter\n# Final Configuration: Exit\n# Manual Configuration: Enter\n# Complete: Enter\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant freebsd14.0 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --cdrom=/var/lib/virt/iso/$(basename ${ISO_URL})\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/oraclelinux10.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"oraclelinux10\"\nISO_URL=\"https://yum.oracle.com/ISOS/OracleLinux/OL10/u1/x86_64/OracleLinux-R10-U1-x86_64-dvd.iso\"\nISO_HASH=\"82fa2b70a18fb268c5ef013e298f85bba0d0e6c7ae882c49a3f67c02ee6d68de\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal-environment\n@standard\n-kexec-tools\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\n\n%post\ngrubby --update-kernel=ALL --remove-args=crashkernel\n%end\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant ol9.5 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/oraclelinux7.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"oraclelinux7\"\nISO_URL=\"https://yum.oracle.com/ISOS/OracleLinux/OL7/u9/x86_64/OracleLinux-R7-U9-Server-x86_64-dvd.iso\"\nISO_HASH=\"28d2928ded40baddcd11884b9a6a611429df12897784923c346057ec5cdd1012\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal\n@core\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant ol7.9 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/oraclelinux8.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"oraclelinux8\"\nISO_URL=\"https://yum.oracle.com/ISOS/OracleLinux/OL8/u10/x86_64/OracleLinux-R8-U10-x86_64-dvd.iso\"\nISO_HASH=\"7676a80eeaafa16903eebb2abba147a3afe230b130cc066d56fdd6854d8da900\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal-environment\n@standard\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant ol8.10 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/oraclelinux9.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"oraclelinux9\"\nISO_URL=\"https://yum.oracle.com/ISOS/OracleLinux/OL9/u7/x86_64/OracleLinux-R9-U7-x86_64-dvd.iso\"\nISO_HASH=\"895751f157727bca8437607e8edb32b7c10d815aba954f5b3dc24c28ac8a10aa\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal-environment\n@standard\n-kexec-tools\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\n\n%post\ngrubby --update-kernel=ALL --remove-args=crashkernel\n%end\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant ol9.5 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/rockylinux10.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"rockylinux10\"\nISO_URL=\"https://sjc.mirror.rackspace.com/rocky/10/isos/x86_64/Rocky-10.1-x86_64-dvd1.iso\"\nISO_HASH=\"55f96d45a052c0ed4f06309480155cb66281a008691eb7f3f359957205b1849a\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal-environment\n@standard\n-kexec-tools\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\n\n%post\ngrubby --update-kernel=ALL --remove-args=crashkernel\n%end\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant rocky9 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/rockylinux8.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"rockylinux8\"\nISO_URL=\"https://sjc.mirror.rackspace.com/rocky/8/isos/x86_64/Rocky-8.10-x86_64-dvd1.iso\"\nISO_HASH=\"642ada8a49dbeca8cca6543b31196019ee3d649a0163b5db0e646c7409364eeb\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal-environment\n@standard\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant rocky8 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/rockylinux9.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"rockylinux9\"\nISO_URL=\"https://sjc.mirror.rackspace.com/rocky/9/isos/x86_64/Rocky-9.7-x86_64-dvd.iso\"\nISO_HASH=\"d48e902325dce6793935b4e13672a0d9a4f958e02d4e23fcf0a8a34c49ef03da\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo tee /var/lib/virt/ks/${NAME}.ks << EOF\ntext\ncdrom\n\n%addon com_redhat_kdump --disable\n%end\n\nkeyboard --xlayouts='us'\nlang en_US.UTF-8\n\nnetwork --bootproto=dhcp --hostname=cloud --activate\n\n%packages\n@^minimal-environment\n@standard\n-kexec-tools\n%end\n\nfirstboot --enable\n\nignoredisk --only-use=vda\nclearpart --all --initlabel\npart /boot/efi --fstype=\"efi\" --ondisk=vda --size=100 --fsoptions=\"umask=0077,shortname=winnt\"\npart / --fstype=\"xfs\" --ondisk=vda --grow\n\ntimezone Etc/UTC --utc\n\nrootpw --plaintext cloud\n\n%post\ngrubby --update-kernel=ALL --remove-args=crashkernel\n%end\nEOF\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --os-variant rocky9 \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}) \\\n  --initrd-inject=/var/lib/virt/ks/${NAME}.ks \\\n  --extra-args=\"console=ttyS0 inst.ks=file:/${NAME}.ks inst.text\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\n\necho \"Compressing image...\"\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/ubuntu24.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"ubuntu2404\"\nISO_URL=\"https://cofractal-sea.mm.fcix.net/ubuntu-releases/24.04/ubuntu-24.04.3-live-server-amd64.iso\"\nISO_HASH=\"c3514bf0056180d09376462a7a1b4f213c1d6e8ea67fae5c25099c6fd3d8274b\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\nsudo mkdir -p /var/lib/virt/init\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo mkdir /var/lib/virt/init/${NAME}\n\nsudo tee /var/lib/virt/init/${NAME}/user-data << EOF\n#cloud-config\nautoinstall:\n  version: 1\n  timezone: \"Etc/UTC\"\n  identity:\n    realname: \"Cloud\"\n    username: cloud\n    password: \"\\$6\\$x7YEknTyUuNSTTVK\\$nq4xoSTrYp7a/Kb1EvtpH97GxG02CFBqELznybQv4XrA7sskq9PI0Y5KADhp9KiwVdrwR6v2IP6wqoxyXj4SP/\"\n    hostname: cloud\n  storage:\n    layout:\n      name: direct\n    config:\n      - type: disk\n        id: disk0\n        match:\n          size: largest\n      - type: partition\n        id: efi-partition\n        device: disk0\n        size: 100M\n        flag: boot\n        grub_device: true\n      - type: partition\n        id: root-partition\n        device: disk0\n        size: -1\n      - type: format\n        id: efi-format\n        volume: efi-partition\n        fstype: fat32\n      - type: format\n        id: root-format\n        volume: root-partition\n        fstype: xfs\n      - type: mount\n        id: efi-mount\n        device: efi-format\n        path: /boot/efi\n      - type: mount\n        id: root-mount\n        device: root-format\n        path: /\nEOF\n\nsudo tee /var/lib/virt/init/${NAME}/meta-data << EOF\ninstance-id: ${NAME}\nlocal-hostname: cloud\nEOF\n\nsudo rm -f /var/lib/virt/init/${NAME}.iso\nsudo xorriso -as mkisofs \\\n  -output /var/lib/virt/init/${NAME}.iso \\\n  -volid cidata \\\n  -joliet \\\n  -rock \\\n  -input-charset utf-8 \\\n  /var/lib/virt/init/${NAME}/user-data \\\n  /var/lib/virt/init/${NAME}/meta-data\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --disk path=/var/lib/virt/init/${NAME}.iso,device=cdrom \\\n  --os-variant ubuntu-lts-latest \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}),kernel=casper/hwe-vmlinuz,initrd=casper/hwe-initrd \\\n  --extra-args=\"console=ttyS0 serial autoinstall\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\nsudo rm -rf /var/lib/virt/init/${NAME}.iso\n\necho \"Compressing image...\"\n\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/install/ubuntu26.sh",
    "content": "#!/bin/bash\nset -ev\nNAME=\"ubuntu2604\"\nISO_URL=\"https://cofractal-sea.mm.fcix.net/ubuntu-releases/26.04/ubuntu-26.04-live-server-amd64.iso\"\nISO_HASH=\"dec49008a71f6098d0bcfc822021f4d042d5f2db279e4d75bdd981304f1ca5d9\"\n\nsudo mkdir -p /var/lib/virt/iso\nsudo mkdir -p /var/lib/virt/ks\nsudo mkdir -p /var/lib/virt/images\nsudo mkdir -p /var/lib/virt/init\n\nsudo virsh destroy ${NAME} || true\nsudo virsh undefine ${NAME} --nvram || true\nsudo rm -f /var/lib/virt/${NAME}.qcow2\n\nif [ ! -f \"/var/lib/virt/iso/$(basename ${ISO_URL})\" ]; then\n  sudo wget -P /var/lib/virt/iso ${ISO_URL}\nfi\n\necho \"${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})\" | sha256sum --check\nif [ $? -ne 0 ]; then\n  echo \"Checksum for ISO failed\"\n  exit 1\nfi\n\nsudo mkdir /var/lib/virt/init/${NAME}\n\nsudo tee /var/lib/virt/init/${NAME}/user-data << EOF\n#cloud-config\nautoinstall:\n  version: 1\n  timezone: \"Etc/UTC\"\n  identity:\n    realname: \"Cloud\"\n    username: cloud\n    password: \"\\$6\\$x7YEknTyUuNSTTVK\\$nq4xoSTrYp7a/Kb1EvtpH97GxG02CFBqELznybQv4XrA7sskq9PI0Y5KADhp9KiwVdrwR6v2IP6wqoxyXj4SP/\"\n    hostname: cloud\n  storage:\n    layout:\n      name: direct\n    config:\n      - type: disk\n        id: disk0\n        match:\n          size: largest\n      - type: partition\n        id: efi-partition\n        device: disk0\n        size: 100M\n        flag: boot\n        grub_device: true\n      - type: partition\n        id: root-partition\n        device: disk0\n        size: -1\n      - type: format\n        id: efi-format\n        volume: efi-partition\n        fstype: fat32\n      - type: format\n        id: root-format\n        volume: root-partition\n        fstype: xfs\n      - type: mount\n        id: efi-mount\n        device: efi-format\n        path: /boot/efi\n      - type: mount\n        id: root-mount\n        device: root-format\n        path: /\nEOF\n\nsudo tee /var/lib/virt/init/${NAME}/meta-data << EOF\ninstance-id: ${NAME}\nlocal-hostname: cloud\nEOF\n\nsudo rm -f /var/lib/virt/init/${NAME}.iso\nsudo xorriso -as mkisofs \\\n  -output /var/lib/virt/init/${NAME}.iso \\\n  -volid cidata \\\n  -joliet \\\n  -rock \\\n  -input-charset utf-8 \\\n  /var/lib/virt/init/${NAME}/user-data \\\n  /var/lib/virt/init/${NAME}/meta-data\n\nsudo virt-install \\\n  --name ${NAME} \\\n  --vcpus 8 \\\n  --memory 8192 \\\n  --boot uefi \\\n  --disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \\\n  --disk path=/var/lib/virt/init/${NAME}.iso,device=cdrom \\\n  --os-variant ubuntu-lts-latest \\\n  --network network=default \\\n  --graphics=none \\\n  --console pty,target_type=serial \\\n  --location=/var/lib/virt/iso/$(basename ${ISO_URL}),kernel=casper/vmlinuz,initrd=casper/initrd \\\n  --extra-args=\"console=ttyS0 serial autoinstall\"\n\nwhile ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q \"shut off\"; do\n  sleep 1\ndone\n\nsudo rm -rf /var/lib/virt/init/${NAME}\nsudo rm -rf /var/lib/virt/init/${NAME}.iso\n\necho \"Compressing image...\"\n\nsudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\nsha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2\n"
  },
  {
    "path": "tools/virt-install/setup/alpine.sh",
    "content": "#!/bin/sh\nset -ev\n\nif [ $(whoami) != \"root\" ]; then\n  echo \"Must be run as root\"\n  exit 1\nfi\n\n#############################################################\n# starting alpine setup\n#############################################################\n\ntee /etc/motd << EOF\nWelcome to Alpine!\n\nThe Alpine Wiki contains a large amount of how-to guides and general\ninformation about administrating Alpine systems.\nSee <https://wiki.alpinelinux.org/>.\n\nEOF\n\necho \"iso9660\" > /etc/filesystems\nsed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub\ngrub-mkconfig -o /boot/grub/grub.cfg\n\nrc-update add sshd default\nrc-update add chronyd default\nrc-update add qemu-guest-agent default\nsetup-cloud-init\n\ntee /etc/init.d/cloud-fix << EOF\n#!/sbin/openrc-run\n\ndescription=\"cloud-init final fix stage\"\n\ndepend() {\n  after cloud-config\n  provide cloud-fix\n}\n\nstart() {\n  if grep -q 'cloud-init=disabled' /proc/cmdline; then\n    ewarn \"\\$RC_SVCNAME is disabled via /proc/cmdline.\"\n  elif test -e /etc/cloud/cloud-init.disabled; then\n    ewarn \"\\$RC_SVCNAME is disabled via cloud-init.disabled file\"\n  else\n    ebegin \"cloud-init fix\"\n    cloud-init modules --mode final\n    eend \\$?\n  fi\n}\nEOF\nchmod +x /etc/init.d/cloud-fix\nrc-update add cloud-fix default\n\nsed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config\nsed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config\nsed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config\ntee -a /etc/ssh/sshd_config << EOF\n\nPermitRootLogin no\nPasswordAuthentication no\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nTrustedUserCAKeys /etc/ssh/trusted\nAuthorizedPrincipalsFile /etc/ssh/principals\nEOF\ntouch /etc/ssh/trusted\ntouch /etc/ssh/principals\n\nmkdir -p /home/alpine\nchown alpine:alpine /home/alpine\nchmod 700 /home/alpine\nusermod -l cloud alpine\nusermod -m -d /home/cloud cloud\ngroupmod -n cloud alpine\nusermod -aG adm,wheel cloud\npasswd -d root\npasswd -l root\npasswd -d cloud\nmkdir -p /home/cloud/.ssh\nchown cloud:cloud /home/cloud/.ssh\ntouch /home/cloud/.ssh/authorized_keys\nchown cloud:cloud /home/cloud/.ssh/authorized_keys\nchmod 700 /home/cloud/.ssh\nchmod 600 /home/cloud/.ssh/authorized_keys\nsed -i '/^%wheel/d' /etc/sudoers\ntee -a /etc/sudoers << EOF\n%wheel ALL=(ALL) NOPASSWD:ALL\nEOF\n\ncloud_password=$(tr -dc 'A-Za-z0-9!@#$%^&*()_+~' < /dev/urandom | head -c 64)\necho \"cloud:$cloud_password\" | chpasswd\npasswd -u cloud\n\ncloud-init clean --machine-id\ntee /etc/resolv.conf << EOF\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nEOF\n\nsync\nsleep 1\n\nfind /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;\nrm -rf /var/tmp/dnf-*\nrm -rf /home/cloud/.cache\nshred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true\nshred -u /root/.ssh/authorized_keys || true\nshred -u /root/.ash_history || true\nshred -u /home/cloud/.ash_history || true\nshred -u /root/.bash_history || true\nshred -u /home/cloud/.bash_history || true\nshred -u /var/log/lastlog || true\nshred -u /var/log/secure || true\nshred -u /var/log/utmp || true\nshred -u /var/log/wtmp || true\nshred -u /var/log/btmp || true\nshred -u /var/log/dmesg || true\nshred -u /var/log/dmesg.old || true\nshred -u /var/lib/systemd/random-seed || true\nrm -rf /var/log/*.gz\nrm -rf /var/log/*.[0-9]\nrm -rf /var/log/*-????????\nrm -rf /var/lib/cloud/instances/*\nrm -f /var/lib/systemd/random-seed\nsync\nfstrim -v /\nsync\n\n#############################################################\n# finished alpine setup, clear history and shutdown:\n# unset HISTFILE && poweroff\n#############################################################\n"
  },
  {
    "path": "tools/virt-install/setup/arch.sh",
    "content": "#!/bin/bash\nset -ev\n\nif [ $(whoami) != \"root\" ]; then\n  echo \"Must be run as root\"\n  exit 1\nfi\n\n#############################################################\n# starting arch setup\n#############################################################\n\nsed -i 's/^timeout.*/timeout 0/' /boot/loader/loader.conf\n\npacman -Syu\npacman -Sy --noconfirm chrony qemu-guest-agent cloud-init cloud-guest-utils dhcpcd\n\nsystemctl enable sshd\nsystemctl enable chronyd\n\nsystemctl enable cloud-init-local\nsystemctl enable cloud-init-main\nsystemctl enable cloud-config\nsystemctl enable cloud-final\nsystemctl disable systemd-networkd-wait-online\nsystemctl mask systemd-networkd-wait-online\n\nsed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config\nsed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config\nsed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config\ntee -a /etc/ssh/sshd_config << EOF\n\nPermitRootLogin no\nPasswordAuthentication no\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nTrustedUserCAKeys /etc/ssh/trusted\nAuthorizedPrincipalsFile /etc/ssh/principals\nEOF\ntouch /etc/ssh/trusted\ntouch /etc/ssh/principals\n\nuseradd -m -G wheel,systemd-journal cloud\npasswd -d root\npasswd -l root\npasswd -d cloud\npasswd -l cloud\nmkdir -p /home/cloud/.ssh\nchown cloud:cloud /home/cloud/.ssh\ntouch /home/cloud/.ssh/authorized_keys\nchown cloud:cloud /home/cloud/.ssh/authorized_keys\nchmod 700 /home/cloud/.ssh\nchmod 600 /home/cloud/.ssh/authorized_keys\nsed -i '/^%wheel/d' /etc/sudoers\ntee -a /etc/sudoers << EOF\n%wheel ALL=(ALL) NOPASSWD:ALL\nEOF\n\ncloud-init clean --machine-id\ntee /etc/resolv.conf << EOF\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nEOF\n\nsync\nsleep 1\n\nfind /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;\nrm -rf /var/tmp/*\nrm -rf /home/cloud/.cache\nshred -u /etc/ssh/*_key /etc/ssh/*_key.pub 2>/dev/null || true\nshred -u /root/.ssh/authorized_keys 2>/dev/null || true\nshred -u /root/.bash_history 2>/dev/null || true\nshred -u /home/cloud/.bash_history 2>/dev/null || true\nshred -u /var/log/lastlog 2>/dev/null || true\nshred -u /var/log/secure 2>/dev/null || true\nshred -u /var/log/utmp 2>/dev/null || true\nshred -u /var/log/wtmp 2>/dev/null || true\nshred -u /var/log/btmp 2>/dev/null || true\nshred -u /var/log/dmesg 2>/dev/null || true\nshred -u /var/log/dmesg.old 2>/dev/null || true\nshred -u /var/lib/systemd/random-seed 2>/dev/null || true\nrm -rf /var/log/*.gz\nrm -rf /var/log/*.[0-9]\nrm -rf /var/log/*-????????\nrm -rf /var/lib/cloud/instances/*\nrm -f /var/lib/systemd/random-seed\nsync\nfstrim -v /\nsync\n\n#############################################################\n# finished arch setup, clear history and shutdown:\n# unset HISTFILE && poweroff\n#############################################################\n"
  },
  {
    "path": "tools/virt-install/setup/debian.sh",
    "content": "#!/bin/bash\nset -ev\n\nif [ $(whoami) != \"root\" ]; then\n  echo \"Must be run as root\"\n  exit 1\nfi\n\n#############################################################\n# starting debian setup\n#############################################################\n\ntee /etc/modprobe.d/floppy-blacklist.conf << EOF\nblacklist floppy\nEOF\n\napt update\napt -y upgrade\napt -y autoremove\n\napt -y install bash-completion qemu-guest-agent cloud-init cloud-initramfs-growroot chrony openssh-server\n\nsystemctl daemon-reload\nsystemctl enable qemu-guest-agent.service\nsystemctl enable cloud-init-local.service\nif systemctl list-unit-files cloud-init-main.service >/dev/null 2>&1; then\n  systemctl enable cloud-init-main.service\nelse\n  systemctl enable cloud-init.service\nfi\nsystemctl enable cloud-config.service\nsystemctl enable cloud-final.service\n\nsed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config\nsed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config\nsed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config\ntee -a /etc/ssh/sshd_config << EOF\n\nPermitRootLogin no\nPasswordAuthentication no\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nTrustedUserCAKeys /etc/ssh/trusted\nAuthorizedPrincipalsFile /etc/ssh/principals\nEOF\ntouch /etc/ssh/trusted\ntouch /etc/ssh/principals\n\npasswd -d root\npasswd -l root\npasswd -d cloud\npasswd -l cloud\nmkdir -p /home/cloud/.ssh\nchown cloud:cloud /home/cloud/.ssh\ntouch /home/cloud/.ssh/authorized_keys\nchown cloud:cloud /home/cloud/.ssh/authorized_keys\nchmod 700 /home/cloud/.ssh\nchmod 600 /home/cloud/.ssh/authorized_keys\nsed -i '/^%sudo/d' /etc/sudoers\ntee -a /etc/sudoers << EOF\n%sudo ALL=(ALL) NOPASSWD:ALL\nEOF\n\nsystemctl enable ssh.service\nufw disable\n\napt clean\n\nrm -f /etc/cloud/cloud.cfg.d/90-installer-network.cfg\nrm -f /etc/cloud/cloud.cfg.d/99-installer.cfg\ncloud-init clean --machine-id\nrm -rf /etc/NetworkManager/system-connections/*\n\nsync\nsleep 1\n\nfind /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;\nrm -rf /var/tmp/dnf-*\nrm -rf /home/cloud/.cache\nshred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true\nshred -u /root/.ssh/authorized_keys || true\nshred -u /root/.bash_history || true\nshred -u /home/cloud/.bash_history || true\nshred -u /var/log/lastlog || true\nshred -u /var/log/secure || true\nshred -u /var/log/utmp || true\nshred -u /var/log/wtmp || true\nshred -u /var/log/btmp || true\nshred -u /var/log/dmesg || true\nshred -u /var/log/dmesg.old || true\nshred -u /var/lib/systemd/random-seed || true\nrm -rf /var/log/*.gz\nrm -rf /var/log/*.[0-9]\nrm -rf /var/log/*-????????\nrm -rf /var/lib/cloud/instances/*\nrm -f /var/lib/systemd/random-seed\nrm -f /etc/machine-id\ntouch /etc/machine-id\nsync\nfstrim -av\nsync\n\n#############################################################\n# finished debian setup, clear history and shutdown:\n# unset HISTFILE && history -c && sudo poweroff\n#############################################################\n"
  },
  {
    "path": "tools/virt-install/setup/fedora.sh",
    "content": "#!/bin/bash\nset -ev\n\nif [ $(whoami) != \"root\" ]; then\n  echo \"Must be run as root\"\n  exit 1\nfi\n\n#############################################################\n# starting fedora setup\n#############################################################\n\ntee /etc/modprobe.d/floppy-blacklist.conf << EOF\nblacklist floppy\nEOF\n\ndnf clean all\ndnf -y update\ndnf -y install bash-completion qemu-guest-agent dnf-utils cloud-init cloud-utils-growpart chrony openssh-server\ndnf -y update\ndnf -y remove cockpit-ws\n\nsed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub\ngrub2-mkconfig -o /boot/grub2/grub.cfg\n\n# cloud-init fix\nif [[ \"$(cloud-init --version 2>&1)\" == *\"25.2\"* ]]; then\n  wget https://dl.fedoraproject.org/pub/fedora/linux/releases/44/Everything/x86_64/os/Packages/c/cloud-init-25.3-3.fc44.noarch.rpm\n  echo \"877c3b272f2202d46a7c1d06185eea262feb5bda637fa49575ae8c9a96e62652  cloud-init-25.3-3.fc44.noarch.rpm\" | dnf -y install cloud-init-25.3-3.fc44.noarch.rpm\n  rm -f cloud-init-25.3-3.fc44.noarch.rpm\nfi\n\nsystemctl daemon-reload\nsystemctl enable qemu-guest-agent.service\nsystemctl enable cloud-init-local.service\nif systemctl list-unit-files cloud-init-main.service >/dev/null 2>&1; then\n  systemctl enable cloud-init-main.service\nelse\n  systemctl enable cloud-init.service\nfi\nsystemctl enable cloud-config.service\nsystemctl enable cloud-final.service\n\nsed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/dnf/dnf.conf\nsed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true\n\nsed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config\nsed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config\nsed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config\ntee -a /etc/ssh/sshd_config << EOF\n\nPermitRootLogin no\nPasswordAuthentication no\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nTrustedUserCAKeys /etc/ssh/trusted\nAuthorizedPrincipalsFile /etc/ssh/principals\nEOF\ntouch /etc/ssh/trusted\ntouch /etc/ssh/principals\nrestorecon -v /etc/ssh/trusted\nrestorecon -v /etc/ssh/principals\n\nuseradd -m -G adm,video,wheel,systemd-journal cloud || true\npasswd -d root\npasswd -l root\npasswd -d cloud\npasswd -l cloud\nmkdir -p /home/cloud/.ssh\nchown cloud:cloud /home/cloud/.ssh\nrestorecon -v /home/cloud/.ssh\ntouch /home/cloud/.ssh/authorized_keys\nchown cloud:cloud /home/cloud/.ssh/authorized_keys\nrestorecon -v /home/cloud/.ssh/authorized_keys\nchmod 700 /home/cloud/.ssh\nchmod 600 /home/cloud/.ssh/authorized_keys\nsed -i '/^%wheel/d' /etc/sudoers\ntee -a /etc/sudoers << EOF\n%wheel ALL=(ALL) NOPASSWD:ALL\nEOF\n\nsystemctl enable sshd\nsystemctl restart sshd\nsystemctl disable firewalld\nsystemctl stop firewalld\nsystemctl start chronyd\nsystemctl enable chronyd\n\ndnf clean all\nrm -rf /var/cache/dnf\n\ncloud-init clean --machine-id\nrm -rf /etc/NetworkManager/system-connections/*\n\nsync\nsleep 1\n\nfind /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;\nrm -rf /var/tmp/dnf-*\nrm -rf /home/cloud/.cache\nshred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true\nshred -u /root/.ssh/authorized_keys || true\nshred -u /root/.bash_history || true\nshred -u /home/cloud/.bash_history || true\nshred -u /var/log/lastlog || true\nshred -u /var/log/secure || true\nshred -u /var/log/utmp || true\nshred -u /var/log/wtmp || true\nshred -u /var/log/btmp || true\nshred -u /var/log/dmesg || true\nshred -u /var/log/dmesg.old || true\nshred -u /var/lib/systemd/random-seed || true\nrm -rf /var/log/*.gz\nrm -rf /var/log/*.[0-9]\nrm -rf /var/log/*-????????\nrm -rf /var/lib/cloud/instances/*\nrm -f /var/lib/systemd/random-seed\nrm -f /etc/machine-id\ntouch /etc/machine-id\nsync\nfstrim -av\nsync\n\n#############################################################\n# finished fedora setup, clear history and shutdown:\n# unset HISTFILE && history -c && poweroff\n#############################################################\n"
  },
  {
    "path": "tools/virt-install/setup/freebsd.sh",
    "content": "#!/bin/bash\nset -ev\n\nif [ $(whoami) != \"root\" ]; then\n  echo \"Must be run as root\"\n  exit 1\nfi\n\n#############################################################\n# starting freebsd setup\n#############################################################\n\nenv PAGER=/bin/cat freebsd-update fetch\nfreebsd-update install || true\nenv ASSUME_ALWAYS_YES=yes pkg update\npkg upgrade -y\n\nsysrc -f /boot/loader.conf autoboot_delay=0\nsysrc ifconfig_vtnet0=\"\"\nsysrc ifconfig_vtnet0_ipv6=\"\"\n\npkg search cloud-init\npkg install -y dual-dhclient py311-cloud-init\nsysrc dhclient_program=\"/usr/local/sbin/dual-dhclient\"\n\npw mod user root -w no\npw mod user cloud -w no\n\nmkdir -p /home/cloud/.ssh\nchown cloud:cloud /home/cloud/.ssh\ntouch /home/cloud/.ssh/authorized_keys\nchown cloud:cloud /home/cloud/.ssh/authorized_keys\nchmod 700 /home/cloud/.ssh\nchmod 600 /home/cloud/.ssh/authorized_keys\n\nsed -i \"\" '/^PermitRootLogin/d' /etc/ssh/sshd_config\nsed -i \"\" '/^PasswordAuthentication/d' /etc/ssh/sshd_config\nsed -i \"\" '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config\nsed -i \"\" '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config\nsed -i \"\" '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config\nsed -i \"\" '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config\ntee -a /etc/ssh/sshd_config << EOF\n\nPermitRootLogin no\nPasswordAuthentication no\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nTrustedUserCAKeys /etc/ssh/trusted\nAuthorizedPrincipalsFile /etc/ssh/principals\nEOF\ntouch /etc/ssh/trusted\ntouch /etc/ssh/principals\n\ntee /etc/sudoers << EOF\n%wheel ALL=(ALL) NOPASSWD:ALL\nEOF\nchmod 600 /etc/sudoers\n\nsysrc swapoff=\"YES\"\nsysrc ifconfig_vtnet0=\"\"\nsysrc cloudinit_enable=\"YES\"\n\ntee /usr/local/etc/cloud/cloud.cfg.d/99_cloud.cfg << EOF\ndatasource_list: [ NoCloud ]\nEOF\n\ncloud-init clean --machine-id\n\ntee /usr/local/etc/rc.d/cloudinitfix << EOF\n#!/bin/sh\n\n# PROVIDE: cloudinitfix\n# REQUIRE: FILESYSTEMS NETWORKING ldconfig devd\n# BEFORE:  LOGIN cloudconfig cloudinit\n\n. /etc/rc.subr\n\nPATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\n\nname=\"cloudinitfix\"\nstart_cmd=\"cloudinitfix_start\"\nstop_cmd=\":\"\nrcvar=\"cloudinit_enable\"\n\ncloudinitfix_start()\n{\n  rm -rf /var/lib/cloud/instances\n}\n\nload_rc_config \\$name\n\n: \\${cloudinitfix_enable=\"NO\"}\n\nrun_rc_command \"\\$1\"\nEOF\nchmod 755 /usr/local/etc/rc.d/cloudinitfix\n\ntee /etc/resolv.conf << EOF\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nEOF\nrm -f /etc/resolv.conf.bak\n\nsync\nrm -f /var/db/dhclient.leases.vtnet0\nrm -f /var/db/dhclient6.leases\nrm -f setup.sh\nrm -rf /root/.cache\nrm -rf /home/cloud/.cache\nrm -f /etc/ssh/*_key*\nrm -f /root/.ssh/authorized_keys\nrm -f /root/.history\nrm -f /home/cloud/.history\nrm -f /root/.bash_history\nrm -f /home/cloud/.bash_history\nfind /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;\nsync\n\n#############################################################\n# finished freebsd setup, clear history and shutdown:\n# unset history && unset HISTFILE && poweroff\n#############################################################\n"
  },
  {
    "path": "tools/virt-install/setup/rhel10.sh",
    "content": "#!/bin/bash\nset -ev\n\nif [ $(whoami) != \"root\" ]; then\n  echo \"Must be run as root\"\n  exit 1\nfi\n\n#############################################################\n# starting rhel10 setup\n#############################################################\n\ntee /etc/modprobe.d/floppy-blacklist.conf << EOF\nblacklist floppy\nEOF\n\ntruncate -s 0 /etc/yum/vars/ociregion || true\n\ndnf clean all\ndnf -y update\ndnf -y install bash-completion qemu-guest-agent cloud-init cloud-utils-growpart chrony openssh-server\ndnf -y remove cockpit-ws\n\nsed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub\ngrub2-mkconfig -o /boot/grub2/grub.cfg\n\nsystemctl daemon-reload\nsystemctl enable qemu-guest-agent.service\n\nsed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/yum.conf\nsed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true\n\nsed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config\nsed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config\nsed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config\ntee -a /etc/ssh/sshd_config << EOF\n\nPermitRootLogin no\nPasswordAuthentication no\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nTrustedUserCAKeys /etc/ssh/trusted\nAuthorizedPrincipalsFile /etc/ssh/principals\nEOF\ntouch /etc/ssh/trusted\ntouch /etc/ssh/principals\nrestorecon -v /etc/ssh/trusted\nrestorecon -v /etc/ssh/principals\n\nuseradd -m -G adm,video,wheel,systemd-journal cloud || true\npasswd -d root\npasswd -l root\npasswd -d cloud\npasswd -l cloud\nmkdir -p /home/cloud/.ssh\nchown cloud:cloud /home/cloud/.ssh\nrestorecon -v /home/cloud/.ssh\ntouch /home/cloud/.ssh/authorized_keys\nchown cloud:cloud /home/cloud/.ssh/authorized_keys\nrestorecon -v /home/cloud/.ssh/authorized_keys\nchmod 700 /home/cloud/.ssh\nchmod 600 /home/cloud/.ssh/authorized_keys\nsed -i '/^%wheel/d' /etc/sudoers\ntee -a /etc/sudoers << EOF\n%wheel ALL=(ALL) NOPASSWD:ALL\nEOF\n\nsystemctl enable sshd\nsystemctl restart sshd\nsystemctl disable firewalld\nsystemctl stop firewalld\nsystemctl start chronyd\nsystemctl enable chronyd\n\ndnf clean all\nrm -rf /var/cache/dnf\n\ncloud-init clean --machine-id\nrm -rf /etc/NetworkManager/system-connections/*\ntee /etc/resolv.conf << EOF\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nEOF\n\nsync\nsleep 1\n\nfind /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;\nrm -rf /var/tmp/dnf-*\nrm -rf /home/cloud/.cache\nshred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true\nshred -u /root/.ssh/authorized_keys || true\nshred -u /root/.bash_history || true\nshred -u /home/cloud/.bash_history || true\nshred -u /var/log/lastlog || true\nshred -u /var/log/secure || true\nshred -u /var/log/utmp || true\nshred -u /var/log/wtmp || true\nshred -u /var/log/btmp || true\nshred -u /var/log/dmesg || true\nshred -u /var/log/dmesg.old || true\nshred -u /var/lib/systemd/random-seed || true\nrm -rf /var/log/*.gz\nrm -rf /var/log/*.[0-9]\nrm -rf /var/log/*-????????\nrm -rf /var/lib/cloud/instances/*\nrm -f /var/lib/systemd/random-seed\nrm -f /etc/machine-id\ntouch /etc/machine-id\nsync\nfstrim -av\nsync\n\n#############################################################\n# finished rhel10 setup, clear history and shutdown:\n# unset HISTFILE && history -c && poweroff\n#############################################################\n"
  },
  {
    "path": "tools/virt-install/setup/rhel7.sh",
    "content": "#!/bin/bash\nset -ev\n\nif [ $(whoami) != \"root\" ]; then\n  echo \"Must be run as root\"\n  exit 1\nfi\n\n#############################################################\n# starting rhel7 setup\n#############################################################\n\nyum-config-manager --disable ol7_ociyum_config || true\ntruncate -s 0 /etc/yum/vars/ociregion || true\n\ntee /etc/modprobe.d/floppy-blacklist.conf << EOF\nblacklist floppy\nEOF\n\nyum clean all\nyum -y update\nyum -y install bash-completion qemu-guest-agent cloud-init cloud-utils-growpart chrony openssh-server\nyum -y remove cockpit-ws\n\nsed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub\ngrub2-mkconfig -o /boot/grub2/grub.cfg\n\nsystemctl daemon-reload\nsystemctl enable qemu-guest-agent.service\n\nrm -f /etc/sysconfig/network-scripts/ifcfg-eth*\n\nsed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/yum.conf\nsed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true\n\nsed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config\nsed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config\nsed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config\ntee -a /etc/ssh/sshd_config << EOF\n\nPermitRootLogin no\nPasswordAuthentication no\nChallengeResponseAuthentication no\nTrustedUserCAKeys /etc/ssh/trusted\nAuthorizedPrincipalsFile /etc/ssh/principals\nEOF\ntouch /etc/ssh/trusted\ntouch /etc/ssh/principals\nrestorecon -v /etc/ssh/trusted\nrestorecon -v /etc/ssh/principals\n\nuseradd -m -G adm,video,wheel,systemd-journal cloud || true\npasswd -d root\npasswd -l root\npasswd -d cloud\npasswd -l cloud\nmkdir -p /home/cloud/.ssh\nchown cloud:cloud /home/cloud/.ssh\nrestorecon -v /home/cloud/.ssh\ntouch /home/cloud/.ssh/authorized_keys\nchown cloud:cloud /home/cloud/.ssh/authorized_keys\nrestorecon -v /home/cloud/.ssh/authorized_keys\nchmod 700 /home/cloud/.ssh\nchmod 600 /home/cloud/.ssh/authorized_keys\nsed -i '/^%wheel/d' /etc/sudoers\ntee -a /etc/sudoers << EOF\n%wheel ALL=(ALL) NOPASSWD:ALL\nEOF\n\nsystemctl enable sshd\nsystemctl restart sshd\nsystemctl disable firewalld\nsystemctl stop firewalld\nsystemctl start chronyd\nsystemctl enable chronyd\n\nyum clean all\nrm -rf /var/cache/yum\n\ncloud-init clean\ntee /etc/resolv.conf << EOF\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nEOF\n\nsync\nsleep 1\n\nfind /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;\nrm -rf /var/tmp/dnf-*\nrm -rf /home/cloud/.cache\nshred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true\nshred -u /root/.ssh/authorized_keys || true\nshred -u /root/.bash_history || true\nshred -u /home/cloud/.bash_history || true\nshred -u /var/log/lastlog || true\nshred -u /var/log/secure || true\nshred -u /var/log/utmp || true\nshred -u /var/log/wtmp || true\nshred -u /var/log/btmp || true\nshred -u /var/log/dmesg || true\nshred -u /var/log/dmesg.old || true\nshred -u /var/lib/systemd/random-seed || true\nrm -rf /var/log/*.gz\nrm -rf /var/log/*.[0-9]\nrm -rf /var/log/*-????????\nrm -rf /var/lib/cloud/instances/*\nrm -f /var/lib/systemd/random-seed\nrm -f /etc/machine-id\ntouch /etc/machine-id\nsync\nfstrim -av\nsync\n\n#############################################################\n# finished rhel7 setup, clear history and shutdown:\n# unset HISTFILE && history -c && poweroff\n#############################################################\n"
  },
  {
    "path": "tools/virt-install/setup/rhel8.sh",
    "content": "#!/bin/bash\nset -ev\n\nif [ $(whoami) != \"root\" ]; then\n  echo \"Must be run as root\"\n  exit 1\nfi\n\n#############################################################\n# starting rhel8 setup\n#############################################################\n\ntee /etc/modprobe.d/floppy-blacklist.conf << EOF\nblacklist floppy\nEOF\n\ntruncate -s 0 /etc/yum/vars/ociregion || true\n\ndnf clean all\ndnf -y update\ndnf -y install bash-completion qemu-guest-agent cloud-init cloud-utils-growpart chrony openssh-server\ndnf -y remove cockpit-ws\n\nsed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub\ngrub2-mkconfig -o /boot/grub2/grub.cfg\n\nsystemctl daemon-reload\nsystemctl enable qemu-guest-agent.service\n\nrm -f /etc/sysconfig/network-scripts/ifcfg-eth*\n\nsed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/yum.conf\nsed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true\n\nsed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config\nsed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config\nsed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config\ntee -a /etc/ssh/sshd_config << EOF\n\nPermitRootLogin no\nPasswordAuthentication no\nChallengeResponseAuthentication no\nTrustedUserCAKeys /etc/ssh/trusted\nAuthorizedPrincipalsFile /etc/ssh/principals\nEOF\ntouch /etc/ssh/trusted\ntouch /etc/ssh/principals\nrestorecon -v /etc/ssh/trusted\nrestorecon -v /etc/ssh/principals\n\nuseradd -m -G adm,video,wheel,systemd-journal cloud || true\npasswd -d root\npasswd -l root\npasswd -d cloud\npasswd -l cloud\nmkdir -p /home/cloud/.ssh\nchown cloud:cloud /home/cloud/.ssh\nrestorecon -v /home/cloud/.ssh\ntouch /home/cloud/.ssh/authorized_keys\nchown cloud:cloud /home/cloud/.ssh/authorized_keys\nrestorecon -v /home/cloud/.ssh/authorized_keys\nchmod 700 /home/cloud/.ssh\nchmod 600 /home/cloud/.ssh/authorized_keys\nsed -i '/^%wheel/d' /etc/sudoers\ntee -a /etc/sudoers << EOF\n%wheel ALL=(ALL) NOPASSWD:ALL\nEOF\n\nsystemctl enable sshd\nsystemctl restart sshd\nsystemctl disable firewalld\nsystemctl stop firewalld\nsystemctl start chronyd\nsystemctl enable chronyd\n\ncloud-init clean --machine-id\nrm -rf /etc/NetworkManager/system-connections/*\ntee /etc/resolv.conf << EOF\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nEOF\n\ndnf clean all\nrm -rf /var/cache/dnf\n\nsync\nsleep 1\n\nfind /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;\nrm -rf /var/tmp/dnf-*\nrm -rf /home/cloud/.cache\nshred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true\nshred -u /root/.ssh/authorized_keys || true\nshred -u /root/.bash_history || true\nshred -u /home/cloud/.bash_history || true\nshred -u /var/log/lastlog || true\nshred -u /var/log/secure || true\nshred -u /var/log/utmp || true\nshred -u /var/log/wtmp || true\nshred -u /var/log/btmp || true\nshred -u /var/log/dmesg || true\nshred -u /var/log/dmesg.old || true\nshred -u /var/lib/systemd/random-seed || true\nrm -rf /var/log/*.gz\nrm -rf /var/log/*.[0-9]\nrm -rf /var/log/*-????????\nrm -rf /var/lib/cloud/instances/*\nrm -f /var/lib/systemd/random-seed\nrm -f /etc/machine-id\ntouch /etc/machine-id\nsync\nfstrim -av\nsync\n\n#############################################################\n# finished rhel8 setup, clear history and shutdown:\n# unset HISTFILE && history -c && poweroff\n#############################################################\n"
  },
  {
    "path": "tools/virt-install/setup/rhel9.sh",
    "content": "#!/bin/bash\nset -ev\n\nif [ $(whoami) != \"root\" ]; then\n  echo \"Must be run as root\"\n  exit 1\nfi\n\n#############################################################\n# starting rhel9 setup\n#############################################################\n\ntee /etc/modprobe.d/floppy-blacklist.conf << EOF\nblacklist floppy\nEOF\n\ntruncate -s 0 /etc/yum/vars/ociregion || true\n\ndnf clean all\ndnf -y update\ndnf -y install bash-completion qemu-guest-agent cloud-init cloud-utils-growpart chrony openssh-server\ndnf -y remove cockpit-ws\n\nsed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub\ngrub2-mkconfig -o /boot/grub2/grub.cfg\n\nsystemctl daemon-reload\nsystemctl enable qemu-guest-agent.service\n\nsed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/yum.conf\nsed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true\n\nsed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config\nsed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config\nsed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config\nsed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config\ntee -a /etc/ssh/sshd_config << EOF\n\nPermitRootLogin no\nPasswordAuthentication no\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nTrustedUserCAKeys /etc/ssh/trusted\nAuthorizedPrincipalsFile /etc/ssh/principals\nEOF\ntouch /etc/ssh/trusted\ntouch /etc/ssh/principals\nrestorecon -v /etc/ssh/trusted\nrestorecon -v /etc/ssh/principals\n\nuseradd -m -G adm,video,wheel,systemd-journal cloud || true\npasswd -d root\npasswd -l root\npasswd -d cloud\npasswd -l cloud\nmkdir -p /home/cloud/.ssh\nchown cloud:cloud /home/cloud/.ssh\nrestorecon -v /home/cloud/.ssh\ntouch /home/cloud/.ssh/authorized_keys\nchown cloud:cloud /home/cloud/.ssh/authorized_keys\nrestorecon -v /home/cloud/.ssh/authorized_keys\nchmod 700 /home/cloud/.ssh\nchmod 600 /home/cloud/.ssh/authorized_keys\nsed -i '/^%wheel/d' /etc/sudoers\ntee -a /etc/sudoers << EOF\n%wheel ALL=(ALL) NOPASSWD:ALL\nEOF\n\nsystemctl enable sshd\nsystemctl restart sshd\nsystemctl disable firewalld\nsystemctl stop firewalld\nsystemctl start chronyd\nsystemctl enable chronyd\n\ndnf clean all\nrm -rf /var/cache/dnf\n\ncloud-init clean --machine-id\nrm -rf /etc/NetworkManager/system-connections/*\ntee /etc/resolv.conf << EOF\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nEOF\n\nsync\nsleep 1\n\nfind /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;\nrm -rf /var/tmp/dnf-*\nrm -rf /home/cloud/.cache\nshred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true\nshred -u /root/.ssh/authorized_keys || true\nshred -u /root/.bash_history || true\nshred -u /home/cloud/.bash_history || true\nshred -u /var/log/lastlog || true\nshred -u /var/log/secure || true\nshred -u /var/log/utmp || true\nshred -u /var/log/wtmp || true\nshred -u /var/log/btmp || true\nshred -u /var/log/dmesg || true\nshred -u /var/log/dmesg.old || true\nshred -u /var/lib/systemd/random-seed || true\nrm -rf /var/log/*.gz\nrm -rf /var/log/*.[0-9]\nrm -rf /var/log/*-????????\nrm -rf /var/lib/cloud/instances/*\nrm -f /var/lib/systemd/random-seed\nrm -f /etc/machine-id\ntouch /etc/machine-id\nsync\nfstrim -av\nsync\n\n#############################################################\n# finished rhel9 setup, clear history and shutdown:\n# unset HISTFILE && history -c && poweroff\n#############################################################\n"
  },
  {
    "path": "tools/webpack_run.sh",
    "content": "#!/bin/bash\nset -e\n\nnpx webpack-cli --config webpack.dev.config --progress --color --watch\n"
  },
  {
    "path": "tpm/tpm.go",
    "content": "package tpm\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/permission\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst systemdTemplate = `[Unit]\nDescription=Pritunl Cloud TPM\nAfter=network.target\n\n[Service]\nType=simple\nUser=%s\nExecStart=swtpm socket --tpm2 --key pwdfile=%s,mode=aes-256-cbc,remove=true,kdf=pbkdf2 --tpmstate dir=%s --ctrl type=unixio,path=%s --log level=5\nTimeoutStopSec=5\nPrivateTmp=true\nProtectHome=true\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\nNetworkNamespacePath=/var/run/netns/%s\n`\n\nfunc WriteService(vmId bson.ObjectID, namespace string) (err error) {\n\tunitPath := paths.GetUnitPathTpm(vmId)\n\ttpmPath := paths.GetTpmPath(vmId)\n\tpwdPath := paths.GetTpmPwdPath(vmId)\n\tsockPath := paths.GetTpmSockPath(vmId)\n\n\toutput := fmt.Sprintf(\n\t\tsystemdTemplate,\n\t\tpermission.GetUserName(vmId),\n\t\tpwdPath,\n\t\ttpmPath,\n\t\tsockPath,\n\t\tnamespace,\n\t)\n\n\terr = utils.CreateWrite(unitPath, output, 0644)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Start(db *database.Database, virt *vm.VirtualMachine) (err error) {\n\tnamespace := vm.GetNamespace(virt.Id, 0)\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\": virt.Id.Hex(),\n\t}).Info(\"tpm: Starting virtual machine tpm\")\n\n\ttpmsPath := paths.GetTpmsPath()\n\ttpmPath := paths.GetTpmPath(virt.Id)\n\tunit := paths.GetUnitNameTpm(virt.Id)\n\tpwdPath := paths.GetTpmPwdPath(virt.Id)\n\n\terr = utils.ExistsMkdir(tpmsPath, 0755)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = utils.ExistsMkdir(tpmPath, 0700)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = permission.InitTpm(virt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t_ = systemd.Stop(unit)\n\n\tsecret, err := GetSecret(db, virt.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif secret == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"tpm: Missing instance tpm secret\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = WriteService(virt.Id, namespace)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Reload()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\ttime.Sleep(15 * time.Second)\n\t\t_ = os.Remove(pwdPath)\n\t}()\n\n\terr = utils.CreateWrite(pwdPath, secret, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = permission.InitTpmPwd(virt)\n\tif err != nil {\n\t\t_ = os.Remove(pwdPath)\n\t\treturn\n\t}\n\n\terr = systemd.Start(unit)\n\tif err != nil {\n\t\t_ = os.Remove(pwdPath)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Stop(virt *vm.VirtualMachine) (err error) {\n\tunit := paths.GetUnitNameTpm(virt.Id)\n\n\t_ = systemd.Stop(unit)\n\n\treturn\n}\n"
  },
  {
    "path": "tpm/utils.go",
    "content": "package tpm\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype instanceData struct {\n\tId        bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tTpmSecret string        `bson:\"tpm_secret\" json:\"-\"`\n}\n\nfunc GenerateSecret() (secret string, err error) {\n\tsecret, err = utils.RandPasswd(128)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetSecret(db *database.Database, vmId bson.ObjectID) (\n\tsecret string, err error) {\n\n\tcoll := db.Instances()\n\n\tdata := &instanceData{}\n\n\terr = coll.FindOne(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": vmId,\n\t\t},\n\t\toptions.FindOne().\n\t\t\tSetProjection(bson.D{{\"tpm_secret\", 1}}),\n\t).Decode(data)\n\n\tsecret = data.TpmSecret\n\n\treturn\n}\n"
  },
  {
    "path": "twilio/twilio.go",
    "content": "package twilio\n\nimport (\n\t\"encoding/xml\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/twilio/twilio-go\"\n\topenapi \"github.com/twilio/twilio-go/rest/api/v2010\"\n)\n\ntype TwimlSay struct {\n\tXMLName xml.Name `xml:\"Say\"`\n\tVoice   string   `xml:\"voice,attr\"`\n\tLoop    string   `xml:\"loop,attr\"`\n\tMessage string   `xml:\",chardata\"`\n}\n\ntype TwimlResponse struct {\n\tXMLName xml.Name  `xml:\"Response\"`\n\tSay     *TwimlSay `xml:\"Say\"`\n}\n\nfunc PhoneCall(number, message string) (err error) {\n\tclient := twilio.NewRestClientWithParams(twilio.ClientParams{\n\t\tUsername: settings.System.TwilioAccount,\n\t\tPassword: settings.System.TwilioSecret,\n\t})\n\n\tparams := &openapi.CreateCallParams{}\n\tparams.SetFrom(settings.System.TwilioNumber)\n\tparams.SetTo(number)\n\n\ttwiml := &TwimlResponse{\n\t\tSay: &TwimlSay{\n\t\t\tVoice:   \"alice\",\n\t\t\tLoop:    \"3\",\n\t\t\tMessage: FilterStrPhone(message, 160),\n\t\t},\n\t}\n\n\ttwimlData, err := xml.Marshal(twiml)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"twilio: Failed to marshal twiml message\"),\n\t\t}\n\t\treturn\n\t}\n\n\tparams.SetTwiml(string(twimlData))\n\n\tresp, err := client.Api.CreateCall(params)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"twilio: Twilio call error\"),\n\t\t}\n\t\treturn\n\t}\n\n\trespSid := *resp.Sid\n\tif respSid == \"\" {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"twilio: Invalid call sid\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc TextMessage(number, message string) (err error) {\n\tclient := twilio.NewRestClientWithParams(twilio.ClientParams{\n\t\tUsername: settings.System.TwilioAccount,\n\t\tPassword: settings.System.TwilioSecret,\n\t})\n\n\tparams := &openapi.CreateMessageParams{}\n\tparams.SetFrom(settings.System.TwilioNumber)\n\tparams.SetTo(number)\n\tparams.SetBody(\"Pritunl Alert: \" + FilterStrMessage(message, 800))\n\n\tresp, err := client.Api.CreateMessage(params)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"twilio: Twilio message error\"),\n\t\t}\n\t\treturn\n\t}\n\n\trespSid := *resp.Sid\n\tif respSid == \"\" {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"twilio: Invalid message sid\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif resp.ErrorCode != nil && resp.ErrorMessage != nil &&\n\t\t*resp.ErrorMessage != \"\" {\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"number\":        number,\n\t\t\t\"message\":       message,\n\t\t\t\"source_number\": settings.System.TwilioNumber,\n\t\t\t\"error_code\":    resp.ErrorCode,\n\t\t\t\"error_message\": resp.ErrorMessage,\n\t\t}).Error(\"twilio: Text message error\")\n\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"twilio: Twilio message error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "twilio/utils.go",
    "content": "package twilio\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\nvar safeCharsPhone = set.NewSet(\n\t'a',\n\t'b',\n\t'c',\n\t'd',\n\t'e',\n\t'f',\n\t'g',\n\t'h',\n\t'i',\n\t'j',\n\t'k',\n\t'l',\n\t'm',\n\t'n',\n\t'o',\n\t'p',\n\t'q',\n\t'r',\n\t's',\n\t't',\n\t'u',\n\t'v',\n\t'w',\n\t'x',\n\t'y',\n\t'z',\n\t'A',\n\t'B',\n\t'C',\n\t'D',\n\t'E',\n\t'F',\n\t'G',\n\t'H',\n\t'I',\n\t'J',\n\t'K',\n\t'L',\n\t'M',\n\t'N',\n\t'O',\n\t'P',\n\t'Q',\n\t'R',\n\t'S',\n\t'T',\n\t'U',\n\t'V',\n\t'W',\n\t'X',\n\t'Y',\n\t'Z',\n\t'0',\n\t'1',\n\t'2',\n\t'3',\n\t'4',\n\t'5',\n\t'6',\n\t'7',\n\t'8',\n\t'9',\n\t'-',\n\t'+',\n\t'=',\n\t'_',\n\t'/',\n\t',',\n\t'.',\n\t':',\n\t'%',\n\t'@',\n\t'!',\n\t' ',\n)\n\nvar safeCharsMessage = set.NewSet(\n\t'a',\n\t'b',\n\t'c',\n\t'd',\n\t'e',\n\t'f',\n\t'g',\n\t'h',\n\t'i',\n\t'j',\n\t'k',\n\t'l',\n\t'm',\n\t'n',\n\t'o',\n\t'p',\n\t'q',\n\t'r',\n\t's',\n\t't',\n\t'u',\n\t'v',\n\t'w',\n\t'x',\n\t'y',\n\t'z',\n\t'A',\n\t'B',\n\t'C',\n\t'D',\n\t'E',\n\t'F',\n\t'G',\n\t'H',\n\t'I',\n\t'J',\n\t'K',\n\t'L',\n\t'M',\n\t'N',\n\t'O',\n\t'P',\n\t'Q',\n\t'R',\n\t'S',\n\t'T',\n\t'U',\n\t'V',\n\t'W',\n\t'X',\n\t'Y',\n\t'Z',\n\t'0',\n\t'1',\n\t'2',\n\t'3',\n\t'4',\n\t'5',\n\t'6',\n\t'7',\n\t'8',\n\t'9',\n\t'-',\n\t'+',\n\t'=',\n\t'_',\n\t'/',\n\t',',\n\t'.',\n\t':',\n\t'#',\n\t'@',\n\t'%',\n\t'!',\n\t'[',\n\t']',\n\t'(',\n\t')',\n\t' ',\n)\n\nfunc filterStr(s string, n int, safe set.Set) string {\n\tif len(s) == 0 {\n\t\treturn \"\"\n\t}\n\n\tif len(s) > n {\n\t\ts = s[:n]\n\t}\n\n\tns := \"\"\n\tfor _, c := range s {\n\t\tif safe.Contains(c) {\n\t\t\tns += string(c)\n\t\t}\n\t}\n\n\treturn ns\n}\n\nfunc FilterStrPhone(s string, n int) string {\n\treturn filterStr(s, n, safeCharsPhone)\n}\n\nfunc FilterStrMessage(s string, n int) string {\n\treturn filterStr(s, n, safeCharsMessage)\n}\n"
  },
  {
    "path": "uhandlers/alert.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/alert\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype alertData struct {\n\tId        bson.ObjectID `json:\"id\"`\n\tName      string        `json:\"name\"`\n\tComment   string        `json:\"comment\"`\n\tRoles     []string      `json:\"roles\"`\n\tResource  string        `json:\"resource\"`\n\tLevel     int           `json:\"level\"`\n\tFrequency int           `bson:\"frequency\" json:\"frequency\"`\n\tIgnores   []string      `bson:\"ignores\" json:\"ignores\"`\n\tValueInt  int           `json:\"value_int\"`\n\tValueStr  string        `json:\"value_str\"`\n}\n\ntype alertsData struct {\n\tAlerts []*alert.Alert `json:\"alerts\"`\n\tCount  int64          `json:\"count\"`\n}\n\nfunc alertPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &alertData{}\n\n\talertId, ok := utils.ParseObjectId(c.Param(\"alert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\talrt, err := alert.GetOrg(db, userOrg, alertId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\talrt.Name = data.Name\n\talrt.Comment = data.Comment\n\talrt.Roles = data.Roles\n\talrt.Resource = data.Resource\n\talrt.Level = data.Level\n\talrt.Frequency = data.Frequency\n\talrt.Ignores = data.Ignores\n\talrt.ValueInt = data.ValueInt\n\talrt.ValueStr = data.ValueStr\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"roles\",\n\t\t\"resource\",\n\t\t\"level\",\n\t\t\"frequency\",\n\t\t\"ignores\",\n\t\t\"value_int\",\n\t\t\"value_str\",\n\t)\n\n\terrData, err := alrt.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = alrt.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_ = event.PublishDispatch(db, \"alert.change\")\n\n\tc.JSON(200, alrt)\n}\n\nfunc alertPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &alertData{\n\t\tName:     \"new-alert\",\n\t\tResource: alert.InstanceOffline,\n\t\tLevel:    alert.Medium,\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\talrt := &alert.Alert{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: userOrg,\n\t\tRoles:        data.Roles,\n\t\tResource:     data.Resource,\n\t\tLevel:        data.Level,\n\t\tFrequency:    data.Frequency,\n\t\tIgnores:      data.Ignores,\n\t\tValueInt:     data.ValueInt,\n\t\tValueStr:     data.ValueStr,\n\t}\n\n\terrData, err := alrt.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = alrt.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_ = event.PublishDispatch(db, \"alert.change\")\n\n\tc.JSON(200, alrt)\n}\n\nfunc alertDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\talertId, ok := utils.ParseObjectId(c.Param(\"alert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := alert.RemoveOrg(db, userOrg, alertId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_ = event.PublishDispatch(db, \"alert.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc alertsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := []bson.ObjectID{}\n\n\terr := c.Bind(&dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = alert.RemoveMultiOrg(db, userOrg, dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_ = event.PublishDispatch(db, \"alert.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc alertGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\talrt := demo.Alerts[0]\n\t\tc.JSON(200, alrt)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\talertId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\talrt, err := alert.GetOrg(db, userOrg, alertId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, alrt)\n}\n\nfunc alertsGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{\n\t\t\"organization\": userOrg,\n\t}\n\n\talertId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = alertId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"name\": &bson.M{\n\t\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\talerts, count, err := alert.GetAllPaged(\n\t\tdb, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdta := &alertsData{\n\t\tAlerts: alerts,\n\t\tCount:  count,\n\t}\n\n\tc.JSON(200, dta)\n}\n"
  },
  {
    "path": "uhandlers/auth.go",
    "content": "package uhandlers\n\nimport (\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/auth\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/cookie\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/device\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/secondary\"\n\t\"github.com/pritunl/pritunl-cloud/session\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/validator\"\n)\n\nfunc authStateGet(c *gin.Context) {\n\tdata := auth.GetState()\n\n\tif demo.IsDemo() {\n\t\tprovider := &auth.StateProvider{\n\t\t\tId:    \"demo\",\n\t\t\tType:  \"demo\",\n\t\t\tLabel: \"demo\",\n\t\t}\n\t\tdata.Providers = append(data.Providers, provider)\n\t}\n\n\tc.JSON(200, data)\n}\n\ntype authData struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\nfunc authSessionPost(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &authData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, errData, err := auth.Local(db, data.Username, data.Password)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserPrimaryApprove,\n\t\taudit.Fields{\n\t\t\t\"method\": \"local\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevAuth, secProviderId, errAudit, errData, err := validator.ValidateUser(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"local\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tif devAuth {\n\t\tdeviceCount, err := device.CountSecondary(db, usr.Id)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tsecType := \"\"\n\t\tvar secProvider bson.ObjectID\n\t\tif deviceCount == 0 {\n\t\t\tif secProviderId.IsZero() {\n\t\t\t\tsecType = secondary.UserDeviceRegister\n\t\t\t\tsecProvider = secondary.DeviceProvider\n\t\t\t} else {\n\t\t\t\tsecType = secondary.User\n\t\t\t\tsecProvider = secProviderId\n\t\t\t}\n\t\t} else {\n\t\t\tsecType = secondary.UserDevice\n\t\t\tsecProvider = secondary.DeviceProvider\n\t\t}\n\n\t\tsecd, err := secondary.New(db, usr.Id, secType, secProvider)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata, err := secd.GetData()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(201, data)\n\t\treturn\n\t} else if !secProviderId.IsZero() {\n\t\tsecd, err := secondary.New(db, usr.Id, secondary.User, secProviderId)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata, err := secd.GetData()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(201, data)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserLogin,\n\t\taudit.Fields{\n\t\t\t\"method\": \"local\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewUser(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tredirectQueryJson(c, c.Request.URL.RawQuery)\n}\n\ntype secondaryData struct {\n\tToken    string `json:\"token\"`\n\tFactor   string `json:\"factor\"`\n\tPasscode string `json:\"passcode\"`\n}\n\nfunc authSecondaryPost(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &secondaryData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token, secondary.User)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := secd.Handle(db, c.Request, data.Factor, data.Passcode)\n\tif err != nil {\n\t\tif _, ok := err.(*secondary.IncompleteError); ok {\n\t\t\tc.Status(206)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLoginFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"method\":      \"secondary\",\n\t\t\t\t\"provider_id\": secd.ProviderId,\n\t\t\t\t\"error\":       errData.Error,\n\t\t\t\t\"message\":     errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserSecondaryApprove,\n\t\taudit.Fields{\n\t\t\t\"provider_id\": secd.ProviderId,\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdeviceAuth, _, errAudit, errData, err := validator.ValidateUser(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"secondary\"\n\t\terrAudit[\"provider_id\"] = secd.ProviderId\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tif deviceAuth {\n\t\tdeviceCount, err := device.CountSecondary(db, usr.Id)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif deviceCount == 0 {\n\t\t\tsecd, err := secondary.New(db, usr.Id,\n\t\t\t\tsecondary.UserDeviceRegister, secondary.DeviceProvider)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdata, err := secd.GetData()\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tc.JSON(201, data)\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserLogin,\n\t\taudit.Fields{\n\t\t\t\"method\":      \"secondary\",\n\t\t\t\"provider_id\": secd.ProviderId,\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewUser(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tredirectQueryJson(c, c.Request.URL.RawQuery)\n}\n\nfunc logoutGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tif authr.IsValid() {\n\t\terr := authr.Remove(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tusr, _ := authr.GetUser(db)\n\tif usr != nil {\n\t\terr := audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLogout,\n\t\t\taudit.Fields{},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tc.Redirect(302, \"/\")\n}\n\nfunc logoutAllGet(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsessions, err := session.GetAll(db, usr.Id, false)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfor _, sess := range sessions {\n\t\terr = sess.Remove(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif authr.IsValid() {\n\t\terr := authr.Remove(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserLogoutAll,\n\t\taudit.Fields{},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.Redirect(302, \"/\")\n}\n\nfunc authRequestGet(c *gin.Context) {\n\tauth.Request(c, auth.User)\n}\n\nfunc authCallbackGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tsig := c.Query(\"sig\")\n\tquery := strings.Split(c.Request.URL.RawQuery, \"&sig=\")[0]\n\n\tusr, tokn, errAudit, errData, err := auth.Callback(db, sig, query)\n\tif err != nil {\n\t\tswitch err.(type) {\n\t\tcase *auth.InvalidState:\n\t\t\tc.Redirect(302, \"/\")\n\t\t\tbreak\n\t\tdefault:\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif usr != nil {\n\t\t\tif errAudit == nil {\n\t\t\t\terrAudit = audit.Fields{\n\t\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\t\"message\": errData.Message,\n\t\t\t\t}\n\t\t\t}\n\t\t\terrAudit[\"method\"] = \"callback\"\n\n\t\t\terr = audit.New(\n\t\t\t\tdb,\n\t\t\t\tc.Request,\n\t\t\t\tusr.Id,\n\t\t\t\taudit.UserLoginFailed,\n\t\t\t\terrAudit,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserPrimaryApprove,\n\t\taudit.Fields{\n\t\t\t\"method\": \"callback\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevAuth, secProviderId, errAudit, errData, err := validator.ValidateUser(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"callback\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tif devAuth {\n\t\tdeviceCount, err := device.CountSecondary(db, usr.Id)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tsecType := \"\"\n\t\tvar secProvider bson.ObjectID\n\t\tif deviceCount == 0 {\n\t\t\tif secProviderId.IsZero() {\n\t\t\t\tsecType = secondary.UserDeviceRegister\n\t\t\t\tsecProvider = secondary.DeviceProvider\n\t\t\t} else {\n\t\t\t\tsecType = secondary.User\n\t\t\t\tsecProvider = secProviderId\n\t\t\t}\n\t\t} else {\n\t\t\tsecType = secondary.UserDevice\n\t\t\tsecProvider = secondary.DeviceProvider\n\t\t}\n\n\t\tsecd, err := secondary.New(db, usr.Id, secType, secProvider)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\turlQuery, err := secd.GetQuery()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif tokn.Query != \"\" {\n\t\t\turlQuery += \"&\" + tokn.Query\n\t\t}\n\n\t\tc.Redirect(302, \"/login?\"+urlQuery)\n\t\treturn\n\t} else if !secProviderId.IsZero() {\n\t\tsecd, err := secondary.New(db, usr.Id, secondary.User, secProviderId)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\turlQuery, err := secd.GetQuery()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif tokn.Query != \"\" {\n\t\t\turlQuery += \"&\" + tokn.Query\n\t\t}\n\n\t\tc.Redirect(302, \"/login?\"+urlQuery)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserLogin,\n\t\taudit.Fields{\n\t\t\t\"method\": \"callback\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewUser(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tredirectQuery(c, tokn.Query)\n}\n\nfunc authU2fAppGet(c *gin.Context) {\n\tc.JSON(200, device.GetFacets())\n}\n\nfunc authWanRegisterGet(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\ttoken := c.Query(\"token\")\n\n\tif node.Self.WebauthnDomain == \"\" {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"webauthn_domain_unavailable\",\n\t\t\tMessage: \"WebAuthn domain must be configured\",\n\t\t}\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, token, secondary.UserDeviceRegister)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserDeviceRegisterRequest,\n\t\taudit.Fields{},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tresp, errData, err := secd.DeviceRegisterRequest(db,\n\t\tutils.GetOrigin(c.Request))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLoginFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"method\":  \"device_register\",\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tc.JSON(200, resp)\n}\n\ntype devicesRegisterData struct {\n\tToken string `json:\"token\"`\n\tName  string `json:\"name\"`\n}\n\nfunc authWanRegisterPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &devicesRegisterData{}\n\n\tbody, err := utils.CopyBody(c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token, secondary.UserDeviceRegister)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_, _, errAudit, errData, err := validator.ValidateUser(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"device_register\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tdevc, errData, err := secd.DeviceRegisterResponse(\n\t\tdb, utils.GetOrigin(c.Request), body, data.Name)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.DeviceRegisterFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserDeviceRegister,\n\t\taudit.Fields{\n\t\t\t\"device_id\": devc.Id,\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserLogin,\n\t\taudit.Fields{\n\t\t\t\"method\": \"device_register\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewUser(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tredirectQueryJson(c, c.Request.URL.RawQuery)\n}\n\nfunc authWanRequestGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\ttoken := c.Query(\"token\")\n\n\tsecd, err := secondary.Get(db, token, secondary.UserDevice)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tresp, errData, err := secd.DeviceRequest(\n\t\tdb, utils.GetOrigin(c.Request))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLoginFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"method\":  \"device\",\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tc.JSON(200, resp)\n}\n\ntype authWanRespondData struct {\n\tToken string `json:\"token\"`\n}\n\nfunc authWanRespondPost(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tdata := &authWanRespondData{}\n\n\tbody, err := utils.CopyBody(c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token, secondary.UserDevice)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(401, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := secd.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_, secProviderId, errAudit, errData, err := validator.ValidateUser(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"device\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLoginFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terrData, err = secd.DeviceRespond(\n\t\tdb, utils.GetOrigin(c.Request), body)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserLoginFailed,\n\t\t\taudit.Fields{\n\t\t\t\t\"method\":  \"device\",\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserDeviceApprove,\n\t\taudit.Fields{},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif !secProviderId.IsZero() {\n\t\tsecd, err := secondary.New(db, usr.Id, secondary.User,\n\t\t\tsecProviderId)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata, err := secd.GetData()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(201, data)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserLogin,\n\t\taudit.Fields{\n\t\t\t\"method\": \"device\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcook := cookie.NewUser(c.Writer, c.Request)\n\n\t_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tredirectQueryJson(c, c.Request.URL.RawQuery)\n}\n"
  },
  {
    "path": "uhandlers/authority.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/authority\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype authorityData struct {\n\tId          bson.ObjectID `json:\"id\"`\n\tName        string        `json:\"name\"`\n\tComment     string        `json:\"comment\"`\n\tType        string        `json:\"type\"`\n\tRoles       []string      `json:\"roles\"`\n\tKey         string        `json:\"key\"`\n\tPrincipals  []string      `json:\"principals\"`\n\tCertificate string        `json:\"certificate\"`\n}\n\ntype authoritiesData struct {\n\tAuthorities []*authority.Authority `json:\"authorities\"`\n\tCount       int64                  `json:\"count\"`\n}\n\nfunc authorityPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &authorityData{}\n\n\tauthorityId, ok := utils.ParseObjectId(c.Param(\"authority_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tauthr, err := authority.GetOrg(db, userOrg, authorityId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tauthr.Name = data.Name\n\tauthr.Comment = data.Comment\n\tauthr.Type = data.Type\n\tauthr.Roles = data.Roles\n\tauthr.Key = data.Key\n\tauthr.Principals = data.Principals\n\tauthr.Certificate = data.Certificate\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"type\",\n\t\t\"organization\",\n\t\t\"roles\",\n\t\t\"key\",\n\t\t\"principals\",\n\t\t\"certificate\",\n\t)\n\n\terrData, err := authr.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = authr.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"authority.change\")\n\n\tc.JSON(200, authr)\n}\n\nfunc authorityPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &authorityData{\n\t\tName: \"new-authority\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfire := &authority.Authority{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tType:         data.Type,\n\t\tOrganization: userOrg,\n\t\tRoles:        data.Roles,\n\t\tKey:          data.Key,\n\t\tPrincipals:   data.Principals,\n\t\tCertificate:  data.Certificate,\n\t}\n\n\terrData, err := fire.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = fire.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"authority.change\")\n\n\tc.JSON(200, fire)\n}\n\nfunc authorityDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tauthorityId, ok := utils.ParseObjectId(c.Param(\"authority_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := authority.RemoveOrg(db, userOrg, authorityId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"authority.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc authoritiesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = authority.RemoveMultiOrg(db, userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"authority.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc authorityGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tauthr := demo.Authorities[0]\n\t\tc.JSON(200, authr)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tauthorityId, ok := utils.ParseObjectId(c.Param(\"authority_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tfire, err := authority.GetOrg(db, userOrg, authorityId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, fire)\n}\n\nfunc authoritiesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &authoritiesData{\n\t\t\tAuthorities: demo.Authorities,\n\t\t\tCount:       int64(len(demo.Authorities)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{\n\t\t\"organization\": userOrg,\n\t}\n\n\tauthrId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = authrId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\tprincipal := strings.TrimSpace(c.Query(\"principal\"))\n\tif principal != \"\" {\n\t\tquery[\"principals\"] = principal\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tauthorities, count, err := authority.GetAllPaged(\n\t\tdb, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &authoritiesData{\n\t\tAuthorities: authorities,\n\t\tCount:       count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "uhandlers/balancer.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/balancer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype balancerData struct {\n\tId           bson.ObjectID       `json:\"id\"`\n\tName         string              `json:\"name\"`\n\tComment      string              `json:\"comment\"`\n\tState        bool                `json:\"state\"`\n\tType         string              `json:\"type\"`\n\tDatacenter   bson.ObjectID       `json:\"datacenter\"`\n\tCertificates []bson.ObjectID     `json:\"certificates\"`\n\tWebSockets   bool                `json:\"websockets\"`\n\tDomains      []*balancer.Domain  `json:\"domains\"`\n\tBackends     []*balancer.Backend `json:\"backends\"`\n\tCheckPath    string              `json:\"check_path\"`\n}\n\ntype balancersData struct {\n\tBalancers []*balancer.Balancer `json:\"balancers\"`\n\tCount     int64                `json:\"count\"`\n}\n\nfunc balancerPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &balancerData{}\n\n\tbalancerId, ok := utils.ParseObjectId(c.Param(\"balancer_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tbalnc, err := balancer.GetOrg(db, userOrg, balancerId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tbalnc.Name = data.Name\n\tbalnc.Comment = data.Comment\n\tbalnc.State = data.State\n\tbalnc.Type = data.Type\n\tbalnc.Datacenter = data.Datacenter\n\tbalnc.Certificates = data.Certificates\n\tbalnc.WebSockets = data.WebSockets\n\tbalnc.Domains = data.Domains\n\tbalnc.Backends = data.Backends\n\tbalnc.CheckPath = data.CheckPath\n\n\texists, err := datacenter.ExistsOrg(db, userOrg, balnc.Datacenter)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"state\",\n\t\t\"type\",\n\t\t\"datacenter\",\n\t\t\"certificates\",\n\t\t\"websockets\",\n\t\t\"domains\",\n\t\t\"backends\",\n\t\t\"check_path\",\n\t)\n\n\terrData, err := balnc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = balnc.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"balancer.change\")\n\n\tbalnc.Json()\n\n\tc.JSON(200, balnc)\n}\n\nfunc balancerPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &balancerData{\n\t\tName: \"new-balancer\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tbalnc := &balancer.Balancer{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tState:        data.State,\n\t\tType:         data.Type,\n\t\tOrganization: userOrg,\n\t\tDatacenter:   data.Datacenter,\n\t\tCertificates: data.Certificates,\n\t\tWebSockets:   data.WebSockets,\n\t\tDomains:      data.Domains,\n\t\tBackends:     data.Backends,\n\t\tCheckPath:    data.CheckPath,\n\t}\n\n\texists, err := datacenter.ExistsOrg(db, userOrg, balnc.Datacenter)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\terrData, err := balnc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = balnc.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"balancer.change\")\n\n\tbalnc.Json()\n\n\tc.JSON(200, balnc)\n}\n\nfunc balancerDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tbalancerId, ok := utils.ParseObjectId(c.Param(\"balancer_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := balancer.RemoveOrg(db, userOrg, balancerId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"balancer.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc balancersDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = balancer.RemoveMultiOrg(db, userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"balancer.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc balancerGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tbalnc := demo.Balancers[0]\n\t\tc.JSON(200, balnc)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tbalancerId, ok := utils.ParseObjectId(c.Param(\"balancer_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tbalnc, err := balancer.GetOrg(db, userOrg, balancerId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tbalnc.Json()\n\n\tc.JSON(200, balnc)\n}\n\nfunc balancersGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &balancersData{\n\t\t\tBalancers: demo.Balancers,\n\t\t\tCount:     int64(len(demo.Balancers)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{\n\t\t\"organization\": userOrg,\n\t}\n\n\tbalancerId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = balancerId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tdatacenter, ok := utils.ParseObjectId(c.Query(\"datacenter\"))\n\tif ok {\n\t\tquery[\"datacenter\"] = datacenter\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tbalncs, count, err := balancer.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfor _, balnc := range balncs {\n\t\tbalnc.Json()\n\t}\n\n\tdata := &balancersData{\n\t\tBalancers: balncs,\n\t\tCount:     count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "uhandlers/certificate.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/acme\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype certificateData struct {\n\tId          bson.ObjectID `json:\"id\"`\n\tName        string        `json:\"name\"`\n\tComment     string        `json:\"comment\"`\n\tType        string        `json:\"type\"`\n\tKey         string        `json:\"key\"`\n\tCertificate string        `json:\"certificate\"`\n\tAcmeDomains []string      `json:\"acme_domains\"`\n\tAcmeAuth    string        `json:\"acme_auth\"`\n\tAcmeSecret  bson.ObjectID `json:\"acme_secret\"`\n\tRefresh     bool          `json:\"refresh\"`\n}\n\ntype certificatesData struct {\n\tCertificates []*certificate.Certificate `json:\"certificates\"`\n\tCount        int64                      `json:\"count\"`\n}\n\nfunc certificatePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &certificateData{}\n\n\tcertId, ok := utils.ParseObjectId(c.Param(\"cert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcert, err := certificate.GetOrg(db, userOrg, certId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif cert.Type == certificate.LetsEncrypt &&\n\t\tcert.AcmeType != certificate.AcmeDNS ||\n\t\tcert.AcmeType == certificate.AcmeHTTP {\n\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"acme_type_blocked\",\n\t\t\tMessage: \"Cannot modify LetsEncrypt HTTP verified certificates\",\n\t\t}\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tif !data.AcmeSecret.IsZero() {\n\t\texists, err := secret.ExistsOrg(db, userOrg, data.AcmeSecret)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tif !exists {\n\t\t\tutils.AbortWithStatus(c, 405)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tdata.AcmeSecret = bson.NilObjectID\n\t}\n\n\tcert.Name = data.Name\n\tcert.Comment = data.Comment\n\tcert.Key = data.Key\n\tcert.Certificate = data.Certificate\n\tcert.Type = data.Type\n\tcert.AcmeDomains = data.AcmeDomains\n\tcert.AcmeType = certificate.AcmeDNS\n\tcert.AcmeAuth = data.AcmeAuth\n\tcert.AcmeSecret = data.AcmeSecret\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"type\",\n\t\t\"acme_domains\",\n\t\t\"acme_type\",\n\t\t\"acme_auth\",\n\t\t\"acme_secret\",\n\t\t\"info\",\n\t)\n\n\tif cert.Type != certificate.LetsEncrypt {\n\t\tcert.Key = data.Key\n\t\tfields.Add(\"key\")\n\t\tcert.Certificate = data.Certificate\n\t\tfields.Add(\"certificate\")\n\t}\n\n\terrData, err := cert.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = cert.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif cert.Type == certificate.LetsEncrypt {\n\t\tacme.RenewBackground(cert, data.Refresh)\n\t}\n\n\tevent.PublishDispatch(db, \"certificate.change\")\n\n\tc.JSON(200, cert)\n}\n\nfunc certificatePost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &certificateData{\n\t\tName: \"new-certificate\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcert := &certificate.Certificate{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: userOrg,\n\t\tType:         data.Type,\n\t\tAcmeDomains:  data.AcmeDomains,\n\t\tAcmeType:     certificate.AcmeDNS,\n\t\tAcmeAuth:     data.AcmeAuth,\n\t\tAcmeSecret:   data.AcmeSecret,\n\t}\n\n\tif cert.Type != certificate.LetsEncrypt {\n\t\tcert.Key = data.Key\n\t\tcert.Certificate = data.Certificate\n\t}\n\n\tif !cert.AcmeSecret.IsZero() {\n\t\t_, err = secret.GetOrg(db, userOrg, cert.AcmeSecret)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcert.AcmeSecret = bson.NilObjectID\n\t}\n\n\terrData, err := cert.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = cert.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif cert.Type == certificate.LetsEncrypt {\n\t\tacme.RenewBackground(cert, false)\n\t}\n\n\tevent.PublishDispatch(db, \"certificate.change\")\n\n\tc.JSON(200, cert)\n}\n\nfunc certificateDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tcertId, ok := utils.ParseObjectId(c.Param(\"cert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := certificate.RemoveOrg(db, userOrg, certId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"certificate.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc certificatesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = certificate.RemoveMultiOrg(db, userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"certificate.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc certificateGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tcert := demo.Certificates[0]\n\t\tc.JSON(200, cert)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tcerts, err := certificate.GetAllNames(db, &bson.M{\n\t\t\t\"organization\": userOrg,\n\t\t})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, certs)\n\t\treturn\n\t}\n\n\tcertId, ok := utils.ParseObjectId(c.Param(\"cert_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tcert, err := certificate.GetOrg(db, userOrg, certId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif demo.IsDemo() {\n\t\tcert.Key = \"demo\"\n\t\tcert.AcmeAccount = \"demo\"\n\t}\n\n\tc.JSON(200, cert)\n}\n\nfunc certificatesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &certificatesData{\n\t\t\tCertificates: demo.Certificates,\n\t\t\tCount:        int64(len(demo.Certificates)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{\n\t\t\"organization\": userOrg,\n\t}\n\n\tcertificateId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = certificateId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tcerts, count, err := certificate.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &certificatesData{\n\t\tCertificates: certs,\n\t\tCount:        count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "uhandlers/check.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc checkGet(c *gin.Context) {\n\tc.String(200, \"ok\")\n}\n"
  },
  {
    "path": "uhandlers/completion.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/block\"\n\t\"github.com/pritunl/pritunl-cloud/certificate\"\n\t\"github.com/pritunl/pritunl-cloud/completion\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/organization\"\n\t\"github.com/pritunl/pritunl-cloud/plan\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\nfunc completionGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tif demo.IsDemo() {\n\t\tdata := &completion.Completion{}\n\n\t\tfor _, item := range demo.Organizations {\n\t\t\tdata.Organizations = append(data.Organizations, &database.Named{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Authorities {\n\t\t\tdata.Authorities = append(data.Authorities, &database.Named{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Policies {\n\t\t\tdata.Policies = append(data.Policies, &database.Named{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Domains {\n\t\t\tdata.Domains = append(data.Domains, &domain.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Vpcs {\n\t\t\tdata.Vpcs = append(data.Vpcs, &vpc.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t\tVpcId:        item.VpcId,\n\t\t\t\tNetwork:      item.Network,\n\t\t\t\tSubnets:      item.Subnets,\n\t\t\t\tDatacenter:   item.Datacenter,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Datacenters {\n\t\t\tdata.Datacenters = append(data.Datacenters, &datacenter.Completion{\n\t\t\t\tId:          item.Id,\n\t\t\t\tName:        item.Name,\n\t\t\t\tNetworkMode: item.NetworkMode,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Blocks {\n\t\t\tdata.Blocks = append(data.Blocks, &block.Completion{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t\tType: item.Type,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Nodes {\n\t\t\tdata.Nodes = append(data.Nodes, &node.Completion{\n\t\t\t\tId:    item.Id,\n\t\t\t\tName:  item.Name,\n\t\t\t\tZone:  item.Zone,\n\t\t\t\tTypes: item.Types,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Pools {\n\t\t\tdata.Pools = append(data.Pools, &pool.Completion{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t\tZone: item.Zone,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Zones {\n\t\t\tdata.Zones = append(data.Zones, &zone.Completion{\n\t\t\t\tId:         item.Id,\n\t\t\t\tDatacenter: item.Datacenter,\n\t\t\t\tName:       item.Name,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Shapes {\n\t\t\tdata.Shapes = append(data.Shapes, &shape.Completion{\n\t\t\t\tId:         item.Id,\n\t\t\t\tName:       item.Name,\n\t\t\t\tDatacenter: item.Datacenter,\n\t\t\t\tFlexible:   item.Flexible,\n\t\t\t\tMemory:     item.Memory,\n\t\t\t\tProcessors: item.Processors,\n\t\t\t})\n\t\t}\n\n\t\timgs, err := image.GetAllCompletion(db, &bson.M{})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tdata.Images = imgs\n\n\t\tfor _, item := range demo.Storages {\n\t\t\tdata.Storages = append(data.Storages, &storage.Completion{\n\t\t\t\tId:   item.Id,\n\t\t\t\tName: item.Name,\n\t\t\t\tType: item.Type,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Instances {\n\t\t\tdata.Instances = append(data.Instances, &instance.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t\tZone:         item.Zone,\n\t\t\t\tVpc:          item.Vpc,\n\t\t\t\tSubnet:       item.Subnet,\n\t\t\t\tNode:         item.Node,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Plans {\n\t\t\tdata.Plans = append(data.Plans, &plan.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Certificates {\n\t\t\tdata.Certificates = append(\n\t\t\t\tdata.Certificates,\n\t\t\t\t&certificate.Completion{\n\t\t\t\t\tId:           item.Id,\n\t\t\t\t\tName:         item.Name,\n\t\t\t\t\tOrganization: item.Organization,\n\t\t\t\t\tType:         item.Type,\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\tfor _, item := range demo.Secrets {\n\t\t\tdata.Secrets = append(data.Secrets, &secret.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t\tType:         item.Type,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Pods {\n\t\t\tdata.Pods = append(data.Pods, &pod.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tName:         item.Name,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t})\n\t\t}\n\n\t\tfor _, item := range demo.Units {\n\t\t\tdata.Units = append(data.Units, &unit.Completion{\n\t\t\t\tId:           item.Id,\n\t\t\t\tPod:          item.Pod,\n\t\t\t\tOrganization: item.Organization,\n\t\t\t\tName:         item.Name,\n\t\t\t\tKind:         item.Kind,\n\t\t\t})\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvar userOrg bson.ObjectID\n\torgIdStr := c.GetHeader(\"Organization\")\n\n\tif orgIdStr != \"\" {\n\t\torgId, ok := utils.ParseObjectId(orgIdStr)\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\torg, err := organization.Get(db, orgId)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tmatch := usr.RolesMatch(org.Roles)\n\t\tif !match {\n\t\t\tutils.AbortWithStatus(c, 401)\n\t\t\treturn\n\t\t}\n\n\t\tuserOrg = org.Id\n\t} else {\n\t\torgs, err := organization.GetAll(db, &bson.M{\n\t\t\t\"roles\": &bson.M{\n\t\t\t\t\"$in\": usr.Roles,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif len(orgs) > 0 {\n\t\t\torg := orgs[0]\n\n\t\t\tmatch := usr.RolesMatch(org.Roles)\n\t\t\tif !match {\n\t\t\t\tutils.AbortWithStatus(c, 401)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tuserOrg = org.Id\n\t\t}\n\t}\n\n\tif userOrg.IsZero() {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tcmpl, err := completion.GetCompletion(db, userOrg, usr.Roles)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, cmpl)\n}\n"
  },
  {
    "path": "uhandlers/csrf.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/csrf\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype csrfData struct {\n\tToken         string `json:\"token\"`\n\tTheme         string `json:\"theme\"`\n\tEditorTheme   string `json:\"editor_theme\"`\n\tOracleLicense bool   `json:\"oracle_license\"`\n}\n\nfunc csrfGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\ttoken, err := csrf.NewToken(db, authr.SessionId())\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\toracleLicense := usr.OracleLicense\n\tif demo.IsDemo() {\n\t\toracleLicense = true\n\t}\n\n\tdata := &csrfData{\n\t\tToken:         token,\n\t\tTheme:         usr.Theme,\n\t\tEditorTheme:   usr.EditorTheme,\n\t\tOracleLicense: oracleLicense,\n\t}\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "uhandlers/datacenter.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc datacentersGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tdcs, err := datacenter.GetAllNamesOrg(db, userOrg)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, dcs)\n}\n"
  },
  {
    "path": "uhandlers/devices.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/device\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/secondary\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/validator\"\n)\n\ntype deviceData struct {\n\tName string `json:\"name\"`\n}\n\nfunc devicePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &deviceData{}\n\n\tdevcId, ok := utils.ParseObjectId(c.Param(\"device_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevc, err := device.GetUser(db, devcId, usr.Id)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevc.Name = data.Name\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t)\n\n\terrData, err := devc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = devc.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\tc.JSON(200, devc)\n}\n\nfunc deviceDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevcId, ok := utils.ParseObjectId(c.Param(\"device_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tcount, err := device.CountSecondary(db, usr.Id)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif count <= 1 {\n\t\tusr.Disabled = true\n\t\terr = usr.CommitFields(db, set.NewSet(\"disabled\"))\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = device.RemoveUser(db, devcId, usr.Id)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tcount, err = device.CountSecondary(db, usr.Id)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif count == 0 {\n\t\tif !usr.Disabled {\n\t\t\tusr.Disabled = true\n\t\t\terr = usr.CommitFields(db, set.NewSet(\"disabled\"))\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserAccountDisable,\n\t\t\taudit.Fields{\n\t\t\t\t\"reason\": \"All authentication devices removed\",\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"device_empty\",\n\t\t\tMessage: \"Account disabled contact an administrator\",\n\t\t}\n\t\tc.JSON(401, errData)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc devicesGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdevices, err := device.GetAllSorted(db, usr.Id)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, devices)\n}\n\ntype devicesWanRegisterRespData struct {\n\tToken   string      `json:\"token\"`\n\tOptions interface{} `json:\"options\"`\n}\n\nfunc deviceWanRegisterGet(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tif node.Self.WebauthnDomain == \"\" {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"webauthn_domain_unavailable\",\n\t\t\tMessage: \"WebAuthn domain must be configured\",\n\t\t}\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_, secProviderId, errAudit, errData, err := validator.ValidateUser(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"add_device_register\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserAuthFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tdeviceCount, err := device.CountSecondary(db, usr.Id)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif deviceCount > 0 || !secProviderId.IsZero() {\n\t\tsecType := \"\"\n\t\tvar secProvider bson.ObjectID\n\n\t\tif deviceCount == 0 {\n\t\t\tsecType = secondary.UserManage\n\t\t\tsecProvider = secProviderId\n\t\t} else {\n\t\t\tsecType = secondary.UserManageDevice\n\t\t\tsecProvider = secondary.DeviceProvider\n\t\t}\n\n\t\tsecd, err := secondary.New(db, usr.Id, secType, secProvider)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata, err := secd.GetData()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(201, data)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.New(db, usr.Id, secondary.UserManageDeviceRegister,\n\t\tsecondary.DeviceProvider)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.UserDeviceRegisterRequest,\n\t\taudit.Fields{},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tjsonResp, errData, err := secd.DeviceRegisterRequest(db,\n\t\tutils.GetOrigin(c.Request))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tresp := &devicesWanRegisterRespData{\n\t\tToken:   secd.Id,\n\t\tOptions: jsonResp,\n\t}\n\n\tc.JSON(200, resp)\n}\n\ntype devicesWanRegisterData struct {\n\tToken string `json:\"token\"`\n\tName  string `json:\"name\"`\n}\n\nfunc deviceWanRegisterPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &devicesWanRegisterData{}\n\n\tbody, err := utils.CopyBody(c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token,\n\t\tsecondary.UserManageDeviceRegister)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(400, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tdevc, errData, err := secd.DeviceRegisterResponse(\n\t\tdb, utils.GetOrigin(c.Request), body, data.Name)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = audit.New(\n\t\tdb,\n\t\tc.Request,\n\t\tusr.Id,\n\t\taudit.DeviceRegister,\n\t\taudit.Fields{\n\t\t\t\"device_id\": devc.Id,\n\t\t},\n\t)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"device.change\")\n\n\tc.JSON(200, nil)\n}\n\ntype deviceSecondaryData struct {\n\tType     string `json:\"type\"`\n\tToken    string `json:\"token\"`\n\tFactor   string `json:\"factor\"`\n\tPasscode string `json:\"passcode\"`\n}\n\nfunc deviceSecondaryPut(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &deviceSecondaryData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token, secondary.UserManage)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(400, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\terrData, err := secd.Handle(db, c.Request, data.Factor, data.Passcode)\n\tif err != nil {\n\t\tif _, ok := err.(*secondary.IncompleteError); ok {\n\t\t\tc.Status(206)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err = secondary.New(db, usr.Id, secondary.UserManageDeviceRegister,\n\t\tsecondary.DeviceProvider)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tjsonResp, errData, err := secd.DeviceRegisterRequest(db,\n\t\tutils.GetOrigin(c.Request))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tresp := &devicesWanRegisterRespData{\n\t\tToken:   secd.Id,\n\t\tOptions: jsonResp,\n\t}\n\n\tc.JSON(200, resp)\n}\n\nfunc deviceWanRequestGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\ttoken := c.Query(\"token\")\n\n\tsecd, err := secondary.Get(db, token, secondary.UserManageDevice)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(400, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tresp, errData, err := secd.DeviceRequest(\n\t\tdb, utils.GetOrigin(c.Request))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tc.JSON(200, resp)\n}\n\ntype deviceWanRespondData struct {\n\tToken string `json:\"token\"`\n}\n\nfunc deviceWanRespondPost(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &deviceWanRespondData{}\n\n\tbody, err := utils.CopyBody(c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecd, err := secondary.Get(db, data.Token, secondary.UserManageDevice)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"secondary_expired\",\n\t\t\t\tMessage: \"Secondary authentication has expired\",\n\t\t\t}\n\t\t\tc.JSON(400, errData)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\t_, secProviderId, errAudit, errData, err := validator.ValidateUser(\n\t\tdb, usr, false, c.Request)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terrData, err = secd.DeviceRespond(\n\t\tdb, utils.GetOrigin(c.Request), body)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tif errAudit == nil {\n\t\t\terrAudit = audit.Fields{\n\t\t\t\t\"error\":   errData.Error,\n\t\t\t\t\"message\": errData.Message,\n\t\t\t}\n\t\t}\n\t\terrAudit[\"method\"] = \"add_device_register\"\n\n\t\terr = audit.New(\n\t\t\tdb,\n\t\t\tc.Request,\n\t\t\tusr.Id,\n\t\t\taudit.UserAuthFailed,\n\t\t\terrAudit,\n\t\t)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tif !secProviderId.IsZero() {\n\t\tsecd, err := secondary.New(db, usr.Id,\n\t\t\tsecondary.UserManage, secProviderId)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata, err := secd.GetData()\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(201, data)\n\t\treturn\n\t}\n\n\tsecd, err = secondary.New(db, usr.Id, secondary.UserManageDeviceRegister,\n\t\tsecondary.DeviceProvider)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tjsonResp, errData, err := secd.DeviceRegisterRequest(db,\n\t\tutils.GetOrigin(c.Request))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tresp := &devicesWanRegisterRespData{\n\t\tToken:   secd.Id,\n\t\tOptions: jsonResp,\n\t}\n\n\tc.JSON(200, resp)\n}\n"
  },
  {
    "path": "uhandlers/disk.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype diskData struct {\n\tId               bson.ObjectID `json:\"id\"`\n\tName             string        `json:\"name\"`\n\tComment          string        `json:\"comment\"`\n\tInstance         bson.ObjectID `json:\"instance\"`\n\tIndex            string        `json:\"index\"`\n\tType             string        `json:\"type\"`\n\tNode             bson.ObjectID `json:\"node\"`\n\tPool             bson.ObjectID `json:\"pool\"`\n\tDeleteProtection bool          `json:\"delete_protection\"`\n\tFileSystem       string        `json:\"file_system\"`\n\tImage            bson.ObjectID `json:\"image\"`\n\tRestoreImage     bson.ObjectID `json:\"restore_image\"`\n\tBacking          bool          `json:\"backing\"`\n\tAction           string        `json:\"action\"`\n\tSize             int           `json:\"size\"`\n\tLvSize           int           `json:\"lv_size\"`\n\tNewSize          int           `json:\"new_size\"`\n\tBackup           bool          `json:\"backup\"`\n}\n\ntype disksMultiData struct {\n\tIds    []bson.ObjectID `json:\"ids\"`\n\tAction string          `json:\"action\"`\n}\n\ntype disksData struct {\n\tDisks []*aggregate.DiskAggregate `json:\"disks\"`\n\tCount int64                      `json:\"count\"`\n}\n\nfunc diskPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := &diskData{}\n\n\tdiskId, ok := utils.ParseObjectId(c.Param(\"disk_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdsk, err := disk.GetOrg(db, userOrg, diskId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"type\",\n\t\t\"instance\",\n\t\t\"delete_protection\",\n\t\t\"index\",\n\t\t\"backup\",\n\t\t\"new_size\",\n\t)\n\n\tif !dta.Instance.IsZero() {\n\t\texists, err := instance.ExistsOrg(db, userOrg, dta.Instance)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tif !exists {\n\t\t\tutils.AbortWithStatus(c, 405)\n\t\t\treturn\n\t\t}\n\t}\n\n\tdsk.PreCommit()\n\n\tif dta.Action != \"\" && dsk.Action != \"\" {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"disk_actin_active\",\n\t\t\tMessage: \"Disk action already active\",\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tif dsk.IsActive() && dta.Action == disk.Snapshot {\n\t\tdsk.Action = disk.Snapshot\n\t\tfields.Add(\"action\")\n\t} else if dsk.IsActive() && dta.Action == disk.Backup {\n\t\tdsk.Action = disk.Backup\n\t\tfields.Add(\"action\")\n\t} else if dsk.IsActive() && dta.Action == disk.Expand {\n\t\tdsk.Action = disk.Expand\n\t\tdsk.NewSize = dta.NewSize\n\t\tfields.Add(\"action\")\n\t} else if dsk.IsActive() && dta.Action == disk.Restore {\n\t\timg, err := image.GetOrg(db, userOrg, dta.RestoreImage)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif img.Disk != dsk.Id {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_restore_image\",\n\t\t\t\tMessage: \"Invalid restore image\",\n\t\t\t}\n\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\n\t\tdsk.Action = disk.Restore\n\t\tdsk.RestoreImage = img.Id\n\n\t\tfields.Add(\"action\")\n\t\tfields.Add(\"restore_image\")\n\t}\n\n\terrData, err := dsk.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = dsk.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, dsk)\n}\n\nfunc diskPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := &diskData{\n\t\tName: \"new-disk\",\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif !dta.Instance.IsZero() {\n\t\texists, err := instance.ExistsOrg(db, userOrg, dta.Instance)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tif !exists {\n\t\t\tutils.AbortWithStatus(c, 405)\n\t\t\treturn\n\t\t}\n\t}\n\n\tnde, err := node.Get(db, dta.Node)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\texists, err := datacenter.ExistsOrg(db, userOrg, nde.Datacenter)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\timgSystemType := \"\"\n\timgSystemKind := \"\"\n\tif !dta.Image.IsZero() {\n\t\timg, err := image.GetOrgPublic(db, userOrg, dta.Image)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\timgSystemType = img.GetSystemType()\n\t\timgSystemKind = img.GetSystemKind()\n\n\t\tstore, err := storage.Get(db, img.Storage)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tavailable, err := data.ImageAvailable(store, img)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tif !available {\n\t\t\tif store.IsOracle() {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_available\",\n\t\t\t\t\tMessage: \"Image not restored from archive\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t} else {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_available\",\n\t\t\t\t\tMessage: \"Image not restored from glacier\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\t}\n\n\tdsk := &disk.Disk{\n\t\tName:             dta.Name,\n\t\tComment:          dta.Comment,\n\t\tOrganization:     userOrg,\n\t\tInstance:         dta.Instance,\n\t\tDatacenter:       nde.Datacenter,\n\t\tZone:             nde.Zone,\n\t\tIndex:            dta.Index,\n\t\tType:             dta.Type,\n\t\tSystemType:       imgSystemType,\n\t\tSystemKind:       imgSystemKind,\n\t\tNode:             dta.Node,\n\t\tPool:             dta.Pool,\n\t\tImage:            dta.Image,\n\t\tDeleteProtection: dta.DeleteProtection,\n\t\tFileSystem:       dta.FileSystem,\n\t\tBacking:          dta.Backing,\n\t\tSize:             dta.Size,\n\t\tLvSize:           dta.LvSize,\n\t\tBackup:           dta.Backup,\n\t}\n\n\terrData, err := dsk.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = dsk.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, dsk)\n}\n\nfunc disksPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &disksMultiData{}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif data.Action != disk.Snapshot && data.Action != disk.Backup {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"invalid_action\",\n\t\t\tMessage: \"Invalid disk action\",\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tdoc := bson.M{\n\t\t\"action\": data.Action,\n\t}\n\n\terr = disk.UpdateMultiOrg(db, userOrg, data.Ids, &doc)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc diskDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tdiskId, ok := utils.ParseObjectId(c.Param(\"disk_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdsk, err := disk.Get(db, diskId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif dsk.DeleteProtection {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"delete_protection\",\n\t\t\tMessage: \"Cannot delete disk with delete protection\",\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tif !dsk.Instance.IsZero() {\n\t\tinst, e := instance.Get(db, dsk.Instance)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif inst.DeleteProtection {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError: \"instance_delete_protection\",\n\t\t\t\tMessage: \"Cannot delete disk attached to \" +\n\t\t\t\t\t\"instance with delete protection\",\n\t\t\t}\n\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = disk.DeleteOrg(db, userOrg, diskId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc disksDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := []bson.ObjectID{}\n\n\terr := c.Bind(&dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = disk.DeleteMultiOrg(db, userOrg, dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"disk.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc diskGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdsk := demo.Disks[0]\n\t\tc.JSON(200, dsk)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tdiskId, ok := utils.ParseObjectId(c.Param(\"disk_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdsk, err := disk.GetOrg(db, userOrg, diskId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, dsk)\n}\n\nfunc disksGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &disksData{\n\t\t\tDisks: demo.Disks,\n\t\t\tCount: int64(len(demo.Disks)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{\n\t\t\"organization\": userOrg,\n\t}\n\n\tdiskId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = diskId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tinst, ok := utils.ParseObjectId(c.Query(\"instance\"))\n\tif ok {\n\t\tquery[\"instance\"] = inst\n\t}\n\n\tnodeId, ok := utils.ParseObjectId(c.Query(\"node\"))\n\tif ok {\n\t\tquery[\"node\"] = nodeId\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tdisks, count, err := aggregate.GetDiskPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdta := &disksData{\n\t\tDisks: disks,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, dta)\n}\n"
  },
  {
    "path": "uhandlers/domain.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/domain\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype domainData struct {\n\tId         bson.ObjectID    `json:\"id\"`\n\tName       string           `json:\"name\"`\n\tComment    string           `json:\"comment\"`\n\tType       string           `json:\"type\"`\n\tSecret     bson.ObjectID    `json:\"secret\"`\n\tRootDomain string           `json:\"root_domain\"`\n\tRecords    []*domain.Record `json:\"records\"`\n}\n\ntype domainsData struct {\n\tDomains []*domain.Domain `json:\"domains\"`\n\tCount   int64            `json:\"count\"`\n}\n\nfunc domainPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &domainData{}\n\n\tdomainId, ok := utils.ParseObjectId(c.Param(\"domain_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdomn, err := domain.GetOrg(db, userOrg, domainId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = domn.LoadRecords(db, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdomn.PreCommit()\n\n\tdomn.Name = data.Name\n\tdomn.Comment = data.Comment\n\tdomn.Type = data.Type\n\tdomn.Secret = data.Secret\n\tdomn.RootDomain = data.RootDomain\n\tdomn.Records = data.Records\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"type\",\n\t\t\"secret\",\n\t\t\"root_domain\",\n\t)\n\n\terrData, err := domn.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = domn.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = domn.CommitRecords(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\n\tc.JSON(200, domn)\n}\n\nfunc domainPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &domainData{\n\t\tName: \"new.domain\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdomn := &domain.Domain{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: userOrg,\n\t\tType:         data.Type,\n\t\tSecret:       data.Secret,\n\t\tRootDomain:   data.RootDomain,\n\t}\n\n\terrData, err := domn.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = domn.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\n\tc.JSON(200, domn)\n}\n\nfunc domainDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tdomainId, ok := utils.ParseObjectId(c.Param(\"domain_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := domain.RemoveOrg(db, userOrg, domainId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc domainsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = domain.RemoveMultiOrg(db, userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"domain.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc domainGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdomn := demo.Domains[0]\n\t\tc.JSON(200, domn)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tdomainId, ok := utils.ParseObjectId(c.Param(\"domain_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdomn, err := domain.GetOrg(db, userOrg, domainId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = domn.LoadRecords(db, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdomn.Json()\n\n\tc.JSON(200, domn)\n}\n\nfunc domainsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &domainsData{\n\t\t\tDomains: demo.Domains,\n\t\t\tCount:   int64(len(demo.Domains)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tdomns, err := domain.GetAllName(db, &bson.M{\n\t\t\t\"organization\": userOrg,\n\t\t})\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, domns)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{\n\t\t\t\"organization\": userOrg,\n\t\t}\n\n\t\tdomainId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = domainId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"name\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\tdomains, count, err := aggregate.GetDomainPaged(\n\t\t\tdb, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata := &domainsData{\n\t\t\tDomains: domains,\n\t\t\tCount:   count,\n\t\t}\n\n\t\tc.JSON(200, data)\n\t}\n}\n"
  },
  {
    "path": "uhandlers/event.go",
    "content": "package uhandlers\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst (\n\twriteTimeout = 10 * time.Second\n\tpingInterval = 30 * time.Second\n\tpingWait     = 40 * time.Second\n)\n\nfunc eventGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tsocket := &event.WebSocket{}\n\n\tdefer func() {\n\t\tsocket.Close()\n\t\tevent.WebSocketsLock.Lock()\n\t\tevent.WebSockets.Remove(socket)\n\t\tevent.WebSocketsLock.Unlock()\n\t}()\n\n\tevent.WebSocketsLock.Lock()\n\tevent.WebSockets.Add(socket)\n\tevent.WebSocketsLock.Unlock()\n\n\tctx, cancel := context.WithCancel(db)\n\tsocket.Cancel = cancel\n\n\tconn, err := event.Upgrader.Upgrade(c.Writer, c.Request, nil)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"uhandlers: Failed to upgrade request\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tsocket.Conn = conn\n\n\terr = conn.SetReadDeadline(time.Now().Add(pingWait))\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"uhandlers: Failed to set read deadline\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tconn.SetPongHandler(func(x string) (err error) {\n\t\terr = conn.SetReadDeadline(time.Now().Add(pingWait))\n\t\tif err != nil {\n\t\t\terr = &errortypes.RequestError{\n\t\t\t\terrors.Wrap(err, \"uhandlers: Failed to set read deadline\"),\n\t\t\t}\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t})\n\n\tlst, err := event.SubscribeListener(db, []string{\"dispatch\"})\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tsocket.Listener = lst\n\n\tticker := time.NewTicker(pingInterval)\n\tsocket.Ticker = ticker\n\tsub := lst.Listen()\n\tdefer lst.Close()\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tr := recover()\n\t\t\tif r != nil && !socket.Closed {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": errors.New(fmt.Sprintf(\"%s\", r)),\n\t\t\t\t}).Error(\"mhandlers: Event panic\")\n\t\t\t}\n\t\t}()\n\t\tfor {\n\t\t\t_, _, err := conn.NextReader()\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase msg, ok := <-sub:\n\t\t\tif !ok {\n\t\t\t\terr = conn.WriteControl(websocket.CloseMessage, []byte{},\n\t\t\t\t\ttime.Now().Add(writeTimeout))\n\t\t\t\tif err != nil {\n\t\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\t\"uhandlers: Failed to set write control\"),\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = conn.SetWriteDeadline(time.Now().Add(writeTimeout))\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"uhandlers: Failed to set write deadline\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = conn.WriteJSON(msg)\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"uhandlers: Failed to set write json\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-ticker.C:\n\t\t\terr = conn.WriteControl(websocket.PingMessage, []byte{},\n\t\t\t\ttime.Now().Add(writeTimeout))\n\t\t\tif err != nil {\n\t\t\t\terr = &errortypes.RequestError{\n\t\t\t\t\terrors.Wrap(err,\n\t\t\t\t\t\t\"uhandlers: Failed to set write control\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "uhandlers/firewall.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/firewall\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype firewallData struct {\n\tId      bson.ObjectID    `json:\"id\"`\n\tName    string           `json:\"name\"`\n\tComment string           `json:\"comment\"`\n\tRoles   []string         `json:\"roles\"`\n\tIngress []*firewall.Rule `json:\"ingress\"`\n}\n\ntype firewallsData struct {\n\tFirewalls []*firewall.Firewall `json:\"firewalls\"`\n\tCount     int64                `json:\"count\"`\n}\n\nfunc firewallPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &firewallData{}\n\n\tfirewallId, ok := utils.ParseObjectId(c.Param(\"firewall_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfire, err := firewall.GetOrg(db, userOrg, firewallId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfire.Name = data.Name\n\tfire.Comment = data.Comment\n\tfire.Roles = data.Roles\n\tfire.Ingress = data.Ingress\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"roles\",\n\t\t\"ingress\",\n\t)\n\n\terrData, err := fire.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = fire.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"firewall.change\")\n\n\tc.JSON(200, fire)\n}\n\nfunc firewallPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &firewallData{\n\t\tName: \"new-firewall\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfire := &firewall.Firewall{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: userOrg,\n\t\tRoles:        data.Roles,\n\t\tIngress:      data.Ingress,\n\t}\n\n\terrData, err := fire.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = fire.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"firewall.change\")\n\n\tc.JSON(200, fire)\n}\n\nfunc firewallDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tfirewallId, ok := utils.ParseObjectId(c.Param(\"firewall_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteOrg(db, \"firewall\", userOrg, firewallId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = firewall.RemoveOrg(db, userOrg, firewallId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"firewall.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc firewallsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteOrgAll(db, \"firewall\", userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = firewall.RemoveMultiOrg(db, userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"firewall.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc firewallGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tfire := demo.Firewalls[0]\n\t\tc.JSON(200, fire)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tfirewallId, ok := utils.ParseObjectId(c.Param(\"firewall_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tfire, err := firewall.GetOrg(db, userOrg, firewallId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, fire)\n}\n\nfunc firewallsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &firewallsData{\n\t\t\tFirewalls: demo.Firewalls,\n\t\t\tCount:     int64(len(demo.Firewalls)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{\n\t\t\"organization\": userOrg,\n\t}\n\n\tfirewallId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = firewallId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\tfirewalls, count, err := firewall.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &firewallsData{\n\t\tFirewalls: firewalls,\n\t\tCount:     count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "uhandlers/handlers.go",
    "content": "package uhandlers\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/config\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/middlewear\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/pritunl/pritunl-cloud/static\"\n)\n\nvar (\n\tstore      *static.Store\n\tfileServer http.Handler\n)\n\nfunc Register(engine *gin.Engine) {\n\tengine.Use(middlewear.Limiter)\n\tengine.Use(middlewear.Counter)\n\tengine.Use(middlewear.Recovery)\n\tengine.Use(middlewear.Headers)\n\n\tdbGroup := engine.Group(\"\")\n\tdbGroup.Use(middlewear.Database)\n\n\tsessGroup := dbGroup.Group(\"\")\n\tsessGroup.Use(middlewear.SessionUser)\n\n\tauthGroup := sessGroup.Group(\"\")\n\tauthGroup.Use(middlewear.AuthUser)\n\n\tcsrfGroup := authGroup.Group(\"\")\n\tcsrfGroup.Use(middlewear.CsrfToken)\n\n\torgGroup := csrfGroup.Group(\"\")\n\torgGroup.Use(middlewear.UserOrg)\n\n\tengine.NoRoute(middlewear.NotFound)\n\n\torgGroup.GET(\"/alert\", alertsGet)\n\torgGroup.GET(\"/alert/:alert_id\", alertGet)\n\torgGroup.PUT(\"/alert/:alert_id\", alertPut)\n\torgGroup.POST(\"/alert\", alertPost)\n\torgGroup.DELETE(\"/alert\", alertsDelete)\n\torgGroup.DELETE(\"/alert/:alert_id\", alertDelete)\n\n\tengine.GET(\"/auth/state\", authStateGet)\n\tdbGroup.POST(\"/auth/session\", authSessionPost)\n\tdbGroup.POST(\"/auth/secondary\", authSecondaryPost)\n\tdbGroup.GET(\"/auth/request\", authRequestGet)\n\tdbGroup.GET(\"/auth/callback\", authCallbackGet)\n\tengine.GET(\"/auth/u2f/app.json\", authU2fAppGet)\n\tdbGroup.GET(\"/auth/webauthn/request\", authWanRequestGet)\n\tdbGroup.POST(\"/auth/webauthn/respond\", authWanRespondPost)\n\tdbGroup.GET(\"/auth/webauthn/register\", authWanRegisterGet)\n\tdbGroup.POST(\"/auth/webauthn/register\", authWanRegisterPost)\n\tsessGroup.GET(\"/logout\", logoutGet)\n\tsessGroup.GET(\"/logout_all\", logoutAllGet)\n\n\torgGroup.GET(\"/authority\", authoritiesGet)\n\torgGroup.GET(\"/authority/:authority_id\", authorityGet)\n\torgGroup.PUT(\"/authority/:authority_id\", authorityPut)\n\torgGroup.POST(\"/authority\", authorityPost)\n\torgGroup.DELETE(\"/authority\", authoritiesDelete)\n\torgGroup.DELETE(\"/authority/:authority_id\", authorityDelete)\n\n\torgGroup.GET(\"/balancer\", balancersGet)\n\torgGroup.GET(\"/balancer/:balancer_id\", balancerGet)\n\torgGroup.PUT(\"/balancer/:balancer_id\", balancerPut)\n\torgGroup.POST(\"/balancer\", balancerPost)\n\torgGroup.DELETE(\"/balancer\", balancersDelete)\n\torgGroup.DELETE(\"/balancer/:balancer_id\", balancerDelete)\n\n\torgGroup.GET(\"/certificate\", certificatesGet)\n\torgGroup.GET(\"/certificate/:cert_id\", certificateGet)\n\torgGroup.PUT(\"/certificate/:cert_id\", certificatePut)\n\torgGroup.POST(\"/certificate\", certificatePost)\n\torgGroup.DELETE(\"/certificate/\", certificatesDelete)\n\torgGroup.DELETE(\"/certificate/:cert_id\", certificateDelete)\n\n\tengine.GET(\"/check\", checkGet)\n\n\tauthGroup.GET(\"/csrf\", csrfGet)\n\n\tcsrfGroup.GET(\"/completion\", completionGet)\n\n\torgGroup.GET(\"/datacenter\", datacentersGet)\n\n\tcsrfGroup.GET(\"/device\", devicesGet)\n\tcsrfGroup.PUT(\"/device/:device_id\", devicePut)\n\tcsrfGroup.DELETE(\"/device/:device_id\", deviceDelete)\n\tcsrfGroup.PUT(\"/device/:device_id/secondary\", deviceSecondaryPut)\n\tcsrfGroup.GET(\"/device/:device_id/request\", deviceWanRequestGet)\n\tcsrfGroup.POST(\"/device/:device_id/respond\", deviceWanRespondPost)\n\tcsrfGroup.GET(\"/device/:device_id/register\", deviceWanRegisterGet)\n\tcsrfGroup.POST(\"/device/:device_id/register\", deviceWanRegisterPost)\n\n\torgGroup.GET(\"/domain\", domainsGet)\n\torgGroup.GET(\"/domain/:domain_id\", domainGet)\n\torgGroup.PUT(\"/domain/:domain_id\", domainPut)\n\torgGroup.POST(\"/domain\", domainPost)\n\torgGroup.DELETE(\"/domain\", domainsDelete)\n\torgGroup.DELETE(\"/domain/:domain_id\", domainDelete)\n\n\torgGroup.GET(\"/disk\", disksGet)\n\torgGroup.GET(\"/disk/:disk_id\", diskGet)\n\torgGroup.PUT(\"/disk\", disksPut)\n\torgGroup.PUT(\"/disk/:disk_id\", diskPut)\n\torgGroup.POST(\"/disk\", diskPost)\n\torgGroup.DELETE(\"/disk\", disksDelete)\n\torgGroup.DELETE(\"/disk/:disk_id\", diskDelete)\n\n\tcsrfGroup.GET(\"/event\", eventGet)\n\n\torgGroup.GET(\"/firewall\", firewallsGet)\n\torgGroup.GET(\"/firewall/:firewall_id\", firewallGet)\n\torgGroup.PUT(\"/firewall/:firewall_id\", firewallPut)\n\torgGroup.POST(\"/firewall\", firewallPost)\n\torgGroup.DELETE(\"/firewall\", firewallsDelete)\n\torgGroup.DELETE(\"/firewall/:firewall_id\", firewallDelete)\n\n\torgGroup.GET(\"/image\", imagesGet)\n\torgGroup.GET(\"/image/:image_id\", imageGet)\n\torgGroup.PUT(\"/image/:image_id\", imagePut)\n\torgGroup.DELETE(\"/image\", imagesDelete)\n\torgGroup.DELETE(\"/image/:image_id\", imageDelete)\n\n\torgGroup.GET(\"/instance\", instancesGet)\n\torgGroup.PUT(\"/instance\", instancesPut)\n\torgGroup.GET(\"/instance/:instance_id\", instanceGet)\n\torgGroup.GET(\"/instance/:instance_id/vnc\", instanceVncGet)\n\torgGroup.PUT(\"/instance/:instance_id\", instancePut)\n\torgGroup.POST(\"/instance\", instancePost)\n\torgGroup.DELETE(\"/instance\", instancesDelete)\n\torgGroup.DELETE(\"/instance/:instance_id\", instanceDelete)\n\n\tcsrfGroup.PUT(\"/license\", licensePut)\n\n\torgGroup.GET(\"/node\", nodesGet)\n\n\torgGroup.GET(\"/plan\", plansGet)\n\torgGroup.GET(\"/plan/:plan_id\", planGet)\n\torgGroup.PUT(\"/plan/:plan_id\", planPut)\n\torgGroup.POST(\"/plan\", planPost)\n\torgGroup.DELETE(\"/plan\", plansDelete)\n\torgGroup.DELETE(\"/plan/:plan_id\", planDelete)\n\n\tcsrfGroup.GET(\"/pool\", poolsGet)\n\n\torgGroup.GET(\"/relations/:kind/:id\", relationsGet)\n\n\torgGroup.GET(\"/secret\", secretsGet)\n\torgGroup.GET(\"/secret/:secr_id\", secretGet)\n\torgGroup.PUT(\"/secret/:secr_id\", secretPut)\n\torgGroup.POST(\"/secret\", secretPost)\n\torgGroup.DELETE(\"/secret\", secretsDelete)\n\torgGroup.DELETE(\"/secret/:secr_id\", secretDelete)\n\n\torgGroup.GET(\"/pod\", podsGet)\n\torgGroup.GET(\"/pod/:pod_id\", podGet)\n\torgGroup.PUT(\"/pod/:pod_id\", podPut)\n\torgGroup.PUT(\"/pod/:pod_id/drafts\", podDraftsPut)\n\torgGroup.PUT(\"/pod/:pod_id/deploy\", podDeployPut)\n\torgGroup.POST(\"/pod\", podPost)\n\torgGroup.DELETE(\"/pod\", podsDelete)\n\torgGroup.DELETE(\"/pod/:pod_id\", podDelete)\n\torgGroup.GET(\"/pod/:pod_id/unit/:unit_id\", podUnitGet)\n\torgGroup.PUT(\"/pod/:pod_id/unit/:unit_id/deployment\",\n\t\tpodUnitDeploymentsPut)\n\torgGroup.PUT(\"/pod/:pod_id/unit/:unit_id/deployment/:deployment_id\",\n\t\tpodUnitDeploymentPut)\n\torgGroup.POST(\"/pod/:pod_id/unit/:unit_id/deployment\",\n\t\tpodUnitDeploymentPost)\n\torgGroup.GET(\n\t\t\"/pod/:pod_id/unit/:unit_id/deployment/:deployment_id/log\",\n\t\tpodUnitDeploymentLogGet,\n\t)\n\torgGroup.GET(\"/pod/:pod_id/unit/:unit_id/spec\", podUnitSpecsGet)\n\torgGroup.GET(\"/pod/:pod_id/unit/:unit_id/spec/:spec_id\", podUnitSpecGet)\n\n\tcsrfGroup.GET(\"/shape\", shapesGet)\n\n\tcsrfGroup.GET(\"/organization\", organizationsGet)\n\n\tcsrfGroup.PUT(\"/theme\", themePut)\n\n\torgGroup.GET(\"/vpc\", vpcsGet)\n\torgGroup.GET(\"/vpc/:vpc_id\", vpcGet)\n\torgGroup.PUT(\"/vpc/:vpc_id\", vpcPut)\n\torgGroup.GET(\"/vpc/:vpc_id/routes\", vpcRoutesGet)\n\torgGroup.PUT(\"/vpc/:vpc_id/routes\", vpcRoutesPut)\n\torgGroup.POST(\"/vpc\", vpcPost)\n\torgGroup.DELETE(\"/vpc\", vpcsDelete)\n\torgGroup.DELETE(\"/vpc/:vpc_id\", vpcDelete)\n\n\torgGroup.GET(\"/zone\", zonesGet)\n\n\tengine.GET(\"/robots.txt\", middlewear.RobotsGet)\n\n\tif constants.Production {\n\t\tsessGroup.GET(\"/\", staticIndexGet)\n\t\tengine.GET(\"/login\", staticLoginGet)\n\t\tengine.GET(\"/logo.png\", staticLogoGet)\n\t\tauthGroup.GET(\"/static/*path\", staticGet)\n\t} else {\n\t\tfs := gin.Dir(config.StaticTestingRoot, false)\n\t\tfileServer = http.FileServer(fs)\n\n\t\tsessGroup.GET(\"/\", staticTestingGet)\n\t\tengine.GET(\"/login\", staticTestingGet)\n\t\tengine.GET(\"/logo.png\", staticTestingGet)\n\t\tauthGroup.GET(\"/static/*path\", staticTestingGet)\n\t}\n}\n\nfunc init() {\n\tmodule := requires.New(\"uhandlers\")\n\tmodule.After(\"settings\")\n\n\tmodule.Handler = func() (err error) {\n\t\tif constants.Production {\n\t\t\tstore, err = static.NewStore(config.StaticRoot)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "uhandlers/image.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype imageData struct {\n\tId      bson.ObjectID `json:\"id\"`\n\tName    string        `json:\"name\"`\n\tComment string        `json:\"comment\"`\n}\n\ntype imagesData struct {\n\tImages []*image.Image `json:\"images\"`\n\tCount  int64          `json:\"count\"`\n}\n\nfunc imagePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := &imageData{}\n\n\timageId, ok := utils.ParseObjectId(c.Param(\"image_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\timg, err := image.GetOrg(db, userOrg, imageId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\timg.Name = dta.Name\n\timg.Comment = dta.Comment\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t)\n\n\terrData, err := img.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = img.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"image.change\")\n\n\tc.JSON(200, img)\n}\n\nfunc imageDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\timageId, ok := utils.ParseObjectId(c.Param(\"image_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := data.DeleteImageOrg(db, userOrg, imageId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"image.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc imagesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := []bson.ObjectID{}\n\n\terr := c.Bind(&dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = data.DeleteImagesOrg(db, userOrg, dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"image.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc imageGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\timageId, ok := utils.ParseObjectId(c.Param(\"image_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\timg, err := image.GetOrgPublic(db, userOrg, imageId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\timg.Json()\n\n\tc.JSON(200, img)\n}\n\nfunc imagesGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tdcId, _ := utils.ParseObjectId(c.Query(\"datacenter\"))\n\tif !dcId.IsZero() {\n\t\tdc, err := datacenter.Get(db, dcId)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tstorages := dc.PublicStorages\n\t\tif storages == nil {\n\t\t\tstorages = []bson.ObjectID{}\n\t\t}\n\n\t\tif len(storages) == 0 {\n\t\t\tc.JSON(200, []bson.ObjectID{})\n\t\t\treturn\n\t\t}\n\n\t\tquery := &bson.M{\n\t\t\t\"organization\": image.Global,\n\t\t\t\"storage\": &bson.M{\n\t\t\t\t\"$in\": dc.PublicStorages,\n\t\t\t},\n\t\t}\n\t\tif demo.IsDemo() {\n\t\t\tquery = &bson.M{\n\t\t\t\t\"organization\": image.Global,\n\t\t\t}\n\t\t}\n\n\t\timages, err := image.GetAllNames(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, img := range images {\n\t\t\timg.Json()\n\t\t}\n\n\t\tif !dc.PrivateStorage.IsZero() {\n\t\t\tquery = &bson.M{\n\t\t\t\t\"organization\": userOrg,\n\t\t\t\t\"storage\":      dc.PrivateStorage,\n\t\t\t}\n\n\t\t\timages2, err := image.GetAllNames(db, query)\n\t\t\tif err != nil {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor _, img := range images2 {\n\t\t\t\timg.Json()\n\t\t\t\timages = append(images, img)\n\t\t\t}\n\t\t}\n\n\t\tc.JSON(200, images)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{\n\t\t\t\"organization\": &bson.M{\n\t\t\t\t\"$in\": []bson.ObjectID{\n\t\t\t\t\timage.Global,\n\t\t\t\t\tuserOrg,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\timageId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = imageId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery = bson.M{\n\t\t\t\t\"$and\": []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"organization\": &bson.M{\n\t\t\t\t\t\t\t\"$in\": []bson.ObjectID{\n\t\t\t\t\t\t\t\timage.Global,\n\t\t\t\t\t\t\t\tuserOrg,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"$or\": []*bson.M{\n\t\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\t\"name\": &bson.M{\n\t\t\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\t\t\tregexp.QuoteMeta(name)),\n\t\t\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\t\"key\": &bson.M{\n\t\t\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\t\t\tregexp.QuoteMeta(name)),\n\t\t\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\ttyp := strings.TrimSpace(c.Query(\"type\"))\n\t\tif typ != \"\" {\n\t\t\tquery[\"type\"] = typ\n\t\t}\n\n\t\timages, count, err := image.GetAllPaged(db, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, img := range images {\n\t\t\timg.Json()\n\t\t}\n\n\t\tdta := &imagesData{\n\t\t\tImages: images,\n\t\t\tCount:  count,\n\t\t}\n\n\t\tc.JSON(200, dta)\n\t}\n}\n"
  },
  {
    "path": "uhandlers/instance.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/data\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/drive\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/image\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n\t\"github.com/pritunl/pritunl-cloud/iscsi\"\n\t\"github.com/pritunl/pritunl-cloud/iso\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/nodeport\"\n\t\"github.com/pritunl/pritunl-cloud/pci\"\n\t\"github.com/pritunl/pritunl-cloud/storage\"\n\t\"github.com/pritunl/pritunl-cloud/usb\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\ntype instanceData struct {\n\tId                  bson.ObjectID       `json:\"id\"`\n\tZone                bson.ObjectID       `json:\"zone\"`\n\tVpc                 bson.ObjectID       `json:\"vpc\"`\n\tSubnet              bson.ObjectID       `json:\"subnet\"`\n\tCloudSubnet         string              `json:\"cloud_subnet\"`\n\tShape               bson.ObjectID       `json:\"shape\"`\n\tNode                bson.ObjectID       `json:\"node\"`\n\tDiskType            string              `json:\"disk_type\"`\n\tDiskPool            bson.ObjectID       `json:\"disk_pool\"`\n\tImage               bson.ObjectID       `json:\"image\"`\n\tImageBacking        bool                `json:\"image_backing\"`\n\tName                string              `json:\"name\"`\n\tComment             string              `json:\"comment\"`\n\tAction              string              `json:\"action\"`\n\tRootEnabled         bool                `json:\"root_enabled\"`\n\tUefi                bool                `json:\"uefi\"`\n\tSecureBoot          bool                `json:\"secure_boot\"`\n\tTpm                 bool                `json:\"tpm\"`\n\tDhcpServer          bool                `json:\"dhcp_server\"`\n\tCloudType           string              `json:\"cloud_type\"`\n\tCloudScript         string              `json:\"cloud_script\"`\n\tDeleteProtection    bool                `json:\"delete_protection\"`\n\tSkipSourceDestCheck bool                `json:\"skip_source_dest_check\"`\n\tInitDiskSize        int                 `json:\"init_disk_size\"`\n\tMemory              int                 `json:\"memory\"`\n\tProcessors          int                 `json:\"processors\"`\n\tRoles               []string            `json:\"roles\"`\n\tIsos                []*iso.Iso          `json:\"isos\"`\n\tUsbDevices          []*usb.Device       `json:\"usb_devices\"`\n\tPciDevices          []*pci.Device       `json:\"pci_devices\"`\n\tDriveDevices        []*drive.Device     `json:\"drive_devices\"`\n\tIscsiDevices        []*iscsi.Device     `json:\"iscsi_devices\"`\n\tMounts              []*instance.Mount   `json:\"mounts\"`\n\tVnc                 bool                `json:\"vnc\"`\n\tSpice               bool                `json:\"spice\"`\n\tGui                 bool                `json:\"gui\"`\n\tNodePorts           []*nodeport.Mapping `json:\"node_ports\"`\n\tNoPublicAddress     bool                `json:\"no_public_address\"`\n\tNoPublicAddress6    bool                `json:\"no_public_address6\"`\n\tNoHostAddress       bool                `json:\"no_host_address\"`\n\tCount               int                 `json:\"count\"`\n}\n\ntype instanceMultiData struct {\n\tIds    []bson.ObjectID `json:\"ids\"`\n\tAction string          `json:\"action\"`\n}\n\ntype instancesData struct {\n\tInstances []*instance.Instance `json:\"instances\"`\n\tCount     int64                `json:\"count\"`\n}\n\nfunc instancePut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := &instanceData{}\n\n\tinstanceId, ok := utils.ParseObjectId(c.Param(\"instance_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tinst, err := instance.GetOrg(db, userOrg, instanceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\texists, err := vpc.ExistsOrg(db, userOrg, dta.Vpc)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\tinst.PreCommit()\n\n\tinst.Name = dta.Name\n\tinst.Comment = dta.Comment\n\tinst.Vpc = dta.Vpc\n\tinst.Subnet = dta.Subnet\n\tinst.CloudSubnet = dta.CloudSubnet\n\tif dta.Action != \"\" {\n\t\tinst.Action = dta.Action\n\t}\n\tinst.Uefi = dta.Uefi\n\tinst.SecureBoot = dta.SecureBoot\n\tinst.Tpm = dta.Tpm\n\tinst.DhcpServer = dta.DhcpServer\n\tinst.CloudType = dta.CloudType\n\tinst.CloudScript = dta.CloudScript\n\tinst.DeleteProtection = dta.DeleteProtection\n\tinst.SkipSourceDestCheck = dta.SkipSourceDestCheck\n\tinst.Memory = dta.Memory\n\tinst.Processors = dta.Processors\n\tinst.Roles = dta.Roles\n\tinst.Isos = dta.Isos\n\tinst.UsbDevices = dta.UsbDevices\n\tinst.PciDevices = dta.PciDevices\n\tinst.DriveDevices = dta.DriveDevices\n\tinst.IscsiDevices = dta.IscsiDevices\n\tinst.Mounts = dta.Mounts\n\tinst.RootEnabled = dta.RootEnabled\n\tinst.Vnc = dta.Vnc\n\tinst.Spice = dta.Spice\n\tinst.Gui = dta.Gui\n\tinst.NodePorts = dta.NodePorts\n\tinst.NoPublicAddress = dta.NoPublicAddress\n\tinst.NoPublicAddress6 = dta.NoPublicAddress6\n\tinst.NoHostAddress = dta.NoHostAddress\n\n\tfields := set.NewSet(\n\t\t\"unix_id\",\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"datacenter\",\n\t\t\"vpc\",\n\t\t\"subnet\",\n\t\t\"dhcp_ip\",\n\t\t\"dhcp_ip6\",\n\t\t\"cloud_subnet\",\n\t\t\"state\",\n\t\t\"restart\",\n\t\t\"restart_block_ip\",\n\t\t\"uefi\",\n\t\t\"secure_boot\",\n\t\t\"tpm\",\n\t\t\"tpm_secret\",\n\t\t\"dhcp_server\",\n\t\t\"cloud_type\",\n\t\t\"cloud_script\",\n\t\t\"delete_protection\",\n\t\t\"skip_source_dest_check\",\n\t\t\"memory\",\n\t\t\"processors\",\n\t\t\"roles\",\n\t\t\"isos\",\n\t\t\"usb_devices\",\n\t\t\"pci_devices\",\n\t\t\"drive_devices\",\n\t\t\"iscsi_devices\",\n\t\t\"mounts\",\n\t\t\"root_enabled\",\n\t\t\"root_passwd\",\n\t\t\"vnc\",\n\t\t\"vnc_display\",\n\t\t\"vnc_password\",\n\t\t\"spice\",\n\t\t\"spice_port\",\n\t\t\"spice_password\",\n\t\t\"gui\",\n\t\t\"node_ports\",\n\t\t\"no_public_address\",\n\t\t\"no_public_address6\",\n\t\t\"no_host_address\",\n\t)\n\n\terrData, err := inst.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tdskChange, err := inst.PostCommit(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = inst.CommitFields(db, fields)\n\tif err != nil {\n\t\t_ = inst.Cleanup(db)\n\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = inst.Cleanup(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\tif dskChange {\n\t\tevent.PublishDispatch(db, \"disk.change\")\n\t}\n\n\tc.JSON(200, inst)\n}\n\nfunc instancePost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := &instanceData{\n\t\tName: \"new-instance\",\n\t}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tzne, err := zone.Get(db, dta.Zone)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\texists, err := datacenter.ExistsOrg(db, userOrg, zne.Datacenter)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\tif !dta.Shape.IsZero() {\n\t\tdta.Node = bson.NilObjectID\n\t\tdta.DiskType = \"\"\n\t\tdta.DiskPool = bson.NilObjectID\n\t} else {\n\t\tnde, err := node.Get(db, dta.Node)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif nde.Zone != zne.Id {\n\t\t\tutils.AbortWithStatus(c, 405)\n\t\t\treturn\n\t\t}\n\n\t\tif dta.DiskType == disk.Lvm {\n\t\t\tpoolMatch := false\n\t\t\tfor _, plId := range nde.Pools {\n\t\t\t\tif plId == dta.DiskPool {\n\t\t\t\t\tpoolMatch = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !poolMatch {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"pool_not_found\",\n\t\t\t\t\tMessage: \"Pool not found\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\texists, err = vpc.ExistsOrg(db, userOrg, dta.Vpc)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\tif !dta.Image.IsZero() {\n\t\timg, err := image.GetOrgPublic(db, userOrg, dta.Image)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_found\",\n\t\t\t\t\tMessage: \"Image not found\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t} else {\n\t\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tstre, err := storage.Get(db, img.Storage)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tavailable, err := data.ImageAvailable(stre, img)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tif !available {\n\t\t\tif stre.IsOracle() {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_available\",\n\t\t\t\t\tMessage: \"Image not restored from archive\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t} else {\n\t\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\t\tError:   \"image_not_available\",\n\t\t\t\t\tMessage: \"Image not restored from glacier\",\n\t\t\t\t}\n\t\t\t\tc.JSON(400, errData)\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\t}\n\n\tinsts := []*instance.Instance{}\n\n\tif dta.Count == 0 {\n\t\tdta.Count = 1\n\t}\n\n\tfor i := 0; i < dta.Count; i++ {\n\t\tname := \"\"\n\t\tif strings.Contains(dta.Name, \"%\") {\n\t\t\tname = fmt.Sprintf(dta.Name, i+1)\n\t\t} else {\n\t\t\tname = dta.Name\n\t\t}\n\n\t\tinst := &instance.Instance{\n\t\t\tAction:              dta.Action,\n\t\t\tOrganization:        userOrg,\n\t\t\tZone:                dta.Zone,\n\t\t\tVpc:                 dta.Vpc,\n\t\t\tSubnet:              dta.Subnet,\n\t\t\tCloudSubnet:         dta.CloudSubnet,\n\t\t\tShape:               dta.Shape,\n\t\t\tNode:                dta.Node,\n\t\t\tDiskType:            dta.DiskType,\n\t\t\tDiskPool:            dta.DiskPool,\n\t\t\tImage:               dta.Image,\n\t\t\tImageBacking:        dta.ImageBacking,\n\t\t\tUefi:                dta.Uefi,\n\t\t\tSecureBoot:          dta.SecureBoot,\n\t\t\tTpm:                 dta.Tpm,\n\t\t\tDhcpServer:          dta.DhcpServer,\n\t\t\tCloudType:           dta.CloudType,\n\t\t\tCloudScript:         dta.CloudScript,\n\t\t\tDeleteProtection:    dta.DeleteProtection,\n\t\t\tSkipSourceDestCheck: dta.SkipSourceDestCheck,\n\t\t\tName:                name,\n\t\t\tComment:             dta.Comment,\n\t\t\tInitDiskSize:        dta.InitDiskSize,\n\t\t\tMemory:              dta.Memory,\n\t\t\tProcessors:          dta.Processors,\n\t\t\tRoles:               dta.Roles,\n\t\t\tIsos:                dta.Isos,\n\t\t\tUsbDevices:          dta.UsbDevices,\n\t\t\tPciDevices:          dta.PciDevices,\n\t\t\tDriveDevices:        dta.DriveDevices,\n\t\t\tIscsiDevices:        dta.IscsiDevices,\n\t\t\tMounts:              dta.Mounts,\n\t\t\tRootEnabled:         dta.RootEnabled,\n\t\t\tVnc:                 dta.Vnc,\n\t\t\tSpice:               dta.Spice,\n\t\t\tGui:                 dta.Gui,\n\t\t\tNodePorts:           dta.NodePorts,\n\t\t\tNoPublicAddress:     dta.NoPublicAddress,\n\t\t\tNoHostAddress:       dta.NoHostAddress,\n\t\t}\n\n\t\terrData, err := inst.Validate(db)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\n\t\terr = inst.SyncNodePorts(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = inst.Insert(db)\n\t\tif err != nil {\n\t\t\t_ = inst.Cleanup(db)\n\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\terr = inst.Cleanup(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tinsts = append(insts, inst)\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\n\tif len(insts) == 1 {\n\t\tc.JSON(200, insts[0])\n\t} else {\n\t\tc.JSON(200, insts)\n\t}\n}\n\nfunc instancesPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := &instanceMultiData{}\n\n\terr := c.Bind(dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif !instance.ValidActions.Contains(dta.Action) {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"invalid_action\",\n\t\t\tMessage: \"Invalid instance action\",\n\t\t}\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tdoc := bson.M{\n\t\t\"action\": dta.Action,\n\t}\n\n\tif dta.Action != instance.Start {\n\t\tdoc[\"restart\"] = false\n\t\tdoc[\"restart_block_ip\"] = false\n\t}\n\n\terr = instance.UpdateMultiOrg(db, userOrg, dta.Ids, &doc)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc instanceDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tinstanceId, ok := utils.ParseObjectId(c.Param(\"instance_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tinst, err := instance.Get(db, instanceId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif inst.DeleteProtection {\n\t\terrData := &errortypes.ErrorData{\n\t\t\tError:   \"delete_protection\",\n\t\t\tMessage: \"Cannot delete instance with delete protection\",\n\t\t}\n\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = instance.DeleteOrg(db, userOrg, instanceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc instancesDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdta := []bson.ObjectID{}\n\n\terr := c.Bind(&dta)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = instance.DeleteMultiOrg(db, userOrg, dta)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc instanceGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tinst := demo.Instances[0]\n\t\tinst.Guest.Timestamp = time.Now()\n\t\tinst.Guest.Heartbeat = time.Now()\n\t\tc.JSON(200, inst)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tinstanceId, ok := utils.ParseObjectId(c.Param(\"instance_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tinst, err := instance.GetOrg(db, userOrg, instanceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif demo.IsDemo() {\n\t\tinst.State = vm.Running\n\t\tinst.Action = instance.Start\n\t\tinst.State = vm.Running\n\t\tinst.Status = \"Running\"\n\t\tinst.PublicIps = []string{\n\t\t\tdemo.RandIp(inst.Id),\n\t\t}\n\t\tinst.PublicIps6 = []string{\n\t\t\tdemo.RandIp6(inst.Id),\n\t\t}\n\t\tinst.PrivateIps = []string{\n\t\t\tdemo.RandPrivateIp(inst.Id),\n\t\t}\n\t\tinst.PrivateIps6 = []string{\n\t\t\tdemo.RandPrivateIp6(inst.Id),\n\t\t}\n\t\tinst.NetworkNamespace = vm.GetNamespace(inst.Id, 0)\n\t}\n\n\tc.JSON(200, inst)\n}\n\nfunc instancesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tfor _, inst := range demo.Instances {\n\t\t\tinst.Guest.Timestamp = time.Now()\n\t\t\tinst.Guest.Heartbeat = time.Now()\n\t\t}\n\n\t\tdata := &instancesData{\n\t\t\tInstances: demo.Instances,\n\t\t\tCount:     int64(len(demo.Instances)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tndeId, _ := utils.ParseObjectId(c.Query(\"node_names\"))\n\tplId, _ := utils.ParseObjectId(c.Query(\"pool_names\"))\n\tif !ndeId.IsZero() {\n\t\tquery := &bson.M{\n\t\t\t\"node\":         ndeId,\n\t\t\t\"organization\": userOrg,\n\t\t}\n\n\t\tinsts, err := instance.GetAllName(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, insts)\n\t} else if !plId.IsZero() {\n\t\tnodes, err := node.GetAllPool(db, plId)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tndeIds := []bson.ObjectID{}\n\n\t\tfor _, nde := range nodes {\n\t\t\tndeIds = append(ndeIds, nde.Id)\n\t\t}\n\n\t\tquery := &bson.M{\n\t\t\t\"node\": &bson.M{\n\t\t\t\t\"$in\": ndeIds,\n\t\t\t},\n\t\t\t\"organization\": userOrg,\n\t\t}\n\n\t\tinsts, err := instance.GetAllName(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, insts)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{\n\t\t\t\"organization\": userOrg,\n\t\t}\n\n\t\tinstId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = instId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"name\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\trole := strings.TrimSpace(c.Query(\"role\"))\n\t\tif role != \"\" {\n\t\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\t\trole := role[1:]\n\t\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\t\"$ne\": role,\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tquery[\"roles\"] = role\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tnetworkNamespace := strings.TrimSpace(c.Query(\"network_namespace\"))\n\t\tif networkNamespace != \"\" {\n\t\t\tquery[\"network_namespace\"] = networkNamespace\n\t\t}\n\n\t\tnodeId, ok := utils.ParseObjectId(c.Query(\"node\"))\n\t\tif ok {\n\t\t\tquery[\"node\"] = nodeId\n\t\t}\n\n\t\tzoneId, ok := utils.ParseObjectId(c.Query(\"zone\"))\n\t\tif ok {\n\t\t\tquery[\"zone\"] = zoneId\n\t\t}\n\n\t\tvpcId, ok := utils.ParseObjectId(c.Query(\"vpc\"))\n\t\tif ok {\n\t\t\tquery[\"vpc\"] = vpcId\n\t\t}\n\n\t\tsubnetId, ok := utils.ParseObjectId(c.Query(\"subnet\"))\n\t\tif ok {\n\t\t\tquery[\"subnet\"] = subnetId\n\t\t}\n\n\t\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\t\tif comment != \"\" {\n\t\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\tinstances, count, err := instance.GetAllPaged(\n\t\t\tdb, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, inst := range instances {\n\t\t\tinst.Json(false)\n\n\t\t\tif demo.IsDemo() {\n\t\t\t\tinst.State = vm.Running\n\t\t\t\tinst.Action = instance.Start\n\t\t\t\tinst.Status = \"Running\"\n\t\t\t\tinst.PublicIps = []string{\n\t\t\t\t\tdemo.RandIp(inst.Id),\n\t\t\t\t}\n\t\t\t\tinst.PublicIps6 = []string{\n\t\t\t\t\tdemo.RandIp6(inst.Id),\n\t\t\t\t}\n\t\t\t\tinst.PrivateIps = []string{\n\t\t\t\t\tdemo.RandPrivateIp(inst.Id),\n\t\t\t\t}\n\t\t\t\tinst.PrivateIps6 = []string{\n\t\t\t\t\tdemo.RandPrivateIp6(inst.Id),\n\t\t\t\t}\n\t\t\t\tinst.NetworkNamespace = vm.GetNamespace(inst.Id, 0)\n\t\t\t}\n\t\t}\n\n\t\tdta := &instancesData{\n\t\t\tInstances: instances,\n\t\t\tCount:     count,\n\t\t}\n\n\t\tc.JSON(200, dta)\n\t}\n}\n\nfunc instanceVncGet(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tinstanceId, ok := utils.ParseObjectId(c.Param(\"instance_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tinst, err := instance.GetOrg(db, userOrg, instanceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = inst.VncConnect(db, c.Writer, c.Request)\n\tif err != nil {\n\t\tif _, ok := err.(*instance.VncDialError); ok {\n\t\t\tutils.AbortWithStatus(c, 504)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "uhandlers/license.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype licenseData struct {\n\tOracle bool `json:\"oracle\"`\n}\n\nfunc licensePut(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, nil)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &licenseData{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr.OracleLicense = data.Oracle\n\n\terr = usr.CommitFields(db, set.NewSet(\"oracle_licese\"))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, data)\n\treturn\n}\n"
  },
  {
    "path": "uhandlers/node.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\nfunc nodesGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tzoneStr := c.Query(\"zone\")\n\tif zoneStr == \"\" {\n\t\tc.JSON(200, []interface{}{})\n\t\treturn\n\t}\n\n\tzneId, _ := utils.ParseObjectId(zoneStr)\n\n\tzne, err := zone.Get(db, zneId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\texists, err := datacenter.ExistsOrg(db, userOrg, zne.Datacenter)\n\tif err != nil {\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\tquery := &bson.M{\n\t\t\"zone\": zneId,\n\t}\n\n\tnodes, err := node.GetAllHypervisors(db, query)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, nodes)\n}\n"
  },
  {
    "path": "uhandlers/organization.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/organization\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc organizationsGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\torgs, err := organization.GetAllNameRoles(db, usr.Roles)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, orgs)\n}\n"
  },
  {
    "path": "uhandlers/plan.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/plan\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype planData struct {\n\tId         bson.ObjectID     `json:\"id\"`\n\tName       string            `json:\"name\"`\n\tComment    string            `json:\"comment\"`\n\tStatements []*plan.Statement `json:\"statements\"`\n}\n\ntype plansData struct {\n\tPlans []*plan.Plan `json:\"plans\"`\n\tCount int64        `json:\"count\"`\n}\n\nfunc planPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &planData{}\n\n\tplanId, ok := utils.ParseObjectId(c.Param(\"plan_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpln, err := plan.GetOrg(db, userOrg, planId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpln.Name = data.Name\n\tpln.Comment = data.Comment\n\n\terr = pln.UpdateStatements(data.Statements)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"statements\",\n\t)\n\n\terrData, err := pln.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pln.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"plan.change\")\n\n\tc.JSON(200, pln)\n}\n\nfunc planPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &planData{\n\t\tName: \"new-plan\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpln := &plan.Plan{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: userOrg,\n\t}\n\n\terr = pln.UpdateStatements(data.Statements)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := pln.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pln.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"plan.change\")\n\n\tc.JSON(200, pln)\n}\n\nfunc planDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tplanId, ok := utils.ParseObjectId(c.Param(\"plan_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := plan.RemoveOrg(db, userOrg, planId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"plan.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc plansDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = plan.RemoveMultiOrg(db, userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"plan.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc planGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tpln := demo.Plans[0]\n\t\tc.JSON(200, pln)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tplanId, ok := utils.ParseObjectId(c.Param(\"plan_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tpln, err := plan.GetOrg(db, userOrg, planId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, pln)\n}\n\nfunc plansGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &plansData{\n\t\t\tPlans: demo.Plans,\n\t\t\tCount: int64(len(demo.Plans)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tquery := bson.M{\n\t\t\t\"organization\": userOrg,\n\t\t}\n\n\t\tplns, err := plan.GetAllName(db, &query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, plns)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{\n\t\t\t\"organization\": userOrg,\n\t\t}\n\n\t\tplanId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = planId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"name\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\tplans, count, err := plan.GetAllPaged(db, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tdata := &plansData{\n\t\t\tPlans: plans,\n\t\t\tCount: count,\n\t\t}\n\n\t\tc.JSON(200, data)\n\t}\n}\n"
  },
  {
    "path": "uhandlers/pod.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/aggregate\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/journal\"\n\t\"github.com/pritunl/pritunl-cloud/pod\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/scheduler\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/pritunl-cloud/unit\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype podData struct {\n\tId               bson.ObjectID     `json:\"id\"`\n\tName             string            `json:\"name\"`\n\tComment          string            `json:\"comment\"`\n\tOrganization     bson.ObjectID     `json:\"organization\"`\n\tDeleteProtection bool              `json:\"delete_protection\"`\n\tUnits            []*unit.UnitInput `json:\"units\"`\n\tDrafts           []*pod.UnitDraft  `json:\"drafts\"`\n\tCount            int               `json:\"count\"`\n}\n\ntype podsData struct {\n\tPods  []*aggregate.PodAggregate `json:\"pods\"`\n\tCount int64                     `json:\"count\"`\n}\n\ntype podsDeployData struct {\n\tCount int           `json:\"count\"`\n\tSpec  bson.ObjectID `json:\"spec\"`\n}\n\ntype deploymentData struct {\n\tId   bson.ObjectID `json:\"id\"`\n\tTags []string      `json:\"tags\"`\n}\n\ntype specsData struct {\n\tSpecs []*spec.Named `json:\"specs\"`\n\tCount int64         `json:\"count\"`\n}\n\nfunc podPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &podData{}\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpd, err := pod.GetOrg(db, userOrg, podId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpd.Name = data.Name\n\tpd.Comment = data.Comment\n\tpd.DeleteProtection = data.DeleteProtection\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"delete_protection\",\n\t)\n\n\terrData, err := pd.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terrData, err = pd.CommitFieldsUnits(db, data.Units, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = pod.UpdateDrafts(db, podId, usr.Id, []*pod.UnitDraft{})\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, pd)\n}\n\nfunc podDraftsPut(c *gin.Context) {\n\tif demo.BlockedSilent(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &podData{}\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terr = pod.UpdateDraftsOrg(db, userOrg, podId, usr.Id, data.Drafts)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, nil)\n}\n\nfunc podDeployPut(c *gin.Context) {\n\tif demo.BlockedSilent(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &podData{}\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tunits, err := unit.GetAll(db, &bson.M{\n\t\t\"pod\":          podId,\n\t\t\"organization\": userOrg,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tunitsDataMap := map[bson.ObjectID]*unit.UnitInput{}\n\tfor _, unitData := range data.Units {\n\t\tunitsDataMap[unitData.Id] = unitData\n\t}\n\n\tfor _, unt := range units {\n\t\tunitData := unitsDataMap[unt.Id]\n\t\tif unitData == nil || unitData.DeploySpec.IsZero() {\n\t\t\tcontinue\n\t\t}\n\n\t\tdeploySpec, e := spec.Get(db, unitData.DeploySpec)\n\t\tif e != nil || deploySpec.Unit != unt.Id {\n\t\t\terrData := &errortypes.ErrorData{\n\t\t\t\tError:   \"unit_deploy_spec_invalid\",\n\t\t\t\tMessage: \"Invalid unit deployment commit\",\n\t\t\t}\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\n\t\tunt.DeploySpec = unitData.DeploySpec\n\t\terr = unt.CommitFields(db, set.NewSet(\"deploy_spec\"))\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tc.JSON(200, nil)\n}\n\nfunc podPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &podData{\n\t\tName: \"new-pod\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpd := &pod.Pod{\n\t\tName:             data.Name,\n\t\tComment:          data.Comment,\n\t\tOrganization:     userOrg,\n\t\tDeleteProtection: data.DeleteProtection,\n\t}\n\n\terrData, err := pd.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pd.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err = pd.InitUnits(db, data.Units)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, pd)\n}\n\nfunc podDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteOrg(db, \"pod\", userOrg, podId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pod.RemoveOrg(db, userOrg, podId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteOrgAll(db, \"pod\", userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = pod.RemoveMultiOrg(db, userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tpd := demo.Pods[0]\n\t\tc.JSON(200, pd)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpodId, ok := utils.ParseObjectId(c.Param(\"pod_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tpd, err := aggregate.GetPod(db, usr.Id, &bson.M{\n\t\t\"_id\":          podId,\n\t\t\"organization\": userOrg,\n\t})\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tc.JSON(200, pd)\n}\n\nfunc podsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &podsData{\n\t\t\tPods:  demo.Pods,\n\t\t\tCount: int64(len(demo.Pods)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{\n\t\t\"organization\": userOrg,\n\t}\n\n\tpodId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = podId\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\trole := strings.TrimSpace(c.Query(\"role\"))\n\tif role != \"\" {\n\t\tif strings.HasPrefix(role, \"~\") {\n\t\t\trole := role[1:]\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$not\": &bson.M{\n\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\tregexp.QuoteMeta(role[1:])),\n\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"$or\"] = []*bson.M{\n\t\t\t\t\t&bson.M{\n\t\t\t\t\t\t\"roles\": &bson.M{\n\t\t\t\t\t\t\t\"$regex\": fmt.Sprintf(\".*%s.*\",\n\t\t\t\t\t\t\t\tregexp.QuoteMeta(role)),\n\t\t\t\t\t\t\t\"$options\": \"i\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.HasPrefix(role, \"!\") {\n\t\t\t\trole = strings.TrimLeft(role, \"!\")\n\t\t\t\tquery[\"roles\"] = &bson.M{\n\t\t\t\t\t\"$ne\": role,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tquery[\"roles\"] = role\n\t\t\t}\n\t\t}\n\t}\n\n\tpods, count, err := aggregate.GetPodsPaged(db, usr.Id,\n\t\t&query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &podsData{\n\t\tPods:  pods,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, data)\n}\n\ntype PodUnit struct {\n\tId          bson.ObjectID           `json:\"id\"`\n\tPod         bson.ObjectID           `json:\"pod\"`\n\tKind        string                  `json:\"kind\"`\n\tDeployments []*aggregate.Deployment `json:\"deployments\"`\n}\n\nfunc podUnitGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\tvar unit *unit.Unit\n\t\tfor _, unt := range demo.Units {\n\t\t\tif unt.Id == unitId {\n\t\t\t\tunit = unt\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tdeplys := []*aggregate.Deployment{}\n\t\tfor _, deply := range demo.Deployments {\n\t\t\tif deply.Unit == unit.Id {\n\t\t\t\tdeplys = append(deplys, deply)\n\t\t\t}\n\t\t}\n\n\t\tdata := &PodUnit{\n\t\t\tId:          unit.Id,\n\t\t\tPod:         demo.Pods[0].Id,\n\t\t\tKind:        unit.Kind,\n\t\t\tDeployments: deplys,\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tunt, err := unit.GetOrg(db, userOrg, unitId)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tdeploys, err := aggregate.GetDeployments(db, unt)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tpdUnit := &PodUnit{\n\t\tId:          unt.Id,\n\t\tPod:         unt.Pod,\n\t\tKind:        unt.Kind,\n\t\tDeployments: deploys,\n\t}\n\n\tc.JSON(200, pdUnit)\n}\n\nfunc podUnitDeploymentsPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tunt, err := unit.GetOrg(db, userOrg, unitId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\taction := c.Query(\"action\")\n\tswitch action {\n\tcase deployment.Archive:\n\t\terr = deployment.ArchiveMulti(db, unt.Id, data)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase deployment.Restore:\n\t\terr = deployment.RestoreMulti(db, unt.Id, data)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase deployment.Destroy:\n\t\terr = deployment.RemoveMulti(db, unt.Id, data)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\t\tbreak\n\tcase deployment.Migrate:\n\t\tcommitId, ok := utils.ParseObjectId(c.Query(\"commit\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\terrData, err := unt.MigrateDeployements(db, commitId, data)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\tc.JSON(400, errData)\n\t\t\treturn\n\t\t}\n\n\t\tbreak\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podUnitDeploymentPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &podsDeployData{}\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tunt, err := unit.GetOrg(db, userOrg, unitId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := scheduler.ManualSchedule(db, unt, data.Spec, data.Count)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\tevent.PublishDispatch(db, \"unit.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podUnitDeploymentPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &deploymentData{}\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdeplyId, ok := utils.ParseObjectId(c.Param(\"deployment_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdeply, err := deployment.GetUnitOrg(db, userOrg, unitId, deplyId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdeply.Tags = data.Tags\n\n\tfields := set.NewSet(\n\t\t\"tags\",\n\t)\n\n\terrData, err := deply.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = deply.CommitFields(db, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"instance.change\")\n\tevent.PublishDispatch(db, \"pod.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc podUnitDeploymentLogGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, demo.DeploymentLogs)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdeplyId, ok := utils.ParseObjectId(c.Param(\"deployment_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tdeply, err := deployment.GetUnitOrg(db, userOrg, unitId, deplyId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tkind := int32(0)\n\tresource := c.Query(\"resource\")\n\tif resource == \"agent\" {\n\t\tkind = journal.DeploymentAgent\n\t}\n\n\tfor _, jrnl := range deply.Journals {\n\t\tif jrnl.Key == resource {\n\t\t\tkind = jrnl.Index\n\t\t}\n\t}\n\n\tif kind == 0 {\n\t\tutils.AbortWithStatus(c, 404)\n\t\treturn\n\t}\n\n\tdata, err := journal.GetOutput(c, db, deply.Id, kind)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tc.JSON(200, data)\n}\n\nfunc podUnitSpecsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\tspecs := []*spec.Named{}\n\t\tfor _, spc := range demo.SpecsNamed {\n\t\t\tif spc.Unit == unitId {\n\t\t\t\tspecs = append(specs, spc)\n\t\t\t}\n\t\t}\n\n\t\tdata := &specsData{\n\t\t\tSpecs: specs,\n\t\t\tCount: int64(len(specs)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tspecs, count, err := spec.GetAllPaged(db, &bson.M{\n\t\t\"unit\":         unitId,\n\t\t\"organization\": userOrg,\n\t}, page, pageCount)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tdata := &specsData{\n\t\tSpecs: specs,\n\t\tCount: count,\n\t}\n\n\tc.JSON(200, data)\n}\n\nfunc podUnitSpecGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tspecId, ok := utils.ParseObjectId(c.Param(\"spec_id\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, spc := range demo.Specs {\n\t\t\tif spc.Id == specId {\n\t\t\t\tc.JSON(200, spc)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tc.AbortWithStatus(404)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tunitId, ok := utils.ParseObjectId(c.Param(\"unit_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tspecId, ok := utils.ParseObjectId(c.Param(\"spec_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tspec, err := spec.GetOne(db, &bson.M{\n\t\t\"_id\":          specId,\n\t\t\"unit\":         unitId,\n\t\t\"organization\": userOrg,\n\t})\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tc.AbortWithStatus(404)\n\t\t} else {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t}\n\t\treturn\n\t}\n\n\tc.JSON(200, spec)\n}\n"
  },
  {
    "path": "uhandlers/pool.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/pool\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc poolsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, demo.Pools)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tpools, err := pool.GetAllNames(db, &bson.M{})\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, pools)\n}\n"
  },
  {
    "path": "uhandlers/relations.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype relationsData struct {\n\tId   any    `json:\"id\"`\n\tKind string `json:\"kind\"`\n\tData string `json:\"data\"`\n}\n\nfunc relationsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tkind := c.Param(\"kind\")\n\t\tresourceId, ok := utils.ParseObjectId(c.Param(\"id\"))\n\t\tif !ok {\n\t\t\tutils.AbortWithStatus(c, 400)\n\t\t\treturn\n\t\t}\n\n\t\tdata := &relationsData{\n\t\t\tId:   resourceId,\n\t\t\tKind: kind,\n\t\t\tData: \"demo\",\n\t\t}\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tkind := c.Param(\"kind\")\n\tresourceId, ok := utils.ParseObjectId(c.Param(\"id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tresp, err := relations.AggregateOrg(db, kind, userOrg, resourceId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif resp == nil {\n\t\tutils.AbortWithStatus(c, 404)\n\t\treturn\n\t}\n\n\tdata := &relationsData{\n\t\tId:   resp.Id,\n\t\tKind: kind,\n\t\tData: resp.Yaml(),\n\t}\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "uhandlers/secret.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/secret\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype secretData struct {\n\tId      bson.ObjectID `json:\"id\"`\n\tName    string        `json:\"name\"`\n\tComment string        `json:\"comment\"`\n\tType    string        `json:\"type\"`\n\tKey     string        `json:\"key\"`\n\tValue   string        `json:\"value\"`\n\tData    string        `json:\"data\"`\n\tRegion  string        `json:\"region\"`\n}\n\ntype secretsData struct {\n\tSecrets []*secret.Secret `json:\"secrets\"`\n\tCount   int64            `json:\"count\"`\n}\n\nfunc secretPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &secretData{}\n\n\tsecrId, ok := utils.ParseObjectId(c.Param(\"secr_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecr, err := secret.GetOrg(db, userOrg, secrId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecr.Name = data.Name\n\tsecr.Comment = data.Comment\n\tsecr.Type = data.Type\n\tsecr.Key = data.Key\n\tsecr.Value = data.Value\n\tsecr.Data = data.Data\n\tsecr.Region = data.Region\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"type\",\n\t\t\"key\",\n\t\t\"value\",\n\t\t\"data\",\n\t\t\"region\",\n\t\t\"public_key\",\n\t\t\"private_key\",\n\t)\n\n\terrData, err := secr.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = secr.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"secret.change\")\n\n\tc.JSON(200, secr)\n}\n\nfunc secretPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &secretData{\n\t\tName: \"new-secret\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tsecr := &secret.Secret{\n\t\tName:         data.Name,\n\t\tComment:      data.Comment,\n\t\tOrganization: userOrg,\n\t\tType:         data.Type,\n\t\tKey:          data.Key,\n\t\tValue:        data.Value,\n\t\tData:         data.Data,\n\t\tRegion:       data.Region,\n\t}\n\n\terrData, err := secr.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = secr.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"secret.change\")\n\n\tc.JSON(200, secr)\n}\n\nfunc secretDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tsecrId, ok := utils.ParseObjectId(c.Param(\"secr_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteOrg(db, \"secret\", userOrg, secrId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = secret.RemoveOrg(db, userOrg, secrId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"secret.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc secretsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Bind error\"),\n\t\t}\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteOrgAll(db, \"secret\", userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = secret.RemoveMultiOrg(db, userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"secret.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc secretGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tsecr := demo.Secrets[0]\n\t\tc.JSON(200, secr)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tsecrId, ok := utils.ParseObjectId(c.Param(\"secr_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tsecr, err := secret.GetOrg(db, userOrg, secrId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif demo.IsDemo() {\n\t\tsecr.Key = \"demo\"\n\t\tsecr.Value = \"demo\"\n\t}\n\n\tc.JSON(200, secr)\n}\n\nfunc secretsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &secretsData{\n\t\t\tSecrets: demo.Secrets,\n\t\t\tCount:   int64(len(demo.Secrets)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\tquery := bson.M{\n\t\t\"organization\": userOrg,\n\t}\n\n\tsecretId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\tif ok {\n\t\tquery[\"_id\"] = secretId\n\t}\n\n\tname := strings.TrimSpace(c.Query(\"name\"))\n\tif name != \"\" {\n\t\tquery[\"name\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tcomment := strings.TrimSpace(c.Query(\"comment\"))\n\tif comment != \"\" {\n\t\tquery[\"comment\"] = &bson.M{\n\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", comment),\n\t\t\t\"$options\": \"i\",\n\t\t}\n\t}\n\n\tsecrs, count, err := secret.GetAllPaged(db, &query, page, pageCount)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tdata := &secretsData{\n\t\tSecrets: secrs,\n\t\tCount:   count,\n\t}\n\n\tc.JSON(200, data)\n}\n"
  },
  {
    "path": "uhandlers/shape.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/shape\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc shapesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, demo.Shapes)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\n\tshapes, err := shape.GetAllNames(db, &bson.M{})\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, shapes)\n}\n"
  },
  {
    "path": "uhandlers/static.go",
    "content": "package uhandlers\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/auth\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/config\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/middlewear\"\n\t\"github.com/pritunl/pritunl-cloud/static\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc staticPath(c *gin.Context, pth string, cache bool) {\n\tpth = config.StaticRoot + pth\n\n\tfile, ok := store.Files[pth]\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 404)\n\t\treturn\n\t}\n\n\tif constants.StaticCache && cache {\n\t\tc.Writer.Header().Add(\"Cache-Control\", \"public, max-age=86400\")\n\t\tc.Writer.Header().Add(\"ETag\", file.Hash)\n\t} else {\n\t\tc.Writer.Header().Add(\"Cache-Control\",\n\t\t\t\"no-cache, no-store, must-revalidate\")\n\t\tc.Writer.Header().Add(\"Pragma\", \"no-cache\")\n\t\tc.Writer.Header().Add(\"Expires\", \"0\")\n\t}\n\n\tif strings.Contains(c.Request.Header.Get(\"Accept-Encoding\"), \"gzip\") {\n\t\tc.Writer.Header().Add(\"Content-Encoding\", \"gzip\")\n\t\tc.Data(200, file.Type, file.GzipData)\n\t} else {\n\t\tc.Data(200, file.Type, file.Data)\n\t}\n}\n\nfunc staticIndexGet(c *gin.Context) {\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tif !authr.IsValid() {\n\t\tfastPth := auth.GetFastUserPath()\n\t\tif fastPth != \"\" {\n\t\t\tc.Redirect(302, fastPth)\n\t\t\treturn\n\t\t}\n\n\t\tc.Redirect(302, \"/login\")\n\t\treturn\n\t}\n\n\tstaticPath(c, \"/uindex.html\", false)\n}\n\nfunc staticLoginGet(c *gin.Context) {\n\tfastPth := auth.GetFastUserPath()\n\tif fastPth != \"\" {\n\t\tc.Redirect(302, fastPth)\n\t\treturn\n\t}\n\n\tstaticPath(c, \"/login.html\", false)\n}\n\nfunc staticLogoGet(c *gin.Context) {\n\tstaticPath(c, \"/logo.png\", true)\n}\n\nfunc staticGet(c *gin.Context) {\n\tstaticPath(c, \"/static\"+c.Params.ByName(\"path\"), true)\n}\n\nfunc staticTestingGet(c *gin.Context) {\n\tpth := c.Params.ByName(\"path\")\n\tif pth == \"\" {\n\t\tif c.Request.URL.Path == \"/config.js\" {\n\t\t\tpth = \"config.js\"\n\t\t} else if c.Request.URL.Path == \"/logo.png\" {\n\t\t\tpth = \"logo.png\"\n\t\t} else if c.Request.URL.Path == \"/build.js\" {\n\t\t\tpth = \"build.js\"\n\t\t} else if c.Request.URL.Path == \"/login\" {\n\t\t\tfastPth := auth.GetFastUserPath()\n\t\t\tif fastPth != \"\" {\n\t\t\t\tc.Redirect(302, fastPth)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tc.Request.URL.Path = \"/login.html\"\n\t\t\tpth = \"login.html\"\n\t\t} else {\n\t\t\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\t\t\tif !authr.IsValid() {\n\t\t\t\tfastPth := auth.GetFastUserPath()\n\t\t\t\tif fastPth != \"\" {\n\t\t\t\t\tc.Redirect(302, fastPth)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tc.Redirect(302, \"/login\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpth = \"uindex.html\"\n\t\t}\n\t}\n\n\tif strings.HasPrefix(c.Request.URL.Path, \"/node_modules/\") ||\n\t\tstrings.HasPrefix(c.Request.URL.Path, \"/jspm_packages/\") {\n\n\t\tc.Writer.Header().Add(\"Cache-Control\", \"public, max-age=86400\")\n\t} else {\n\t\tc.Writer.Header().Add(\"Cache-Control\",\n\t\t\t\"no-cache, no-store, must-revalidate\")\n\t\tc.Writer.Header().Add(\"Pragma\", \"no-cache\")\n\t\tc.Writer.Header().Add(\"Expires\", \"0\")\n\t}\n\n\tif c.Request.URL.Path == \"/\" {\n\t\tc.Request.URL.Path = \"/uindex.html\"\n\t}\n\n\tc.Writer.Header().Add(\"Content-Type\", static.GetMimeType(pth))\n\n\tgzipWriter := middlewear.NewGzipWriter(c)\n\tdefer gzipWriter.Close()\n\tfileServer.ServeHTTP(gzipWriter, c.Request)\n}\n"
  },
  {
    "path": "uhandlers/theme.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/pritunl-cloud/authorizer\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype themeData struct {\n\tTheme       string `json:\"theme\"`\n\tEditorTheme string `json:\"editor_theme\"`\n}\n\nfunc themePut(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tc.JSON(200, nil)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tauthr := c.MustGet(\"authorizer\").(*authorizer.Authorizer)\n\tdata := &themeData{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr, err := authr.GetUser(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tusr.Theme = data.Theme\n\tusr.EditorTheme = data.EditorTheme\n\n\terr = usr.CommitFields(db, set.NewSet(\"theme\", \"editor_theme\"))\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, data)\n\treturn\n}\n"
  },
  {
    "path": "uhandlers/utils.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype redirectData struct {\n\tRedirect string `json:\"redirect\"`\n}\n\nfunc redirectQuery(c *gin.Context, query string) {\n\tif query != \"\" {\n\t\tc.Redirect(302, \"/?\"+query)\n\t} else {\n\t\tc.Redirect(302, \"/\"+query)\n\t}\n}\n\nfunc redirectQueryJson(c *gin.Context, query string) {\n\tdata := redirectData{\n\t\tRedirect: \"/\",\n\t}\n\n\tif query != \"\" {\n\t\tdata.Redirect += \"?\" + query\n\t}\n\n\tc.JSON(202, data)\n}\n"
  },
  {
    "path": "uhandlers/vpc.go",
    "content": "package uhandlers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/demo\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/relations\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vpc\"\n)\n\ntype vpcData struct {\n\tId            bson.ObjectID `json:\"id\"`\n\tName          string        `json:\"name\"`\n\tComment       string        `json:\"comment\"`\n\tNetwork       string        `json:\"network\"`\n\tIcmpRedirects bool          `json:\"icmp_redirects\"`\n\tSubnets       []*vpc.Subnet `json:\"subnets\"`\n\tDatacenter    bson.ObjectID `json:\"datacenter\"`\n\tRoutes        []*vpc.Route  `json:\"routes\"`\n\tMaps          []*vpc.Map    `json:\"maps\"`\n}\n\ntype vpcsData struct {\n\tVpcs  []*vpc.Vpc `json:\"vpcs\"`\n\tCount int64      `json:\"count\"`\n}\n\nfunc vpcPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &vpcData{}\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\texists, err := datacenter.ExistsOrg(db, userOrg, data.Datacenter)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\tvc, err := vpc.GetOrg(db, userOrg, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif vc.Organization != userOrg {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\tvc.PreCommit()\n\n\tvc.Name = data.Name\n\tvc.Comment = data.Comment\n\tvc.IcmpRedirects = data.IcmpRedirects\n\tvc.Routes = data.Routes\n\tvc.Maps = data.Maps\n\tvc.Subnets = data.Subnets\n\n\tfields := set.NewSet(\n\t\t\"name\",\n\t\t\"comment\",\n\t\t\"icmp_redirects\",\n\t\t\"routes\",\n\t\t\"maps\",\n\t\t\"subnets\",\n\t)\n\n\terrData, err := vc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terrData, err = vc.PostCommit(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vc.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tvc.Json()\n\n\tc.JSON(200, vc)\n}\n\nfunc vpcPost(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := &vpcData{\n\t\tName: \"new-vpc\",\n\t}\n\n\terr := c.Bind(data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\texists, err := datacenter.ExistsOrg(db, userOrg, data.Datacenter)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\tvc := &vpc.Vpc{\n\t\tName:          data.Name,\n\t\tComment:       data.Comment,\n\t\tNetwork:       data.Network,\n\t\tSubnets:       data.Subnets,\n\t\tOrganization:  userOrg,\n\t\tDatacenter:    data.Datacenter,\n\t\tIcmpRedirects: data.IcmpRedirects,\n\t\tRoutes:        data.Routes,\n\t\tMaps:          data.Maps,\n\t}\n\n\tvc.InitVpc()\n\n\terrData, err := vc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vc.Insert(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tvc.Json()\n\n\tc.JSON(200, vc)\n}\n\nfunc vpcDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\texists, err := vpc.ExistsOrg(db, userOrg, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\tif !exists {\n\t\tutils.AbortWithStatus(c, 405)\n\t\treturn\n\t}\n\n\terrData, err := relations.CanDeleteOrg(db, \"vpc\", userOrg, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vpc.RemoveOrg(db, userOrg, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc vpcsDelete(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []bson.ObjectID{}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tfor _, vpcId := range data {\n\t\texists, e := vpc.ExistsOrg(db, userOrg, vpcId)\n\t\tif e != nil {\n\t\t\tutils.AbortWithError(c, 500, e)\n\t\t\treturn\n\t\t}\n\t\tif !exists {\n\t\t\tutils.AbortWithStatus(c, 405)\n\t\t\treturn\n\t\t}\n\t}\n\n\terrData, err := relations.CanDeleteOrgAll(db, \"vpc\", userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vpc.RemoveMultiOrg(db, userOrg, data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tc.JSON(200, nil)\n}\n\nfunc vpcGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tvc := demo.Vpcs[0]\n\t\tc.JSON(200, vc)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tvc, err := vpc.GetOrg(db, userOrg, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvc.Json()\n\n\tc.JSON(200, vc)\n}\n\nfunc vpcRoutesGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tvc := demo.Vpcs[0]\n\t\tc.JSON(200, vc.Routes)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\tvc, err := vpc.GetOrg(db, userOrg, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, vc.Routes)\n}\n\nfunc vpcRoutesPut(c *gin.Context) {\n\tif demo.Blocked(c) {\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\tdata := []*vpc.Route{}\n\n\tvpcId, ok := utils.ParseObjectId(c.Param(\"vpc_id\"))\n\tif !ok {\n\t\tutils.AbortWithStatus(c, 400)\n\t\treturn\n\t}\n\n\terr := c.Bind(&data)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvc, err := vpc.GetOrg(db, userOrg, vpcId)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tvc.Routes = data\n\n\tfields := set.NewSet(\n\t\t\"routes\",\n\t)\n\n\terrData, err := vc.Validate(db)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tif errData != nil {\n\t\tc.JSON(400, errData)\n\t\treturn\n\t}\n\n\terr = vc.CommitFields(db, fields)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tevent.PublishDispatch(db, \"vpc.change\")\n\n\tvc.Json()\n\n\tc.JSON(200, vc)\n}\n\nfunc vpcsGet(c *gin.Context) {\n\tif demo.IsDemo() {\n\t\tdata := &vpcsData{\n\t\t\tVpcs:  demo.Vpcs,\n\t\t\tCount: int64(len(demo.Vpcs)),\n\t\t}\n\n\t\tc.JSON(200, data)\n\t\treturn\n\t}\n\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tif c.Query(\"names\") == \"true\" {\n\t\tquery := &bson.M{\n\t\t\t\"organization\": userOrg,\n\t\t}\n\n\t\tvpcs, err := vpc.GetAllNames(db, query)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(200, vpcs)\n\t} else {\n\t\tpage, _ := strconv.ParseInt(c.Query(\"page\"), 10, 0)\n\t\tpageCount, _ := strconv.ParseInt(c.Query(\"page_count\"), 10, 0)\n\n\t\tquery := bson.M{\n\t\t\t\"organization\": userOrg,\n\t\t}\n\n\t\tvpcId, ok := utils.ParseObjectId(c.Query(\"id\"))\n\t\tif ok {\n\t\t\tquery[\"_id\"] = vpcId\n\t\t}\n\n\t\tname := strings.TrimSpace(c.Query(\"name\"))\n\t\tif name != \"\" {\n\t\t\tquery[\"name\"] = &bson.M{\n\t\t\t\t\"$regex\":   fmt.Sprintf(\".*%s.*\", regexp.QuoteMeta(name)),\n\t\t\t\t\"$options\": \"i\",\n\t\t\t}\n\t\t}\n\n\t\tnetwork := strings.TrimSpace(c.Query(\"network\"))\n\t\tif network != \"\" {\n\t\t\tquery[\"network\"] = network\n\t\t}\n\n\t\tdc, ok := utils.ParseObjectId(c.Query(\"datacenter\"))\n\t\tif ok {\n\t\t\tquery[\"datacenter\"] = dc\n\t\t}\n\n\t\tvpcs, count, err := vpc.GetAllPaged(db, &query, page, pageCount)\n\t\tif err != nil {\n\t\t\tutils.AbortWithError(c, 500, err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, vc := range vpcs {\n\t\t\tvc.Json()\n\t\t}\n\n\t\tdata := &vpcsData{\n\t\t\tVpcs:  vpcs,\n\t\t\tCount: count,\n\t\t}\n\n\t\tc.JSON(200, data)\n\t}\n}\n"
  },
  {
    "path": "uhandlers/zone.go",
    "content": "package uhandlers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/datacenter\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/zone\"\n)\n\nfunc zonesGet(c *gin.Context) {\n\tdb := c.MustGet(\"db\").(*database.Database)\n\tuserOrg := c.MustGet(\"organization\").(bson.ObjectID)\n\n\tdcIds, err := datacenter.DistinctOrg(db, userOrg)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tzones, err := zone.GetAllNamedDc(db, dcIds)\n\tif err != nil {\n\t\tutils.AbortWithError(c, 500, err)\n\t\treturn\n\t}\n\n\tc.JSON(200, zones)\n}\n"
  },
  {
    "path": "unit/unit.go",
    "content": "package unit\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/deployment\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/spec\"\n\t\"github.com/pritunl/tools/errors\"\n)\n\ntype Unit struct {\n\tId            bson.ObjectID    `bson:\"_id,omitempty\" json:\"id\"`\n\tPod           bson.ObjectID    `bson:\"pod\" json:\"pod\"`\n\tOrganization  bson.ObjectID    `bson:\"organization\" json:\"organization\"`\n\tName          string           `bson:\"name\" json:\"name\"`\n\tKind          string           `bson:\"kind\" json:\"kind\"`\n\tCount         int              `bson:\"count\" json:\"count\"`\n\tDeployments   []bson.ObjectID  `bson:\"deployments\" json:\"deployments\"`\n\tSpec          string           `bson:\"spec\" json:\"spec\"`\n\tSpecIndex     int              `bson:\"spec_index\" json:\"spec_index\"`\n\tSpecTimestamp time.Time        `bson:\"spec_timestamp\" json:\"-\"`\n\tLastSpec      bson.ObjectID    `bson:\"last_spec\" json:\"last_spec\"`\n\tDeploySpec    bson.ObjectID    `bson:\"deploy_spec\" json:\"deploy_spec\"`\n\tHash          string           `bson:\"hash\" json:\"hash\"`\n\tJournals      map[string]int32 `bson:\"journals\" json:\"-\"`\n\tJournalsIndex int32            `bson:\"journals_index\" json:\"-\"`\n\tjournalsLock  sync.Mutex       `bson:\"-\" json:\"-\"`\n\tnewUnit       bool             `bson:\"-\" json:\"-\"`\n}\n\ntype Completion struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tPod          bson.ObjectID `bson:\"pod\" json:\"pod\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tKind         string        `bson:\"kind\" json:\"kind\"`\n}\n\ntype UnitInput struct {\n\tId         bson.ObjectID `json:\"id\"`\n\tName       string        `json:\"name\"`\n\tSpec       string        `json:\"spec\"`\n\tDeploySpec bson.ObjectID `json:\"deploy_spec\"`\n\tDelete     bool          `json:\"delete\"`\n}\n\nfunc (u *Unit) Refresh(db *database.Database) (err error) {\n\tcoll := db.Units()\n\n\tunt := &Unit{}\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\": u.Id,\n\t}).Decode(unt)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tu.Id = unt.Id\n\tu.Pod = unt.Pod\n\tu.Organization = unt.Organization\n\tu.Name = unt.Name\n\tu.Kind = unt.Kind\n\tu.Count = unt.Count\n\tu.Deployments = unt.Deployments\n\tu.Spec = unt.Spec\n\tu.SpecIndex = unt.SpecIndex\n\tu.SpecTimestamp = unt.SpecTimestamp\n\tu.LastSpec = unt.LastSpec\n\tu.DeploySpec = unt.DeploySpec\n\tu.Hash = unt.Hash\n\tu.Journals = unt.Journals\n\tu.JournalsIndex = unt.JournalsIndex\n\treturn\n}\n\nfunc (u *Unit) RefreshJournals(db *database.Database) (err error) {\n\tcoll := db.Units()\n\n\tunt := &Unit{}\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\": u.Id,\n\t}).Decode(unt)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tu.journalsLock.Lock()\n\tu.Journals = unt.Journals\n\tu.JournalsIndex = unt.JournalsIndex\n\tu.journalsLock.Unlock()\n\n\treturn\n}\n\nfunc (u *Unit) HasDeployment(deployId bson.ObjectID) bool {\n\tif u.Deployments != nil {\n\t\tfor _, deplyId := range u.Deployments {\n\t\t\tif deplyId == deployId {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (u *Unit) Reserve(db *database.Database, deployId bson.ObjectID,\n\toverrideCount int) (reserved bool, err error) {\n\n\tcoll := db.Units()\n\n\tif overrideCount == 0 {\n\t\tif len(u.Deployments) >= u.Count {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tif len(u.Deployments) >= overrideCount {\n\t\t\treturn\n\t\t}\n\t}\n\n\tresp, err := coll.UpdateOne(db, bson.M{\n\t\t\"_id\":   u.Id,\n\t\t\"pod\":   u.Pod,\n\t\t\"count\": u.Count,\n\t\t\"deployments\": bson.M{\n\t\t\t\"$size\": len(u.Deployments),\n\t\t},\n\t}, bson.M{\n\t\t\"$push\": bson.M{\n\t\t\t\"deployments\": deployId,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif resp.MatchedCount == 1 && resp.ModifiedCount == 1 {\n\t\treserved = true\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) RestoreDeployment(db *database.Database,\n\tdeployId bson.ObjectID) (err error) {\n\n\tcoll := db.Units()\n\n\t_, err = coll.UpdateOne(db, bson.M{\n\t\t\"_id\": u.Id,\n\t}, bson.M{\n\t\t\"$push\": bson.M{\n\t\t\t\"deployments\": deployId,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) RemoveDeployement(db *database.Database,\n\tdeployId bson.ObjectID) (err error) {\n\n\tcoll := db.Units()\n\n\t_, err = coll.UpdateOne(db, bson.M{\n\t\t\"_id\": u.Id,\n\t}, bson.M{\n\t\t\"$pull\": bson.M{\n\t\t\t\"deployments\": deployId,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) MigrateDeployements(db *database.Database,\n\tnewSpecId bson.ObjectID, deplyIds []bson.ObjectID) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tcoll := db.Deployments()\n\n\tnewSpc, err := spec.Get(db, newSpecId)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif newSpc.Pod != u.Pod || newSpc.Unit != u.Id {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Newf(\"spec: Invalid unit\"),\n\t\t}\n\t\treturn\n\t}\n\n\tdeplys, err := deployment.GetAll(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": deplyIds,\n\t\t},\n\t\t\"pod\":  u.Pod,\n\t\t\"unit\": u.Id,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tspcMap := map[bson.ObjectID]*spec.Spec{}\n\n\tfor _, deply := range deplys {\n\t\toldSpc := spcMap[deply.Spec]\n\t\tif oldSpc == nil {\n\t\t\toldSpc, err = spec.Get(db, deply.Spec)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tspcMap[oldSpc.Id] = oldSpc\n\t\t}\n\n\t\terrData, err = oldSpc.CanMigrate(db, deply, newSpc)\n\t\tif err != nil || errData != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": deplyIds,\n\t\t},\n\t\t\"pod\":  u.Pod,\n\t\t\"unit\": u.Id,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"action\":   deployment.Migrate,\n\t\t\t\"new_spec\": newSpc.Id,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) newSpec(db *database.Database, spc *spec.Spec, newUnit bool) (\n\tnewSpec *spec.Spec, errData *errortypes.ErrorData, err error) {\n\n\tu.Name = spc.Name\n\tu.Count = spc.Count\n\tu.Spec = spc.Data\n\n\tif u.Kind == \"\" {\n\t\tu.Kind = spc.Kind\n\t} else if u.Kind != spc.Kind {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"spec_kind_invalid\",\n\t\t\tMessage: \"Cannot change spec kind\",\n\t\t}\n\t\treturn\n\t}\n\n\tif newUnit {\n\t\tspc.Index = 1\n\t\tspc.Timestamp = time.Now()\n\t} else {\n\t\ttimestamp, index, e := NewSpec(db, u.Pod, u.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tspc.Index = index\n\t\tspc.Timestamp = timestamp\n\t}\n\n\tnewSpec = spc\n\n\tu.Hash = spc.Hash\n\tu.LastSpec = spc.Id\n\tif u.DeploySpec.IsZero() {\n\t\tu.DeploySpec = spc.Id\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) updateSpec(db *database.Database, spc *spec.Spec) (\n\tupdateSpec *spec.Spec, errData *errortypes.ErrorData, err error) {\n\n\tcurSpc, e := spec.Get(db, u.LastSpec)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\n\tcurSpc.Name = spc.Name\n\tcurSpc.Count = spc.Count\n\tcurSpc.Data = spc.Data\n\n\tupdateSpec = curSpc\n\n\tu.Name = curSpc.Name\n\tu.Count = curSpc.Count\n\tu.Spec = curSpc.Data\n\n\tif u.Kind == \"\" {\n\t\tu.Kind = spc.Kind\n\t} else if u.Kind != spc.Kind {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"spec_kind_invalid\",\n\t\t\tMessage: \"Cannot change spec kind\",\n\t\t}\n\t\treturn\n\t}\n\n\tu.Hash = curSpc.Hash\n\tu.LastSpec = curSpc.Id\n\tif u.DeploySpec.IsZero() {\n\t\tu.DeploySpec = curSpc.Id\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) getKind(db *database.Database, key string) (\n\tkind int32, err error) {\n\n\tu.journalsLock.Lock()\n\tdefer u.journalsLock.Unlock()\n\n\tif u.Journals == nil {\n\t\tu.Journals = map[string]int32{}\n\t}\n\n\tkind, ok := u.Journals[key]\n\tif ok && kind != 0 {\n\t\treturn\n\t}\n\n\tjrnls := map[string]int32{}\n\tfor key, index := range u.Journals {\n\t\tjrnls[key] = index\n\t}\n\tindex := u.JournalsIndex\n\tif index == 0 {\n\t\tindex = 248000\n\t}\n\tindex += 1\n\tjrnls[key] = index\n\n\tcoll := db.Units()\n\n\tquery := bson.M{\n\t\t\"_id\": u.Id,\n\t}\n\tif u.JournalsIndex == 0 {\n\t\tquery[\"$or\"] = []bson.M{\n\t\t\t{\"journals_index\": bson.M{\"$exists\": false}},\n\t\t\t{\"journals_index\": 0},\n\t\t}\n\t} else {\n\t\tquery[\"journals_index\"] = u.JournalsIndex\n\t}\n\n\tif !u.newUnit {\n\t\tresp, e := coll.UpdateOne(db, query, bson.M{\n\t\t\t\"$set\": bson.M{\n\t\t\t\t\"journals_index\": index,\n\t\t\t\t\"journals\":       jrnls,\n\t\t\t},\n\t\t})\n\t\tif e != nil {\n\t\t\terr = database.ParseError(e)\n\t\t\treturn\n\t\t}\n\n\t\tif resp.ModifiedCount < 1 {\n\t\t\tkind = 0\n\t\t\treturn\n\t\t}\n\t}\n\n\tu.Journals = jrnls\n\tu.JournalsIndex = index\n\tkind = index\n\n\treturn\n}\n\nfunc (u *Unit) GetKind(db *database.Database, key string) (\n\tkind int32, err error) {\n\n\tfor i := 0; i < 3; i++ {\n\t\tkind, err = u.getKind(db, key)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif kind != 0 {\n\t\t\tbreak\n\t\t}\n\n\t\terr = u.RefreshJournals(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif kind == 0 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"unit: Failed to get journal kind index\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) Parse(db *database.Database, newUnit bool) (\n\tnewSpec *spec.Spec, updateSpec *spec.Spec,\n\terrData *errortypes.ErrorData, err error) {\n\n\tif newUnit {\n\t\tu.newUnit = true\n\t}\n\n\tspc := spec.New(u.Pod, u.Id, u.Organization, u.Spec)\n\n\terrData, err = spc.Parse(db, u)\n\tif err != nil {\n\t\treturn\n\t}\n\tif errData != nil {\n\t\treturn\n\t}\n\n\tisNewSpec := u.Hash != spc.Hash\n\tif !isNewSpec && u.Name != spc.Name || u.Count != spc.Count {\n\t\tupdateSpec, errData, err = u.updateSpec(db, spc)\n\t\tif err != nil {\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t\tisNewSpec = true\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif errData != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif isNewSpec {\n\t\tnewSpec, errData, err = u.newSpec(db, spc, newUnit)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif errData != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) Commit(db *database.Database) (err error) {\n\tcoll := db.Units()\n\n\terr = coll.Commit(u.Id, u)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Units()\n\n\terr = coll.CommitFields(u.Id, u, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *Unit) Insert(db *database.Database) (err error) {\n\tcoll := db.Units()\n\n\tif u.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"unit: Cannot insert unit without id\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := coll.InsertOne(db, u)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tu.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  },
  {
    "path": "unit/utils.go",
    "content": "package unit\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nfunc Get(db *database.Database, unitId bson.ObjectID) (\n\tunt *Unit, err error) {\n\n\tcoll := db.Units()\n\tunt = &Unit{}\n\n\terr = coll.FindOneId(unitId, unt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, unitId bson.ObjectID) (\n\tunt *Unit, err error) {\n\n\tcoll := db.Units()\n\tunt = &Unit{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          unitId,\n\t\t\"organization\": orgId,\n\t}).Decode(unt)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (units []*Unit, err error) {\n\tcoll := db.Units()\n\tunits = []*Unit{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tunt := &Unit{}\n\t\terr = cursor.Decode(unt)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tunits = append(units, unt)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllMap(db *database.Database, query *bson.M) (\n\tunitsMap map[bson.ObjectID]*Unit, err error) {\n\n\tcoll := db.Units()\n\tunitsMap = map[bson.ObjectID]*Unit{}\n\n\tcursor, err := coll.Find(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tunt := &Unit{}\n\t\terr = cursor.Decode(unt)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tunitsMap[unt.Id] = unt\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc NewSpec(db *database.Database,\n\tpodId, unitId bson.ObjectID) (timestamp time.Time,\n\tindex int, err error) {\n\n\tcoll := db.Units()\n\n\tupdateOpts := options.FindOneAndUpdate().\n\t\tSetProjection(&bson.M{\n\t\t\t\"spec_index\":     1,\n\t\t\t\"spec_timestamp\": 1,\n\t\t}).\n\t\tSetReturnDocument(options.After)\n\n\tunit := &Unit{}\n\n\terr = coll.FindOneAndUpdate(db, &bson.M{\n\t\t\"_id\": unitId,\n\t\t\"pod\": podId,\n\t}, &bson.M{\n\t\t\"$inc\": &bson.M{\n\t\t\t\"spec_index\": 1,\n\t\t},\n\t\t\"$currentDate\": &bson.M{\n\t\t\t\"spec_timestamp\": true,\n\t\t},\n\t}, updateOpts).Decode(unit)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tindex = unit.SpecIndex\n\ttimestamp = unit.SpecTimestamp\n\n\treturn\n}\n\nfunc Remove(db *database.Database, untId bson.ObjectID) (err error) {\n\tcoll := db.Schedulers()\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": untId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\tcoll = db.Units()\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": untId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, untId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.Schedulers()\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          untId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\tcoll = db.Units()\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\":          untId,\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveAll(db *database.Database, query *bson.M) (err error) {\n\tcoll := db.Schedulers()\n\t_, err = coll.DeleteMany(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\tcoll = db.Units()\n\t_, err = coll.DeleteMany(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database,\n\tuntIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Schedulers()\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": untIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\tcoll = db.Units()\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": untIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tuntIds []bson.ObjectID) (err error) {\n\n\tcoll := db.Schedulers()\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": untIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\tcoll = db.Units()\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": untIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "upgrade/created.go",
    "content": "package upgrade\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/disk\"\n\t\"github.com/pritunl/pritunl-cloud/instance\"\n)\n\nfunc createdUpgrade(db *database.Database) (err error) {\n\tinsts, err := instance.GetAll(db, &bson.M{\n\t\t\"created\": &bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, inst := range insts {\n\t\tinst.Created = inst.Id.Timestamp()\n\t\terr = inst.CommitFields(db, set.NewSet(\"created\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tdisks, err := disk.GetAll(db, &bson.M{\n\t\t\"created\": &bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, disk := range disks {\n\t\tdisk.Created = disk.Id.Timestamp()\n\t\terr = disk.CommitFields(db, set.NewSet(\"created\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "upgrade/instance.go",
    "content": "package upgrade\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nfunc instanceUpgrade(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"virt_timestamp\": bson.M{\n\t\t\t\"$exists\": true,\n\t\t},\n\t}, []bson.M{\n\t\tbson.M{\n\t\t\t\"$set\": bson.M{\n\t\t\t\t\"timestamp\": \"$virt_timestamp\",\n\t\t\t},\n\t\t},\n\t\tbson.M{\n\t\t\t\"$unset\": \"virt_timestamp\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "upgrade/journal.go",
    "content": "package upgrade\n\nimport (\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/journal\"\n)\n\nfunc journalUpgrade(db *database.Database) (err error) {\n\tcoll := db.Journal()\n\n\tcursor, err := coll.Find(db, &bson.M{\n\t\t\"c\": &bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, options.Find().\n\t\tSetSort(bson.D{\n\t\t\t{\"t\", 1},\n\t\t\t{\"_id\", 1},\n\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tvar count int32\n\tvar lastTime time.Time\n\ti := 0\n\n\tfor cursor.Next(db) {\n\t\tjrnl := &journal.Journal{}\n\t\terr = cursor.Decode(jrnl)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tif jrnl.Timestamp.Unix() != lastTime.Unix() {\n\t\t\tcount = 1\n\t\t}\n\t\tlastTime = jrnl.Timestamp\n\n\t\tif i%1000 == 0 {\n\t\t\tprintln(count)\n\t\t}\n\t\ti += 1\n\n\t\t_, err = coll.UpdateOne(db, &bson.M{\n\t\t\t\"_id\": jrnl.Id,\n\t\t}, &bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"c\": count,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tcount += 1\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "upgrade/node.go",
    "content": "package upgrade\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nfunc nodeUpgrade(db *database.Database) (err error) {\n\tcoll := db.Nodes()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"available_interfaces\": bson.M{\n\t\t\t\"$exists\": true,\n\t\t},\n\t\t\"available_interfaces.0\": bson.M{\n\t\t\t\"$type\": \"string\",\n\t\t},\n\t}, []bson.M{\n\t\tbson.M{\n\t\t\t\"$unset\": \"available_interfaces\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"available_bridges\": bson.M{\n\t\t\t\"$exists\": true,\n\t\t},\n\t\t\"available_bridges.0\": bson.M{\n\t\t\t\"$type\": \"string\",\n\t\t},\n\t}, []bson.M{\n\t\tbson.M{\n\t\t\t\"$unset\": \"available_bridges\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "upgrade/objectid.go",
    "content": "package upgrade\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nfunc objectIdUpgrade(db *database.Database) (err error) {\n\tnilObjectID := bson.NilObjectID\n\n\tcoll := db.Alerts()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"organization\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"organization\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Authorities()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"organization\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"organization\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Balancers()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"organization\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"organization\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"datacenter\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"datacenter\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Certificates()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"organization\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"organization\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"acme_secret\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"acme_secret\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Deployments()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"datacenter\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"datacenter\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"zone\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"zone\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"node\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"node\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"instance\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"instance\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"image\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"image\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Disks()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"node\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"node\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"pool\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"pool\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"organization\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"organization\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"instance\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"instance\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"source_instance\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"source_instance\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"deployment\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"deployment\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"image\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"image\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"restore_image\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"restore_image\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Domains()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"organization\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"organization\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"lock_id\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"lock_id\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Firewalls()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"$or\": []*bson.M{\n\t\t\t&bson.M{\n\t\t\t\t\"organization\": nil,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"organization\": &bson.M{\n\t\t\t\t\t\"$exists\": false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"organization\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Images()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"deployment\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"deployment\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Instances()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"disk_pool\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"disk_pool\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"node\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"node\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"shape\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"shape\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"deployment\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"deployment\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Plans()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"organization\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"organization\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Secrets()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"organization\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"organization\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Zones()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"datacenter\": bson.M{\n\t\t\t\"$exists\": false,\n\t\t},\n\t}, bson.M{\n\t\t\"$set\": bson.M{\n\t\t\t\"datacenter\": nilObjectID,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "upgrade/roles.go",
    "content": "package upgrade\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nfunc rolesUpgrade(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"network_roles\": bson.M{\n\t\t\t\"$exists\": true,\n\t\t},\n\t}, []bson.M{\n\t\tbson.M{\n\t\t\t\"$set\": bson.M{\n\t\t\t\t\"roles\": \"$network_roles\",\n\t\t\t},\n\t\t},\n\t\tbson.M{\n\t\t\t\"$unset\": \"network_roles\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Nodes()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"network_roles\": bson.M{\n\t\t\t\"$exists\": true,\n\t\t},\n\t}, []bson.M{\n\t\tbson.M{\n\t\t\t\"$set\": bson.M{\n\t\t\t\t\"roles\": \"$network_roles\",\n\t\t\t},\n\t\t},\n\t\tbson.M{\n\t\t\t\"$unset\": \"network_roles\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Firewalls()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"network_roles\": bson.M{\n\t\t\t\"$exists\": true,\n\t\t},\n\t}, []bson.M{\n\t\tbson.M{\n\t\t\t\"$set\": bson.M{\n\t\t\t\t\"roles\": \"$network_roles\",\n\t\t\t},\n\t\t},\n\t\tbson.M{\n\t\t\t\"$unset\": \"network_roles\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Authorities()\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"network_roles\": bson.M{\n\t\t\t\"$exists\": true,\n\t\t},\n\t}, []bson.M{\n\t\tbson.M{\n\t\t\t\"$set\": bson.M{\n\t\t\t\t\"roles\": \"$network_roles\",\n\t\t\t},\n\t\t},\n\t\tbson.M{\n\t\t\t\"$unset\": \"network_roles\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "upgrade/state.go",
    "content": "package upgrade\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nfunc instStateUpgrade(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\n\t_, err = coll.UpdateMany(db, bson.M{\n\t\t\"virt_state\": bson.M{\n\t\t\t\"$exists\": true,\n\t\t},\n\t}, []bson.M{\n\t\tbson.M{\n\t\t\t\"$set\": bson.M{\n\t\t\t\t\"state\": \"$virt_state\",\n\t\t\t},\n\t\t},\n\t\tbson.M{\n\t\t\t\"$unset\": \"virt_state\",\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "upgrade/upgrade.go",
    "content": "package upgrade\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nfunc Upgrade() (err error) {\n\tdb := database.GetDatabase()\n\tdefer db.Close()\n\n\terr = nodeUpgrade(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = rolesUpgrade(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = instanceUpgrade(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = createdUpgrade(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = zoneDatacenterUpgrade(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = instStateUpgrade(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = objectIdUpgrade(db)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "upgrade/zone_datacenter.go",
    "content": "package upgrade\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\ntype zoneUgradeDoc struct {\n\tId         bson.ObjectID `bson:\"_id\"`\n\tNode       bson.ObjectID `bson:\"node\"`\n\tDatacenter bson.ObjectID `bson:\"datacenter\"`\n\tZone       bson.ObjectID `bson:\"zone\"`\n}\n\nfunc zoneDatacenterUpgrade(db *database.Database) (err error) {\n\tzoneColl := db.Zones()\n\n\tzoneDatacenterMap := make(map[bson.ObjectID]bson.ObjectID)\n\tnodeMap := make(map[bson.ObjectID]*zoneUgradeDoc)\n\n\tgetDatacenterForZone := func(zoneID bson.ObjectID) (\n\t\tbson.ObjectID, error) {\n\n\t\tif datacenterID, ok := zoneDatacenterMap[zoneID]; ok {\n\t\t\treturn datacenterID, nil\n\t\t}\n\n\t\tzne := &zoneUgradeDoc{}\n\t\terr := zoneColl.FindOne(db, bson.M{\n\t\t\t\"_id\": zoneID,\n\t\t}).Decode(zne)\n\t\tif err != nil {\n\t\t\treturn bson.NilObjectID, database.ParseError(err)\n\t\t}\n\n\t\tzoneDatacenterMap[zoneID] = zne.Datacenter\n\t\treturn zne.Datacenter, nil\n\t}\n\n\tgetNode := func(nodeId bson.ObjectID) (\n\t\t*zoneUgradeDoc, error) {\n\n\t\tif nde, ok := nodeMap[nodeId]; ok {\n\t\t\treturn nde, nil\n\t\t}\n\n\t\tcoll := db.Nodes()\n\n\t\tnde := &zoneUgradeDoc{}\n\t\terr := coll.FindOne(db, bson.M{\n\t\t\t\"_id\": nodeId,\n\t\t}).Decode(nde)\n\t\tif err != nil {\n\t\t\treturn nil, database.ParseError(err)\n\t\t}\n\n\t\tnodeMap[nodeId] = nde\n\t\treturn nde, nil\n\t}\n\n\tcoll := db.Nodes()\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tbson.M{\n\t\t\t\"zone\":       bson.M{\"$exists\": true},\n\t\t\t\"datacenter\": bson.M{\"$exists\": false},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdoc := &zoneUgradeDoc{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\n\t\tdatacenterID, err := getDatacenterForZone(doc.Zone)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = coll.UpdateOne(\n\t\t\tdb,\n\t\t\tbson.M{\"_id\": doc.Id},\n\t\t\tbson.M{\"$set\": bson.M{\"datacenter\": datacenterID}},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\t}\n\terr = cursor.Err()\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\n\tcoll = db.Deployments()\n\tcursor, err = coll.Find(\n\t\tdb,\n\t\tbson.M{\n\t\t\t\"zone\":       bson.M{\"$exists\": true},\n\t\t\t\"datacenter\": bson.M{\"$exists\": false},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdoc := &zoneUgradeDoc{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\n\t\tdatacenterID, err := getDatacenterForZone(doc.Zone)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = coll.UpdateOne(\n\t\t\tdb,\n\t\t\tbson.M{\"_id\": doc.Id},\n\t\t\tbson.M{\"$set\": bson.M{\"datacenter\": datacenterID}},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\t}\n\terr = cursor.Err()\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\n\tcoll = db.Instances()\n\tcursor, err = coll.Find(\n\t\tdb,\n\t\tbson.M{\n\t\t\t\"zone\":       bson.M{\"$exists\": true},\n\t\t\t\"datacenter\": bson.M{\"$exists\": false},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdoc := &zoneUgradeDoc{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\n\t\tdatacenterID, err := getDatacenterForZone(doc.Zone)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = coll.UpdateOne(\n\t\t\tdb,\n\t\t\tbson.M{\"_id\": doc.Id},\n\t\t\tbson.M{\"$set\": bson.M{\"datacenter\": datacenterID}},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\t}\n\terr = cursor.Err()\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\n\tcoll = db.Pools()\n\tcursor, err = coll.Find(\n\t\tdb,\n\t\tbson.M{\n\t\t\t\"zone\":       bson.M{\"$exists\": true},\n\t\t\t\"datacenter\": bson.M{\"$exists\": false},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdoc := &zoneUgradeDoc{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\n\t\tdatacenterID, err := getDatacenterForZone(doc.Zone)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = coll.UpdateOne(\n\t\t\tdb,\n\t\t\tbson.M{\"_id\": doc.Id},\n\t\t\tbson.M{\"$set\": bson.M{\"datacenter\": datacenterID}},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\t}\n\terr = cursor.Err()\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\n\tcoll = db.Specs()\n\tcursor, err = coll.Find(\n\t\tdb,\n\t\tbson.M{\n\t\t\t\"zone\":       bson.M{\"$exists\": true},\n\t\t\t\"datacenter\": bson.M{\"$exists\": false},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdoc := &zoneUgradeDoc{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\n\t\tdatacenterID, err := getDatacenterForZone(doc.Zone)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = coll.UpdateOne(\n\t\t\tdb,\n\t\t\tbson.M{\"_id\": doc.Id},\n\t\t\tbson.M{\"$set\": bson.M{\"datacenter\": datacenterID}},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\t}\n\terr = cursor.Err()\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\n\tcoll = db.Disks()\n\tcursor, err = coll.Find(\n\t\tdb,\n\t\tbson.M{\n\t\t\t\"zone\":       bson.M{\"$exists\": false},\n\t\t\t\"datacenter\": bson.M{\"$exists\": false},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tdoc := &zoneUgradeDoc{}\n\t\terr = cursor.Decode(doc)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\n\t\tnde, err := getNode(doc.Node)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = coll.UpdateOne(\n\t\t\tdb,\n\t\t\tbson.M{\"_id\": doc.Id},\n\t\t\tbson.M{\"$set\": bson.M{\n\t\t\t\t\"datacenter\": nde.Datacenter,\n\t\t\t\t\"zone\":       nde.Zone,\n\t\t\t}},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn database.ParseError(err)\n\t\t}\n\t}\n\terr = cursor.Err()\n\tif err != nil {\n\t\treturn database.ParseError(err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "usb/usb.go",
    "content": "package usb\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nconst (\n\tsyncInterval = 6 * time.Second\n)\n\nvar (\n\tsyncLast               time.Time\n\tsyncLock               sync.Mutex\n\tdevicesCache           []*Device\n\tdevicesIdMapCache      map[string]*Device\n\tdevicesBusMapCache     map[string]*Device\n\tdevicesBusPathMapCache map[string]*Device\n)\n\ntype Device struct {\n\tName       string `bson:\"name\" json:\"name\"`\n\tVendor     string `bson:\"vendor\" json:\"vendor\"`\n\tProduct    string `bson:\"product\" json:\"product\"`\n\tBus        string `bson:\"bus\" json:\"bus\"`\n\tAddress    string `bson:\"address\" json:\"address\"`\n\tDeviceName string `bson:\"-\" json:\"-\"`\n\tDevicePath string `bson:\"-\" json:\"-\"`\n\tBusPath    string `bson:\"-\" json:\"-\"`\n}\n\nfunc (d *Device) GetQemuId() string {\n\treturn fmt.Sprintf(\"usb_%s_%s_%s_%s_%d\",\n\t\td.Bus,\n\t\td.Address,\n\t\td.Vendor,\n\t\td.Product,\n\t\tutils.RandInt(1111, 9999),\n\t)\n}\n\nfunc (d *Device) Unbind() (err error) {\n\tunbindPath := path.Join(d.DevicePath, \"driver\", \"unbind\")\n\n\texists, err := utils.Exists(unbindPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif exists {\n\t\terr = ioutil.WriteFile(\n\t\t\tunbindPath,\n\t\t\t[]byte(d.DeviceName),\n\t\t\t0644,\n\t\t)\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrapf(err, \"usb: Failed to unbind '%s'\", unbindPath),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc syncDevices() (err error) {\n\tsyncLock.Lock()\n\tdefer syncLock.Unlock()\n\n\tdevices := []*Device{}\n\tdevicesIdMap := map[string]*Device{}\n\tdevicesBusMap := map[string]*Device{}\n\tdevicesBusPathMap := map[string]*Device{}\n\tbasePath := \"/sys/bus/usb/devices/\"\n\n\tfiles, err := ioutil.ReadDir(basePath)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"usb: Failed to read dir '%s'\", basePath),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, file := range files {\n\t\tdevName := file.Name()\n\t\tif strings.Contains(devName, \":\") ||\n\t\t\tstrings.HasPrefix(devName, \"usb\") {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tdevPath := filepath.Join(basePath, devName)\n\n\t\tvendor, e := utils.ReadExists(filepath.Join(devPath, \"idVendor\"))\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif vendor == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tproduct, e := utils.ReadExists(filepath.Join(devPath, \"idProduct\"))\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif product == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tbusNum, e := utils.ReadExists(filepath.Join(devPath, \"busnum\"))\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif busNum == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tdevNum, e := utils.ReadExists(filepath.Join(devPath, \"devnum\"))\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tif devNum == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tmanufacturerDesc, e := utils.ReadExists(\n\t\t\tfilepath.Join(devPath, \"manufacturer\"))\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tproductDesc, e := utils.ReadExists(\n\t\t\tfilepath.Join(devPath, \"product\"))\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif manufacturerDesc == \"\" {\n\t\t\tmanufacturerDesc = \"Unknown Manufacturer\"\n\t\t}\n\t\tif productDesc == \"\" {\n\t\t\tproductDesc = \"Unknown Product\"\n\t\t}\n\n\t\tname := utils.FilterStr(strings.TrimSpace(manufacturerDesc)+\n\t\t\t\" \"+strings.TrimSpace(productDesc), 256)\n\t\tvendor = strings.TrimSpace(vendor)\n\t\tproduct = strings.TrimSpace(product)\n\t\tbusNum = fmt.Sprintf(\"%03s\", strings.TrimSpace(busNum))\n\t\tdevNum = fmt.Sprintf(\"%03s\", strings.TrimSpace(devNum))\n\t\tbusPath := filepath.Join(\"/dev/bus/usb\", busNum, devNum)\n\n\t\tdevice := &Device{\n\t\t\tName:       name,\n\t\t\tVendor:     vendor,\n\t\t\tProduct:    product,\n\t\t\tBus:        busNum,\n\t\t\tAddress:    devNum,\n\t\t\tDeviceName: devName,\n\t\t\tDevicePath: devPath,\n\t\t\tBusPath:    busPath,\n\t\t}\n\n\t\tdevices = append(devices, device)\n\t\tdevicesIdMap[device.Vendor+\":\"+device.Product] = device\n\t\tdevicesBusMap[device.Bus+\"-\"+device.Address] = device\n\t\tdevicesBusPathMap[device.BusPath] = device\n\t}\n\n\tdevicesCache = devices\n\tdevicesIdMapCache = devicesIdMap\n\tdevicesBusMapCache = devicesBusMap\n\tdevicesBusPathMapCache = devicesBusPathMap\n\tsyncLast = time.Now()\n\n\treturn\n}\n\nfunc GetDevices() (devices []*Device, err error) {\n\tif time.Since(syncLast) > syncInterval {\n\t\terr = syncDevices()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tsyncLock.Lock()\n\tdevices = devicesCache\n\tsyncLock.Unlock()\n\treturn\n}\n\nfunc GetDevice(bus, address, vendor, product string) (\n\tdevice *Device, err error) {\n\n\tif time.Since(syncLast) > syncInterval {\n\t\terr = syncDevices()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tsyncLock.Lock()\n\tif bus != \"\" && address != \"\" {\n\t\tdevice = devicesBusMapCache[bus+\"-\"+address]\n\t\tif device != nil && vendor != \"\" && product != \"\" {\n\t\t\tif device.Vendor != vendor || device.Product != product {\n\t\t\t\tdevice = nil\n\t\t\t}\n\t\t}\n\t} else {\n\t\tdevice = devicesIdMapCache[vendor+\":\"+product]\n\t\tif device != nil && bus != \"\" && address != \"\" {\n\t\t\tif device.Bus != bus || device.Address != address {\n\t\t\t\tdevice = nil\n\t\t\t}\n\t\t}\n\t}\n\tsyncLock.Unlock()\n\treturn\n}\n\nfunc GetDeviceId(vendor, product string) (device *Device, err error) {\n\tif time.Since(syncLast) > syncInterval {\n\t\terr = syncDevices()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tsyncLock.Lock()\n\tdevice = devicesIdMapCache[vendor+\":\"+product]\n\tsyncLock.Unlock()\n\treturn\n}\n\nfunc GetDeviceBus(bus, address string) (device *Device, err error) {\n\tif time.Since(syncLast) > syncInterval {\n\t\terr = syncDevices()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tsyncLock.Lock()\n\tdevice = devicesBusMapCache[bus+\"-\"+address]\n\tsyncLock.Unlock()\n\treturn\n}\n\nfunc GetDeviceBusPath(busPath string) (device *Device, err error) {\n\tif time.Since(syncLast) > syncInterval {\n\t\terr = syncDevices()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tsyncLock.Lock()\n\tdevice = devicesBusPathMapCache[busPath]\n\tsyncLock.Unlock()\n\treturn\n}\n"
  },
  {
    "path": "usb/utils.go",
    "content": "package usb\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nvar (\n\treg = regexp.MustCompile(\"[^a-z0-9]+\")\n)\n\nfunc Available(db *database.Database, instId, nodeId bson.ObjectID,\n\tdevice *Device) (available bool, err error) {\n\n\tcoll := db.Instances()\n\n\tquery := bson.M{\n\t\t\"node\": nodeId,\n\t}\n\n\tif !instId.IsZero() {\n\t\tquery[\"_id\"] = &bson.M{\n\t\t\t\"$ne\": instId,\n\t\t}\n\t}\n\n\tif device.Vendor != \"\" && device.Product != \"\" {\n\t\tquery[\"usb_devices\"] = bson.M{\n\t\t\t\"$elemMatch\": bson.M{\n\t\t\t\t\"vendor\":  device.Vendor,\n\t\t\t\t\"product\": device.Product,\n\t\t\t},\n\t\t}\n\t} else if device.Bus != \"\" && device.Address != \"\" {\n\t\tquery[\"usb_devices\"] = bson.M{\n\t\t\t\"$elemMatch\": bson.M{\n\t\t\t\t\"bus\":     device.Bus,\n\t\t\t\t\"address\": device.Address,\n\t\t\t},\n\t\t}\n\t}\n\n\tcount, err := coll.CountDocuments(db, query)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif count == 0 {\n\t\tavailable = true\n\t}\n\n\treturn\n}\n\nfunc FilterId(deviceId string) string {\n\tdeviceId = strings.ToLower(deviceId)\n\tdeviceId = reg.ReplaceAllString(deviceId, \"\")\n\tif len(deviceId) != 4 {\n\t\treturn \"\"\n\t}\n\treturn deviceId\n}\n\nfunc FilterAddr(addr string) string {\n\taddr = strings.ToLower(addr)\n\taddr = reg.ReplaceAllString(addr, \"\")\n\tif len(addr) != 3 {\n\t\treturn \"\"\n\t}\n\treturn addr\n}\n"
  },
  {
    "path": "user/constants.go",
    "content": "package user\n\nimport (\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\nconst (\n\tLocal     = \"local\"\n\tApi       = \"api\"\n\tAzure     = \"azure\"\n\tAuthZero  = \"authzero\"\n\tGoogle    = \"google\"\n\tOneLogin  = \"onelogin\"\n\tOkta      = \"okta\"\n\tJumpCloud = \"jumpcloud\"\n)\n\nvar (\n\ttypes = set.NewSet(\n\t\tLocal,\n\t\tApi,\n\t\tAzure,\n\t\tAuthZero,\n\t\tGoogle,\n\t\tOneLogin,\n\t\tOkta,\n\t\tJumpCloud,\n\t)\n)\n"
  },
  {
    "path": "user/user.go",
    "content": "package user\n\nimport (\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/go-webauthn/webauthn/webauthn\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/device\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\ntype User struct {\n\tId              bson.ObjectID         `bson:\"_id,omitempty\" json:\"id\"`\n\tType            string                `bson:\"type\" json:\"type\"`\n\tProvider        bson.ObjectID         `bson:\"provider\" json:\"provider\"`\n\tUsername        string                `bson:\"username\" json:\"username\"`\n\tPassword        string                `bson:\"password\" json:\"-\"`\n\tComment         string                `bson:\"comment\" json:\"comment\"`\n\tDefaultPassword string                `bson:\"default_password\" json:\"-\"`\n\tToken           string                `bson:\"token\" json:\"token\"`\n\tSecret          string                `bson:\"secret\" json:\"secret\"`\n\tTheme           string                `bson:\"theme\" json:\"-\"`\n\tEditorTheme     string                `bson:\"editor_theme\" json:\"-\"`\n\tLastActive      time.Time             `bson:\"last_active\" json:\"last_active\"`\n\tLastSync        time.Time             `bson:\"last_sync\" json:\"last_sync\"`\n\tRoles           []string              `bson:\"roles\" json:\"roles\"`\n\tAdministrator   string                `bson:\"administrator\" json:\"administrator\"`\n\tDisabled        bool                  `bson:\"disabled\" json:\"disabled\"`\n\tActiveUntil     time.Time             `bson:\"active_until\" json:\"active_until\"`\n\tPermissions     []string              `bson:\"permissions\" json:\"permissions\"`\n\tOracleLicense   bool                  `bson:\"oracle_licese\" json:\"oracle_license\"`\n\tWanCredentials  []webauthn.Credential `bson:\"-\" json:\"-\"`\n}\n\nfunc (u *User) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif u.Roles == nil {\n\t\tu.Roles = []string{}\n\t}\n\n\tif u.Permissions == nil {\n\t\tu.Permissions = []string{}\n\t}\n\n\tif !types.Contains(u.Type) {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"user_type_invalid\",\n\t\t\tMessage: \"User type is not valid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif u.Username == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"user_username_invalid\",\n\t\t\tMessage: \"User username is not valid\",\n\t\t}\n\t\treturn\n\t}\n\n\tif u.Type == Local && u.Password == \"\" {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"user_password_missing\",\n\t\t\tMessage: \"User password is not set\",\n\t\t}\n\t\treturn\n\t}\n\n\tu.Format()\n\n\treturn\n}\n\nfunc (u *User) Format() {\n\tif u.Type == Local {\n\t\tu.Username = strings.ToLower(u.Username)\n\t}\n\n\troles := []string{}\n\trolesSet := set.NewSet()\n\n\tfor _, role := range u.Roles {\n\t\trolesSet.Add(role)\n\t}\n\n\tfor role := range rolesSet.Iter() {\n\t\troles = append(roles, role.(string))\n\t}\n\n\tsort.Strings(roles)\n\n\tu.Roles = roles\n}\n\nfunc (u *User) SuperExists(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tif u.Administrator != \"super\" && !u.Id.IsZero() {\n\t\texists, e := hasSuperSkip(db, u.Id)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tif !exists {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"user_missing_super\",\n\t\t\t\tMessage: \"Missing super administrator\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (u *User) Commit(db *database.Database) (err error) {\n\tcoll := db.Users()\n\n\terr = coll.Commit(u.Id, u)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *User) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Users()\n\n\terr = coll.CommitFields(u.Id, u, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *User) Insert(db *database.Database) (err error) {\n\tcoll := db.Users()\n\n\tif !u.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"user: User already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\t_, err = coll.InsertOne(db, u)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *User) Upsert(db *database.Database) (err error) {\n\tcoll := db.Users()\n\n\topts := options.FindOneAndUpdate().\n\t\tSetUpsert(true).\n\t\tSetReturnDocument(options.After)\n\n\terr = coll.FindOneAndUpdate(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"type\":     u.Type,\n\t\t\t\"username\": u.Username,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$setOnInsert\": u,\n\t\t},\n\t\topts,\n\t).Decode(u)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *User) RolesMatch(roles []string) bool {\n\tusrRoles := set.NewSet()\n\tfor _, role := range u.Roles {\n\t\tusrRoles.Add(role)\n\t}\n\n\tfor _, role := range roles {\n\t\tif usrRoles.Contains(role) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (u *User) RolesMerge(roles []string) bool {\n\tnewRoles := set.NewSet()\n\tcurRoles := set.NewSet()\n\n\tfor _, role := range roles {\n\t\tnewRoles.Add(role)\n\t}\n\n\tfor _, role := range u.Roles {\n\t\tnewRoles.Add(role)\n\t\tcurRoles.Add(role)\n\t}\n\n\tif !curRoles.IsEqual(newRoles) {\n\t\trls := []string{}\n\n\t\tfor role := range newRoles.Iter() {\n\t\t\trls = append(rls, role.(string))\n\t\t}\n\n\t\tu.Roles = rls\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (u *User) RolesOverwrite(roles []string) bool {\n\tnewRoles := set.NewSet()\n\tcurRoles := set.NewSet()\n\n\tfor _, role := range roles {\n\t\tnewRoles.Add(role)\n\t}\n\n\tfor _, role := range u.Roles {\n\t\tcurRoles.Add(role)\n\t}\n\n\tif !curRoles.IsEqual(newRoles) {\n\t\tu.Roles = roles\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (u *User) SetPassword(password string) (err error) {\n\tif u.Type != Local {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.New(\"user: User type cannot store password\"),\n\t\t}\n\t\treturn\n\t}\n\n\thash, err := bcrypt.GenerateFromPassword([]byte(password), 10)\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"user: Failed to hash password\"),\n\t\t}\n\t\treturn\n\t}\n\n\tu.Password = string(hash)\n\tu.DefaultPassword = \"\"\n\n\treturn\n}\n\nfunc (u *User) GenerateDefaultPassword() (err error) {\n\tpasswd, err := utils.RandStr(12)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = u.SetPassword(passwd)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tu.DefaultPassword = passwd\n\n\treturn\n}\n\nfunc (u *User) CheckPassword(password string) bool {\n\tif u.Type != Local || u.Password == \"\" {\n\t\treturn false\n\t}\n\n\terr := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (u *User) GenerateToken() (err error) {\n\tu.Token, err = utils.RandStr(48)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tu.Secret, err = utils.RandStr(48)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *User) GetDevices(db *database.Database) (\n\tdevices []*device.Device, err error) {\n\n\tdevices, err = device.GetAll(db, u.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (u *User) LoadWebAuthnDevices(db *database.Database) (\n\tdevices []*device.Device, hasU2f bool, err error) {\n\n\tdevices, err = device.GetAll(db, u.Id)\n\tif err != nil {\n\t\treturn\n\t}\n\n\twanCredentials := []webauthn.Credential{}\n\tfor _, devc := range devices {\n\t\tswitch devc.Type {\n\t\tcase device.WebAuthn:\n\t\t\tbreak\n\t\tcase device.U2f:\n\t\t\thasU2f = true\n\t\t\tbreak\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\twanCred, e := devc.UnmarshalWebauthn()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\twanCredentials = append(wanCredentials, wanCred)\n\t}\n\n\tu.WanCredentials = wanCredentials\n\n\treturn\n}\n\nfunc (u *User) WebAuthnID() []byte {\n\treturn u.Id[:]\n}\n\nfunc (u *User) WebAuthnName() string {\n\treturn u.Username\n}\n\nfunc (u *User) WebAuthnDisplayName() string {\n\treturn u.Username\n}\n\nfunc (u *User) WebAuthnIcon() string {\n\treturn \"\"\n}\n\nfunc (u *User) WebAuthnCredentials() []webauthn.Credential {\n\treturn u.WanCredentials\n}\n\nfunc init() {\n\tmodule := requires.New(\"user\")\n\tmodule.After(\"settings\")\n\n\tmodule.Handler = func() (err error) {\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tcoll := db.Users()\n\n\t\tcursor, err := coll.Find(db, &bson.M{})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t\tdefer cursor.Close(db)\n\n\t\tfor cursor.Next(db) {\n\t\t\tusr := &User{}\n\t\t\terr = cursor.Decode(usr)\n\t\t\tif err != nil {\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnewUsername := strings.ToLower(usr.Username)\n\t\t\tif usr.Username != newUsername {\n\t\t\t\terr = coll.UpdateId(usr.Id, &bson.M{\n\t\t\t\t\t\"$set\": &bson.M{\n\t\t\t\t\t\t\"username\": newUsername,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcount, err := Count(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif count == 0 {\n\t\t\tlogrus.Info(\"user: Creating default super user\")\n\n\t\t\tusr := User{\n\t\t\t\tType:          Local,\n\t\t\t\tUsername:      \"pritunl\",\n\t\t\t\tAdministrator: \"super\",\n\t\t\t\tRoles:         []string{\"org\"},\n\t\t\t}\n\n\t\t\terr = usr.GenerateDefaultPassword()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = usr.Validate(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terr = usr.Insert(db)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "user/utils.go",
    "content": "package user\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, userId bson.ObjectID) (\n\tusr *User, err error) {\n\n\tcoll := db.Users()\n\tusr = &User{}\n\n\terr = coll.FindOneId(userId, usr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetUpdate(db *database.Database, userId bson.ObjectID) (\n\tusr *User, err error) {\n\n\tcoll := db.Users()\n\tusr = &User{}\n\ttimestamp := time.Now()\n\n\terr = coll.FindOneAndUpdate(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": userId,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"last_active\": timestamp,\n\t\t\t},\n\t\t},\n\t).Decode(usr)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tusr.LastActive = timestamp\n\n\treturn\n}\n\nfunc GetTokenUpdate(db *database.Database, token string) (\n\tusr *User, err error) {\n\n\tcoll := db.Users()\n\tusr = &User{}\n\ttimestamp := time.Now()\n\n\terr = coll.FindOneAndUpdate(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"token\": token,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"last_active\": timestamp,\n\t\t\t},\n\t\t},\n\t).Decode(usr)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tusr.LastActive = timestamp\n\n\treturn\n}\n\nfunc GetUsername(db *database.Database, typ, username string) (\n\tusr *User, err error) {\n\n\tcoll := db.Users()\n\tusr = &User{}\n\n\tif username == \"\" {\n\t\terr = &errortypes.NotFoundError{\n\t\t\terrors.New(\"user: Username empty\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"type\":     typ,\n\t\t\"username\": username,\n\t}).Decode(usr)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M, page, pageCount int64) (\n\tusers []*User, count int64, err error) {\n\n\tcoll := db.Users()\n\tusers = []*User{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\topts := options.Find().\n\t\tSetSort(bson.D{{\"username\", 1}})\n\n\tif pageCount != 0 {\n\t\tif pageCount == 0 {\n\t\t\tpageCount = 20\n\t\t}\n\t\tmaxPage := count / pageCount\n\t\tif count == pageCount {\n\t\t\tmaxPage = 0\n\t\t}\n\t\tpage = utils.Min64(page, maxPage)\n\t\tskip := utils.Min64(page*pageCount, count)\n\t\topts.SetSkip(skip).SetLimit(pageCount)\n\t}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\topts,\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tusr := &User{}\n\t\terr = cursor.Decode(usr)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tusers = append(users, usr)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, userIds []bson.ObjectID) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tcoll := db.Users()\n\topts := options.Count().\n\t\tSetLimit(1)\n\n\tcount, err := coll.CountDocuments(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$nin\": userIds,\n\t\t\t},\n\t\t\t\"administrator\": \"super\",\n\t\t},\n\t\topts,\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif count == 0 {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"user_remove_super\",\n\t\t\tMessage: \"Cannot remove all super administrators\",\n\t\t}\n\t\treturn\n\t}\n\n\tcoll = db.Sessions()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"user\": &bson.M{\n\t\t\t\"$in\": userIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Users()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": userIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Count(db *database.Database) (count int64, err error) {\n\tcoll := db.Users()\n\n\tcount, err = coll.CountDocuments(db, &bson.M{})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc hasSuperSkip(db *database.Database, skipId bson.ObjectID) (\n\texists bool, err error) {\n\n\tcoll := db.Users()\n\topts := options.Count().\n\t\tSetLimit(1)\n\n\tcount, err := coll.CountDocuments(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$ne\": skipId,\n\t\t\t},\n\t\t\t\"administrator\": \"super\",\n\t\t},\n\t\topts,\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif count > 0 {\n\t\texists = true\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "useragent/useragent.go",
    "content": "package useragent\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/geo\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/ua-parser/uap-go/uaparser\"\n)\n\nvar (\n\tparser = uaparser.NewFromSaved()\n)\n\nconst (\n\tLinux        = \"linux\"         // Linux = Debian + Linux + Ubuntu\n\tMacOs1010    = \"macos_1010\"    // macOS 10.10 = Mac OS X (10/10)\n\tMacOs1011    = \"macos_1011\"    // macOS 10.11 = Mac OS X (10/11)\n\tMacOs1012    = \"macos_1012\"    // macOS 10.12 = Mac OS X (10/12)\n\tMacOs1013    = \"macos_1013\"    // macOS 10.13 = Mac OS X (10/13)\n\tMacOs1014    = \"macos_1014\"    // macOS 10.14 = Mac OS X (10/14)\n\tMacOs1015    = \"macos_1015\"    // macOS 10.15 = Mac OS X (10/15)\n\tMacOs11      = \"macos_11\"      // macOS 11 = Mac OS X (11)\n\tMacOs12      = \"macos_12\"      // macOS 12 = Mac OS X (12)\n\tMacOs13      = \"macos_13\"      // macOS 13 = Mac OS X (13)\n\tMacOs14      = \"macos_14\"      // macOS 14 = Mac OS X (14)\n\tMacOs15      = \"macos_15\"      // macOS 15 = Mac OS X (15)\n\tMacOs16      = \"macos_16\"      // macOS 16 = Mac OS X (16)\n\tWindowsXp    = \"windows_xp\"    // Windows XP = Windows XP\n\tWindows7     = \"windows_7\"     // Windows 7 = Windows 7\n\tWindowsVista = \"windows_vista\" // Windows Vista = Windows Vista\n\tWindows8     = \"windows_8\"     // Windows 8 = Windows 8 + Windows 8.1 + Windows RT 8.1\n\tWindows10    = \"windows_10\"    // Windows 10 = Windows 10\n\tWindows11    = \"windows_11\"    // Windows 11 = Windows 11\n\tChromeOs     = \"chrome_os\"     // Chrome OS = Chrome OS\n\tIos8         = \"ios_8\"         // iOS 8 = iOS (8/x)\n\tIos9         = \"ios_9\"         // iOS 9 = iOS (9/x)\n\tIos10        = \"ios_10\"        // iOS 10 = iOS (10/x)\n\tIos11        = \"ios_11\"        // iOS 11 = iOS (11/x)\n\tIos12        = \"ios_12\"        // iOS 12 = iOS (12/x)\n\tIos13        = \"ios_13\"        // iOS 13 = iOS (13/x)\n\tIos14        = \"ios_14\"        // iOS 14 = iOS (14/x)\n\tIos15        = \"ios_15\"        // iOS 15 = iOS (15/x)\n\tIos16        = \"ios_16\"        // iOS 16 = iOS (16/x)\n\tIos17        = \"ios_17\"        // iOS 17 = iOS (17/x)\n\tIos18        = \"ios_18\"        // iOS 18 = iOS (18/x)\n\tIos19        = \"ios_19\"        // iOS 19 = iOS (19/x)\n\tIos20        = \"ios_20\"        // iOS 20 = iOS (20/x)\n\tAndroid4     = \"android_4\"     // Android KitKat 4.4 = Android (4/4)\n\tAndroid5     = \"android_5\"     // Android Lollipop 5.0 = Android (5/x)\n\tAndroid6     = \"android_6\"     // Android Marshmallow 6.0 = Android (6/x)\n\tAndroid7     = \"android_7\"     // Android Nougat 7.0 = Android (7/x)\n\tAndroid8     = \"android_8\"     // Android Oreo 8.0 = Android (8/x)\n\tAndroid9     = \"android_9\"     // Android Pie 9.0 = Android (9/x)\n\tAndroid10    = \"android_10\"    // Android 10.0 = Android (10/x)\n\tAndroid11    = \"android_11\"    // Android 11.0 = Android (11/x)\n\tAndroid12    = \"android_12\"    // Android 12.0 = Android (12/x)\n\tAndroid13    = \"android_13\"    // Android 13.0 = Android (13/x)\n\tAndroid14    = \"android_14\"    // Android 14.0 = Android (14/x)\n\tAndroid15    = \"android_15\"    // Android 15.0 = Android (15/x)\n\tAndroid16    = \"android_16\"    // Android 16.0 = Android (16/x)\n\tBlackberry10 = \"blackberry_10\" // Blackerry 10 = BlackBerry OS (10/x)\n\tWindowsPhone = \"windows_phone\" // Windows Phone = Windows Phone\n\tFirefoxOs    = \"firefox_os\"    // Firefox OS = Firefox OS\n\tKindle       = \"kindle\"        // Kindle = Kindle\n)\n\nconst (\n\tChrome                 = \"chrome\"                   // Chrome = Chrome + Chromium\n\tChromeMobile           = \"chrome_mobile\"            // Chrome Mobile = Chrome Mobile + Chrome Mobile iOS + Chrome Mobile WebView\n\tSafari                 = \"safari\"                   // Safari = Safari\n\tSafariMobile           = \"safari_mobile\"            // Safari Mobile = Mobile Safari + Mobile Safari UI/WKWebView\n\tFirefox                = \"firefox\"                  // Firefox = Firefox + Firefox Beta\n\tFirefoxMobile          = \"firefox_mobile\"           // Firefox Mobile = Firefox Mobile + Firefox iOS\n\tEdge                   = \"edge\"                     // Microsoft Edge = Edge\n\tInternetExplorer       = \"internet_explorer\"        // Internet Explorer = IE\n\tInternetExplorerMobile = \"internet_explorer_mobile\" // Internet Explorer Mobile = IE Mobile\n\tOpera                  = \"opera\"                    // Opera = Opera\n\tOperaMobile            = \"opera_mobile\"             // Opera Mobile = Opera Mini + Opera Mobile + Opera Tablet + Opera Coast\n)\n\ntype Agent struct {\n\tOperatingSystem string  `bson:\"operating_system\" json:\"operating_system\"`\n\tBrowser         string  `bson:\"browser\" json:\"browser\"`\n\tIp              string  `bson:\"ip\" json:\"ip\"`\n\tIsp             string  `bson:\"isp\" json:\"isp\"`\n\tContinent       string  `bson:\"continent\" json:\"continent\"`\n\tContinentCode   string  `bson:\"continent_code\" json:\"continent_code\"`\n\tCountry         string  `bson:\"country\" json:\"country\"`\n\tCountryCode     string  `bson:\"country_code\" json:\"country_code\"`\n\tRegion          string  `bson:\"region\" json:\"region\"`\n\tRegionCode      string  `bson:\"region_code\" json:\"region_code\"`\n\tCity            string  `bson:\"city\" json:\"city\"`\n\tLatitude        float64 `bson:\"latitude\" json:\"latitude\"`\n\tLongitude       float64 `bson:\"longitude\" json:\"longitude\"`\n}\n\nfunc Parse(db *database.Database, r *http.Request) (agnt *Agent, err error) {\n\tif settings.System.Demo {\n\t\treturn\n\t}\n\n\tclient := parser.Parse(r.UserAgent())\n\n\tip := node.Self.GetRemoteAddr(r)\n\n\tge, err := geo.Get(db, ip)\n\tif err != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"error\": err,\n\t\t}).Error(\"agent: Failed to get geo IP information\")\n\t\terr = nil\n\t\treturn\n\t}\n\n\tagnt = &Agent{\n\t\tIp:            ip,\n\t\tIsp:           ge.Isp,\n\t\tContinent:     ge.Continent,\n\t\tContinentCode: ge.ContinentCode,\n\t\tCountry:       ge.Country,\n\t\tCountryCode:   ge.CountryCode,\n\t\tRegion:        ge.Region,\n\t\tRegionCode:    ge.RegionCode,\n\t\tCity:          ge.City,\n\t\tLongitude:     ge.Longitude,\n\t\tLatitude:      ge.Latitude,\n\t}\n\n\tswitch client.Os.Family {\n\tcase \"Android\":\n\t\tswitch client.Os.Major {\n\t\tcase \"4\":\n\t\t\tif client.Os.Minor == \"4\" {\n\t\t\t\tagnt.OperatingSystem = Android4\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tbreak\n\t\tcase \"5\":\n\t\t\tagnt.OperatingSystem = Android5\n\t\t\tbreak\n\t\tcase \"6\":\n\t\t\tagnt.OperatingSystem = Android6\n\t\t\tbreak\n\t\tcase \"7\":\n\t\t\tagnt.OperatingSystem = Android7\n\t\t\tbreak\n\t\tcase \"8\":\n\t\t\tagnt.OperatingSystem = Android8\n\t\t\tbreak\n\t\tcase \"9\":\n\t\t\tagnt.OperatingSystem = Android9\n\t\t\tbreak\n\t\tcase \"10\":\n\t\t\tagnt.OperatingSystem = Android10\n\t\t\tbreak\n\t\tcase \"11\":\n\t\t\tagnt.OperatingSystem = Android11\n\t\t\tbreak\n\t\tcase \"12\":\n\t\t\tagnt.OperatingSystem = Android12\n\t\t\tbreak\n\t\tcase \"13\":\n\t\t\tagnt.OperatingSystem = Android13\n\t\t\tbreak\n\t\tcase \"14\":\n\t\t\tagnt.OperatingSystem = Android14\n\t\t\tbreak\n\t\tcase \"15\":\n\t\t\tagnt.OperatingSystem = Android15\n\t\t\tbreak\n\t\tcase \"16\":\n\t\t\tagnt.OperatingSystem = Android16\n\t\t\tbreak\n\t\t}\n\t\tbreak\n\tcase \"BlackBerry OS\":\n\t\tif client.Os.Major == \"10\" {\n\t\t\tagnt.OperatingSystem = Blackberry10\n\t\t\tbreak\n\t\t}\n\t\tbreak\n\tcase \"Firefox OS\":\n\t\tagnt.OperatingSystem = FirefoxOs\n\t\tbreak\n\tcase \"iOS\":\n\t\tswitch client.Os.Major {\n\t\tcase \"8\":\n\t\t\tagnt.OperatingSystem = Ios8\n\t\t\tbreak\n\t\tcase \"9\":\n\t\t\tagnt.OperatingSystem = Ios9\n\t\t\tbreak\n\t\tcase \"10\":\n\t\t\tagnt.OperatingSystem = Ios10\n\t\t\tbreak\n\t\tcase \"11\":\n\t\t\tagnt.OperatingSystem = Ios11\n\t\t\tbreak\n\t\tcase \"12\":\n\t\t\tagnt.OperatingSystem = Ios12\n\t\t\tbreak\n\t\tcase \"13\":\n\t\t\tagnt.OperatingSystem = Ios13\n\t\t\tbreak\n\t\tcase \"14\":\n\t\t\tagnt.OperatingSystem = Ios14\n\t\t\tbreak\n\t\tcase \"15\":\n\t\t\tagnt.OperatingSystem = Ios15\n\t\t\tbreak\n\t\tcase \"16\":\n\t\t\tagnt.OperatingSystem = Ios16\n\t\t\tbreak\n\t\tcase \"17\":\n\t\t\tagnt.OperatingSystem = Ios17\n\t\t\tbreak\n\t\tcase \"18\":\n\t\t\tagnt.OperatingSystem = Ios18\n\t\t\tbreak\n\t\tcase \"19\":\n\t\t\tagnt.OperatingSystem = Ios19\n\t\t\tbreak\n\t\tcase \"20\":\n\t\t\tagnt.OperatingSystem = Ios20\n\t\t\tbreak\n\t\t}\n\t\tbreak\n\tcase \"Kindle\":\n\t\tagnt.OperatingSystem = Kindle\n\t\tbreak\n\tcase \"Mac OS X\":\n\t\tswitch client.Os.Major {\n\t\tcase \"10\":\n\t\t\tswitch client.Os.Minor {\n\t\t\tcase \"10\":\n\t\t\t\tagnt.OperatingSystem = MacOs1010\n\t\t\t\tbreak\n\t\t\tcase \"11\":\n\t\t\t\tagnt.OperatingSystem = MacOs1011\n\t\t\t\tbreak\n\t\t\tcase \"12\":\n\t\t\t\tagnt.OperatingSystem = MacOs1012\n\t\t\t\tbreak\n\t\t\tcase \"13\":\n\t\t\t\tagnt.OperatingSystem = MacOs1013\n\t\t\t\tbreak\n\t\t\tcase \"14\":\n\t\t\t\tagnt.OperatingSystem = MacOs1014\n\t\t\t\tbreak\n\t\t\tcase \"15\":\n\t\t\t\tagnt.OperatingSystem = MacOs1015\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tbreak\n\t\tcase \"11\":\n\t\t\tagnt.OperatingSystem = MacOs11\n\t\t\tbreak\n\t\tcase \"12\":\n\t\t\tagnt.OperatingSystem = MacOs12\n\t\t\tbreak\n\t\tcase \"13\":\n\t\t\tagnt.OperatingSystem = MacOs13\n\t\t\tbreak\n\t\tcase \"14\":\n\t\t\tagnt.OperatingSystem = MacOs14\n\t\t\tbreak\n\t\tcase \"15\":\n\t\t\tagnt.OperatingSystem = MacOs15\n\t\t\tbreak\n\t\tcase \"16\":\n\t\t\tagnt.OperatingSystem = MacOs16\n\t\t\tbreak\n\t\t}\n\t\tbreak\n\tcase \"Windows Phone\":\n\t\tagnt.OperatingSystem = WindowsPhone\n\t\tbreak\n\tcase \"Windows XP\":\n\t\tagnt.OperatingSystem = WindowsXp\n\t\tbreak\n\tcase \"Windows 7\":\n\t\tagnt.OperatingSystem = Windows7\n\t\tbreak\n\tcase \"Windows Vista\":\n\t\tagnt.OperatingSystem = WindowsVista\n\t\tbreak\n\tcase \"Windows 8\", \"Windows 8.1\", \"Windows RT 8.1\":\n\t\tagnt.OperatingSystem = Windows8\n\t\tbreak\n\tcase \"Windows 10\":\n\t\tagnt.OperatingSystem = Windows10\n\t\tbreak\n\tcase \"Windows 11\":\n\t\tagnt.OperatingSystem = Windows11\n\t\tbreak\n\tcase \"Chrome OS\":\n\t\tagnt.OperatingSystem = ChromeOs\n\t\tbreak\n\tcase \"Linux\", \"Debian\", \"Ubuntu\":\n\t\tagnt.OperatingSystem = Linux\n\t\tbreak\n\t}\n\n\tswitch client.UserAgent.Family {\n\tcase \"Chrome\", \"Chromium\":\n\t\tagnt.Browser = Chrome\n\t\tbreak\n\tcase \"Chrome Mobile\", \"Chrome Mobile iOS\", \"Chrome Mobile WebView\":\n\t\tagnt.Browser = ChromeMobile\n\t\tbreak\n\tcase \"Safari\":\n\t\tagnt.Browser = Safari\n\t\tbreak\n\tcase \"Mobile Safari\", \"Mobile Safari UI/WKWebView\":\n\t\tagnt.Browser = SafariMobile\n\t\tbreak\n\tcase \"Firefox\", \"Firefox Beta\":\n\t\tagnt.Browser = Firefox\n\t\tbreak\n\tcase \"Firefox Mobile\", \"Firefox iOS\":\n\t\tagnt.Browser = FirefoxMobile\n\t\tbreak\n\tcase \"Edge\":\n\t\tagnt.Browser = Edge\n\t\tbreak\n\tcase \"IE\":\n\t\tagnt.Browser = InternetExplorer\n\t\tbreak\n\tcase \"IE Mobile\":\n\t\tagnt.Browser = InternetExplorerMobile\n\t\tbreak\n\tcase \"Opera\":\n\t\tagnt.Browser = Opera\n\t\tbreak\n\tcase \"Opera Mini\", \"Opera Mobile\", \"Opera Tablet\", \"Opera Coast\":\n\t\tagnt.Browser = OperaMobile\n\t\tbreak\n\t}\n\n\treturn\n}\n\nfunc (a *Agent) Diff(agnt *Agent) bool {\n\tif a.OperatingSystem != agnt.OperatingSystem ||\n\t\ta.Browser != agnt.Browser ||\n\t\ta.Ip != agnt.Ip ||\n\t\ta.Isp != agnt.Isp ||\n\t\ta.Continent != agnt.Continent ||\n\t\ta.ContinentCode != agnt.ContinentCode ||\n\t\ta.Country != agnt.Country ||\n\t\ta.CountryCode != agnt.CountryCode ||\n\t\ta.Region != agnt.Region ||\n\t\ta.RegionCode != agnt.RegionCode ||\n\t\ta.City != agnt.City ||\n\t\ta.Longitude != agnt.Longitude ||\n\t\ta.Latitude != agnt.Latitude {\n\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "utils/crypto.go",
    "content": "package utils\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/gob\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"hash/crc32\"\n\t\"math\"\n\t\"math/big\"\n\tmathrand \"math/rand\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nvar (\n\trandRe       = regexp.MustCompile(\"[^a-zA-Z0-9]+\")\n\trandPasswdRe = regexp.MustCompile(\n\t\t\"[^23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ]+\")\n)\n\nfunc CrcHash(input interface{}) (sum uint32, err error) {\n\thash := crc32.NewIEEE()\n\tenc := gob.NewEncoder(hash)\n\n\terr = enc.Encode(input)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"utils: Failed to encode crc input\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsum = hash.Sum32()\n\treturn\n}\n\nfunc RandStr(n int) (str string, err error) {\n\tfor i := 0; i < 10; i++ {\n\t\tinput, e := RandBytes(int(math.Ceil(float64(n) * 1.25)))\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\toutput := base64.RawStdEncoding.EncodeToString(input)\n\t\toutput = randRe.ReplaceAllString(output, \"\")\n\n\t\tif len(output) < n {\n\t\t\tcontinue\n\t\t}\n\n\t\tstr = output[:n]\n\t\tbreak\n\t}\n\n\tif str == \"\" {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"utils: Random generate error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RandPasswd(n int) (str string, err error) {\n\tfor i := 0; i < 10; i++ {\n\t\tinput, e := RandBytes(n * 2)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\toutput := base64.RawStdEncoding.EncodeToString(input)\n\t\toutput = randPasswdRe.ReplaceAllString(output, \"\")\n\n\t\tif len(output) < n {\n\t\t\tcontinue\n\t\t}\n\n\t\tstr = output[:n]\n\t\tbreak\n\t}\n\n\tif str == \"\" {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"utils: Random generate error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RandBytes(size int) (bytes []byte, err error) {\n\tbytes = make([]byte, size)\n\t_, err = rand.Read(bytes)\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"utils: Random read error\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RandMacAddr() (addr string, err error) {\n\tbytes := make([]byte, 6)\n\t_, err = rand.Read(bytes)\n\tif err != nil {\n\t\terr = &errortypes.UnknownError{\n\t\t\terrors.Wrap(err, \"utils: Random read error\"),\n\t\t}\n\t\treturn\n\t}\n\n\taddr = strings.ToUpper(fmt.Sprintf(\"%x\", bytes))\n\treturn\n}\n\nfunc GenerateRsaKey() (encodedPriv, encodedPub []byte, err error) {\n\tprivateKey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to generate rsa key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tblockPriv := &pem.Block{\n\t\tType:  \"RSA PRIVATE KEY\",\n\t\tBytes: x509.MarshalPKCS1PrivateKey(privateKey),\n\t}\n\tencodedPriv = pem.EncodeToMemory(blockPriv)\n\n\tbytesPub, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"utils: Failed to marshal rsa public key\"),\n\t\t}\n\t\treturn\n\t}\n\n\tblockPub := &pem.Block{\n\t\tType:  \"PUBLIC KEY\",\n\t\tBytes: bytesPub,\n\t}\n\tencodedPub = pem.EncodeToMemory(blockPub)\n\n\treturn\n}\n\nfunc RandObjectId() (oid bson.ObjectID, err error) {\n\trid, err := RandBytes(12)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcopy(oid[:], rid)\n\treturn\n}\n\nfunc RandInt(min, max int) int {\n\treturn mathrand.Intn(max-min+1) + min\n}\n\nfunc init() {\n\tn, err := rand.Int(rand.Reader, big.NewInt(9223372036854775806))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tmathrand.Seed(n.Int64())\n}\n"
  },
  {
    "path": "utils/dns.go",
    "content": "package utils\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc DnsLookup(server, host string) (addrs []string, err error) {\n\tserverIp := net.ParseIP(server)\n\tif serverIp == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"utils: Invalid DNS server address\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif serverIp.To4() == nil {\n\t\tserver = fmt.Sprintf(\"[%s]:53\", serverIp.String())\n\t} else {\n\t\tserver = fmt.Sprintf(\"%s:53\", serverIp.String())\n\t}\n\n\tresolver := &net.Resolver{\n\t\tPreferGo: true,\n\t\tDial: func(ctx context.Context, network,\n\t\t\taddress string) (net.Conn, error) {\n\n\t\t\tdialer := net.Dialer{\n\t\t\t\tTimeout: 3 * time.Second,\n\t\t\t}\n\n\t\t\treturn dialer.DialContext(ctx, network, server)\n\t\t},\n\t}\n\n\taddrs, err = resolver.LookupHost(context.Background(), host)\n\tif err != nil {\n\t\terr = &errortypes.RequestError{\n\t\t\terrors.Wrap(err, \"utils: DNS lookup failed\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif addrs == nil {\n\t\taddrs = []string{}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "utils/files.go",
    "content": "package utils\n\nimport (\n\t\"bufio\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nvar invalidPaths = set.NewSet(\"/\", \"\", \".\", \"./\")\n\nconst pathSafeLimit = 256\n\nvar pathSafeChars = set.NewSet(\n\t'a',\n\t'b',\n\t'c',\n\t'd',\n\t'e',\n\t'f',\n\t'g',\n\t'h',\n\t'i',\n\t'j',\n\t'k',\n\t'l',\n\t'm',\n\t'n',\n\t'o',\n\t'p',\n\t'q',\n\t'r',\n\t's',\n\t't',\n\t'u',\n\t'v',\n\t'w',\n\t'x',\n\t'y',\n\t'z',\n\t'A',\n\t'B',\n\t'C',\n\t'D',\n\t'E',\n\t'F',\n\t'G',\n\t'H',\n\t'I',\n\t'J',\n\t'K',\n\t'L',\n\t'M',\n\t'N',\n\t'O',\n\t'P',\n\t'Q',\n\t'R',\n\t'S',\n\t'T',\n\t'U',\n\t'V',\n\t'W',\n\t'X',\n\t'Y',\n\t'Z',\n\t'0',\n\t'1',\n\t'2',\n\t'3',\n\t'4',\n\t'5',\n\t'6',\n\t'7',\n\t'8',\n\t'9',\n\t'-',\n\t'_',\n\t'.',\n\t'+',\n\t'=',\n\t'@',\n\t'/',\n)\n\nfunc FilterPath(pth string) string {\n\tif len(pth) > pathSafeLimit {\n\t\tpth = pth[:pathSafeLimit]\n\t}\n\n\tcleaned := \"\"\n\tfor _, c := range pth {\n\t\tif pathSafeChars.Contains(c) {\n\t\t\tcleaned += string(c)\n\t\t}\n\t}\n\n\tcleaned = filepath.Clean(cleaned)\n\tcleaned, err := filepath.Abs(cleaned)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tcleaned = filepath.FromSlash(cleaned)\n\tcleaned = strings.ReplaceAll(cleaned, \"..\", \"\")\n\n\treturn cleaned\n}\n\nfunc FilterRelPath(pth string) string {\n\tif len(pth) > pathSafeLimit {\n\t\tpth = pth[:pathSafeLimit]\n\t}\n\n\tcleaned := \"\"\n\tfor _, c := range pth {\n\t\tif pathSafeChars.Contains(c) {\n\t\t\tcleaned += string(c)\n\t\t}\n\t}\n\n\tcleaned = filepath.Clean(cleaned)\n\tcleaned = filepath.FromSlash(cleaned)\n\tcleaned = strings.ReplaceAll(cleaned, \"..\", \"\")\n\n\treturn cleaned\n}\n\nfunc Chmod(pth string, mode os.FileMode) (err error) {\n\terr = os.Chmod(pth, mode)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to chmod %s\", pth),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Exists(pth string) (exists bool, err error) {\n\t_, err = os.Stat(pth)\n\tif err == nil {\n\t\texists = true\n\t\treturn\n\t}\n\n\tif os.IsNotExist(err) {\n\t\terr = nil\n\t\treturn\n\t}\n\n\terr = &errortypes.ReadError{\n\t\terrors.Wrapf(err, \"utils: Failed to stat %s\", pth),\n\t}\n\treturn\n}\n\nfunc ExistsDir(pth string) (exists bool, err error) {\n\tstat, err := os.Stat(pth)\n\tif err == nil {\n\t\texists = stat.IsDir()\n\t\treturn\n\t}\n\n\tif os.IsNotExist(err) {\n\t\terr = nil\n\t\treturn\n\t}\n\n\terr = &errortypes.ReadError{\n\t\terrors.Wrapf(err, \"utils: Failed to stat %s\", pth),\n\t}\n\treturn\n}\n\nfunc ExistsFile(pth string) (exists bool, err error) {\n\tstat, err := os.Stat(pth)\n\tif err == nil {\n\t\texists = !stat.IsDir()\n\t\treturn\n\t}\n\n\tif os.IsNotExist(err) {\n\t\terr = nil\n\t\treturn\n\t}\n\n\terr = &errortypes.ReadError{\n\t\terrors.Wrapf(err, \"utils: Failed to stat %s\", pth),\n\t}\n\treturn\n}\n\nfunc ExistsMkdir(pth string, perm os.FileMode) (err error) {\n\texists, err := ExistsDir(pth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !exists {\n\t\terr = os.MkdirAll(pth, perm)\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to mkdir %s\", pth),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc ExistsRemove(pth string) (err error) {\n\texists, err := Exists(pth)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif exists {\n\t\terr = os.RemoveAll(pth)\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to rm %s\", pth),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Remove(path string) (err error) {\n\tif invalidPaths.Contains(path) {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Invalid remove path '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\terr = os.Remove(path)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to remove '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveAll(path string) (err error) {\n\tif invalidPaths.Contains(path) {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Invalid remove path '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\terr = os.RemoveAll(path)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to remove '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveWildcard(matchPath string) (n int, err error) {\n\tmatches, err := filepath.Glob(matchPath)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Error matching path '%s'\", matchPath),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(matches) == 0 {\n\t\treturn\n\t}\n\n\tdelErrors := []string{}\n\tfor _, pth := range matches {\n\t\tfileInfo, err := os.Stat(pth)\n\t\tif err != nil {\n\t\t\tdelErrors = append(delErrors, fmt.Sprintf(\"%s: %v\", pth, err))\n\t\t\tcontinue\n\t\t}\n\n\t\tif fileInfo.IsDir() {\n\t\t\tcontinue\n\t\t}\n\n\t\terr = os.Remove(pth)\n\t\tif err != nil {\n\t\t\tdelErrors = append(delErrors, fmt.Sprintf(\"%s: %v\", pth, err))\n\t\t} else {\n\t\t\tn += 1\n\t\t}\n\t}\n\n\tif len(delErrors) > 0 {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Delete errors '%s'\",\n\t\t\t\tstrings.Join(delErrors, \",\")),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ContainsDir(pth string) (hasDir bool, err error) {\n\texists, err := ExistsDir(pth)\n\tif !exists {\n\t\treturn\n\t}\n\n\tentries, err := ioutil.ReadDir(pth)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"queue: Failed to read dir %s\", pth),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\thasDir = true\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc Open(path string, perm os.FileMode) (file *os.File, err error) {\n\tfile, err = os.OpenFile(path, os.O_RDWR|os.O_TRUNC, perm)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to open '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Read(path string) (data string, err error) {\n\tdataByt, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to read '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\tdata = string(dataByt)\n\treturn\n}\n\nfunc ReadExists(path string) (data string, err error) {\n\tdataByt, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\terr = nil\n\t\t\treturn\n\t\t}\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to read '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\tdata = string(dataByt)\n\treturn\n}\n\nfunc ReadLines(path string) (lines []string, err error) {\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to open '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr = file.Close()\n\t\tif err != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to read '%s'\", path),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\tlines = []string{}\n\treader := bufio.NewReader(file)\n\tfor {\n\t\tline, e := reader.ReadString('\\n')\n\t\tif e != nil {\n\t\t\tbreak\n\t\t}\n\t\tlines = append(lines, strings.Trim(line, \"\\n\"))\n\t}\n\n\treturn\n}\n\nfunc Write(path string, data string, perm os.FileMode) (err error) {\n\tfile, err := Open(path, perm)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr = file.Close()\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to write '%s'\", path),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\t_, err = file.WriteString(data)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to write to file '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Create(path string, perm os.FileMode) (file *os.File, err error) {\n\tfile, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to create '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc CreateWrite(path string, data string, perm os.FileMode) (err error) {\n\tfile, err := Create(path, perm)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr = file.Close()\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrapf(err, \"utils: Failed to write '%s'\", path),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\t_, err = file.WriteString(data)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to write to file '%s'\", path),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc FileSha256(pth string) (hash string, err error) {\n\tfile, err := os.Open(pth)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to read '%s'\", pth),\n\t\t}\n\t\treturn\n\t}\n\tdefer file.Close()\n\n\thasher := sha256.New()\n\t_, err = io.Copy(hasher, file)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to read '%s'\", pth),\n\t\t}\n\t\treturn\n\t}\n\n\thash = fmt.Sprintf(\"%x\", hasher.Sum(nil))\n\treturn\n}\n"
  },
  {
    "path": "utils/filter.go",
    "content": "package utils\n\nimport (\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n)\n\nconst nameSafeLimit = 128\n\nvar nameSafeChar = set.NewSet(\n\t'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',\n\t'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',\n\t'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',\n\t'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',\n\t'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',\n\t'-', '.',\n)\n\nvar nameCmdSafeChar = set.NewSet(\n\t'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',\n\t'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',\n\t'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',\n\t'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',\n\t'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',\n\t'-', '.',\n)\n\nvar unitSafeChar = set.NewSet(\n\t'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',\n\t'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',\n\t'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',\n\t'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',\n\t'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',\n\t':', '-', '_', '.', '\\\\', '@',\n)\n\nfunc FilterName(s string) string {\n\tif len(s) == 0 {\n\t\treturn \"\"\n\t}\n\n\tif s == \"self\" {\n\t\ts = \"invalid-name\"\n\t}\n\n\tif len(s) > nameSafeLimit {\n\t\ts = s[:nameSafeLimit]\n\t}\n\n\tvar ns strings.Builder\n\tfor _, c := range s {\n\t\tif nameSafeChar.Contains(c) {\n\t\t\tns.WriteString(string(c))\n\t\t}\n\t}\n\n\treturn ns.String()\n}\n\nfunc FilterNameCmd(s string) string {\n\tif len(s) == 0 {\n\t\treturn \"\"\n\t}\n\n\tif s == \"self\" {\n\t\ts = \"invalid-name\"\n\t}\n\n\tif len(s) > nameSafeLimit {\n\t\ts = s[:nameSafeLimit]\n\t}\n\n\tvar ns strings.Builder\n\tfor _, c := range s {\n\t\tif nameCmdSafeChar.Contains(c) {\n\t\t\tns.WriteString(string(c))\n\t\t}\n\t}\n\n\treturn strings.ToLower(ns.String())\n}\n\nfunc FilterUnit(s string) string {\n\tif len(s) == 0 {\n\t\treturn \"\"\n\t}\n\n\tif len(s) > nameSafeLimit {\n\t\ts = s[:nameSafeLimit]\n\t}\n\n\tvar ns strings.Builder\n\tfor _, c := range s {\n\t\tif unitSafeChar.Contains(c) {\n\t\t\tns.WriteString(string(c))\n\t\t}\n\t}\n\n\treturn ns.String()\n}\n\nfunc FilterDomain(s string) string {\n\ts = FilterName(strings.ToLower(s))\n\ts = strings.TrimPrefix(s, \".\")\n\ts = strings.TrimSuffix(s, \".\")\n\treturn s\n}\n"
  },
  {
    "path": "utils/limiter.go",
    "content": "package utils\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype Limiter struct {\n\tcounter int\n\tlimit   int\n\tlock    sync.Mutex\n}\n\nfunc (l *Limiter) Acquire() (acquired bool) {\n\tl.lock.Lock()\n\tif l.counter < l.limit {\n\t\tl.counter += 1\n\t\tacquired = true\n\t}\n\tl.lock.Unlock()\n\treturn\n}\n\nfunc (l *Limiter) Release() {\n\tl.lock.Lock()\n\tl.counter -= 1\n\tif l.counter < 0 {\n\t\tpanic(\"limiter: Counter below zero\")\n\t}\n\tl.lock.Unlock()\n}\n\nfunc NewLimiter(limit int) *Limiter {\n\treturn &Limiter{\n\t\tcounter: 0,\n\t\tlimit:   limit,\n\t\tlock:    sync.Mutex{},\n\t}\n}\n\ntype TimeLimiter struct {\n\tlastRelease time.Time\n\tduration    time.Duration\n\tacquired    bool\n\tlock        sync.Mutex\n}\n\nfunc (l *TimeLimiter) SetDuration(duration time.Duration) {\n\tl.lock.Lock()\n\tl.duration = duration\n\tl.lock.Unlock()\n}\n\nfunc (l *TimeLimiter) Acquire() (acquired bool) {\n\tl.lock.Lock()\n\tdefer l.lock.Unlock()\n\n\tif l.acquired {\n\t\treturn false\n\t}\n\n\tif time.Since(l.lastRelease) >= l.duration {\n\t\tl.acquired = true\n\t\tacquired = true\n\t}\n\n\treturn\n}\n\nfunc (l *TimeLimiter) Release() {\n\tl.lock.Lock()\n\tdefer l.lock.Unlock()\n\n\tif !l.acquired {\n\t\tpanic(\"limiter: Release called without acquire\")\n\t}\n\n\tl.lastRelease = time.Now()\n\tl.acquired = false\n}\n\nfunc NewTimeLimiter(duration time.Duration) *TimeLimiter {\n\treturn &TimeLimiter{\n\t\tlastRelease: time.Time{},\n\t\tduration:    duration,\n\t\tacquired:    false,\n\t\tlock:        sync.Mutex{},\n\t}\n}\n"
  },
  {
    "path": "utils/math.go",
    "content": "package utils\n\nimport (\n\t\"math\"\n)\n\nfunc Max(x, y int) int {\n\tif x > y {\n\t\treturn x\n\t}\n\treturn y\n}\n\nfunc Min(x, y int) int {\n\tif x < y {\n\t\treturn x\n\t}\n\treturn y\n}\n\nfunc Max64(x, y int64) int64 {\n\tif x > y {\n\t\treturn x\n\t}\n\treturn y\n}\n\nfunc Min64(x, y int64) int64 {\n\tif x < y {\n\t\treturn x\n\t}\n\treturn y\n}\n\nfunc ToFixed(x float64, p int) float64 {\n\ty := math.Pow(10, float64(p))\n\treturn float64(int(x*y+math.Copysign(0.5, x*y))) / y\n}\n"
  },
  {
    "path": "utils/misc.go",
    "content": "package utils\n\nimport (\n\t\"container/list\"\n\t\"io/ioutil\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"runtime/debug\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar isSystemd *bool\n\nvar safeChars = set.NewSet(\n\t'a',\n\t'b',\n\t'c',\n\t'd',\n\t'e',\n\t'f',\n\t'g',\n\t'h',\n\t'i',\n\t'j',\n\t'k',\n\t'l',\n\t'm',\n\t'n',\n\t'o',\n\t'p',\n\t'q',\n\t'r',\n\t's',\n\t't',\n\t'u',\n\t'v',\n\t'w',\n\t'x',\n\t'y',\n\t'z',\n\t'A',\n\t'B',\n\t'C',\n\t'D',\n\t'E',\n\t'F',\n\t'G',\n\t'H',\n\t'I',\n\t'J',\n\t'K',\n\t'L',\n\t'M',\n\t'N',\n\t'O',\n\t'P',\n\t'Q',\n\t'R',\n\t'S',\n\t'T',\n\t'U',\n\t'V',\n\t'W',\n\t'X',\n\t'Y',\n\t'Z',\n\t'0',\n\t'1',\n\t'2',\n\t'3',\n\t'4',\n\t'5',\n\t'6',\n\t'7',\n\t'8',\n\t'9',\n\t'-',\n\t'+',\n\t'=',\n\t'_',\n\t'/',\n\t',',\n\t'.',\n\t'~',\n\t'@',\n\t'#',\n\t'!',\n\t'&',\n\t' ',\n)\n\nfunc FilterStr(s string, n int) string {\n\tif len(s) == 0 {\n\t\treturn \"\"\n\t}\n\n\tif len(s) > n {\n\t\ts = s[:n]\n\t}\n\n\tvar ns strings.Builder\n\tfor _, c := range s {\n\t\tif safeChars.Contains(c) {\n\t\t\tns.WriteString(string(c))\n\t\t}\n\t}\n\n\treturn ns.String()\n}\n\nfunc SinceAbs(t time.Time) (s time.Duration) {\n\ts = time.Since(t)\n\tif s < 0 {\n\t\ts = s * -1\n\t}\n\treturn\n}\n\nfunc PointerBool(x bool) *bool {\n\treturn &x\n}\n\nfunc PointerInt(x int) *int {\n\treturn &x\n}\n\nfunc PointerString(x string) *string {\n\treturn &x\n}\n\nfunc Int8Str(arr []int8) string {\n\tb := make([]byte, 0, len(arr))\n\tfor _, v := range arr {\n\t\tif v == 0x00 {\n\t\t\tbreak\n\t\t}\n\t\tb = append(b, byte(v))\n\t}\n\treturn string(b)\n}\n\nfunc HasPreSuf(src, pre, suf string) bool {\n\treturn strings.HasPrefix(src, pre) && strings.HasSuffix(src, suf)\n}\n\nfunc IsSystemd() bool {\n\tif isSystemd != nil {\n\t\treturn *isSystemd\n\t}\n\n\tdata, err := ioutil.ReadFile(\"/proc/1/cmdline\")\n\tif err == nil {\n\t\tparts := strings.Split(string(data), \"\\x00\")\n\t\tif len(parts) > 0 && strings.Contains(\n\t\t\tstrings.ToLower(parts[0]), \"systemd\") {\n\n\t\t\tisSysd := true\n\t\t\tisSystemd = &isSysd\n\t\t\treturn true\n\t\t}\n\t}\n\n\tdata, err = ioutil.ReadFile(\"/proc/1/comm\")\n\tif err == nil {\n\t\tif strings.Contains(strings.ToLower(string(data)), \"systemd\") {\n\t\t\tisSysd := true\n\t\t\tisSystemd = &isSysd\n\t\t\treturn true\n\t\t}\n\t}\n\n\tcmd := exec.Command(\"ps\", \"-p\", \"1\", \"-o\", \"comm=\")\n\toutput, err := cmd.Output()\n\tif err == nil {\n\t\tif strings.Contains(strings.ToLower(string(output)), \"systemd\") {\n\t\t\tisSysd := true\n\t\t\tisSystemd = &isSysd\n\t\t\treturn true\n\t\t}\n\t}\n\n\tisSysd := false\n\tisSystemd = &isSysd\n\treturn false\n}\n\nfunc CompareStringSlices(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := range a {\n\t\tif a[i] != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc CompareStringSlicesUnsorted(a, b []string) bool {\n\taSet := set.NewSet()\n\tfor _, val := range a {\n\t\taSet.Add(val)\n\t}\n\n\tbSet := set.NewSet()\n\tfor _, val := range b {\n\t\tbSet.Add(val)\n\t}\n\n\treturn aSet.IsEqual(bSet)\n}\n\nfunc HasMatchingItem(s1, s2 []string) bool {\n\troleMap := make(map[string]struct{})\n\tfor _, role := range s2 {\n\t\troleMap[role] = struct{}{}\n\t}\n\tfor _, role := range s1 {\n\t\t_, exists := roleMap[role]\n\t\tif exists {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc RecoverLog(msg string) {\n\tpanc := recover()\n\tif panc != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"trace\": string(debug.Stack()),\n\t\t\t\"panic\": panc,\n\t\t}).Error(\"sync: Panic in goroutine\")\n\t}\n}\n\nfunc CopyList(src *list.List) *list.List {\n\tdst := list.New()\n\tfor x := src.Front(); x != nil; x = x.Next() {\n\t\tdst.PushBack(x.Value)\n\t}\n\treturn dst\n}\n\nfunc ToSnakeCase(s string) string {\n\tvar result []rune\n\tfor i, r := range s {\n\t\tif i > 0 && unicode.IsUpper(r) {\n\t\t\tresult = append(result, '_')\n\t\t}\n\t\tresult = append(result, unicode.ToLower(r))\n\t}\n\treturn string(result)\n}\n\nfunc GetIntVer(version string) int {\n\tre := regexp.MustCompile(`\\d+`)\n\tver := re.FindAllString(version, -1)\n\n\tif len(ver) == 0 {\n\t\treturn 0\n\t}\n\n\tlastNum, err := strconv.Atoi(ver[len(ver)-1])\n\tif err != nil {\n\t\treturn 0\n\t}\n\tver[len(ver)-1] = strconv.Itoa(lastNum + 4000)\n\n\tvar builder strings.Builder\n\tfor _, v := range ver {\n\t\tnum, err := strconv.Atoi(v)\n\t\tif err != nil {\n\t\t\treturn 0\n\t\t}\n\t\tbuilder.WriteString(strings.Repeat(\n\t\t\t\"0\", 4-len(strconv.Itoa(num))) + strconv.Itoa(num))\n\t}\n\n\tresult, err := strconv.Atoi(builder.String())\n\tif err != nil {\n\t\treturn 0\n\t}\n\n\treturn result\n}\n"
  },
  {
    "path": "utils/multilock.go",
    "content": "package utils\n\nimport (\n\t\"sync\"\n)\n\ntype MultiLock struct {\n\tcounts map[string]int\n\tlocks  map[string]*sync.Mutex\n\tlock   sync.Mutex\n}\n\nfunc (m *MultiLock) Lock(id string) {\n\tm.lock.Lock()\n\tval := m.counts[id]\n\tlock, ok := m.locks[id]\n\tif !ok {\n\t\tlock = &sync.Mutex{}\n\t\tm.locks[id] = lock\n\t}\n\tm.counts[id] = val + 1\n\tm.lock.Unlock()\n\n\tlock.Lock()\n}\n\nfunc (m *MultiLock) Unlock(id string) {\n\tm.lock.Lock()\n\tval := m.counts[id]\n\tlock := m.locks[id]\n\tif val <= 1 {\n\t\tdelete(m.counts, id)\n\t\tdelete(m.locks, id)\n\t} else {\n\t\tm.counts[id] = val - 1\n\t\tlock.Unlock()\n\t}\n\tm.lock.Unlock()\n}\n\nfunc (m *MultiLock) Locked(id string) bool {\n\tm.lock.Lock()\n\t_, ok := m.locks[id]\n\tm.lock.Unlock()\n\treturn ok\n}\n\nfunc NewMultiLock() *MultiLock {\n\treturn &MultiLock{\n\t\tcounts: map[string]int{},\n\t\tlocks:  map[string]*sync.Mutex{},\n\t\tlock:   sync.Mutex{},\n\t}\n}\n"
  },
  {
    "path": "utils/multitimeoutlock.go",
    "content": "package utils\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype MultiTimeoutLock struct {\n\tcounts    map[string]int\n\tlocks     map[string]*sync.Mutex\n\tlock      sync.Mutex\n\tstate     map[bson.ObjectID]bool\n\tstateLock sync.Mutex\n\ttimeout   time.Duration\n}\n\nfunc (m *MultiTimeoutLock) Lock(id string) (lockId bson.ObjectID) {\n\tm.lock.Lock()\n\tval := m.counts[id]\n\tlock, ok := m.locks[id]\n\tif !ok {\n\t\tlock = &sync.Mutex{}\n\t\tm.locks[id] = lock\n\t}\n\tm.counts[id] = val + 1\n\tm.lock.Unlock()\n\n\tlock.Lock()\n\n\tlockId = bson.NewObjectID()\n\tm.stateLock.Lock()\n\tm.state[lockId] = true\n\tm.stateLock.Unlock()\n\n\tif !constants.LockDebug {\n\t\treturn\n\t}\n\n\tstart := time.Now()\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\tm.stateLock.Lock()\n\t\t\tstate := m.state[lockId]\n\t\t\tm.stateLock.Unlock()\n\t\t\tif !state {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif time.Since(start) > m.timeout {\n\t\t\t\terr := &errortypes.TimeoutError{\n\t\t\t\t\terrors.New(\"utils: Multi lock timeout\"),\n\t\t\t\t}\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"utils: Multi lock timed out\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (m *MultiTimeoutLock) LockOpen(id string) (\n\tacquired bool, lockId bson.ObjectID) {\n\n\tm.lock.Lock()\n\tval := m.counts[id]\n\tlock, ok := m.locks[id]\n\tif ok {\n\t\tm.lock.Unlock()\n\t\treturn\n\t}\n\n\tlock = &sync.Mutex{}\n\tm.locks[id] = lock\n\tm.counts[id] = val + 1\n\tm.lock.Unlock()\n\n\tacquired = true\n\n\tlock.Lock()\n\n\tlockId = bson.NewObjectID()\n\tm.stateLock.Lock()\n\tm.state[lockId] = true\n\tm.stateLock.Unlock()\n\n\tif !constants.LockDebug {\n\t\treturn\n\t}\n\n\tstart := time.Now()\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\tm.stateLock.Lock()\n\t\t\tstate := m.state[lockId]\n\t\t\tm.stateLock.Unlock()\n\t\t\tif !state {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif time.Since(start) > m.timeout {\n\t\t\t\terr := &errortypes.TimeoutError{\n\t\t\t\t\terrors.New(\"utils: Multi lock timeout\"),\n\t\t\t\t}\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"utils: Multi lock timed out\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (m *MultiTimeoutLock) LockTimeout(id string,\n\ttimeout time.Duration) (lockId bson.ObjectID) {\n\n\tm.lock.Lock()\n\tval := m.counts[id]\n\tlock, ok := m.locks[id]\n\tif !ok {\n\t\tlock = &sync.Mutex{}\n\t\tm.locks[id] = lock\n\t}\n\tm.counts[id] = val + 1\n\tm.lock.Unlock()\n\n\tlock.Lock()\n\n\tlockId = bson.NewObjectID()\n\tm.stateLock.Lock()\n\tm.state[lockId] = true\n\tm.stateLock.Unlock()\n\n\tif !constants.LockDebug {\n\t\treturn\n\t}\n\n\tstart := time.Now()\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\tm.stateLock.Lock()\n\t\t\tstate := m.state[lockId]\n\t\t\tm.stateLock.Unlock()\n\t\t\tif !state {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif time.Since(start) > timeout {\n\t\t\t\terr := &errortypes.TimeoutError{\n\t\t\t\t\terrors.New(\"utils: Multi lock timeout\"),\n\t\t\t\t}\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"utils: Multi lock timed out\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (m *MultiTimeoutLock) LockOpenTimeout(id string,\n\ttimeout time.Duration) (acquired bool, lockId bson.ObjectID) {\n\n\tm.lock.Lock()\n\tval := m.counts[id]\n\tlock, ok := m.locks[id]\n\tif ok {\n\t\tm.lock.Unlock()\n\t\treturn\n\t}\n\n\tlock = &sync.Mutex{}\n\tm.locks[id] = lock\n\tm.counts[id] = val + 1\n\tm.lock.Unlock()\n\n\tacquired = true\n\n\tlock.Lock()\n\n\tlockId = bson.NewObjectID()\n\tm.stateLock.Lock()\n\tm.state[lockId] = true\n\tm.stateLock.Unlock()\n\n\tif !constants.LockDebug {\n\t\treturn\n\t}\n\n\tstart := time.Now()\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\tm.stateLock.Lock()\n\t\t\tstate := m.state[lockId]\n\t\t\tm.stateLock.Unlock()\n\t\t\tif !state {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif time.Since(start) > timeout {\n\t\t\t\terr := &errortypes.TimeoutError{\n\t\t\t\t\terrors.New(\"utils: Multi lock timeout\"),\n\t\t\t\t}\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"utils: Multi lock timed out\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (m *MultiTimeoutLock) Unlock(id string, lockId bson.ObjectID) {\n\tm.lock.Lock()\n\tval := m.counts[id]\n\tlock := m.locks[id]\n\tif val <= 1 {\n\t\tdelete(m.counts, id)\n\t\tdelete(m.locks, id)\n\t} else {\n\t\tm.counts[id] = val - 1\n\t\tlock.Unlock()\n\t}\n\tm.lock.Unlock()\n\n\tm.stateLock.Lock()\n\tdelete(m.state, lockId)\n\tm.stateLock.Unlock()\n}\n\nfunc (m *MultiTimeoutLock) DelayUnlock(id string, lockId bson.ObjectID,\n\tdur time.Duration) {\n\n\tgo func() {\n\t\ttime.Sleep(dur)\n\t\tm.Unlock(id, lockId)\n\t}()\n}\n\nfunc (m *MultiTimeoutLock) Locked(id string) bool {\n\tm.lock.Lock()\n\t_, ok := m.locks[id]\n\tm.lock.Unlock()\n\treturn ok\n}\n\nfunc NewMultiTimeoutLock(timeout time.Duration) *MultiTimeoutLock {\n\treturn &MultiTimeoutLock{\n\t\tcounts:    map[string]int{},\n\t\tlocks:     map[string]*sync.Mutex{},\n\t\tlock:      sync.Mutex{},\n\t\tstate:     map[bson.ObjectID]bool{},\n\t\tstateLock: sync.Mutex{},\n\t\ttimeout:   timeout,\n\t}\n}\n"
  },
  {
    "path": "utils/network.go",
    "content": "package utils\n\nimport (\n\t\"encoding/binary\"\n\t\"io/ioutil\"\n\t\"math/big\"\n\t\"net\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nvar (\n\tprivate10 = net.IPNet{\n\t\tIP:   net.IPv4(10, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n\tprivate100 = net.IPNet{\n\t\tIP:   net.IPv4(100, 64, 0, 0),\n\t\tMask: net.CIDRMask(10, 32),\n\t}\n\tprivate172 = net.IPNet{\n\t\tIP:   net.IPv4(172, 16, 0, 0),\n\t\tMask: net.CIDRMask(12, 32),\n\t}\n\tprivate192 = net.IPNet{\n\t\tIP:   net.IPv4(192, 168, 0, 0),\n\t\tMask: net.CIDRMask(16, 32),\n\t}\n\tprivate198 = net.IPNet{\n\t\tIP:   net.IPv4(198, 18, 0, 0),\n\t\tMask: net.CIDRMask(15, 32),\n\t}\n\treserved6 = net.IPNet{\n\t\tIP:   net.IPv4(6, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n\treserved11 = net.IPNet{\n\t\tIP:   net.IPv4(11, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n\treserved21 = net.IPNet{\n\t\tIP:   net.IPv4(21, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n\treserved25 = net.IPNet{\n\t\tIP:   net.IPv4(25, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n\treserved26 = net.IPNet{\n\t\tIP:   net.IPv4(26, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n\treserved53 = net.IPNet{\n\t\tIP:   net.IPv4(53, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n\treserved57 = net.IPNet{\n\t\tIP:   net.IPv4(57, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n\n\tloopback127 = net.IPNet{\n\t\tIP:   net.IPv4(127, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n\tlinkLocal169 = net.IPNet{\n\t\tIP:   net.IPv4(169, 254, 0, 0),\n\t\tMask: net.CIDRMask(16, 32),\n\t}\n\tmulticast224 = net.IPNet{\n\t\tIP:   net.IPv4(224, 0, 0, 0),\n\t\tMask: net.CIDRMask(4, 32),\n\t}\n\tbroadcast255 = net.IPNet{\n\t\tIP:   net.IPv4(255, 255, 255, 255),\n\t\tMask: net.CIDRMask(32, 32),\n\t}\n\tzeroconf0 = net.IPNet{\n\t\tIP:   net.IPv4(0, 0, 0, 0),\n\t\tMask: net.CIDRMask(8, 32),\n\t}\n)\n\nfunc IsPrivateIp(ip net.IP) bool {\n\tif ip == nil {\n\t\treturn false\n\t}\n\n\tif ip.To4() == nil {\n\t\treturn (ip[0] & 0xfe) == 0xfc\n\t}\n\n\tif private10.Contains(ip) ||\n\t\tprivate100.Contains(ip) ||\n\t\tprivate172.Contains(ip) ||\n\t\tprivate192.Contains(ip) ||\n\t\tprivate198.Contains(ip) ||\n\t\treserved6.Contains(ip) ||\n\t\treserved11.Contains(ip) ||\n\t\treserved21.Contains(ip) ||\n\t\treserved25.Contains(ip) ||\n\t\treserved26.Contains(ip) ||\n\t\treserved53.Contains(ip) ||\n\t\treserved57.Contains(ip) {\n\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc IsPublicIp(ip net.IP) bool {\n\tif ip == nil {\n\t\treturn false\n\t}\n\n\tif ip.To4() == nil {\n\t\tif (ip[0] & 0xfe) == 0xfc {\n\t\t\treturn false\n\t\t}\n\t\tif ip[0] == 0xfe && (ip[1]&0xc0) == 0x80 {\n\t\t\treturn false\n\t\t}\n\t\tif ip.Equal(net.IPv6loopback) {\n\t\t\treturn false\n\t\t}\n\t\tif ip.Equal(net.IPv6unspecified) {\n\t\t\treturn false\n\t\t}\n\t\tif ip[0] == 0xff {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\n\tif IsPrivateIp(ip) ||\n\t\tloopback127.Contains(ip) ||\n\t\tlinkLocal169.Contains(ip) ||\n\t\tmulticast224.Contains(ip) ||\n\t\tbroadcast255.Contains(ip) ||\n\t\tzeroconf0.Contains(ip) {\n\n\t\treturn false\n\t}\n\n\treturn true\n}\n\ntype Address struct {\n\tAddress net.IP\n\tNetwork *net.IPNet\n\tIp6     bool\n\tPrivate bool\n\tPublic  bool\n}\n\nfunc ParseAddress(addrStr string) (addr *Address) {\n\taddrStr = strings.TrimSpace(addrStr)\n\tif addrStr == \"\" {\n\t\treturn\n\t}\n\n\tif strings.Contains(addrStr, \"/\") {\n\t\tip, network, err := net.ParseCIDR(addrStr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif ip == nil {\n\t\t\treturn\n\t\t}\n\n\t\taddr = &Address{\n\t\t\tAddress: ip,\n\t\t\tNetwork: network,\n\t\t\tIp6:     ip.To4() == nil,\n\t\t\tPrivate: IsPrivateIp(ip),\n\t\t\tPublic:  IsPublicIp(ip),\n\t\t}\n\t\treturn\n\t}\n\n\tip := net.ParseIP(addrStr)\n\tif ip == nil {\n\t\treturn\n\t}\n\n\taddr = &Address{\n\t\tAddress: ip,\n\t\tIp6:     ip.To4() == nil,\n\t\tPrivate: IsPrivateIp(ip),\n\t\tPublic:  IsPublicIp(ip),\n\t}\n\treturn\n}\n\nfunc IncIpAddress(ip net.IP) {\n\tfor j := len(ip) - 1; j >= 0; j-- {\n\t\tip[j]++\n\t\tif ip[j] > 0 {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc DecIpAddress(ip net.IP) {\n\tfor j := len(ip) - 1; j >= 0; j-- {\n\t\tip[j]--\n\t\tif ip[j] < 255 {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc CopyIpAddress(src net.IP) net.IP {\n\tdst := make(net.IP, len(src))\n\tcopy(dst, src)\n\treturn dst\n}\n\nfunc IpAddress2BigInt(ip net.IP) (n *big.Int, bits int) {\n\tn = &big.Int{}\n\tn.SetBytes(ip)\n\tif len(ip) == net.IPv4len {\n\t\tbits = 32\n\t} else {\n\t\tbits = 128\n\t}\n\treturn\n}\n\nfunc BigInt2IpAddress(n *big.Int, bits int) net.IP {\n\tbyt := n.Bytes()\n\tip := make([]byte, bits/8)\n\tfor i := 1; i <= len(byt); i++ {\n\t\tip[len(ip)-i] = byt[len(byt)-i]\n\t}\n\treturn ip\n}\n\nfunc IpAddress2Int(ip net.IP) int64 {\n\tif len(ip) == 16 {\n\t\treturn int64(binary.BigEndian.Uint32(ip[12:16]))\n\t}\n\treturn int64(binary.BigEndian.Uint32(ip))\n}\n\nfunc Int2IpAddress(n int64) net.IP {\n\tip := make(net.IP, 4)\n\tbinary.BigEndian.PutUint32(ip, uint32(n))\n\treturn ip\n}\n\nfunc Int2IpIndex(n int64) (x int64, err error) {\n\tif n%2 != 0 {\n\t\terr = errortypes.ParseError{\n\t\t\terrors.Newf(\"utils: Odd network int divide %d\", n),\n\t\t}\n\t\treturn\n\t}\n\n\tx = n / 2\n\treturn\n}\n\nfunc GetFirstIpIndex(network *net.IPNet) (n int64, err error) {\n\tstartIp := CopyIpAddress(network.IP)\n\tstartInt := IpAddress2Int(startIp)\n\n\tstartIndex, err := Int2IpIndex(startInt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tn = startIndex + 1\n\treturn\n}\n\nfunc GetLastIpIndex(network *net.IPNet) (n int64, err error) {\n\tendIp := GetLastIpAddress(network)\n\tendInt := IpAddress2Int(endIp) - 1\n\n\tendIndex, err := Int2IpIndex(endInt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tn = endIndex - 1\n\treturn\n}\n\nfunc IpIndex2Ip(index int64) (x, y net.IP) {\n\tx = Int2IpAddress(index * 2)\n\ty = CopyIpAddress(x)\n\tIncIpAddress(y)\n\treturn\n}\n\nfunc GetLastIpAddress(network *net.IPNet) net.IP {\n\tprefixLen, bits := network.Mask.Size()\n\tif prefixLen == bits {\n\t\treturn CopyIpAddress(network.IP)\n\t}\n\tstart, bits := IpAddress2BigInt(network.IP)\n\tn := uint(bits) - uint(prefixLen)\n\tend := big.NewInt(1)\n\tend.Lsh(end, n)\n\tend.Sub(end, big.NewInt(1))\n\tend.Or(end, start)\n\treturn BigInt2IpAddress(end, bits)\n}\n\nfunc NetworkContains(x, y *net.IPNet) bool {\n\treturn x.Contains(y.IP) && x.Contains(GetLastIpAddress(y))\n}\n\nfunc ParseIpMask(mask string) net.IPMask {\n\tmaskIp := net.ParseIP(mask)\n\tif maskIp == nil {\n\t\treturn nil\n\t}\n\treturn net.IPv4Mask(maskIp[12], maskIp[13], maskIp[14], maskIp[15])\n}\n\nfunc GetNamespaces() (namespaces []string, err error) {\n\titems, err := ioutil.ReadDir(\"/var/run/netns\")\n\tif err != nil {\n\t\tif os.IsNotExist(os.ErrNotExist) {\n\t\t\tnamespaces = []string{}\n\t\t\terr = nil\n\t\t} else {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(err, \"utils: Failed to read network namespaces\"),\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\tnamespaces = []string{}\n\tfor _, item := range items {\n\t\tnamespaces = append(namespaces, item.Name())\n\t}\n\n\treturn\n}\n\nfunc GetInterfaces() (ifaces []string, err error) {\n\tifaces, _, err = GetInterfacesSet()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetInterfacesSet() (ifaces []string, ifacesSet set.Set, err error) {\n\titems, err := ioutil.ReadDir(\"/sys/class/net\")\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to read network interfaces\"),\n\t\t}\n\t\treturn\n\t}\n\n\tifaces = []string{}\n\tifacesSet = set.NewSet()\n\tfor _, item := range items {\n\t\tname := item.Name()\n\n\t\tif name == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tifaces = append(ifaces, name)\n\t\tifacesSet.Add(name)\n\t}\n\n\texists, err := ExistsDir(\"/etc/sysconfig/network-scripts\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif exists {\n\t\titems, err = ioutil.ReadDir(\"/etc/sysconfig/network-scripts\")\n\t\tif err != nil {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(err, \"utils: Failed to read network scripts\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfor _, item := range items {\n\t\t\tname := item.Name()\n\n\t\t\tif !strings.HasPrefix(name, \"ifcfg-\") ||\n\t\t\t\t!strings.Contains(name, \":\") {\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tname = name[6:]\n\t\t\tnames := strings.Split(name, \":\")\n\t\t\tif len(names) != 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif name == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif ifacesSet.Contains(names[0]) && !ifacesSet.Contains(name) {\n\t\t\t\tifaces = append(ifaces, name)\n\t\t\t\tifacesSet.Add(name)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GetInterfaceUpper(iface string) (upper string, err error) {\n\tiface = strings.Split(iface, \":\")[0]\n\n\titems, err := ioutil.ReadDir(\"/sys/class/net/\" + iface)\n\tif err != nil {\n\t\tif os.IsNotExist(os.ErrNotExist) {\n\t\t\terr = nil\n\t\t} else {\n\t\t\terr = &errortypes.ReadError{\n\t\t\t\terrors.Wrap(err, \"utils: Failed to read network interface\"),\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, item := range items {\n\t\tname := item.Name()\n\t\tif strings.HasPrefix(name, \"upper_\") {\n\t\t\tupper = name[6:]\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc IsInterfaceBridge(iface string) (bridge bool, err error) {\n\tbridge, err = ExistsDir(\n\t\tpath.Join(\"/\", \"sys\", \"class\", \"net\", iface, \"bridge\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc FilterIp(input string) string {\n\tinput = strings.TrimSpace(input)\n\tif input == \"\" {\n\t\treturn \"\"\n\t}\n\tip := net.ParseIP(input)\n\tif ip == nil {\n\t\treturn \"\"\n\t}\n\treturn ip.String()\n}\n"
  },
  {
    "path": "utils/proc.go",
    "content": "package utils\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/tools/commander\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tclockTicks = 0\n)\n\nfunc Exec(dir, name string, arg ...string) (err error) {\n\tcmd := exec.Command(name, arg...)\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\n\tif dir != \"\" {\n\t\tcmd.Dir = dir\n\t}\n\n\terr = cmd.Run()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExecInput(dir, input, name string, arg ...string) (err error) {\n\tcmd := exec.Command(name, arg...)\n\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\n\tstdin, err := cmd.StdinPipe()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err,\n\t\t\t\t\"utils: Failed to get stdin in exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\tif dir != \"\" {\n\t\tcmd.Dir = dir\n\t}\n\n\terr = cmd.Start()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\tvar wrErr error\n\tgo func() {\n\t\tdefer func() {\n\t\t\twrErr = stdin.Close()\n\t\t\tif wrErr != nil {\n\t\t\t\twrErr = &errortypes.ExecError{\n\t\t\t\t\terrors.Wrapf(\n\t\t\t\t\t\twrErr,\n\t\t\t\t\t\t\"utils: Failed to close stdin in exec '%s'\",\n\t\t\t\t\t\tname,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\t_, wrErr = io.WriteString(stdin, input)\n\t\tif wrErr != nil {\n\t\t\twrErr = &errortypes.ExecError{\n\t\t\t\terrors.Wrapf(\n\t\t\t\t\twrErr,\n\t\t\t\t\t\"utils: Failed to write stdin in exec '%s'\",\n\t\t\t\t\tname,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\terr = cmd.Wait()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\tif wrErr != nil {\n\t\terr = wrErr\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExecInputOutput(input, name string, arg ...string) (\n\toutput string, err error) {\n\n\tcmd := exec.Command(name, arg...)\n\n\tstdout := &bytes.Buffer{}\n\n\tcmd.Stdout = stdout\n\tcmd.Stderr = os.Stderr\n\n\tstdin, err := cmd.StdinPipe()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to get stdin in exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\terr = cmd.Start()\n\tif err != nil {\n\t\tstdin.Close()\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\tvar wrErr error\n\tgo func() {\n\t\tdefer func() {\n\t\t\twrErr = stdin.Close()\n\t\t\tif wrErr != nil {\n\t\t\t\twrErr = &errortypes.ExecError{\n\t\t\t\t\terrors.Wrapf(\n\t\t\t\t\t\twrErr,\n\t\t\t\t\t\t\"utils: Failed to close stdin in exec '%s'\",\n\t\t\t\t\t\tname,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\t_, wrErr = io.WriteString(stdin, input)\n\t\tif wrErr != nil {\n\t\t\twrErr = &errortypes.ExecError{\n\t\t\t\terrors.Wrapf(\n\t\t\t\t\twrErr,\n\t\t\t\t\t\"utils: Failed to write stdin in exec '%s'\",\n\t\t\t\t\tname,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\terr = cmd.Wait()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\tif wrErr != nil {\n\t\terr = wrErr\n\t\treturn\n\t}\n\n\toutput = string(stdout.Bytes())\n\n\treturn\n}\n\nfunc ExecInputOutputCombindLogged(input, name string, arg ...string) (\n\toutput string, err error) {\n\n\tcmd := exec.Command(name, arg...)\n\n\tstdout := &bytes.Buffer{}\n\tstderr := &bytes.Buffer{}\n\n\tcmd.Stdout = stdout\n\tcmd.Stderr = stderr\n\n\tstdin, err := cmd.StdinPipe()\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to get stdin in exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\terr = cmd.Start()\n\tif err != nil {\n\t\tstdin.Close()\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\tvar wrErr error\n\tgo func() {\n\t\tdefer func() {\n\t\t\twrErr = stdin.Close()\n\t\t\tif wrErr != nil {\n\t\t\t\twrErr = &errortypes.ExecError{\n\t\t\t\t\terrors.Wrapf(\n\t\t\t\t\t\twrErr,\n\t\t\t\t\t\t\"utils: Failed to close stdin in exec '%s'\",\n\t\t\t\t\t\tname,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\t_, wrErr = io.WriteString(stdin, input)\n\t\tif wrErr != nil {\n\t\t\twrErr = &errortypes.ExecError{\n\t\t\t\terrors.Wrapf(\n\t\t\t\t\twrErr,\n\t\t\t\t\t\"utils: Failed to write stdin in exec '%s'\",\n\t\t\t\t\tname,\n\t\t\t\t),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\terr = cmd.Wait()\n\n\toutput = stdout.String()\n\terrOutput := stderr.String()\n\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"output\":       output,\n\t\t\t\"error_output\": errOutput,\n\t\t\t\"cmd\":          name,\n\t\t\t\"arg\":          arg,\n\t\t\t\"error\":        err,\n\t\t}).Error(\"utils: Process exec error\")\n\n\t\treturn\n\t}\n\n\tif wrErr != nil {\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"output\":       output,\n\t\t\t\"error_output\": errOutput,\n\t\t\t\"cmd\":          name,\n\t\t\t\"arg\":          arg,\n\t\t\t\"error\":        wrErr,\n\t\t}).Error(\"utils: Process exec error\")\n\n\t\treturn\n\t}\n\n\toutput = string(stdout.Bytes())\n\n\treturn\n}\n\nfunc ExecOutput(dir, name string, arg ...string) (output string, err error) {\n\tcmd := exec.Command(name, arg...)\n\tcmd.Stderr = os.Stderr\n\n\tif dir != \"\" {\n\t\tcmd.Dir = dir\n\t}\n\n\toutputByt, err := cmd.Output()\n\tif outputByt != nil {\n\t\toutput = string(outputByt)\n\t}\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExecCombinedOutput(dir, name string, arg ...string) (\n\toutput string, err error) {\n\n\tcmd := exec.Command(name, arg...)\n\n\tif dir != \"\" {\n\t\tcmd.Dir = dir\n\t}\n\n\toutputByt, err := cmd.CombinedOutput()\n\tif outputByt != nil {\n\t\toutput = string(outputByt)\n\t}\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExecCombinedOutputLogged(ignores []string, name string, arg ...string) (\n\toutput string, err error) {\n\n\tcmd := exec.Command(name, arg...)\n\n\toutputByt, err := cmd.CombinedOutput()\n\tif outputByt != nil {\n\t\toutput = string(outputByt)\n\t}\n\n\tif err != nil && ignores != nil {\n\t\tfor _, ignore := range ignores {\n\t\t\tif strings.Contains(output, ignore) {\n\t\t\t\terr = nil\n\t\t\t\toutput = \"\"\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"output\": output,\n\t\t\t\"cmd\":    name,\n\t\t\t\"arg\":    arg,\n\t\t\t\"error\":  err,\n\t\t}).Error(\"utils: Process exec error\")\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExecCombinedOutputLoggedDir(ignores []string,\n\tdir, name string, arg ...string) (\n\toutput string, err error) {\n\n\tcmd := exec.Command(name, arg...)\n\tif dir != \"\" {\n\t\tcmd.Dir = dir\n\t}\n\n\toutputByt, err := cmd.CombinedOutput()\n\tif outputByt != nil {\n\t\toutput = string(outputByt)\n\t}\n\n\tif err != nil && ignores != nil {\n\t\tfor _, ignore := range ignores {\n\t\t\tif strings.Contains(output, ignore) {\n\t\t\t\terr = nil\n\t\t\t\toutput = \"\"\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"output\": output,\n\t\t\t\"cmd\":    name,\n\t\t\t\"arg\":    arg,\n\t\t\t\"error\":  err,\n\t\t}).Error(\"utils: Process exec error\")\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExecOutputLogged(ignores []string, name string, arg ...string) (\n\toutput string, err error) {\n\n\tcmd := exec.Command(name, arg...)\n\n\tstdout := &bytes.Buffer{}\n\tstderr := &bytes.Buffer{}\n\n\tcmd.Stdout = stdout\n\tcmd.Stderr = stderr\n\n\terr = cmd.Run()\n\toutput = stdout.String()\n\terrOutput := stderr.String()\n\n\tif err != nil && ignores != nil {\n\t\tfor _, ignore := range ignores {\n\t\t\tif strings.Contains(output, ignore) ||\n\t\t\t\tstrings.Contains(errOutput, ignore) {\n\n\t\t\t\terr = nil\n\t\t\t\toutput = \"\"\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif err != nil {\n\t\terr = &errortypes.ExecError{\n\t\t\terrors.Wrapf(err, \"utils: Failed to exec '%s'\", name),\n\t\t}\n\n\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\"output\":       output,\n\t\t\t\"error_output\": errOutput,\n\t\t\t\"cmd\":          name,\n\t\t\t\"arg\":          arg,\n\t\t\t\"error\":        err,\n\t\t}).Error(\"utils: Process exec error\")\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc getClockTicks() (ticks int) {\n\tif clockTicks != 0 {\n\t\tticks = clockTicks\n\t\treturn\n\t}\n\n\tresp, err := commander.Exec(&commander.Opt{\n\t\tName:    \"getconf\",\n\t\tArgs:    []string{\"CLK_TCK\"},\n\t\tPipeOut: true,\n\t\tPipeErr: true,\n\t})\n\tif err != nil {\n\t\tticks = 100\n\t\tclockTicks = 100\n\t\treturn\n\t}\n\n\tif resp.Output != nil {\n\t\tticks, _ = strconv.Atoi(strings.TrimSpace(string(resp.Output)))\n\t}\n\n\tif ticks == 0 {\n\t\tticks = 100\n\t\tclockTicks = 100\n\t\treturn\n\t}\n\n\tclockTicks = ticks\n\treturn\n}\n\nfunc GetProcessTimestamp(pid int) (timestamp time.Time, err error) {\n\tprocPath := filepath.Join(\"/proc\", strconv.Itoa(pid))\n\n\t_, err = os.Stat(procPath)\n\tif os.IsNotExist(err) {\n\t\terr = nil\n\t\treturn\n\t}\n\n\tstatPath := filepath.Join(procPath, \"stat\")\n\tstatData, err := os.ReadFile(statPath)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to read process stat\"),\n\t\t}\n\t\treturn\n\t}\n\n\tstatFields := strings.Fields(string(statData))\n\tif len(statFields) < 22 {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Invalid process state format\"),\n\t\t}\n\t\treturn\n\t}\n\n\tstartTimeTicks, err := strconv.ParseInt(statFields[21], 10, 64)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to process stat\"),\n\t\t}\n\t\treturn\n\t}\n\n\tuptimeData, err := os.ReadFile(\"/proc/uptime\")\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to read uptime\"),\n\t\t}\n\t\treturn\n\t}\n\n\tuptimeFields := strings.Fields(string(uptimeData))\n\tif len(uptimeFields) < 1 {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Invalid uptime format\"),\n\t\t}\n\t\treturn\n\t}\n\n\tsystemUptimeSec, err := strconv.ParseFloat(uptimeFields[0], 64)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to process uptime\"),\n\t\t}\n\t\treturn\n\t}\n\n\tprocessStartTimeSec := float64(startTimeTicks) / float64(getClockTicks())\n\tprocessUptimeSec := systemUptimeSec - processStartTimeSec\n\ttimestamp = time.Now().Add(\n\t\ttime.Duration(processUptimeSec * -float64(time.Second)))\n\n\treturn\n}\n"
  },
  {
    "path": "utils/prompt.go",
    "content": "package utils\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nvar (\n\tAssumeYes = false\n)\n\nfunc parseYesNo(input string) (val bool, err error) {\n\tinput = strings.ToLower(input)\n\tif input == \"y\" || input == \"yes\" {\n\t\tval = true\n\t\treturn\n\t} else if input == \"n\" || input == \"no\" {\n\t\tval = false\n\t\treturn\n\t}\n\n\terr = &errortypes.ParseError{\n\t\terrors.New(\"prompt: Invalid confirm input\"),\n\t}\n\treturn\n}\n\nfunc ConfirmDefault(label string, def bool) (resp bool, err error) {\n\tif AssumeYes {\n\t\tresp = true\n\t\treturn\n\t}\n\n\tvar prompt string\n\tif def {\n\t\tprompt = fmt.Sprintf(\"%s [Y/n]: \", label)\n\t} else {\n\t\tprompt = fmt.Sprintf(\"%s [y/N]: \", label)\n\t}\n\n\tfmt.Print(prompt)\n\n\treader := bufio.NewReader(os.Stdin)\n\tinput, err := reader.ReadString('\\n')\n\tif err != nil {\n\t\treturn\n\t}\n\n\tinput = strings.TrimSpace(input)\n\n\tif input == \"\" {\n\t\tresp = def\n\t\treturn\n\t}\n\n\tresp, err = parseYesNo(input)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "utils/psutil_freebsd.go",
    "content": "package utils\n\nimport (\n\t\"encoding/binary\"\n\t\"runtime\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"golang.org/x/sys/unix\"\n)\n\ntype MemInfo struct {\n\tTotal                uint64\n\tFree                 uint64\n\tAvailable            uint64\n\tBuffers              uint64\n\tCached               uint64\n\tUsed                 uint64\n\tUsedPercent          float64\n\tDirty                uint64\n\tSwapTotal            uint64\n\tSwapFree             uint64\n\tSwapUsed             uint64\n\tSwapUsedPercent      float64\n\tHugePagesTotal       uint64\n\tHugePagesFree        uint64\n\tHugePagesReserved    uint64\n\tHugePagesUsed        uint64\n\tHugePagesUsedPercent float64\n\tHugePageSize         uint64\n}\n\nfunc getSysctlUint64(name string) (uint64, error) {\n\tvalue32, err := unix.SysctlUint32(name)\n\tif err == nil {\n\t\treturn uint64(value32), nil\n\t}\n\treturn unix.SysctlUint64(name)\n}\n\nfunc GetMemInfo() (info *MemInfo, err error) {\n\tinfo = &MemInfo{}\n\n\ttotalMem, err := getSysctlUint64(\"hw.physmem\")\n\tif err != nil {\n\t\treturn nil, &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to read physmem\"),\n\t\t}\n\t}\n\tinfo.Total = totalMem / 1024\n\n\tpageSize, err := getSysctlUint64(\"hw.pagesize\")\n\tif err != nil {\n\t\treturn nil, &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to read pagesize\"),\n\t\t}\n\t}\n\n\tfreePages, err := getSysctlUint64(\"vm.stats.vm.v_free_count\")\n\tif err != nil {\n\t\treturn nil, &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to read freecount\"),\n\t\t}\n\t}\n\n\tinfo.Free = (uint64(freePages) * uint64(pageSize)) / 1024\n\tinfo.Available = info.Free\n\tinfo.Used = info.Total - info.Free\n\n\tif info.Total > 0 {\n\t\tinfo.UsedPercent = float64(info.Used) / float64(info.Total) * 100.0\n\t}\n\n\treturn info, nil\n}\n\ntype LoadStat struct {\n\tCpuUnits int\n\tLoad1    float64\n\tLoad5    float64\n\tLoad15   float64\n}\n\nfunc LoadAverage() (ld *LoadStat, err error) {\n\tcount := runtime.NumCPU()\n\tcountFloat := float64(count)\n\n\tloadavgRaw, err := unix.SysctlRaw(\"vm.loadavg\")\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to read loadavg\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif len(loadavgRaw) < 12 {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.New(\"utils: Invalid loadavg size\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfscale := float64(1 << 11)\n\tif len(loadavgRaw) >= 20 {\n\t\treadFscale := float64(binary.LittleEndian.Uint32(loadavgRaw[16:20]))\n\t\tif readFscale > 0 {\n\t\t\tfscale = readFscale\n\t\t}\n\t}\n\n\tload1 := float64(binary.LittleEndian.Uint32(loadavgRaw[0:4])) / fscale\n\tload5 := float64(binary.LittleEndian.Uint32(loadavgRaw[4:8])) / fscale\n\tload15 := float64(binary.LittleEndian.Uint32(loadavgRaw[8:12])) / fscale\n\n\tld = &LoadStat{\n\t\tCpuUnits: count,\n\t\tLoad1:    ToFixed(load1/countFloat*100, 2),\n\t\tLoad5:    ToFixed(load5/countFloat*100, 2),\n\t\tLoad15:   ToFixed(load15/countFloat*100, 2),\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "utils/psutil_linux.go",
    "content": "package utils\n\nimport (\n\t\"io/ioutil\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\ntype MemInfo struct {\n\tTotal                uint64\n\tFree                 uint64\n\tAvailable            uint64\n\tBuffers              uint64\n\tCached               uint64\n\tUsed                 uint64\n\tUsedPercent          float64\n\tDirty                uint64\n\tSwapTotal            uint64\n\tSwapFree             uint64\n\tSwapUsed             uint64\n\tSwapUsedPercent      float64\n\tHugePagesTotal       uint64\n\tHugePagesFree        uint64\n\tHugePagesReserved    uint64\n\tHugePagesUsed        uint64\n\tHugePagesUsedPercent float64\n\tHugePageSize         uint64\n}\n\nfunc GetMemInfo() (info *MemInfo, err error) {\n\tinfo = &MemInfo{}\n\n\tlines, err := ReadLines(\"/proc/meminfo\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, line := range lines {\n\t\tfields := strings.Split(line, \":\")\n\t\tif len(fields) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tkey := strings.TrimSpace(fields[0])\n\t\tvalue := strings.TrimSpace(fields[1])\n\t\tvalue = strings.Replace(value, \" kB\", \"\", -1)\n\n\t\tswitch key {\n\t\tcase \"MemTotal\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse mem total\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.Total = valueInt\n\t\tcase \"MemFree\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse mem free\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.Free = valueInt\n\t\tcase \"MemAvailable\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse mem available\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.Available = valueInt\n\t\tcase \"Buffers\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse buffers\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.Buffers = valueInt\n\t\tcase \"Cached\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse cached\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.Cached = valueInt\n\t\tcase \"Dirty\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse dirty\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.Dirty = valueInt\n\t\tcase \"SwapTotal\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse swap total\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.SwapTotal = valueInt\n\t\tcase \"SwapFree\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse swap free\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.SwapFree = valueInt\n\t\tcase \"HugePages_Total\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse hugepages total\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.HugePagesTotal = valueInt\n\t\tcase \"HugePages_Free\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse hugepages total\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.HugePagesFree = valueInt\n\t\tcase \"HugePages_Rsvd\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e,\n\t\t\t\t\t\t\"utils: Failed to parse hugepages reserved\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.HugePagesReserved = valueInt\n\t\tcase \"Hugepagesize\":\n\t\t\tvalueInt, e := strconv.ParseUint(value, 10, 64)\n\t\t\tif e != nil {\n\t\t\t\terr = &errortypes.ParseError{\n\t\t\t\t\terrors.Wrap(e, \"utils: Failed to parse hugepages size\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tinfo.HugePageSize = valueInt\n\t\t}\n\t}\n\n\tinfo.Used = info.Total - info.Free - info.Buffers - info.Cached\n\tinfo.UsedPercent = float64(info.Used) / float64(info.Total) * 100.0\n\n\tinfo.SwapUsed = info.SwapTotal - info.SwapFree\n\tif info.SwapUsed != 0 {\n\t\tinfo.SwapUsedPercent = float64(\n\t\t\tinfo.SwapUsed) / float64(info.SwapTotal) * 100.0\n\t}\n\n\tinfo.HugePagesUsed = (info.HugePagesTotal - info.HugePagesFree) +\n\t\tinfo.HugePagesReserved\n\tif info.HugePagesUsed != 0 {\n\t\tinfo.HugePagesUsedPercent = float64(\n\t\t\tinfo.HugePagesUsed) / float64(info.HugePagesTotal) * 100.0\n\t}\n\n\treturn\n}\n\ntype LoadStat struct {\n\tCpuUnits int\n\tLoad1    float64\n\tLoad5    float64\n\tLoad15   float64\n}\n\nfunc LoadAverage() (ld *LoadStat, err error) {\n\tcount := runtime.NumCPU()\n\tcountFloat := float64(count)\n\t_ = countFloat\n\n\tld = &LoadStat{}\n\n\tline, err := ioutil.ReadFile(\"/proc/loadavg\")\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"utils: Failed to read loadavg\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvalues := strings.Fields(string(line))\n\tif len(values) < 3 {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"utils: Invalid loadavg data\"),\n\t\t}\n\t\treturn\n\t}\n\n\tload1, err := strconv.ParseFloat(values[0], 64)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"utils: Invalid load1 data\"),\n\t\t}\n\t\treturn\n\t}\n\tload5, err := strconv.ParseFloat(values[1], 64)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"utils: Invalid load5 data\"),\n\t\t}\n\t\treturn\n\t}\n\tload15, err := strconv.ParseFloat(values[2], 64)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"utils: Invalid load15 data\"),\n\t\t}\n\t\treturn\n\t}\n\n\tld = &LoadStat{\n\t\tCpuUnits: count,\n\t\tLoad1:    ToFixed(load1/countFloat*100, 2),\n\t\tLoad5:    ToFixed(load5/countFloat*100, 2),\n\t\tLoad15:   ToFixed(load15/countFloat*100, 2),\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "utils/randomname.go",
    "content": "package utils\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"math/rand\"\n)\n\nvar (\n\trandElm = []string{\n\t\t\"copper\",\n\t\t\"argon\",\n\t\t\"xenon\",\n\t\t\"radon\",\n\t\t\"cobalt\",\n\t\t\"nickel\",\n\t\t\"carbon\",\n\t\t\"helium\",\n\t\t\"nitrogen\",\n\t\t\"radium\",\n\t\t\"lithium\",\n\t\t\"silicon\",\n\t}\n)\n\nfunc RandName() (name string) {\n\tname = fmt.Sprintf(\"%s-%d\", randElm[rand.Intn(len(randElm))],\n\t\trand.Intn(8999)+1000)\n\treturn\n}\n\nfunc RandIp() string {\n\treturn fmt.Sprintf(\"26.197.%d.%d\", rand.Intn(250)+4, rand.Intn(250)+4)\n}\n\nfunc RandIp6() (addr string) {\n\taddr = \"2604:4080\"\n\trandByt, _ := RandBytes(12)\n\trandHex := fmt.Sprintf(\"%x\", randByt)\n\n\tbuf := bytes.Buffer{}\n\tfor i, run := range randHex {\n\t\tif i%4 == 0 && i != len(randHex)-1 {\n\t\t\tbuf.WriteRune(':')\n\t\t}\n\t\tbuf.WriteRune(run)\n\t}\n\n\taddr += buf.String()\n\n\treturn\n}\n\nfunc RandPrivateIp() string {\n\treturn fmt.Sprintf(\"10.232.%d.%d\", rand.Intn(250)+4, rand.Intn(250)+4)\n}\n\nfunc RandPrivateIp6() (addr string) {\n\taddr = \"fd97:7d1d\"\n\trandByt, _ := RandBytes(12)\n\trandHex := fmt.Sprintf(\"%x\", randByt)\n\n\tbuf := bytes.Buffer{}\n\tfor i, run := range randHex {\n\t\tif i%4 == 0 && i != len(randHex)-1 {\n\t\t\tbuf.WriteRune(':')\n\t\t}\n\t\tbuf.WriteRune(run)\n\t}\n\n\taddr += buf.String()\n\n\treturn\n}\n"
  },
  {
    "path": "utils/request.go",
    "content": "package utils\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gin-gonic/gin/render\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype NopCloser struct {\n\tio.Reader\n}\n\nfunc (NopCloser) Close() error {\n\treturn nil\n}\n\nvar httpErrCodes = map[int]string{\n\t400: \"Bad Request\",\n\t401: \"Unauthorized\",\n\t402: \"Payment Required\",\n\t403: \"Forbidden\",\n\t404: \"Not Found\",\n\t405: \"Method Not Allowed\",\n\t406: \"Not Acceptable\",\n\t407: \"Proxy Authentication Required\",\n\t408: \"Request Timeout\",\n\t409: \"Conflict\",\n\t410: \"Gone\",\n\t411: \"Length Required\",\n\t412: \"Precondition Failed\",\n\t413: \"Payload Too Large\",\n\t414: \"URI Too Long\",\n\t415: \"Unsupported Media Type\",\n\t416: \"Range Not Satisfiable\",\n\t417: \"Expectation Failed\",\n\t421: \"Misdirected Request\",\n\t422: \"Unprocessable Entity\",\n\t423: \"Locked\",\n\t424: \"Failed Dependency\",\n\t426: \"Upgrade Required\",\n\t428: \"Precondition Required\",\n\t429: \"Too Many Requests\",\n\t431: \"Request Header Fields Too Large\",\n\t451: \"Unavailable For Legal Reasons\",\n\t500: \"Internal Server Error\",\n\t501: \"Not Implemented\",\n\t502: \"Bad Gateway\",\n\t503: \"Service Unavailable\",\n\t504: \"Gateway Timeout\",\n\t505: \"HTTP Version Not Supported\",\n\t506: \"Variant Also Negotiates\",\n\t507: \"Insufficient Storage\",\n\t508: \"Loop Detected\",\n\t510: \"Not Extended\",\n\t511: \"Network Authentication Required\",\n}\n\nfunc CopyBody(r *http.Request) (buffer *bytes.Buffer, err error) {\n\tbody, err := ioutil.ReadAll(r.Body)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"handler: Request read error\"),\n\t\t}\n\t\treturn\n\t}\n\t_ = r.Body.Close()\n\n\tr.Body = ioutil.NopCloser(bytes.NewBuffer(body))\n\tbuffer = bytes.NewBuffer(body)\n\n\treturn\n}\n\nfunc StripPort(hostport string) string {\n\tcolon := strings.IndexByte(hostport, ':')\n\tif colon == -1 {\n\t\treturn hostport\n\t}\n\n\tn := strings.Count(hostport, \":\")\n\tif n > 1 {\n\t\tif i := strings.IndexByte(hostport, ']'); i != -1 {\n\t\t\treturn strings.TrimPrefix(hostport[:i], \"[\")\n\t\t}\n\t\treturn hostport\n\t}\n\n\treturn hostport[:colon]\n}\n\nfunc FormatHostPort(hostname string, port int) string {\n\tif strings.Contains(hostname, \":\") {\n\t\thostname = \"[\" + hostname + \"]\"\n\t}\n\treturn fmt.Sprintf(\"%s:%d\", hostname, port)\n}\n\nfunc ParseObjectId(strId string) (objId bson.ObjectID, ok bool) {\n\tif strId == \"\" {\n\t\tobjId = bson.NilObjectID\n\t\treturn\n\t}\n\n\tobjectId, err := bson.ObjectIDFromHex(strId)\n\tif err != nil {\n\t\tobjId = bson.NilObjectID\n\t\treturn\n\t}\n\n\tobjId = objectId\n\tok = true\n\treturn\n}\n\nfunc ObjectIdHex(strId string) (objId bson.ObjectID) {\n\tif strId == \"\" {\n\t\tobjId = bson.NilObjectID\n\t\treturn\n\t}\n\n\tobjectId, err := bson.ObjectIDFromHex(strId)\n\tif err != nil {\n\t\tobjId = bson.NilObjectID\n\t\treturn\n\t}\n\n\tobjId = objectId\n\treturn\n}\n\nfunc GetStatusMessage(code int) string {\n\treturn fmt.Sprintf(\"%d %s\", code, http.StatusText(code))\n}\n\nfunc AbortWithStatus(c *gin.Context, code int) {\n\tr := render.String{\n\t\tFormat: GetStatusMessage(code),\n\t}\n\n\tc.Status(code)\n\tr.WriteContentType(c.Writer)\n\tc.Writer.WriteHeaderNow()\n\tr.Render(c.Writer)\n\tc.Abort()\n}\n\nfunc AbortWithError(c *gin.Context, code int, err error) {\n\tAbortWithStatus(c, code)\n\tc.Error(err)\n}\n\nfunc WriteStatus(w http.ResponseWriter, code int) {\n\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\tw.Header().Set(\"X-Content-Type-Options\", \"nosniff\")\n\tw.WriteHeader(code)\n\tfmt.Fprintln(w, GetStatusMessage(code))\n}\n\nfunc WriteText(w http.ResponseWriter, code int, text string) {\n\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\tw.Header().Set(\"X-Content-Type-Options\", \"nosniff\")\n\tw.WriteHeader(code)\n\tfmt.Fprintln(w, text)\n}\n\nfunc WriteUnauthorized(w http.ResponseWriter, msg string) {\n\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\tw.Header().Set(\"X-Content-Type-Options\", \"nosniff\")\n\tw.WriteHeader(401)\n\tfmt.Fprintln(w, \"401 \"+msg)\n}\n\nfunc CloneHeader(src http.Header) (dst http.Header) {\n\tdst = make(http.Header, len(src))\n\tfor k, vv := range src {\n\t\tvv2 := make([]string, len(vv))\n\t\tcopy(vv2, vv)\n\t\tdst[k] = vv2\n\t}\n\treturn dst\n}\n\nfunc GetLocation(r *http.Request, domains []string) string {\n\thost := \"\"\n\n\tif r.Host != \"\" {\n\t\thost = r.Host\n\t} else if r.URL.Host != \"\" {\n\t\thost = r.URL.Host\n\t}\n\n\tif host != \"\" {\n\t\tfor _, domain := range domains {\n\t\t\tif domain == host {\n\t\t\t\treturn \"https://\" + domain\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, domain := range domains {\n\t\tif domain != \"\" {\n\t\t\treturn \"https://\" + domain\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\nfunc GetOrigin(r *http.Request) string {\n\torigin := r.Header.Get(\"Origin\")\n\tif origin == \"\" {\n\t\thost := \"\"\n\t\tswitch {\n\t\tcase r.Host != \"\":\n\t\t\thost = r.Host\n\t\t\tbreak\n\t\tcase r.URL.Host != \"\":\n\t\t\thost = r.URL.Host\n\t\t\tbreak\n\t\t}\n\t\torigin = \"https://\" + host\n\t}\n\n\treturn origin\n}\n\nfunc CheckRequestN(resp *http.Response, msg string, codes []int) (err error) {\n\tfor _, code := range codes {\n\t\tif resp.StatusCode == code {\n\t\t\treturn\n\t\t}\n\t}\n\n\tbodyStr := \"\"\n\tbodyBytes, readErr := io.ReadAll(io.LimitReader(resp.Body, 10*1024))\n\tif readErr != nil {\n\t\tbodyStr = fmt.Sprintf(\"[%v]\", readErr)\n\t} else {\n\t\tbodyStr = string(bodyBytes)\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"body\":        bodyStr,\n\t\t\"status_code\": resp.StatusCode,\n\t\t\"message\":     msg,\n\t}).Error(msg)\n\n\terr = &errortypes.RequestError{\n\t\terrors.Newf(\"request: Response status error %d\", resp.StatusCode),\n\t}\n\treturn\n}\n\nfunc CheckRequest(resp *http.Response, msg string) (err error) {\n\tif resp.StatusCode == 200 {\n\t\treturn\n\t}\n\n\tbodyStr := \"\"\n\tbodyBytes, readErr := io.ReadAll(io.LimitReader(resp.Body, 10*1024))\n\tif readErr != nil {\n\t\tbodyStr = fmt.Sprintf(\"[%v]\", readErr)\n\t} else {\n\t\tbodyStr = string(bodyBytes)\n\t}\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"body\":        bodyStr,\n\t\t\"status_code\": resp.StatusCode,\n\t\t\"message\":     msg,\n\t}).Error(msg)\n\n\terr = &errortypes.RequestError{\n\t\terrors.Newf(\"request: Response status error %d\", resp.StatusCode),\n\t}\n\treturn\n}\n"
  },
  {
    "path": "utils/sort.go",
    "content": "package utils\n\nimport (\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n)\n\nvar (\n\tnumRe = regexp.MustCompile(`(\\d+|\\D+)`)\n)\n\ntype ObjectIdSlice []bson.ObjectID\n\nfunc (o ObjectIdSlice) Len() int {\n\treturn len(o)\n}\n\nfunc (o ObjectIdSlice) Less(i, j int) bool {\n\treturn o[i].Hex() < o[j].Hex()\n}\n\nfunc (o ObjectIdSlice) Swap(i, j int) {\n\to[i], o[j] = o[j], o[i]\n}\n\nfunc SortObjectIds(x []bson.ObjectID) {\n\tsort.Sort(ObjectIdSlice(x))\n}\n\nfunc NaturalCompare(a, b string) int {\n\taParts := numRe.FindAllString(a, -1)\n\tbParts := numRe.FindAllString(b, -1)\n\n\tminLen := len(aParts)\n\tif len(bParts) < minLen {\n\t\tminLen = len(bParts)\n\t}\n\n\tfor i := 0; i < minLen; i++ {\n\t\taPart := aParts[i]\n\t\tbPart := bParts[i]\n\n\t\taNum, aErr := strconv.Atoi(aPart)\n\t\tbNum, bErr := strconv.Atoi(bPart)\n\n\t\tif aErr == nil && bErr == nil {\n\t\t\tif aNum != bNum {\n\t\t\t\treturn aNum - bNum\n\t\t\t}\n\t\t} else if (aErr == nil) != (bErr == nil) {\n\t\t\tif aErr == nil {\n\t\t\t\treturn -1\n\t\t\t}\n\t\t\treturn 1\n\t\t} else {\n\t\t\tif aPart != bPart {\n\t\t\t\treturn strings.Compare(aPart, bPart)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn len(aParts) - len(bParts)\n}\n"
  },
  {
    "path": "utils/timeoutlock.go",
    "content": "package utils\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/constants\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype TimeoutLock struct {\n\tlock      sync.Mutex\n\tstate     map[bson.ObjectID]bool\n\tstateLock sync.Mutex\n\ttimeout   time.Duration\n}\n\nfunc (l *TimeoutLock) Lock() (id bson.ObjectID) {\n\tid = bson.NewObjectID()\n\tl.lock.Lock()\n\n\tl.stateLock.Lock()\n\tl.state[id] = true\n\tl.stateLock.Unlock()\n\n\tif !constants.LockDebug {\n\t\treturn\n\t}\n\n\tstart := time.Now()\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\tl.stateLock.Lock()\n\t\t\tstate := l.state[id]\n\t\t\tl.stateLock.Unlock()\n\t\t\tif !state {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif time.Since(start) > l.timeout {\n\t\t\t\terr := &errortypes.TimeoutError{\n\t\t\t\t\terrors.New(\"utils: Multi lock timeout\"),\n\t\t\t\t}\n\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"error\": err,\n\t\t\t\t}).Error(\"utils: Lock timed out\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn\n}\n\nfunc (l *TimeoutLock) Unlock(id bson.ObjectID) {\n\tl.lock.Unlock()\n\tl.stateLock.Lock()\n\tdelete(l.state, id)\n\tl.stateLock.Unlock()\n}\n\nfunc NewTimeoutLock(timeout time.Duration) *TimeoutLock {\n\treturn &TimeoutLock{\n\t\tlock:      sync.Mutex{},\n\t\tstate:     map[bson.ObjectID]bool{},\n\t\tstateLock: sync.Mutex{},\n\t\ttimeout:   timeout,\n\t}\n}\n"
  },
  {
    "path": "utils/unix.go",
    "content": "package utils\n\nimport (\n\t\"crypto/sha512\"\n\t\"strconv\"\n)\n\nconst b64x24Chars = \"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"\n\nfunc Base64x24(src []byte) (hash []byte) {\n\tif len(src) == 0 {\n\t\treturn []byte{}\n\t}\n\n\thashSize := (len(src) * 8) / 6\n\tif (len(src) % 6) != 0 {\n\t\thashSize += 1\n\t}\n\thash = make([]byte, hashSize)\n\n\tdst := hash\n\tfor len(src) > 0 {\n\t\tswitch len(src) {\n\t\tdefault:\n\t\t\tdst[0] = b64x24Chars[src[0]&0x3f]\n\t\t\tdst[1] = b64x24Chars[((src[0]>>6)|(src[1]<<2))&0x3f]\n\t\t\tdst[2] = b64x24Chars[((src[1]>>4)|(src[2]<<4))&0x3f]\n\t\t\tdst[3] = b64x24Chars[(src[2]>>2)&0x3f]\n\t\t\tsrc = src[3:]\n\t\t\tdst = dst[4:]\n\t\tcase 2:\n\t\t\tdst[0] = b64x24Chars[src[0]&0x3f]\n\t\t\tdst[1] = b64x24Chars[((src[0]>>6)|(src[1]<<2))&0x3f]\n\t\t\tdst[2] = b64x24Chars[(src[1]>>4)&0x3f]\n\t\t\tsrc = src[2:]\n\t\t\tdst = dst[3:]\n\t\tcase 1:\n\t\t\tdst[0] = b64x24Chars[src[0]&0x3f]\n\t\t\tdst[1] = b64x24Chars[(src[0]>>6)&0x3f]\n\t\t\tsrc = src[1:]\n\t\t\tdst = dst[2:]\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc GenerateShadow(passwd string) (output string, err error) {\n\tvar i int\n\trounds := 4096\n\n\tsaltStr, err := RandStr(8)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsalt := []byte(saltStr)\n\tpasswdByt := []byte(passwd)\n\n\talternateHash := sha512.New()\n\talternateHash.Write(passwdByt)\n\talternateHash.Write(salt)\n\talternateHash.Write(passwdByt)\n\talernateSum := alternateHash.Sum(nil)\n\n\taSeqHash := sha512.New()\n\taSeqHash.Write(passwdByt)\n\taSeqHash.Write(salt)\n\tfor i = len(passwdByt); i > 64; i -= 64 {\n\t\taSeqHash.Write(alernateSum)\n\t}\n\taSeqHash.Write(alernateSum[0:i])\n\n\tfor i = len(passwdByt); i > 0; i >>= 1 {\n\t\tif (i & 1) != 0 {\n\t\t\taSeqHash.Write(alernateSum)\n\t\t} else {\n\t\t\taSeqHash.Write(passwdByt)\n\t\t}\n\t}\n\taSeqSum := aSeqHash.Sum(nil)\n\n\tpSeqHash := sha512.New()\n\tfor i = 0; i < len(passwdByt); i++ {\n\t\tpSeqHash.Write(passwdByt)\n\t}\n\tpSeqSum := pSeqHash.Sum(nil)\n\n\tpSeq := make([]byte, 0, len(passwdByt))\n\tfor i = len(passwdByt); i > 64; i -= 64 {\n\t\tpSeq = append(pSeq, pSeqSum...)\n\t}\n\tpSeq = append(pSeq, pSeqSum[0:i]...)\n\n\tsSeqHash := sha512.New()\n\tfor i = 0; i < (16 + int(aSeqSum[0])); i++ {\n\t\tsSeqHash.Write(salt)\n\t}\n\tsSeqSum := sSeqHash.Sum(nil)\n\n\tsSeq := make([]byte, 0, len(salt))\n\tfor i = len(salt); i > 64; i -= 64 {\n\t\tsSeq = append(sSeq, sSeqSum...)\n\t}\n\tsSeq = append(sSeq, sSeqSum[0:i]...)\n\n\tcSum := aSeqSum\n\n\tfor i = 0; i < rounds; i++ {\n\t\tC := sha512.New()\n\n\t\tif (i & 1) != 0 {\n\t\t\tC.Write(pSeq)\n\t\t} else {\n\t\t\tC.Write(cSum)\n\t\t}\n\n\t\tif (i % 3) != 0 {\n\t\t\tC.Write(sSeq)\n\t\t}\n\n\t\tif (i % 7) != 0 {\n\t\t\tC.Write(pSeq)\n\t\t}\n\n\t\tif (i & 1) != 0 {\n\t\t\tC.Write(cSum)\n\t\t} else {\n\t\t\tC.Write(pSeq)\n\t\t}\n\n\t\tcSum = C.Sum(nil)\n\t}\n\n\tout := make([]byte, 0, 123)\n\tout = append(out, \"$6$\"...)\n\tout = append(out, []byte(\"rounds=\"+strconv.Itoa(rounds)+\"$\")...)\n\tout = append(out, salt...)\n\tout = append(out, '$')\n\tout = append(out, Base64x24([]byte{\n\t\tcSum[42], cSum[21], cSum[0],\n\t\tcSum[1], cSum[43], cSum[22],\n\t\tcSum[23], cSum[2], cSum[44],\n\t\tcSum[45], cSum[24], cSum[3],\n\t\tcSum[4], cSum[46], cSum[25],\n\t\tcSum[26], cSum[5], cSum[47],\n\t\tcSum[48], cSum[27], cSum[6],\n\t\tcSum[7], cSum[49], cSum[28],\n\t\tcSum[29], cSum[8], cSum[50],\n\t\tcSum[51], cSum[30], cSum[9],\n\t\tcSum[10], cSum[52], cSum[31],\n\t\tcSum[32], cSum[11], cSum[53],\n\t\tcSum[54], cSum[33], cSum[12],\n\t\tcSum[13], cSum[55], cSum[34],\n\t\tcSum[35], cSum[14], cSum[56],\n\t\tcSum[57], cSum[36], cSum[15],\n\t\tcSum[16], cSum[58], cSum[37],\n\t\tcSum[38], cSum[17], cSum[59],\n\t\tcSum[60], cSum[39], cSum[18],\n\t\tcSum[19], cSum[61], cSum[40],\n\t\tcSum[41], cSum[20], cSum[62],\n\t\tcSum[63],\n\t})...)\n\n\taSeqHash.Reset()\n\talternateHash.Reset()\n\tpSeqHash.Reset()\n\tfor i = 0; i < len(aSeqSum); i++ {\n\t\taSeqSum[i] = 0\n\t}\n\tfor i = 0; i < len(alernateSum); i++ {\n\t\talernateSum[i] = 0\n\t}\n\tfor i = 0; i < len(pSeq); i++ {\n\t\tpSeq[i] = 0\n\t}\n\n\toutput = string(out)\n\n\treturn\n}\n"
  },
  {
    "path": "utils/webauthn.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/go-webauthn/webauthn/protocol\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc ParseWebauthnError(err error) (newErr error) {\n\tif e, ok := err.(*protocol.Error); ok {\n\t\tnewErr = &errortypes.AuthenticationError{\n\t\t\terrors.Wrapf(\n\t\t\t\terr, \"secondary: Webauthn error %s - %s - %s\",\n\t\t\t\te.Type, e.DevInfo, e.Details,\n\t\t\t),\n\t\t}\n\t} else {\n\t\tnewErr = &errortypes.AuthenticationError{\n\t\t\terrors.Wrap(err, fmt.Sprintf(\n\t\t\t\t\"secondary: Webauthn unknown error\")),\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "validator/validator.go",
    "content": "package validator\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/audit\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/event\"\n\t\"github.com/pritunl/pritunl-cloud/policy\"\n\t\"github.com/pritunl/pritunl-cloud/user\"\n)\n\nfunc ValidateAdmin(db *database.Database, usr *user.User,\n\tisApi bool, r *http.Request) (deviceAuth bool,\n\tsecProvider bson.ObjectID, errAudit audit.Fields,\n\terrData *errortypes.ErrorData, err error) {\n\n\tif !usr.ActiveUntil.IsZero() && usr.ActiveUntil.Before(time.Now()) {\n\t\tusr.ActiveUntil = time.Time{}\n\t\tusr.Disabled = true\n\t\terr = usr.CommitFields(db, set.NewSet(\"active_until\", \"disabled\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"user.change\")\n\n\t\terrAudit = audit.Fields{\n\t\t\t\"error\":   \"user_disabled\",\n\t\t\t\"message\": \"User is disabled from expired active time\",\n\t\t}\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unauthorized\",\n\t\t\tMessage: \"Not authorized\",\n\t\t}\n\t\treturn\n\t}\n\n\tif usr.Disabled {\n\t\terrAudit = audit.Fields{\n\t\t\t\"error\":   \"user_disabled\",\n\t\t\t\"message\": \"User is disabled\",\n\t\t}\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unauthorized\",\n\t\t\tMessage: \"Not authorized\",\n\t\t}\n\t\treturn\n\t}\n\n\tif usr.Administrator != \"super\" {\n\t\terrAudit = audit.Fields{\n\t\t\t\"error\":   \"user_not_super\",\n\t\t\t\"message\": \"User is not super user\",\n\t\t}\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unauthorized\",\n\t\t\tMessage: \"Not authorized\",\n\t\t}\n\t\treturn\n\t}\n\n\tif !isApi {\n\t\tpolicies, e := policy.GetRoles(db, usr.Roles)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, polcy := range policies {\n\t\t\terrData, err = polcy.ValidateUser(db, usr, r)\n\t\t\tif err != nil || errData != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tfor _, polcy := range policies {\n\t\t\tif polcy.Disabled {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif polcy.AdminDeviceSecondary {\n\t\t\t\tdeviceAuth = true\n\t\t\t}\n\n\t\t\tif !polcy.AdminSecondary.IsZero() && secProvider.IsZero() {\n\t\t\t\tsecProvider = polcy.AdminSecondary\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc ValidateUser(db *database.Database, usr *user.User,\n\tisApi bool, r *http.Request) (deviceAuth bool,\n\tsecProvider bson.ObjectID, errAudit audit.Fields,\n\terrData *errortypes.ErrorData, err error) {\n\n\tif !usr.ActiveUntil.IsZero() && usr.ActiveUntil.Before(time.Now()) {\n\t\tusr.ActiveUntil = time.Time{}\n\t\tusr.Disabled = true\n\t\terr = usr.CommitFields(db, set.NewSet(\"active_until\", \"disabled\"))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tevent.PublishDispatch(db, \"user.change\")\n\n\t\terrAudit = audit.Fields{\n\t\t\t\"error\":   \"user_disabled\",\n\t\t\t\"message\": \"User is disabled from expired active time\",\n\t\t}\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unauthorized\",\n\t\t\tMessage: \"Not authorized\",\n\t\t}\n\t\treturn\n\t}\n\n\tif usr.Disabled {\n\t\terrAudit = audit.Fields{\n\t\t\t\"error\":   \"user_disabled\",\n\t\t\t\"message\": \"User is disabled\",\n\t\t}\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"unauthorized\",\n\t\t\tMessage: \"Not authorized\",\n\t\t}\n\t\treturn\n\t}\n\n\tif !isApi {\n\t\tpolicies, e := policy.GetRoles(db, usr.Roles)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, polcy := range policies {\n\t\t\terrData, err = polcy.ValidateUser(db, usr, r)\n\t\t\tif err != nil || errData != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tfor _, polcy := range policies {\n\t\t\tif polcy.Disabled {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif polcy.UserDeviceSecondary {\n\t\t\t\tdeviceAuth = true\n\t\t\t}\n\n\t\t\tif !polcy.UserSecondary.IsZero() && secProvider.IsZero() {\n\t\t\t\tsecProvider = polcy.UserSecondary\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "version/cache.go",
    "content": "package version\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\nvar (\n\tcacheStore = map[string]*cache{}\n\tcacheLock  = sync.Mutex{}\n)\n\nconst (\n\tcacheTtl = 5 * time.Minute\n)\n\ntype cache struct {\n\tVersion   int\n\tTimestamp time.Time\n}\n\nfunc cacheCheck(module string, ver int) (supported bool) {\n\tcacheLock.Lock()\n\tdefer cacheLock.Unlock()\n\n\tcach, ok := cacheStore[module]\n\tif !ok {\n\t\treturn true\n\t}\n\n\tif time.Since(cach.Timestamp) > cacheTtl {\n\t\tdelete(cacheStore, module)\n\t\treturn true\n\t}\n\n\treturn ver >= cach.Version\n}\n\nfunc cacheSet(module string, ver int) {\n\tcacheLock.Lock()\n\tdefer cacheLock.Unlock()\n\n\tcacheStore[module] = &cache{\n\t\tVersion:   ver,\n\t\tTimestamp: time.Now(),\n\t}\n\n\texisting, ok := cacheStore[module]\n\tif !ok || ver > existing.Version {\n\t\tcacheStore[module] = &cache{\n\t\t\tVersion:   ver,\n\t\t\tTimestamp: time.Now(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "version/utils.go",
    "content": "package version\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n)\n\nfunc Check(db *database.Database, module string, ver int) (\n\tsupported bool, err error) {\n\n\tif !cacheCheck(module, ver) {\n\t\treturn false, nil\n\t}\n\n\tcoll := db.Versions()\n\tvr := &Version{}\n\n\terr = coll.FindOneId(module, vr)\n\tif err != nil {\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\tvr = nil\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif vr == nil || ver >= vr.Version {\n\t\tsupported = true\n\t\treturn\n\t}\n\n\tcacheSet(module, vr.Version)\n\treturn\n}\n\nfunc Set(db *database.Database, module string, ver int) (err error) {\n\tcoll := db.Versions()\n\n\topts := options.UpdateOne().\n\t\tSetUpsert(true)\n\n\t_, err = coll.UpdateOne(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": module,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$max\": &bson.M{\n\t\t\t\t\"version\": ver,\n\t\t\t},\n\t\t\t\"$setOnInsert\": &bson.M{\n\t\t\t\t\"_id\": module,\n\t\t\t},\n\t\t},\n\t\topts,\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "version/version.go",
    "content": "package version\n\ntype Version struct {\n\tModule  string `bson:\"_id,omitempty\" json:\"id\"`\n\tVersion int    `bson:\"version,omitempty\" json:\"version\"`\n}\n"
  },
  {
    "path": "virtiofs/systemd.go",
    "content": "package virtiofs\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst systemdTemplate = `[Unit]\nDescription=Pritunl Cloud VirtIO-FS Daemon\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nExecStart=%s --socket-path=\"%s\" --shared-dir=\"%s\" --sandbox=namespace\nTimeoutStopSec=5\nPrivateTmp=true\nProtectSystem=full\nProtectHostname=true\nProtectKernelTunables=true\n`\n\nfunc WriteService(vmId bson.ObjectID,\n\tshareId, sharePath string) (err error) {\n\n\tunitPath := paths.GetUnitPathShare(vmId, shareId)\n\tsockPath := paths.GetShareSockPath(vmId, shareId)\n\n\toutput := fmt.Sprintf(\n\t\tsystemdTemplate,\n\t\tGetVirtioFsdPath(),\n\t\tsockPath,\n\t\tsharePath,\n\t)\n\n\terr = utils.CreateWrite(unitPath, output, 0600)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Start(db *database.Database, virt *vm.VirtualMachine,\n\tshareId, sharePath string) (err error) {\n\n\tunit := paths.GetUnitNameShare(virt.Id, shareId)\n\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"id\":           virt.Id.Hex(),\n\t\t\"systemd_unit\": unit,\n\t}).Info(\"virtiofs: Starting virtual machine virtiofsd\")\n\n\t_ = systemd.Stop(unit)\n\n\terr = WriteService(virt.Id, shareId, sharePath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Reload()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = systemd.Start(unit)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Stop(virt *vm.VirtualMachine, shareId string) (err error) {\n\tunit := paths.GetUnitNameShare(virt.Id, shareId)\n\n\t_ = systemd.Stop(unit)\n\n\treturn\n}\n"
  },
  {
    "path": "virtiofs/utils.go",
    "content": "package virtiofs\n\nimport (\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nconst (\n\tLibexec = \"/usr/libexec/virtiofsd\"\n\tSystem  = \"/usr/bin/virtiofsd\"\n)\n\nfunc GetVirtioFsdPath() string {\n\texists, _ := utils.Exists(System)\n\tif exists {\n\t\treturn System\n\t}\n\treturn Libexec\n}\n"
  },
  {
    "path": "virtiofs/virtiofs.go",
    "content": "package virtiofs\n\nimport (\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/organization\"\n\t\"github.com/pritunl/pritunl-cloud/paths\"\n\t\"github.com/pritunl/pritunl-cloud/systemd\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n)\n\nfunc StartAll(db *database.Database,\n\tvirt *vm.VirtualMachine) (err error) {\n\n\tmounts := []*vm.Mount{}\n\n\tunitPathShares := paths.GetUnitPathShares(virt.Id)\n\t_, err = utils.RemoveWildcard(unitPathShares)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(virt.Mounts) == 0 {\n\t\treturn\n\t}\n\n\torg, err := organization.Get(db, virt.Organization)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif org == nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.New(\"virtiofs: Failed to get org\"),\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, mount := range virt.Mounts {\n\t\tmatchPath := false\n\t\tmatchRoles := false\n\n\t\tfor _, share := range node.Self.Shares {\n\t\t\tif share.MatchPath(mount.HostPath) {\n\t\t\t\tmatchPath = true\n\n\t\t\t\tif utils.HasMatchingItem(share.Roles, org.Roles) {\n\t\t\t\t\tmatchRoles = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !matchPath && !matchRoles {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"virtiofs: Failed to find matching \"+\n\t\t\t\t\t\"share path for mount '%s'\", mount.HostPath),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif !matchPath || !matchRoles {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Newf(\"virtiofs: Failed to find matching \"+\n\t\t\t\t\t\"role for mount '%s'\", mount.HostPath),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tmounts = append(mounts, mount)\n\t}\n\n\tfor _, mount := range mounts {\n\t\tshareId := paths.GetShareId(virt.Id, mount.Name)\n\n\t\terr = Start(db, virt, shareId, mount.HostPath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\treturn\n}\n\nfunc StopAll(virt *vm.VirtualMachine) (err error) {\n\tunit := paths.GetUnitNameShares(virt.Id)\n\n\t_ = systemd.Stop(unit)\n\n\treturn\n}\n"
  },
  {
    "path": "vm/constants.go",
    "content": "package vm\n\nconst (\n\tStarting     = \"starting\"\n\tRunning      = \"running\"\n\tStopped      = \"stopped\"\n\tFailed       = \"failed\"\n\tUpdating     = \"updating\"\n\tProvisioning = \"provisioning\"\n\tBridge       = \"bridge\"\n\tVxlan        = \"vxlan\"\n\n\tPhysical = \"physical\"\n\tLvm      = \"lvm\"\n)\n"
  },
  {
    "path": "vm/sort.go",
    "content": "package vm\n\ntype SortDisks []*Disk\n\nfunc (d SortDisks) Len() int {\n\treturn len(d)\n}\n\nfunc (d SortDisks) Swap(i, j int) {\n\td[i], d[j] = d[j], d[i]\n}\n\nfunc (d SortDisks) Less(i, j int) bool {\n\treturn d[i].Index < d[j].Index\n}\n"
  },
  {
    "path": "vm/utils.go",
    "content": "package vm\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"encoding/base32\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n)\n\nfunc GetMacAddr(id bson.ObjectID, secondId bson.ObjectID) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thash.Write([]byte(secondId.Hex()))\n\tmacHash := fmt.Sprintf(\"%x\", hash.Sum(nil))\n\tmacHash = macHash[:10]\n\tmacBuf := bytes.Buffer{}\n\n\tfor i, run := range macHash {\n\t\tmacBuf.WriteRune(run)\n\t\tif i%2 == 1 && i != len(macHash)-1 {\n\t\t\tmacBuf.WriteRune(':')\n\t\t}\n\t}\n\n\treturn \"00:\" + macBuf.String()\n}\n\nfunc GetMacAddrExternal(id bson.ObjectID,\n\tsecondId bson.ObjectID) string {\n\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thash.Write([]byte(secondId.Hex()))\n\tmacHash := fmt.Sprintf(\"%x\", hash.Sum(nil))\n\tmacHash = macHash[:10]\n\tmacBuf := bytes.Buffer{}\n\n\tfor i, run := range macHash {\n\t\tmacBuf.WriteRune(run)\n\t\tif i%2 == 1 && i != len(macHash)-1 {\n\t\t\tmacBuf.WriteRune(':')\n\t\t}\n\t}\n\n\treturn \"02:\" + macBuf.String()\n}\n\nfunc GetMacAddrExternal6(id bson.ObjectID,\n\tsecondId bson.ObjectID) string {\n\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thash.Write([]byte(secondId.Hex()))\n\tmacHash := fmt.Sprintf(\"%x\", hash.Sum(nil))\n\tmacHash = macHash[:10]\n\tmacBuf := bytes.Buffer{}\n\n\tfor i, run := range macHash {\n\t\tmacBuf.WriteRune(run)\n\t\tif i%2 == 1 && i != len(macHash)-1 {\n\t\t\tmacBuf.WriteRune(':')\n\t\t}\n\t}\n\n\treturn \"08:\" + macBuf.String()\n}\n\nfunc GetMacAddrInternal(id bson.ObjectID,\n\tsecondId bson.ObjectID) string {\n\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thash.Write([]byte(secondId.Hex()))\n\tmacHash := fmt.Sprintf(\"%x\", hash.Sum(nil))\n\tmacHash = macHash[:10]\n\tmacBuf := bytes.Buffer{}\n\n\tfor i, run := range macHash {\n\t\tmacBuf.WriteRune(run)\n\t\tif i%2 == 1 && i != len(macHash)-1 {\n\t\t\tmacBuf.WriteRune(':')\n\t\t}\n\t}\n\n\treturn \"04:\" + macBuf.String()\n}\n\nfunc GetMacAddrHost(id bson.ObjectID,\n\tsecondId bson.ObjectID) string {\n\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thash.Write([]byte(secondId.Hex()))\n\tmacHash := fmt.Sprintf(\"%x\", hash.Sum(nil))\n\tmacHash = macHash[:10]\n\tmacBuf := bytes.Buffer{}\n\n\tfor i, run := range macHash {\n\t\tmacBuf.WriteRune(run)\n\t\tif i%2 == 1 && i != len(macHash)-1 {\n\t\t\tmacBuf.WriteRune(':')\n\t\t}\n\t}\n\n\treturn \"06:\" + macBuf.String()\n}\n\nfunc GetMacAddrNodePort(id bson.ObjectID,\n\tsecondId bson.ObjectID) string {\n\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thash.Write([]byte(secondId.Hex()))\n\tmacHash := fmt.Sprintf(\"%x\", hash.Sum(nil))\n\tmacHash = macHash[:10]\n\tmacBuf := bytes.Buffer{}\n\n\tfor i, run := range macHash {\n\t\tmacBuf.WriteRune(run)\n\t\tif i%2 == 1 && i != len(macHash)-1 {\n\t\t\tmacBuf.WriteRune(':')\n\t\t}\n\t}\n\n\treturn \"0a:\" + macBuf.String()\n}\n\nfunc GetIface(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"p%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceVirt(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"v%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceExternal(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"e%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceNodeExternal(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"r%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceInternal(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"i%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceNodeInternal(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"j%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceHost(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"h%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceNodePort(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"m%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceCloud(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"o%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceCloudVirt(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"t%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetIfaceVlan(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"x%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetNamespace(id bson.ObjectID, n int) string {\n\thash := md5.New()\n\thash.Write([]byte(id.Hex()))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"n%s%d\", strings.ToLower(hashSum), n)\n}\n\nfunc GetHostVxlanIface(parentIface string) string {\n\thash := md5.New()\n\thash.Write([]byte(parentIface))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"k%s0\", strings.ToLower(hashSum))\n}\n\nfunc GetHostBridgeIface(parentIface string) string {\n\thash := md5.New()\n\thash.Write([]byte(parentIface))\n\thashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]\n\treturn fmt.Sprintf(\"b%s0\", strings.ToLower(hashSum))\n}\n"
  },
  {
    "path": "vm/vm.go",
    "content": "package vm\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/usb\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype VirtualMachine struct {\n\tId                  bson.ObjectID     `json:\"id\"`\n\tOrganization        bson.ObjectID     `json:\"organization\"`\n\tUnixId              int               `json:\"unix_id\"`\n\tState               string            `json:\"state\"`\n\tTimestamp           time.Time         `json:\"timestamp\"`\n\tQemuVersion         string            `json:\"qemu_version\"`\n\tDiskType            string            `json:\"disk_type\"`\n\tDiskPool            bson.ObjectID     `json:\"disk_pool\"`\n\tImage               bson.ObjectID     `json:\"image\"`\n\tProcessors          int               `json:\"processors\"`\n\tMemory              int               `json:\"memory\"`\n\tHugepages           bool              `json:\"hugepages\"`\n\tVnc                 bool              `json:\"vnc\"`\n\tVncDisplay          int               `json:\"vnc_display\"`\n\tSpice               bool              `json:\"spice\"`\n\tSpicePort           int               `json:\"spice_port\"`\n\tGui                 bool              `json:\"gui\"`\n\tDisks               []*Disk           `json:\"disks\"`\n\tDisksAvailable      bool              `json:\"-\"`\n\tNetworkAdapters     []*NetworkAdapter `json:\"network_adapters\"`\n\tCloudSubnet         string            `json:\"cloud_subnet\"`\n\tCloudVnic           string            `json:\"cloud_vnic\"`\n\tCloudVnicAttach     string            `json:\"cloud_vnic_attach\"`\n\tCloudPrivateIp      string            `json:\"cloud_private_ip\"`\n\tCloudPublicIp       string            `json:\"cloud_public_ip\"`\n\tCloudPublicIp6      string            `json:\"cloud_public_ip6\"`\n\tDhcpIp              string            `json:\"dhcp_ip\"`\n\tDhcpIp6             string            `json:\"dhcp_ip6\"`\n\tUefi                bool              `json:\"uefi\"`\n\tSecureBoot          bool              `json:\"secure_boot\"`\n\tTpm                 bool              `json:\"tpm\"`\n\tDhcpServer          bool              `json:\"dhcp_server\"`\n\tDeployment          bson.ObjectID     `json:\"deployment\"`\n\tCloudType           string            `json:\"cloud_type\"`\n\tSystemKind          string            `json:\"system_kind\"`\n\tNoPublicAddress     bool              `json:\"no_public_address\"`\n\tNoPublicAddress6    bool              `json:\"no_public_address6\"`\n\tNoHostAddress       bool              `json:\"no_host_address\"`\n\tIsos                []*Iso            `json:\"isos\"`\n\tUsbDevices          []*UsbDevice      `json:\"usb_devices\"`\n\tUsbDevicesAvailable bool              `json:\"-\"`\n\tPciDevices          []*PciDevice      `json:\"pci_devices\"`\n\tDriveDevices        []*DriveDevice    `json:\"drive_devices\"`\n\tIscsiDevices        []*IscsiDevice    `json:\"iscsi_devices\"`\n\tMounts              []*Mount          `json:\"mounts\"`\n\tImdsVersion         int               `json:\"imds_version\"`\n\tImdsClientSecret    string            `json:\"-\"`\n\tImdsDhcpSecret      string            `json:\"imds_dhcp_secret\"`\n\tImdsHostSecret      string            `json:\"imds_host_secret\"`\n}\n\nfunc (v *VirtualMachine) HasExternalNetwork() bool {\n\treturn v.Vnc || v.Spice || (v.IscsiDevices != nil &&\n\t\tlen(v.IscsiDevices) > 0)\n}\n\nfunc (v *VirtualMachine) ProtectHome() bool {\n\treturn !v.Gui\n}\n\nfunc (v *VirtualMachine) ProtectTmp() bool {\n\treturn !v.Gui\n}\n\nfunc (v *VirtualMachine) Running() bool {\n\treturn v.State == Starting || v.State == Running\n}\n\nfunc (v *VirtualMachine) GenerateImdsSecret() (err error) {\n\tv.ImdsVersion = 1\n\n\tv.ImdsClientSecret, err = utils.RandStr(32)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tv.ImdsDhcpSecret, err = utils.RandStr(32)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tv.ImdsHostSecret, err = utils.RandStr(32)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\ntype Disk struct {\n\tId    bson.ObjectID `json:\"id\"`\n\tIndex int           `json:\"index\"`\n\tPath  string        `json:\"path\"`\n}\n\ntype Iso struct {\n\tName string `json:\"name\"`\n}\n\ntype UsbDevice struct {\n\tId      string `json:\"id\"`\n\tVendor  string `json:\"vendor\"`\n\tProduct string `json:\"product\"`\n\tBus     string `json:\"bus\"`\n\tAddress string `json:\"address\"`\n}\n\nfunc (u *UsbDevice) Key() string {\n\treturn fmt.Sprintf(\"%s_%s_%s_%s\",\n\t\tu.Bus,\n\t\tu.Address,\n\t\tu.Vendor,\n\t\tu.Product,\n\t)\n}\n\nfunc (u *UsbDevice) Copy() (device *UsbDevice) {\n\tdevice = &UsbDevice{\n\t\tId:      u.Id,\n\t\tVendor:  u.Vendor,\n\t\tProduct: u.Product,\n\t\tBus:     u.Bus,\n\t\tAddress: u.Address,\n\t}\n\n\treturn\n}\n\nfunc (u *UsbDevice) GetQemuId() string {\n\treturn fmt.Sprintf(\"usb_%s_%s_%s_%s_%d\",\n\t\tu.Bus,\n\t\tu.Address,\n\t\tu.Vendor,\n\t\tu.Product,\n\t\tutils.RandInt(1111, 9999),\n\t)\n}\n\nfunc (u *UsbDevice) GetDevice() (device *usb.Device, err error) {\n\tdevice, err = usb.GetDevice(u.Bus, u.Address, u.Vendor, u.Product)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\ntype PciDevice struct {\n\tSlot string `json:\"slot\"`\n}\n\ntype DriveDevice struct {\n\tId     string `json:\"id\"`\n\tType   string `json:\"type\"`\n\tVgName string `json:\"vg_name\"`\n\tLvName string `json:\"lv_name\"`\n}\n\ntype IscsiDevice struct {\n\tUri string `json:\"iscsi\"`\n}\n\ntype Mount struct {\n\tName     string `json:\"name\"`\n\tType     string `json:\"type\"`\n\tPath     string `json:\"path\"`\n\tHostPath string `json:\"host_path\"`\n}\n\nfunc (d *Disk) GetId() bson.ObjectID {\n\tidStr := strings.Split(path.Base(d.Path), \".\")[0]\n\n\tobjId, err := bson.ObjectIDFromHex(idStr)\n\tif err != nil {\n\t\treturn bson.NilObjectID\n\t}\n\treturn objId\n}\n\nfunc (d *Disk) Copy() (dsk *Disk) {\n\tdsk = &Disk{\n\t\tId:    d.Id,\n\t\tIndex: d.Index,\n\t\tPath:  d.Path,\n\t}\n\n\treturn\n}\n\ntype NetworkAdapter struct {\n\tType       string        `json:\"type\"`\n\tMacAddress string        `json:\"mac_address\"`\n\tVpc        bson.ObjectID `json:\"vpc\"`\n\tSubnet     bson.ObjectID `json:\"subnet\"`\n\tIpAddress  string        `json:\"ip_address,omitempty\"`\n\tIpAddress6 string        `json:\"ip_address6,omitempty\"`\n}\n\nfunc (v *VirtualMachine) Commit(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\n\taddrs := []string{}\n\taddrs6 := []string{}\n\n\tfor _, adapter := range v.NetworkAdapters {\n\t\tif adapter.IpAddress != \"\" {\n\t\t\taddrs = append(addrs, adapter.IpAddress)\n\t\t}\n\t\tif adapter.IpAddress6 != \"\" {\n\t\t\taddrs6 = append(addrs6, adapter.IpAddress6)\n\t\t}\n\t}\n\n\tdata := bson.M{\n\t\t\"state\":      v.State,\n\t\t\"timestamp\":  v.Timestamp,\n\t\t\"public_ips\": addrs,\n\t}\n\n\tif v.State == Running || len(addrs6) > 0 {\n\t\tdata[\"public_ips6\"] = addrs6\n\t}\n\n\tif v.QemuVersion != \"\" {\n\t\tdata[\"qemu_version\"] = v.QemuVersion\n\t}\n\n\terr = coll.UpdateId(v.Id, &bson.M{\n\t\t\"$set\": data,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif !v.Deployment.IsZero() {\n\t\tcoll = db.Deployments()\n\n\t\tfields := bson.M{\n\t\t\t\"instance_data.public_ips\": addrs,\n\t\t}\n\n\t\tif v.State == Running || len(addrs6) > 0 {\n\t\t\tfields[\"instance_data.public_ips6\"] = addrs6\n\t\t}\n\n\t\terr = coll.UpdateId(v.Deployment, &bson.M{\n\t\t\t\"$set\": fields,\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (v *VirtualMachine) CommitCloudVnic(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\n\terr = coll.UpdateId(v.Id, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"cloud_vnic\":        v.CloudVnic,\n\t\t\t\"cloud_vnic_attach\": v.CloudVnicAttach,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (v *VirtualMachine) CommitCloudIps(db *database.Database) (err error) {\n\tcoll := db.Instances()\n\n\tcloudPivateAddrs := []string{}\n\tif v.CloudPrivateIp != \"\" {\n\t\tcloudPivateAddrs = append(cloudPivateAddrs, v.CloudPrivateIp)\n\t}\n\n\tcloudPublicAddrs := []string{}\n\tif v.CloudPublicIp != \"\" {\n\t\tcloudPublicAddrs = append(cloudPublicAddrs, v.CloudPublicIp)\n\t}\n\n\tcloudPublicAddrs6 := []string{}\n\tif v.CloudPublicIp6 != \"\" {\n\t\tcloudPublicAddrs6 = append(cloudPublicAddrs6, v.CloudPublicIp6)\n\t}\n\n\terr = coll.UpdateId(v.Id, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"cloud_private_ips\": cloudPivateAddrs,\n\t\t\t\"cloud_public_ips\":  cloudPublicAddrs,\n\t\t\t\"cloud_public_ips6\": cloudPublicAddrs6,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tif !v.Deployment.IsZero() {\n\t\tcoll = db.Deployments()\n\n\t\terr = coll.UpdateId(v.Deployment, &bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"instance_data.cloud_private_ips\": cloudPivateAddrs,\n\t\t\t\t\"instance_data.cloud_public_ips\":  cloudPublicAddrs,\n\t\t\t\t\"instance_data.cloud_public_ips6\": cloudPublicAddrs6,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (v *VirtualMachine) CommitState(db *database.Database, action string) (\n\terr error) {\n\n\tcoll := db.Instances()\n\n\taddrs := []string{}\n\taddrs6 := []string{}\n\n\tfor _, adapter := range v.NetworkAdapters {\n\t\tif adapter.IpAddress != \"\" {\n\t\t\taddrs = append(addrs, adapter.IpAddress)\n\t\t}\n\t\tif adapter.IpAddress6 != \"\" {\n\t\t\taddrs6 = append(addrs6, adapter.IpAddress6)\n\t\t}\n\t}\n\n\tdata := bson.M{\n\t\t\"action\":      action,\n\t\t\"state\":       v.State,\n\t\t\"timestamp\":   v.Timestamp,\n\t\t\"public_ips\":  addrs,\n\t\t\"public_ips6\": addrs6,\n\t}\n\n\tif v.QemuVersion != \"\" {\n\t\tdata[\"qemu_version\"] = v.QemuVersion\n\t}\n\n\terr = coll.UpdateId(v.Id, &bson.M{\n\t\t\"$set\": data,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "vmdk/utils.go",
    "content": "package vmdk\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/google/uuid\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n)\n\nfunc SetRandUuid(diskPath string) (err error) {\n\tdiskUuid := uuid.New()\n\n\tdiskFile, err := os.OpenFile(diskPath, os.O_RDWR, 0)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to open file\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr = diskFile.Close()\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(err, \"vmdk: Failed to write file\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\tbuffer := make([]byte, 10000)\n\tnRead, err := diskFile.Read(buffer)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to read file\"),\n\t\t}\n\t\treturn\n\t}\n\n\ti := bytes.Index(buffer, []byte(\"ddb.uuid.image=\"))\n\n\tnewBuffer := append(buffer[:i+16], []byte(diskUuid.String())...)\n\tnewBuffer = append(newBuffer, buffer[i+52:]...)\n\n\tnWrite, err := diskFile.WriteAt(newBuffer, 0)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to write file\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif nRead != nWrite {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.New(\"vmdk: Write count mismatch\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = diskFile.Sync()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to sync file\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc SetUuid(diskPath string, diskUuid string) (err error) {\n\tdiskFile, err := os.OpenFile(diskPath, os.O_RDWR, 0)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to open file\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr = diskFile.Close()\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(err, \"vmdk: Failed to write file\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\tbuffer := make([]byte, 10000)\n\tnRead, err := diskFile.Read(buffer)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to read file\"),\n\t\t}\n\t\treturn\n\t}\n\n\ti := bytes.Index(buffer, []byte(\"ddb.uuid.image=\"))\n\n\tnewBuffer := append(buffer[:i+16], []byte(diskUuid)...)\n\tnewBuffer = append(newBuffer, buffer[i+52:]...)\n\n\tnWrite, err := diskFile.WriteAt(newBuffer, 0)\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to write file\"),\n\t\t}\n\t\treturn\n\t}\n\n\tif nRead != nWrite {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.New(\"vmdk: Write count mismatch\"),\n\t\t}\n\t\treturn\n\t}\n\n\terr = diskFile.Sync()\n\tif err != nil {\n\t\terr = &errortypes.WriteError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to sync file\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetUuid(diskPath string) (diskUuid string, err error) {\n\tdiskFile, err := os.Open(diskPath)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to open file\"),\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr = diskFile.Close()\n\t\tif err != nil {\n\t\t\terr = &errortypes.WriteError{\n\t\t\t\terrors.Wrap(err, \"vmdk: Failed to write file\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}()\n\n\tbuffer := make([]byte, 10000)\n\t_, err = diskFile.Read(buffer)\n\tif err != nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.Wrap(err, \"vmdk: Failed to read file\"),\n\t\t}\n\t\treturn\n\t}\n\n\ti := bytes.Index(buffer, []byte(\"ddb.uuid.image=\"))\n\n\tdiskUuid = string(buffer[i+16 : i+52])\n\n\treturn\n}\n"
  },
  {
    "path": "vpc/constants.go",
    "content": "package vpc\n\nconst (\n\tDestination = \"destination\"\n)\n"
  },
  {
    "path": "vpc/ip.go",
    "content": "package vpc\n\nimport (\n\t\"net\"\n\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype VpcIp struct {\n\tId       bson.ObjectID `bson:\"_id,omitempty\"`\n\tVpc      bson.ObjectID `bson:\"vpc\"`\n\tSubnet   bson.ObjectID `bson:\"subnet\"`\n\tIp       int64         `bson:\"ip\"`\n\tInstance bson.ObjectID `bson:\"instance\"`\n}\n\nfunc (i *VpcIp) GetIp() net.IP {\n\treturn utils.Int2IpAddress(i.Ip * 2)\n}\n\nfunc (i *VpcIp) GetIps() (net.IP, net.IP) {\n\treturn utils.IpIndex2Ip(i.Ip)\n}\n"
  },
  {
    "path": "vpc/subnet.go",
    "content": "package vpc\n\nimport (\n\t\"net\"\n\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Subnet struct {\n\tId      bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName    string        `bson:\"name\" json:\"name\"`\n\tNetwork string        `bson:\"network\" json:\"network\"`\n}\n\nfunc (s *Subnet) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\ts.Name = utils.FilterName(s.Name)\n\n\treturn\n}\n\nfunc (s *Subnet) GetNetwork() (network *net.IPNet, err error) {\n\t_, network, err = net.ParseCIDR(s.Network)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"vpc: Failed to parse subnet\"),\n\t\t}\n\t\treturn\n\t}\n\treturn\n}\n\nfunc (s *Subnet) GetIndexRange() (start, stop int64, err error) {\n\tnetwork, err := s.GetNetwork()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstart, err = utils.GetFirstIpIndex(network)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstop, err = utils.GetLastIpIndex(network)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "vpc/utils.go",
    "content": "package vpc\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"net\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc GetIp6(vpcId, instId bson.ObjectID) net.IP {\n\tnetHash := md5.New()\n\tnetHash.Write(vpcId[:])\n\tnetHashSum := fmt.Sprintf(\"%x\", netHash.Sum(nil))[:12]\n\n\tinstHash := md5.New()\n\tinstHash.Write(instId[:])\n\tinstHashSum := \"0\" + fmt.Sprintf(\"%x\", instHash.Sum(nil))[:15]\n\n\tip := fmt.Sprintf(\"fd97%s%s\", netHashSum, instHashSum)\n\tipBuf := bytes.Buffer{}\n\n\tfor i, run := range ip {\n\t\tif i%4 == 0 && i != 0 && i != len(ip)-1 {\n\t\t\tipBuf.WriteRune(':')\n\t\t}\n\t\tipBuf.WriteRune(run)\n\t}\n\n\treturn net.ParseIP(ipBuf.String())\n}\n\nfunc GetGatewayIp6(vpcId, instId bson.ObjectID) net.IP {\n\tnetHash := md5.New()\n\tnetHash.Write(vpcId[:])\n\tnetHashSum := fmt.Sprintf(\"%x\", netHash.Sum(nil))[:12]\n\n\tinstHash := md5.New()\n\tinstHash.Write([]byte(\"gateway\"))\n\tinstHash.Write(instId[:])\n\tinstHashSum := \"0\" + fmt.Sprintf(\"%x\", instHash.Sum(nil))[:15]\n\n\tip := fmt.Sprintf(\"fd97%s%s\", netHashSum, instHashSum)\n\tipBuf := bytes.Buffer{}\n\n\tfor i, run := range ip {\n\t\tif i%4 == 0 && i != 0 && i != len(ip)-1 {\n\t\t\tipBuf.WriteRune(':')\n\t\t}\n\t\tipBuf.WriteRune(run)\n\t}\n\n\treturn net.ParseIP(ipBuf.String())\n}\n\nfunc GetLinkIp6(vpcId, instId bson.ObjectID) net.IP {\n\tnetHash := md5.New()\n\tnetHash.Write(vpcId[:])\n\tnetHashSum := fmt.Sprintf(\"%x\", netHash.Sum(nil))[:12]\n\n\tinstHash := md5.New()\n\tinstHash.Write(instId[:])\n\tinstHashSum := \"0\" + fmt.Sprintf(\"%x\", instHash.Sum(nil))[:15]\n\n\tip := fmt.Sprintf(\"fd97%s%s\", netHashSum, instHashSum)\n\tipBuf := bytes.Buffer{}\n\n\tfor i, run := range ip {\n\t\tif i%4 == 0 && i != 0 && i != len(ip)-1 {\n\t\t\tipBuf.WriteRune(':')\n\t\t}\n\t\tipBuf.WriteRune(run)\n\t}\n\n\treturn net.ParseIP(ipBuf.String())\n}\n\nfunc GetGatewayLinkIp6(vpcId, instId bson.ObjectID) net.IP {\n\tnetHash := md5.New()\n\tnetHash.Write(vpcId[:])\n\tnetHashSum := fmt.Sprintf(\"%x\", netHash.Sum(nil))[:12]\n\n\tinstHash := md5.New()\n\tinstHash.Write([]byte(\"gateway\"))\n\tinstHash.Write(instId[:])\n\tinstHashSum := \"0\" + fmt.Sprintf(\"%x\", instHash.Sum(nil))[:15]\n\n\tip := fmt.Sprintf(\"fd97%s%s\", netHashSum, instHashSum)\n\tipBuf := bytes.Buffer{}\n\n\tfor i, run := range ip {\n\t\tif i%4 == 0 && i != 0 && i != len(ip)-1 {\n\t\t\tipBuf.WriteRune(':')\n\t\t}\n\t\tipBuf.WriteRune(run)\n\t}\n\n\treturn net.ParseIP(ipBuf.String())\n}\n\nfunc Get(db *database.Database, vcId bson.ObjectID) (\n\tvc *Vpc, err error) {\n\n\tcoll := db.Vpcs()\n\tvc = &Vpc{}\n\n\terr = coll.FindOneId(vcId, vc)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOrg(db *database.Database, orgId, vcId bson.ObjectID) (\n\tvc *Vpc, err error) {\n\n\tcoll := db.Vpcs()\n\tvc = &Vpc{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"_id\":          vcId,\n\t\t\"organization\": orgId,\n\t}).Decode(vc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ExistsOrg(db *database.Database, orgId, vcId bson.ObjectID) (\n\texists bool, err error) {\n\n\tcoll := db.Vpcs()\n\tn, err := coll.CountDocuments(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\":          vcId,\n\t\t\t\"organization\": orgId,\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif n > 0 {\n\t\texists = true\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database, query *bson.M) (\n\tvcs []*Vpc, err error) {\n\n\tcoll := db.Vpcs()\n\tvcs = []*Vpc{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tvc := &Vpc{}\n\t\terr = cursor.Decode(vc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvcs = append(vcs, vc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (vc *Vpc, err error) {\n\tcoll := db.Vpcs()\n\tvc = &Vpc{}\n\n\terr = coll.FindOne(db, query).Decode(vc)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNames(db *database.Database, query *bson.M) (\n\tvpcs []*Vpc, err error) {\n\n\tcoll := db.Vpcs()\n\tvpcs = []*Vpc{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetProjection(bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"organization\", 1},\n\t\t\t\t{\"datacenter\", 1},\n\t\t\t\t{\"type\", 1},\n\t\t\t\t{\"subnets\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tvc := &Vpc{}\n\t\terr = cursor.Decode(vc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvpcs = append(vpcs, vc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (vcs []*Vpc, count int64, err error) {\n\n\tcoll := db.Vpcs()\n\tvcs = []*Vpc{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(bson.D{{\"name\", 1}}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tvc := &Vpc{}\n\t\terr = cursor.Decode(vc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvcs = append(vcs, vc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetIds(db *database.Database, ids []bson.ObjectID) (\n\tvcs []*Vpc, err error) {\n\n\tcoll := db.Vpcs()\n\tvcs = []*Vpc{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": ids,\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tvc := &Vpc{}\n\t\terr = cursor.Decode(vc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvcs = append(vcs, vc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetDatacenter(db *database.Database, dcId bson.ObjectID) (\n\tvcs []*Vpc, err error) {\n\n\tcoll := db.Vpcs()\n\tvcs = []*Vpc{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"datacenter\": dcId,\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tvc := &Vpc{}\n\t\terr = cursor.Decode(vc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvcs = append(vcs, vc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc DistinctIds(db *database.Database, matchIds []bson.ObjectID) (\n\tidsSet set.Set, err error) {\n\n\tcoll := db.Images()\n\tids := []bson.ObjectID{}\n\tidsSet = set.NewSet()\n\n\terr = coll.Distinct(\n\t\tdb,\n\t\t\"_id\",\n\t\t&bson.M{\n\t\t\t\"_id\": &bson.M{\n\t\t\t\t\"$in\": matchIds,\n\t\t\t},\n\t\t},\n\t).Decode(&ids)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tfor _, id := range ids {\n\t\tidsSet.Add(id)\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, vcId bson.ObjectID) (err error) {\n\tcoll := db.VpcsIp()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"vpc\": vcId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Vpcs()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": vcId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveOrg(db *database.Database, orgId, vcId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.VpcsIp()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"vpc\": vcId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Vpcs()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"organization\": orgId,\n\t\t\"_id\":          vcId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, vcIds []bson.ObjectID) (err error) {\n\tcoll := db.VpcsIp()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"vpc\": &bson.M{\n\t\t\t\"$in\": vcIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Vpcs()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": vcIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,\n\tvcIds []bson.ObjectID) (err error) {\n\n\tcoll := db.VpcsIp()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"vpc\": &bson.M{\n\t\t\t\"$in\": vcIds,\n\t\t},\n\t\t\"organization\": orgId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tcoll = db.Vpcs()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": vcIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetIpsMapped(db *database.Database, ids []bson.ObjectID) (\n\tvpcsMap map[bson.ObjectID][]*VpcIp, err error) {\n\n\tcoll := db.VpcsIp()\n\tvpcsMap = map[bson.ObjectID][]*VpcIp{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"vpc\": &bson.M{\n\t\t\t\t\"$in\": ids,\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tvc := &VpcIp{}\n\t\terr = cursor.Decode(vc)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tvpcsMap[vc.Vpc] = append(vpcsMap[vc.Vpc], vc)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc RemoveInstanceIps(db *database.Database, instId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.VpcsIp()\n\n\t_, err = coll.UpdateMany(db, &bson.M{\n\t\t\"instance\": instId,\n\t}, &bson.M{\n\t\t\"$set\": &bson.M{\n\t\t\t\"instance\": nil,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveInstanceIp(db *database.Database, instId,\n\tvpcId bson.ObjectID) (err error) {\n\n\tcoll := db.VpcsIp()\n\n\t_, err = coll.UpdateOne(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"vpc\":      vpcId,\n\t\t\t\"instance\": instId,\n\t\t},\n\t\t&bson.M{\n\t\t\t\"$set\": &bson.M{\n\t\t\t\t\"instance\": nil,\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "vpc/vpc.go",
    "content": "package vpc\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"math/rand/v2\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/requires\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Route struct {\n\tDestination string `bson:\"destination\" json:\"destination\"`\n\tTarget      string `bson:\"target\" json:\"target\"`\n}\n\ntype Map struct {\n\tType        string `bson:\"type\" json:\"type\"`\n\tDestination string `bson:\"destination\" json:\"destination\"`\n\tTarget      string `bson:\"target\" json:\"target\"`\n}\n\ntype Arp struct {\n\tIp  string `bson:\"ip\" json:\"ip\"`\n\tMac string `bson:\"mac\" json:\"mac\"`\n}\n\ntype Vpc struct {\n\tId               bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName             string        `bson:\"name\" json:\"name\"`\n\tComment          string        `bson:\"comment\" json:\"comment\"`\n\tVpcId            int           `bson:\"vpc_id\" json:\"vpc_id\"`\n\tNetwork          string        `bson:\"network\" json:\"network\"`\n\tNetwork6         string        `bson:\"-\" json:\"network6\"`\n\tSubnets          []*Subnet     `bson:\"subnets\" json:\"subnets\"`\n\tOrganization     bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tDatacenter       bson.ObjectID `bson:\"datacenter\" json:\"datacenter\"`\n\tIcmpRedirects    bool          `bson:\"icmp_redirects\" json:\"icmp_redirects\"`\n\tRoutes           []*Route      `bson:\"routes\" json:\"routes\"`\n\tMaps             []*Map        `bson:\"maps\" json:\"maps\"`\n\tArps             []*Arp        `bson:\"arps\" json:\"arps\"`\n\tDeleteProtection bool          `bson:\"delete_protection\" json:\"delete_protection\"`\n\tcurSubnets       []*Subnet     `bson:\"-\" json:\"-\"`\n}\n\ntype Completion struct {\n\tId           bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tName         string        `bson:\"name\" json:\"name\"`\n\tOrganization bson.ObjectID `bson:\"organization\" json:\"organization\"`\n\tVpcId        int           `bson:\"vpc_id\" json:\"vpc_id\"`\n\tNetwork      string        `bson:\"network\" json:\"network\"`\n\tSubnets      []*Subnet     `bson:\"subnets\" json:\"subnets\"`\n\tDatacenter   bson.ObjectID `bson:\"datacenter\" json:\"datacenter\"`\n}\n\nfunc (v *Vpc) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tv.Name = utils.FilterName(v.Name)\n\n\tif v.Organization.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"organization_required\",\n\t\t\tMessage: \"Missing required organization\",\n\t\t}\n\t\treturn\n\t}\n\n\tif v.Datacenter.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"datacenter_required\",\n\t\t\tMessage: \"Missing required datacenter\",\n\t\t}\n\t\treturn\n\t}\n\n\tnetwork, e := v.GetNetwork()\n\tif e != nil {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"network_invalid\",\n\t\t\tMessage: \"Network address invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tnetwork6, e := v.GetNetwork6()\n\tif e != nil {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"network_invalid6\",\n\t\t\tMessage: \"IPv6 network address invalid\",\n\t\t}\n\t\treturn\n\t}\n\n\tv.Network = network.String()\n\n\tif v.Subnets == nil {\n\t\tv.Subnets = []*Subnet{}\n\t}\n\n\tsubnetRanges := []struct {\n\t\tId    bson.ObjectID\n\t\tStart int64\n\t\tStop  int64\n\t}{}\n\tsubs := []*Subnet{}\n\tfor _, sub := range v.Subnets {\n\t\terrData, err = sub.Validate(db)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif errData != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif sub.Network == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tsubNetwork, e := sub.GetNetwork()\n\t\tif e != nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"subnet_network_invalid\",\n\t\t\t\tMessage: \"Subnet network address invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tcidr, _ := subNetwork.Mask.Size()\n\t\tif cidr < 8 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"subnet_network_size_invalid\",\n\t\t\t\tMessage: \"Subnet network size too big\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif cidr > 28 {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"subnet_network_size_invalid\",\n\t\t\t\tMessage: \"Subnet network size too small\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tsub.Network = subNetwork.String()\n\n\t\tif !utils.NetworkContains(network, subNetwork) {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"subnet_network_range_invalid\",\n\t\t\t\tMessage: \"Subnet network outside of VPC network\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tsubStart, subStop, e := sub.GetIndexRange()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tsubnetRanges = append(subnetRanges, struct {\n\t\t\tId    bson.ObjectID\n\t\t\tStart int64\n\t\t\tStop  int64\n\t\t}{\n\t\t\tId:    sub.Id,\n\t\t\tStart: subStart,\n\t\t\tStop:  subStop,\n\t\t})\n\n\t\tsubs = append(subs, sub)\n\t}\n\tv.Subnets = subs\n\n\tfor _, sub := range v.Subnets {\n\t\tsubStart, subStop, e := sub.GetIndexRange()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tfor _, s := range subnetRanges {\n\t\t\tif s.Id == sub.Id {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (subStart >= s.Start && subStart <= s.Stop) ||\n\t\t\t\t(subStop >= s.Start && subStop <= s.Stop) {\n\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"subnet_network_range_overlap\",\n\t\t\t\t\tMessage: \"VPC cannot have overlapping subnets\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif v.Routes == nil {\n\t\tv.Routes = []*Route{}\n\t}\n\n\tdestinations := set.NewSet()\n\tfor _, route := range v.Routes {\n\t\tif destinations.Contains(route.Destination) {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"route_duplicate_destination\",\n\t\t\t\tMessage: \"Duplicate mp destinations\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdestinations.Add(route.Destination)\n\n\t\tif strings.Contains(route.Destination, \":\") !=\n\t\t\tstrings.Contains(route.Target, \":\") {\n\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"route_target_destination_invalid\",\n\t\t\t\tMessage: \"Route target/destination invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t_, destination, e := net.ParseCIDR(route.Destination)\n\t\tif e != nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"route_destination_invalid\",\n\t\t\t\tMessage: \"Route destination invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\troute.Destination = destination.String()\n\n\t\tif route.Destination == \"0.0.0.0/0\" || route.Destination == \"::/0\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"route_destination_invalid\",\n\t\t\t\tMessage: \"Route destination invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\ttarget := net.ParseIP(route.Target)\n\t\tif target == nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"route_target_invalid\",\n\t\t\t\tMessage: \"Route target invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\troute.Target = target.String()\n\n\t\tif route.Target == \"0.0.0.0\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"route_target_invalid\",\n\t\t\t\tMessage: \"Route target invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif !strings.Contains(route.Target, \":\") {\n\t\t\tif !network.Contains(target) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"route_target_invalid_network\",\n\t\t\t\t\tMessage: \"Route target not in VPC network\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tif !network6.Contains(target) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"route_target_invalid_network6\",\n\t\t\t\t\tMessage: \"Route target not in VPC IPv6 network\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tmaps := []*Map{}\n\tdestinations = set.NewSet()\n\tfor _, mp := range v.Maps {\n\t\tif mp.Target == \"\" && mp.Destination == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif mp.Type == \"\" {\n\t\t\tmp.Type = Destination\n\t\t}\n\n\t\tif mp.Type != Destination {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"map_invalid_type\",\n\t\t\t\tMessage: \"Map type invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t_, destination, e := net.ParseCIDR(mp.Destination)\n\t\tif e != nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"map_destination_invalid\",\n\t\t\t\tMessage: \"Map destination invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tmp.Destination = destination.String()\n\n\t\ttarget := net.ParseIP(mp.Target)\n\t\tif target == nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"map_target_invalid\",\n\t\t\t\tMessage: \"Map target invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tmp.Target = target.String()\n\n\t\tif destinations.Contains(mp.Destination) {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"map_duplicate_destination\",\n\t\t\t\tMessage: \"Duplicate map destinations\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdestinations.Add(mp.Destination)\n\n\t\tif strings.Contains(mp.Destination, \":\") !=\n\t\t\tstrings.Contains(mp.Target, \":\") {\n\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"map_target_destination_invalid\",\n\t\t\t\tMessage: \"Map target/destination invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif mp.Destination == \"0.0.0.0/0\" || mp.Destination == \"::/0\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"map_destination_invalid\",\n\t\t\t\tMessage: \"Map destination invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif mp.Target == \"0.0.0.0\" {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"map_target_invalid\",\n\t\t\t\tMessage: \"Map target invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif !strings.Contains(mp.Target, \":\") {\n\t\t\tif !network.Contains(target) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"map_target_invalid_network\",\n\t\t\t\t\tMessage: \"Map target not in VPC network\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tif !network6.Contains(target) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"map_target_invalid_network6\",\n\t\t\t\t\tMessage: \"Map target not in VPC IPv6 network\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tmaps = append(maps, mp)\n\t}\n\tv.Maps = maps\n\n\tarps := []*Arp{}\n\tips := set.NewSet()\n\tfor _, ap := range v.Arps {\n\t\tif ap.Ip == \"\" && ap.Mac == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tarpIp := net.ParseIP(ap.Ip)\n\t\tif arpIp == nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"arp_ip_invalid\",\n\t\t\t\tMessage: \"Arp IP invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tap.Ip = arpIp.String()\n\n\t\tif ips.Contains(ap.Ip) {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"arp_duplicate_destination\",\n\t\t\t\tMessage: \"Duplicate arp destinations\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tips.Add(ap.Ip)\n\n\t\tarpMac, e := net.ParseMAC(ap.Mac)\n\t\tif e != nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"arp_mac_invalid\",\n\t\t\t\tMessage: \"Arp mac invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tap.Mac = arpMac.String()\n\n\t\tif !strings.Contains(ap.Ip, \":\") {\n\t\t\tif !network.Contains(arpIp) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"arp_ip_subnet_invalid\",\n\t\t\t\t\tMessage: \"ARP IP outside of VPC network\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tif !network6.Contains(arpIp) {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"arp_ip6_subnet_invalid\",\n\t\t\t\t\tMessage: \"ARP IP outside of VPC network\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tarps = append(arps, ap)\n\t}\n\tv.Arps = arps\n\n\treturn\n}\n\nfunc (v *Vpc) PreCommit() {\n\tif v.Subnets == nil {\n\t\tv.curSubnets = []*Subnet{}\n\t} else {\n\t\tv.curSubnets = v.Subnets\n\t}\n}\n\nfunc (v *Vpc) PostCommit(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tcurSubnets := map[bson.ObjectID]*Subnet{}\n\tfor _, sub := range v.curSubnets {\n\t\tcurSubnets[sub.Id] = sub\n\t}\n\n\tnewIds := set.NewSet()\n\tfor _, sub := range v.Subnets {\n\t\tnewIds.Add(sub.Id)\n\n\t\tcurSub := curSubnets[sub.Id]\n\t\tif !sub.Id.IsZero() && curSub != nil {\n\t\t\tif curSub.Network != sub.Network {\n\t\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\t\tError:   \"subnet_network_modified\",\n\t\t\t\t\tMessage: \"Cannot modify VPC subnet\",\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tsub.Id = bson.NewObjectID()\n\n\t\t\tfor _, s := range v.curSubnets {\n\t\t\t\tif s.Network == sub.Network {\n\t\t\t\t\tsub.Id = s.Id\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, sub := range v.curSubnets {\n\t\tif !newIds.Contains(sub.Id) {\n\t\t\terr = v.RemoveSubnet(db, sub.Id)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (v *Vpc) Json() {\n\tnetHash := md5.New()\n\tnetHash.Write(v.Id[:])\n\tnetHashSum := fmt.Sprintf(\"%x\", netHash.Sum(nil))[:12]\n\n\tip := fmt.Sprintf(\"fd97%s\", netHashSum)\n\tipBuf := bytes.Buffer{}\n\n\tfor i, run := range ip {\n\t\tif i%4 == 0 && i != 0 && i != len(ip)-1 {\n\t\t\tipBuf.WriteRune(':')\n\t\t}\n\t\tipBuf.WriteRune(run)\n\t}\n\n\tv.Network6 = ipBuf.String() + \"::/64\"\n}\n\nfunc (v *Vpc) GetSubnet(id bson.ObjectID) (sub *Subnet) {\n\tif v.Subnets == nil || id.IsZero() {\n\t\treturn\n\t}\n\n\tfor _, s := range v.Subnets {\n\t\tif s.Id == id {\n\t\t\tsub = s\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (v *Vpc) GetSubnetName(name string) (sub *Subnet) {\n\tif v.Subnets == nil || name == \"\" {\n\t\treturn\n\t}\n\n\tfor _, s := range v.Subnets {\n\t\tif s.Name == name {\n\t\t\tsub = s\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (v *Vpc) GetNetwork() (network *net.IPNet, err error) {\n\t_, network, err = net.ParseCIDR(v.Network)\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"vpc: Failed to parse network\"),\n\t\t}\n\t\treturn\n\t}\n\treturn\n}\n\nfunc (v *Vpc) GetNetwork6() (network *net.IPNet, err error) {\n\tnetHash := md5.New()\n\tnetHash.Write(v.Id[:])\n\tnetHashSum := fmt.Sprintf(\"%x\", netHash.Sum(nil))[:12]\n\n\tip := fmt.Sprintf(\"fd97%s\", netHashSum)\n\tipBuf := bytes.Buffer{}\n\n\tfor i, run := range ip {\n\t\tif i%4 == 0 && i != 0 && i != len(ip)-1 {\n\t\t\tipBuf.WriteRune(':')\n\t\t}\n\t\tipBuf.WriteRune(run)\n\t}\n\n\t_, network, err = net.ParseCIDR(ipBuf.String() + \"::/64\")\n\tif err != nil {\n\t\terr = &errortypes.ParseError{\n\t\t\terrors.Wrap(err, \"vpc: Failed to parse network\"),\n\t\t}\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (v *Vpc) InitVpc() {\n\tif v.Subnets != nil {\n\t\tfor _, sub := range v.Subnets {\n\t\t\tsub.Id = bson.NewObjectID()\n\t\t}\n\t}\n}\n\nfunc (v *Vpc) GetGateway() (ip net.IP, err error) {\n\tnetwork, err := v.GetNetwork()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tip = network.IP\n\tutils.IncIpAddress(ip)\n\n\treturn\n}\n\nfunc (v *Vpc) GetGateway6() (ip net.IP, err error) {\n\tnetwork, err := v.GetNetwork6()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tip = network.IP\n\tutils.IncIpAddress(ip)\n\n\treturn\n}\n\nfunc (v *Vpc) GetIp(db *database.Database,\n\tsubId, instId bson.ObjectID) (instIp, gateIp net.IP, err error) {\n\n\tsubnet := v.GetSubnet(subId)\n\tif subnet == nil {\n\t\terr = &errortypes.ReadError{\n\t\t\terrors.New(\"vpc: Subnet does not exist\"),\n\t\t}\n\t\treturn\n\t}\n\n\tcoll := db.VpcsIp()\n\tvpcIp := &VpcIp{}\n\n\terr = coll.FindOne(db, &bson.M{\n\t\t\"vpc\":      v.Id,\n\t\t\"instance\": instId,\n\t}).Decode(vpcIp)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tvpcIp = nil\n\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\terr = nil\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif vpcIp == nil {\n\t\tvpcIp = &VpcIp{}\n\t\topts := options.FindOneAndUpdate().\n\t\t\tSetReturnDocument(options.After)\n\n\t\terr = coll.FindOneAndUpdate(\n\t\t\tdb,\n\t\t\t&bson.M{\n\t\t\t\t\"vpc\":      v.Id,\n\t\t\t\t\"subnet\":   subId,\n\t\t\t\t\"instance\": nil,\n\t\t\t},\n\t\t\t&bson.M{\n\t\t\t\t\"$set\": &bson.M{\n\t\t\t\t\t\"instance\": instId,\n\t\t\t\t},\n\t\t\t},\n\t\t\topts,\n\t\t).Decode(vpcIp)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\tvpcIp = nil\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif vpcIp == nil {\n\t\tvpcIp = &VpcIp{}\n\n\t\terr = coll.FindOne(\n\t\t\tdb,\n\t\t\t&bson.M{\n\t\t\t\t\"vpc\":    v.Id,\n\t\t\t\t\"subnet\": subId,\n\t\t\t},\n\t\t\toptions.FindOne().\n\t\t\t\tSetSort(bson.D{{\"ip\", -1}}),\n\t\t).Decode(vpcIp)\n\t\tif err != nil {\n\t\t\tvpcIp = nil\n\t\t\terr = database.ParseError(err)\n\t\t\tif _, ok := err.(*database.NotFoundError); ok {\n\t\t\t\terr = nil\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tstart, stop, e := subnet.GetIndexRange()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tcurIp := start\n\t\tif vpcIp != nil {\n\t\t\tstart = vpcIp.Ip + 1\n\t\t}\n\n\t\tfor {\n\t\t\tif curIp > stop {\n\t\t\t\terr = &errortypes.NotFoundError{\n\t\t\t\t\terrors.New(\"vpc: Address pool full\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvpcIp = &VpcIp{\n\t\t\t\tVpc:      v.Id,\n\t\t\t\tSubnet:   subId,\n\t\t\t\tIp:       curIp,\n\t\t\t\tInstance: instId,\n\t\t\t}\n\n\t\t\t_, err = coll.InsertOne(db, vpcIp)\n\t\t\tif err != nil {\n\t\t\t\tvpcIp = nil\n\t\t\t\terr = database.ParseError(err)\n\t\t\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\t\t\terr = nil\n\t\t\t\t\tcurIp += 1\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\tinstIp, gateIp = vpcIp.GetIps()\n\n\tgateIp, err = v.GetGateway()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (v *Vpc) GetIp6(instId bson.ObjectID) net.IP {\n\treturn GetIp6(v.Id, instId)\n}\n\nfunc (v *Vpc) GetLinkIp6(instId bson.ObjectID) net.IP {\n\treturn GetLinkIp6(v.Id, instId)\n}\n\nfunc (v *Vpc) GetGatewayIp6(instId bson.ObjectID) net.IP {\n\treturn GetGatewayIp6(v.Id, instId)\n}\n\nfunc (v *Vpc) GetGatewayLinkIp6(instId bson.ObjectID) net.IP {\n\treturn GetGatewayLinkIp6(v.Id, instId)\n}\n\nfunc (v *Vpc) RemoveSubnet(db *database.Database, subId bson.ObjectID) (\n\terr error) {\n\n\tcoll := db.VpcsIp()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"vpc\":    v.Id,\n\t\t\"subnet\": subId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (v *Vpc) Commit(db *database.Database) (err error) {\n\tcoll := db.Vpcs()\n\n\terr = coll.Commit(v.Id, v)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (v *Vpc) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Vpcs()\n\n\terr = coll.CommitFields(v.Id, v, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (v *Vpc) Insert(db *database.Database) (err error) {\n\tcoll := db.Vpcs()\n\n\tif !v.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"vpc: Vpc already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\tvpcIds := []int{}\n\tparts := strings.Split(settings.Hypervisor.VlanRanges, \",\")\n\n\tfor _, part := range parts {\n\t\tpart = strings.TrimSpace(part)\n\t\tif part == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tbounds := strings.Split(part, \"-\")\n\t\tif len(bounds) != 2 {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"vpc: Invalid vlan range format\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tstart, e := strconv.Atoi(strings.TrimSpace(bounds[0]))\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"vpc: Invalid start vlan\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tend, e := strconv.Atoi(strings.TrimSpace(bounds[1]))\n\t\tif e != nil {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.Wrap(e, \"vpc: Invalid end vlan\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif start >= end {\n\t\t\terr = &errortypes.ParseError{\n\t\t\t\terrors.New(\"vpc: Start vlan larger than end vlan\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfor i := start; i <= end; i++ {\n\t\t\tvpcIds = append(vpcIds, i)\n\t\t}\n\t}\n\n\trand.Shuffle(len(vpcIds), func(i, j int) {\n\t\tvpcIds[i], vpcIds[j] = vpcIds[j], vpcIds[i]\n\t})\n\n\tfor _, vpcId := range vpcIds {\n\t\tv.VpcId = vpcId\n\n\t\tresp, e := coll.InsertOne(db, v)\n\t\tif e != nil {\n\t\t\terr = database.ParseError(e)\n\t\t\tif _, ok := err.(*database.DuplicateKeyError); ok {\n\t\t\t\terr = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tv.Id = resp.InsertedID.(bson.ObjectID)\n\t\treturn\n\t}\n\n\terr = &errortypes.DatabaseError{\n\t\terrors.New(\"vpc: No available vlan IDs\"),\n\t}\n\treturn\n}\n\nfunc init() {\n\tmodule := requires.New(\"vpc\")\n\tmodule.After(\"settings\")\n\n\tmodule.Handler = func() (err error) {\n\t\tdb := database.GetDatabase()\n\t\tdefer db.Close()\n\n\t\tcoll := db.VpcsIp()\n\n\t\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\t\"subnet\": nil,\n\t\t})\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "vxlan/vxlan.go",
    "content": "package vxlan\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/ip\"\n\t\"github.com/pritunl/pritunl-cloud/iproute\"\n\t\"github.com/pritunl/pritunl-cloud/node\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/state\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n\t\"github.com/pritunl/pritunl-cloud/vm\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tcurIfaces         set.Set\n\tcurDatabase       set.Set\n\tcurDatabaseIfaces set.Set\n)\n\nfunc initIfaces(stat *state.State, internaIfaces []string) (err error) {\n\tifaces := set.NewSet()\n\tnewCurIfaces := set.NewSet()\n\tfor _, iface := range stat.Interfaces() {\n\t\tifaces.Add(iface)\n\t\tif len(iface) == 14 && (strings.HasPrefix(iface, \"k\") ||\n\t\t\tstrings.HasPrefix(iface, \"b\")) {\n\n\t\t\tnewCurIfaces.Add(iface)\n\t\t}\n\t}\n\n\tparentVxIfaces := map[string]string{}\n\tparentBrIfaces := map[string]string{}\n\tnewIfaces := set.NewSet()\n\tfor _, iface := range internaIfaces {\n\t\tvxIface := vm.GetHostVxlanIface(iface)\n\t\tbrIface := vm.GetHostBridgeIface(iface)\n\n\t\tparentVxIfaces[vxIface] = iface\n\t\tparentBrIfaces[brIface] = iface\n\n\t\tnewIfaces.Add(vxIface)\n\t\tnewIfaces.Add(brIface)\n\t}\n\n\tremIfaces := newCurIfaces.Copy()\n\tremIfaces.Subtract(newIfaces)\n\tfor ifaceInf := range remIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"b\") {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"bridge\": iface,\n\t\t\t}).Info(\"vxlan: Removing bridge\")\n\n\t\t\t_, _ = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"Cannot find device\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"set\", \"dev\",\n\t\t\t\tiface, \"down\",\n\t\t\t)\n\t\t\t_ = iproute.BridgeDelete(\"\", iface)\n\t\t}\n\t}\n\tfor ifaceInf := range remIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"k\") {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"vxlan\": iface,\n\t\t\t}).Info(\"vxlan: Removing vxlan\")\n\n\t\t\t_, _ = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"Cannot find device\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"del\", iface,\n\t\t\t)\n\t\t}\n\t}\n\n\ttime.Sleep(200 * time.Millisecond)\n\n\tnewCurIfaces.Intersect(newIfaces)\n\tfor ifaceInf := range newCurIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"b\") {\n\t\t\tparentIface := parentBrIfaces[iface]\n\t\t\tif parentIface == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvxIface := vm.GetHostVxlanIface(parentIface)\n\t\t\tif ifaces.Contains(vxIface) {\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"does not exist\"},\n\t\t\t\t\t\"ip\", \"link\", \"set\",\n\t\t\t\t\tvxIface, \"master\", iface,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\ttime.Sleep(300 * time.Millisecond)\n\n\tfor ifaceInf := range newCurIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"b\") {\n\t\t\tparentIface := parentBrIfaces[iface]\n\t\t\tif parentIface == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"set\", \"dev\",\n\t\t\t\tiface, \"up\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\ttime.Sleep(500 * time.Millisecond)\n\n\tfor ifaceInf := range newCurIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"k\") {\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"set\", \"dev\",\n\t\t\t\tiface, \"up\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tip.ClearIfacesCache(\"\")\n\n\tcurIfaces = newCurIfaces\n\n\treturn\n}\n\nfunc initDatabase(stat *state.State, internaIfaces []string) (err error) {\n\toutput, err := utils.ExecOutput(\"\", \"bridge\", \"fdb\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnodeSelf := stat.Node()\n\n\tnodeDc := stat.NodeDatacenter()\n\tif nodeDc == nil {\n\t\treturn\n\t}\n\n\tnodes := stat.Nodes()\n\tif nodes == nil {\n\t\tnodes = []*node.Node{}\n\t}\n\n\tnewDb := set.NewSet()\n\tfor _, nde := range nodes {\n\t\tif nde.Id == nodeSelf.Id || nde.Datacenter != nodeDc.Id ||\n\t\t\tnde.PrivateIps == nil || !nodeDc.Vxlan() {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, privateIp := range nde.PrivateIps {\n\t\t\tnewDb.Add(privateIp)\n\t\t}\n\t}\n\n\tnewCurDb := set.NewSet()\n\tnewCurIfaces := set.NewSet()\n\tifaceBridgeDb := map[string]set.Set{}\n\n\tfor _, line := range strings.Split(output, \"\\n\") {\n\t\tfields := strings.Fields(line)\n\t\tif len(fields) != 7 || fields[0] != \"00:00:00:00:00:00\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tiface := fields[2]\n\t\tif len(iface) != 14 || !strings.HasPrefix(iface, \"k\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tdest := fields[4]\n\n\t\tbridgeSet := ifaceBridgeDb[iface]\n\t\tif bridgeSet == nil {\n\t\t\tbridgeSet = set.NewSet()\n\t\t\tifaceBridgeDb[iface] = bridgeSet\n\t\t}\n\n\t\tnewCurIfaces.Add(iface)\n\t\tbridgeSet.Add(dest)\n\t\tnewCurDb.Add(dest)\n\t}\n\n\tfor ifaceInf := range newCurIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\t\tifaceDb := ifaceBridgeDb[iface]\n\n\t\taddDb := newDb.Copy()\n\t\taddDb.Subtract(ifaceDb)\n\t\tfor destInf := range addDb.Iter() {\n\t\t\tdest := destInf.(string)\n\t\t\tif dest == \"\" {\n\t\t\t\tlogrus.Warning(\"vxlan: Empty destination\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"bridge\", \"fdb\",\n\t\t\t\t\"append\", \"00:00:00:00:00:00\",\n\t\t\t\t\"dev\", iface,\n\t\t\t\t\"dst\", dest,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tremDb := ifaceDb.Copy()\n\t\tremDb.Subtract(newDb)\n\t\tfor destInf := range remDb.Iter() {\n\t\t\tdest := destInf.(string)\n\t\t\tif dest == \"\" {\n\t\t\t\tlogrus.Warning(\"vxlan: Empty destination\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"Cannot find device\",\n\t\t\t\t\t\"No such file\",\n\t\t\t\t},\n\t\t\t\t\"bridge\", \"fdb\",\n\t\t\t\t\"del\", \"00:00:00:00:00:00\",\n\t\t\t\t\"dev\", iface,\n\t\t\t\t\"dst\", dest,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t}\n\n\tcurDatabase = newCurDb\n\tcurDatabaseIfaces = newCurIfaces\n\n\treturn\n}\n\nfunc syncIfaces(stat *state.State, internaIfaces []string,\n\tifacesData map[string]*ip.Iface, retry bool) (err error) {\n\n\tcIfaces := curIfaces\n\tnodeSelf := stat.Node()\n\tclearCache := false\n\n\tlostIfaces := set.NewSet()\n\tfor ifaceInf := range cIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\t\tif ifacesData[iface] == nil {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"iface\": iface,\n\t\t\t}).Error(\"vxlan: Lost vxlan interface\")\n\t\t\tlostIfaces.Add(iface)\n\t\t}\n\t}\n\tcIfaces.Subtract(lostIfaces)\n\n\tparentVxIfaces := map[string]string{}\n\tparentBrIfaces := map[string]string{}\n\tvxBrIfaces := map[string]string{}\n\tnewIfaces := set.NewSet()\n\tif internaIfaces != nil && stat.VxLan() {\n\t\tfor _, iface := range internaIfaces {\n\t\t\tvxIface := vm.GetHostVxlanIface(iface)\n\t\t\tbrIface := vm.GetHostBridgeIface(iface)\n\n\t\t\tparentVxIfaces[vxIface] = iface\n\t\t\tparentBrIfaces[brIface] = iface\n\t\t\tvxBrIfaces[vxIface] = brIface\n\n\t\t\tnewIfaces.Add(vxIface)\n\t\t\tnewIfaces.Add(brIface)\n\t\t}\n\t}\n\n\tremIfaces := cIfaces.Copy()\n\tremIfaces.Subtract(newIfaces)\n\tfor ifaceInf := range remIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"b\") {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"bridge\": iface,\n\t\t\t}).Info(\"vxlan: Removing bridge\")\n\n\t\t\t_, _ = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"Cannot find device\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"set\", \"dev\",\n\t\t\t\tiface, \"down\",\n\t\t\t)\n\t\t\t_ = iproute.BridgeDelete(\"\", iface)\n\n\t\t\tclearCache = true\n\t\t}\n\t}\n\tfor ifaceInf := range remIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"k\") {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"vxlan\": iface,\n\t\t\t}).Info(\"vxlan: Removing vxlan\")\n\n\t\t\t_, _ = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"Cannot find device\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"del\", iface,\n\t\t\t)\n\n\t\t\tclearCache = true\n\t\t}\n\t}\n\n\taddIfaces := newIfaces.Copy()\n\taddIfaces.Subtract(cIfaces)\n\tfor ifaceInf := range addIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"k\") {\n\t\t\tvxId := settings.Hypervisor.VxlanId\n\t\t\tdestPort := settings.Hypervisor.VxlanDestPort\n\t\t\tparentIface := parentVxIfaces[iface]\n\n\t\t\tlocalIp := \"\"\n\t\t\tif nodeSelf.PrivateIps != nil {\n\t\t\t\tlocalIp = nodeSelf.PrivateIps[parentIface]\n\t\t\t}\n\n\t\t\tif localIp == \"\" {\n\t\t\t\tif !retry {\n\t\t\t\t\tnodeSelf.SyncNetwork(true)\n\t\t\t\t\terr = syncIfaces(stat, internaIfaces, ifacesData, true)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\terr = &errortypes.NotFoundError{\n\t\t\t\t\terrors.New(\"vxlan: Missing private IP for \" +\n\t\t\t\t\t\t\"internal interface\"),\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"vxlan\": iface,\n\t\t\t}).Info(\"vxlan: Adding vxlan\")\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"File exists\",\n\t\t\t\t},\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"add\", iface,\n\t\t\t\t\"type\", \"vxlan\",\n\t\t\t\t\"id\", strconv.Itoa(vxId),\n\t\t\t\t\"local\", localIp,\n\t\t\t\t\"dstport\", strconv.Itoa(destPort),\n\t\t\t\t\"dev\", parentIface,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"set\", \"dev\",\n\t\t\t\tiface, \"up\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tclearCache = true\n\t\t}\n\t}\n\n\tfor ifaceInf := range addIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"b\") {\n\t\t\tparentIface := parentBrIfaces[iface]\n\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"bridge\": iface,\n\t\t\t}).Info(\"vxlan: Adding bridge\")\n\n\t\t\terr = iproute.BridgeAdd(\"\", iface)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"link\", \"set\",\n\t\t\t\tvm.GetHostVxlanIface(parentIface), \"master\", iface,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"ip\", \"link\",\n\t\t\t\t\"set\", \"dev\",\n\t\t\t\tiface, \"up\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tclearCache = true\n\t\t}\n\t}\n\n\texistIfaces := cIfaces.Copy()\n\texistIfaces.Subtract(remIfaces)\n\tfor ifaceInf := range existIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tif strings.HasPrefix(iface, \"k\") {\n\t\t\tbrIface := vxBrIfaces[iface]\n\n\t\t\tifaceData := ifacesData[iface]\n\t\t\tif ifaceData != nil && ifaceData.Master != brIface {\n\t\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\t\"vxlan\":  iface,\n\t\t\t\t\t\"bridge\": brIface,\n\t\t\t\t}).Warn(\"vxlan: Correct vxlan master\")\n\n\t\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t\t[]string{\"does not exist\"},\n\t\t\t\t\t\"ip\", \"link\", \"set\",\n\t\t\t\t\tiface, \"master\", brIface,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tclearCache = true\n\t\t\t}\n\t\t}\n\t}\n\n\tif clearCache {\n\t\tip.ClearIfacesCache(\"\")\n\t}\n\n\tcurIfaces = newIfaces\n\n\treturn\n}\n\nfunc syncDatabase(stat *state.State, internaIfaces []string) (err error) {\n\tnodeSelf := stat.Node()\n\tcDatabase := curDatabase\n\tcIfaces := curDatabaseIfaces\n\n\tnodes := stat.Nodes()\n\tif nodes == nil {\n\t\tnodes = []*node.Node{}\n\t}\n\n\tnodeDc := stat.NodeDatacenter()\n\tif nodeDc == nil {\n\t\treturn\n\t}\n\n\tnewIfaces := set.NewSet()\n\tfor _, iface := range internaIfaces {\n\t\tnewIfaces.Add(vm.GetHostVxlanIface(iface))\n\t}\n\n\tnewDb := set.NewSet()\n\tfor _, nde := range nodes {\n\t\tif nde.Id == nodeSelf.Id || nde.Datacenter != nodeDc.Id ||\n\t\t\tnde.PrivateIps == nil || !nodeDc.Vxlan() {\n\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, privateIp := range nde.PrivateIps {\n\t\t\tnewDb.Add(privateIp)\n\t\t}\n\t}\n\n\taddDb := newDb.Copy()\n\taddDb.Subtract(cDatabase)\n\tfor destInf := range addDb.Iter() {\n\t\tdest := destInf.(string)\n\t\tif dest == \"\" {\n\t\t\tlogrus.Warning(\"vxlan: Empty destination\")\n\t\t\tcontinue\n\t\t}\n\n\t\tfor ifaceInf := range newIfaces.Iter() {\n\t\t\tiface := ifaceInf.(string)\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"bridge\", \"fdb\",\n\t\t\t\t\"append\", \"00:00:00:00:00:00\",\n\t\t\t\t\"dev\", iface,\n\t\t\t\t\"dst\", dest,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tremDb := cDatabase.Copy()\n\tremDb.Subtract(newDb)\n\tfor destInf := range remDb.Iter() {\n\t\tdest := destInf.(string)\n\t\tif dest == \"\" {\n\t\t\tlogrus.Warning(\"vxlan: Empty destination\")\n\t\t\tcontinue\n\t\t}\n\n\t\tfor ifaceInf := range newIfaces.Iter() {\n\t\t\tiface := ifaceInf.(string)\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\t[]string{\n\t\t\t\t\t\"Cannot find device\",\n\t\t\t\t\t\"No such file\",\n\t\t\t\t},\n\t\t\t\t\"bridge\", \"fdb\",\n\t\t\t\t\"del\", \"00:00:00:00:00:00\",\n\t\t\t\t\"dev\", iface,\n\t\t\t\t\"dst\", dest,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\taddIfaces := newIfaces.Copy()\n\taddIfaces.Subtract(cIfaces)\n\tfor ifaceInf := range addIfaces.Iter() {\n\t\tiface := ifaceInf.(string)\n\n\t\tfor destInf := range newDb.Iter() {\n\t\t\tdest := destInf.(string)\n\t\t\tif dest == \"\" {\n\t\t\t\tlogrus.Warning(\"vxlan: Empty destination\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t_, err = utils.ExecCombinedOutputLogged(\n\t\t\t\tnil,\n\t\t\t\t\"bridge\", \"fdb\",\n\t\t\t\t\"append\", \"00:00:00:00:00:00\",\n\t\t\t\t\"dev\", iface,\n\t\t\t\t\"dst\", dest,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tcurDatabase = newDb\n\tcurDatabaseIfaces = newIfaces\n\n\treturn\n}\n\nfunc ApplyState(stat *state.State) (err error) {\n\tnodeSelf := stat.Node()\n\tinternaIfaces := nodeSelf.InternalInterfaces\n\n\tif curIfaces == nil {\n\t\terr = initIfaces(stat, internaIfaces)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif curDatabase == nil {\n\t\terr = initDatabase(stat, internaIfaces)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tifacesData, err := ip.GetIfacesCached(\"\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = syncIfaces(stat, internaIfaces, ifacesData, false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = syncDatabase(stat, internaIfaces)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "www/.gitignore",
    "content": "*.js\n*.map\nnode_modules/*\njspm_packages/*\n!webpack.config.js\n!webpack.dev.config.js\n!static/*\n!dist/**/*.js\n!dist/**/*.map\n"
  },
  {
    "path": "www/README.md",
    "content": "### pritunl-cloud-www\n\n```\nnpm install\ncd ./node_modules/@github/webauthn-json/dist/\nln -sf ./esm/* ./\ncd ../../../../\n```\n\n### development\n\n```\n./node_modules/.bin/tsc --watch\n./node_modules/.bin/webpack-cli --config webpack.dev.config --progress --color --watch\n```\n\n#### production\n\n```\nsh build.sh\n```\n\n### clean\n\n```\nrm -rf app/*.js*\nrm -rf app/**/*.js*\n```\n\n### internal\n\n```\n# desktop\nrsync --human-readable --archive --xattrs --progress --delete --exclude \"/node_modules/*\" --exclude \"/jspm_packages/*\" --exclude \"app/*.js\" --exclude \"app/*.js.map\" --exclude \"app/**/*.js\" --exclude \"app/**/*.js.map\" /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/ $NPM_SERVER:/home/cloud/pritunl-cloud-www/\n\n# npm-server\ncd /home/cloud/pritunl-cloud-www/\nrm package-lock.json\nrm -rf node_modules\nnpm install\nrm ./node_modules/react-stripe-checkout/index.d.ts\ncd ./node_modules/@github/webauthn-json/dist/\nln -sf ./esm/* ./\ncd ../../../../\n\n# desktop\nscp $NPM_SERVER:/home/cloud/pritunl-cloud-www/package.json /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/package.json\nscp $NPM_SERVER:/home/cloud/pritunl-cloud-www/package-lock.json /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/package-lock.json\nrsync --human-readable --archive --xattrs --progress --delete $NPM_SERVER:/home/cloud/pritunl-cloud-www/node_modules/ /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/node_modules/\nrsync --human-readable --archive --xattrs --progress --delete --exclude \"/node_modules/*\" --exclude \"/jspm_packages/*\" --exclude \"app/*.js\" --exclude \"app/*.js.map\" --exclude \"app/**/*.js\" --exclude \"app/**/*.js.map\" /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/ $NPM_SERVER:/home/cloud/pritunl-cloud-www/\n\n# npm-server\nsh build.sh\n\n# desktop\nrsync --human-readable --archive --xattrs --progress --delete $NPM_SERVER:/home/cloud/pritunl-cloud-www/dist/ /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/dist/\nrsync --human-readable --archive --xattrs --progress --delete $NPM_SERVER:/home/cloud/pritunl-cloud-www/dist-dev/ /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/dist-dev/\n```\n"
  },
  {
    "path": "www/app/Alert.ts",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport * as Blueprint from '@blueprintjs/core';\n\nlet toaster: Blueprint.Toaster;\n\nexport function success(message: string, timeout?: number): string {\n\tif (timeout === undefined) {\n\t\ttimeout = 5000;\n\t}\n\n\treturn toaster.show({\n\t\tintent: Blueprint.Intent.SUCCESS,\n\t\tmessage: message,\n\t\ttimeout: timeout,\n\t});\n}\n\nexport function info(message: string, timeout?: number): string {\n\tif (timeout === undefined) {\n\t\ttimeout = 5000;\n\t}\n\n\treturn toaster.show({\n\t\tintent: Blueprint.Intent.PRIMARY,\n\t\tmessage: message,\n\t\ttimeout: timeout,\n\t});\n}\n\nexport function warning(message: string, timeout?: number): string {\n\tif (timeout === undefined) {\n\t\ttimeout = 5000;\n\t}\n\n\treturn toaster.show({\n\t\tintent: Blueprint.Intent.WARNING,\n\t\tmessage: message,\n\t\ttimeout: timeout,\n\t});\n}\n\nexport function error(message: string, timeout?: number): string {\n\tif (timeout === undefined) {\n\t\ttimeout = 5000;\n\t}\n\n\treturn toaster.show({\n\t\tintent: Blueprint.Intent.DANGER,\n\t\tmessage: message,\n\t\ttimeout: timeout,\n\t});\n}\n\nexport function errorRes(res: SuperAgent.Response, message: string,\n\t\t\t\t\t\t\t\t\t\t\t\t timeout?: number): string {\n\tif (timeout === undefined) {\n\t\ttimeout = 5000;\n\t}\n\n\ttry {\n\t\tmessage = res.body.error_msg || message;\n\t} catch(err) {\n\t}\n\n\treturn toaster.show({\n\t\tintent: Blueprint.Intent.DANGER,\n\t\tmessage: message,\n\t\ttimeout: timeout,\n\t});\n}\n\nexport function dismiss(key: string) {\n\ttoaster.dismiss(key);\n}\n\nexport function init() {\n\tif (toaster) {\n\t\treturn;\n\t}\n\n\tif (Blueprint.OverlayToaster) {\n\t\tBlueprint.OverlayToaster.createAsync({\n\t\t\tposition: Blueprint.Position.BOTTOM,\n\t\t}).then((toastr) => {\n\t\t\ttoaster = toastr\n\t\t});\n\t} else {\n\t\tconsole.error('Failed to load toaster')\n\t}\n}\n"
  },
  {
    "path": "www/app/App.tsx",
    "content": "/// <reference path=\"References.d.ts\"/>\nimport * as Monaco from \"monaco-editor\";\nimport * as MonacoEditor from \"@monaco-editor/react\";\n\nMonacoEditor.loader.config({\n\tmonaco: Monaco\n})\n\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport * as Blueprint from '@blueprintjs/core';\nimport Main from './components/Main';\nimport * as Alert from './Alert';\nimport * as Event from './Event';\nimport * as Csrf from './Csrf';\nimport * as MiscUtils from './utils/MiscUtils';\nimport * as CompletionActions from './actions/CompletionActions';\n\nimport hljs from 'highlight.js/lib/core';\nimport plaintext from 'highlight.js/lib/languages/plaintext';\nimport bash from 'highlight.js/lib/languages/bash';\nimport python from 'highlight.js/lib/languages/python';\nimport yaml from 'highlight.js/lib/languages/yaml';\n\nCsrf.load().then((): void => {\n\tBlueprint.FocusStyleManager.onlyShowFocusOnTabs();\n\tAlert.init();\n\tEvent.init();\n\n\tnew MiscUtils.SyncInterval(\n\t\tasync () => {\n\t\t\tlet lastSync = CompletionActions.lastSync()\n\t\t\tif (lastSync && (Date.now() - lastSync) > 5000) {\n\t\t\t\tCompletionActions.sync();\n\t\t\t}\n\t\t},\n\t\t1000,\n\t)\n\n\thljs.registerLanguage('plaintext', plaintext)\n\thljs.registerLanguage('shell', bash)\n\thljs.registerLanguage('python', python)\n\thljs.registerLanguage('yaml', yaml)\n\n\tReactDOM.render(\n\t\t<Blueprint.OverlaysProvider>\n\t\t\t<div><Main/></div>\n\t\t</Blueprint.OverlaysProvider>,\n\t\tdocument.getElementById('app'),\n\t);\n});\n"
  },
  {
    "path": "www/app/Constants.ts",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport * as MobileDetect from 'mobile-detect';\n\nlet md = new MobileDetect(window.navigator.userAgent);\n\nexport const user: boolean = !!(window as any).user;\n\nexport const mobile = !!md.mobile();\nexport const mobileOs = md.os();\n\nexport const loadDelay = 700;\n\nexport const u2fErrorCodes: {[index: number]: string} = {\n\t0: 'ok',\n\t1: 'other',\n\t2: 'bad request',\n\t3: 'configuration unsupported',\n\t4: 'device ineligible',\n\t5: 'timed out',\n};\n\nexport const sessionTypes: {[key: string]: string} = {\n\tadmin: 'Admin',\n\tuser: 'User',\n};\n\nexport const operatingSystems: {[key: string]: string} = {\n\tlinux: 'Linux',\n\tmacos_1010: 'macOS 10.10',\n\tmacos_1011: 'macOS 10.11',\n\tmacos_1012: 'macOS 10.12',\n\tmacos_1013: 'macOS 10.13',\n\tmacos_1014: 'macOS 10.14',\n\tmacos_1015: 'macOS 10.15',\n\tmacos11: 'macOS 11',\n\tmacos12: 'macOS 12',\n\tmacos13: 'macOS 13',\n\tmacos14: 'macOS 14',\n\tmacos15: 'macOS 15',\n\tmacos16: 'macOS 16',\n\twindows_xp: 'Windows XP',\n\twindows_7: 'Windows 7',\n\twindows_vista: 'Windows Vista',\n\twindows_8: 'Windows 8',\n\twindows_10: 'Windows 10',\n\twindows_11: 'Windows 11',\n\tchrome_os: 'Chrome OS',\n\tios_8: 'iOS 8',\n\tios_9: 'iOS 9',\n\tios_10: 'iOS 10',\n\tios_11: 'iOS 11',\n\tios_12: 'iOS 12',\n\tios_13: 'iOS 13',\n\tios_14: 'iOS 14',\n\tios_15: 'iOS 15',\n\tios_16: 'iOS 16',\n\tios_17: 'iOS 17',\n\tios_18: 'iOS 18',\n\tios_19: 'iOS 19',\n\tios_20: 'iOS 20',\n\tandroid_4: 'Android KitKat 4.4',\n\tandroid_5: 'Android Lollipop 5',\n\tandroid_6: 'Android Marshmallow 6',\n\tandroid_7: 'Android Nougat 7',\n\tandroid_8: 'Android Oreo 8',\n\tandroid_9: 'Android Pie 9',\n\tandroid_10: 'Android 10',\n\tandroid_11: 'Android 11',\n\tandroid_12: 'Android 12',\n\tandroid_13: 'Android 13',\n\tandroid_14: 'Android 14',\n\tandroid_15: 'Android 15',\n\tandroid_16: 'Android 16',\n\tblackberry_10: 'Blackerry 10',\n\twindows_phone: 'Windows Phone',\n\tfirefox_os: 'Firefox OS',\n\tkindle: 'Kindle',\n};\n\nexport const browsers: {[key: string]: string} = {\n\tchrome: 'Chrome',\n\tchrome_mobile: 'Chrome Mobile',\n\tsafari: 'Safari',\n\tsafari_mobile: 'Safari Mobile',\n\tfirefox: 'Firefox',\n\tfirefox_mobile: 'Firefox Mobile',\n\tedge: 'Microsoft Edge',\n\tinternet_explorer: 'Internet Explorer',\n\tinternet_explorer_mobile: 'Internet Explorer Mobile',\n\topera: 'Opera',\n\topera_mobile: 'Opera Mobile',\n};\n\nexport const locations: {[key: string]: string} = {\n\tUS: 'United States',\n\tUS_AL: 'Alabama, US',\n\tUS_AK: 'Alaska, US',\n\tUS_AZ: 'Arizona, US',\n\tUS_AR: 'Arkansas, US',\n\tUS_CA: 'California, US',\n\tUS_CO: 'Colorado, US',\n\tUS_CT: 'Connecticut, US',\n\tUS_DE: 'Delaware, US',\n\tUS_FL: 'Florida, US',\n\tUS_GA: 'Georgia, US',\n\tUS_HI: 'Hawaii, US',\n\tUS_ID: 'Idaho, US',\n\tUS_IL: 'Illinois, US',\n\tUS_IN: 'Indiana, US',\n\tUS_IA: 'Iowa, US',\n\tUS_KS: 'Kansas, US',\n\tUS_KY: 'Kentucky, US',\n\tUS_LA: 'Louisiana, US',\n\tUS_ME: 'Maine, US',\n\tUS_MD: 'Maryland, US',\n\tUS_MA: 'Massachusetts, US',\n\tUS_MI: 'Michigan, US',\n\tUS_MN: 'Minnesota, US',\n\tUS_MS: 'Mississippi, US',\n\tUS_MO: 'Missouri, US',\n\tUS_MT: 'Montana, US',\n\tUS_NE: 'Nebraska, US',\n\tUS_NV: 'Nevada, US',\n\tUS_NH: 'New Hampshire, US',\n\tUS_NJ: 'New Jersey, US',\n\tUS_NM: 'New Mexico, US',\n\tUS_NY: 'New York, US',\n\tUS_NC: 'North Carolina, US',\n\tUS_ND: 'North Dakota, US',\n\tUS_OH: 'Ohio, US',\n\tUS_OK: 'Oklahoma, US',\n\tUS_OR: 'Oregon, US',\n\tUS_PA: 'Pennsylvania, US',\n\tUS_RI: 'Rhode Island, US',\n\tUS_SC: 'South Carolina, US',\n\tUS_SD: 'South Dakota, US',\n\tUS_TN: 'Tennessee, US',\n\tUS_TX: 'Texas, US',\n\tUS_UT: 'Utah, US',\n\tUS_VT: 'Vermont, US',\n\tUS_VA: 'Virginia, US',\n\tUS_WA: 'Washington, US',\n\tUS_DC: 'Washington DC, US',\n\tUS_WV: 'West Virginia, US',\n\tUS_WI: 'Wisconsin, US',\n\tUS_WY: 'Wyoming, US',\n\tAF: 'Afghanistan',\n\tAX: 'Åland Islands',\n\tAL: 'Albania',\n\tDZ: 'Algeria',\n\tAS: 'American Samoa',\n\tAD: 'Andorra',\n\tAO: 'Angola',\n\tAI: 'Anguilla',\n\tAQ: 'Antarctica',\n\tAG: 'Antigua and Barbuda',\n\tAR: 'Argentina',\n\tAM: 'Armenia',\n\tAW: 'Aruba',\n\tAU: 'Australia',\n\tAT: 'Austria',\n\tAZ: 'Azerbaijan',\n\tBS: 'Bahamas',\n\tBH: 'Bahrain',\n\tBD: 'Bangladesh',\n\tBB: 'Barbados',\n\tBY: 'Belarus',\n\tBE: 'Belgium',\n\tBZ: 'Belize',\n\tBJ: 'Benin',\n\tBM: 'Bermuda',\n\tBT: 'Bhutan',\n\tBO: 'Bolivia',\n\tBQ: 'Bonaire',\n\tBA: 'Bosnia and Herzegovina',\n\tBW: 'Botswana',\n\tBV: 'Bouvet Island',\n\tBR: 'Brazil',\n\tIO: 'British Indian Ocean Territory',\n\tBN: 'Brunei Darussalam',\n\tBG: 'Bulgaria',\n\tBF: 'Burkina Faso',\n\tBI: 'Burundi',\n\tCV: 'Cabo Verde',\n\tKH: 'Cambodia',\n\tCM: 'Cameroon',\n\tCA: 'Canada',\n\tKY: 'Cayman Islands',\n\tCF: 'Central African Republic',\n\tTD: 'Chad',\n\tCL: 'Chile',\n\tCN: 'China',\n\tCX: 'Christmas Island',\n\tCC: 'Cocos Islands',\n\tCO: 'Colombia',\n\tKM: 'Comoros',\n\tCG: 'Congo',\n\tCD: 'Congo Democratic Republic',\n\tCK: 'Cook Islands',\n\tCR: 'Costa Rica',\n\tCI: 'Côte dIvoire',\n\tHR: 'Croatia',\n\tCU: 'Cuba',\n\tCW: 'Curaçao',\n\tCY: 'Cyprus',\n\tCZ: 'Czechia',\n\tDK: 'Denmark',\n\tDJ: 'Djibouti',\n\tDM: 'Dominica',\n\tDO: 'Dominican Republic',\n\tEC: 'Ecuador',\n\tEG: 'Egypt',\n\tSV: 'El Salvador',\n\tGQ: 'Equatorial Guinea',\n\tER: 'Eritrea',\n\tEE: 'Estonia',\n\tET: 'Ethiopia',\n\tFK: 'Falkland Islands',\n\tFO: 'Faroe Islands',\n\tFJ: 'Fiji',\n\tFI: 'Finland',\n\tFR: 'France',\n\tGF: 'French Guiana',\n\tPF: 'French Polynesia',\n\tTF: 'French Southern Territories',\n\tGA: 'Gabon',\n\tGM: 'Gambia',\n\tGE: 'Georgia',\n\tDE: 'Germany',\n\tGH: 'Ghana',\n\tGI: 'Gibraltar',\n\tGR: 'Greece',\n\tGL: 'Greenland',\n\tGD: 'Grenada',\n\tGP: 'Guadeloupe',\n\tGU: 'Guam',\n\tGT: 'Guatemala',\n\tGG: 'Guernsey',\n\tGN: 'Guinea',\n\tGW: 'Guinea-Bissau',\n\tGY: 'Guyana',\n\tHT: 'Haiti',\n\tHM: 'Heard Island and McDonald Islands',\n\tVA: 'Holy See',\n\tHN: 'Honduras',\n\tHK: 'Hong Kong',\n\tHU: 'Hungary',\n\tIS: 'Iceland',\n\tIN: 'India',\n\tID: 'Indonesia',\n\tIR: 'Iran',\n\tIQ: 'Iraq',\n\tIE: 'Ireland',\n\tIM: 'Isle of Man',\n\tIL: 'Israel',\n\tIT: 'Italy',\n\tJM: 'Jamaica',\n\tJP: 'Japan',\n\tJE: 'Jersey',\n\tJO: 'Jordan',\n\tKZ: 'Kazakhstan',\n\tKE: 'Kenya',\n\tKI: 'Kiribati',\n\tKP: 'North Korea',\n\tKR: 'South Korea',\n\tKW: 'Kuwait',\n\tKG: 'Kyrgyzstan',\n\tLA: 'Lao Peoples',\n\tLV: 'Latvia',\n\tLB: 'Lebanon',\n\tLS: 'Lesotho',\n\tLR: 'Liberia',\n\tLY: 'Libya',\n\tLI: 'Liechtenstein',\n\tLT: 'Lithuania',\n\tLU: 'Luxembourg',\n\tMO: 'Macao',\n\tMK: 'Macedonia',\n\tMG: 'Madagascar',\n\tMW: 'Malawi',\n\tMY: 'Malaysia',\n\tMV: 'Maldives',\n\tML: 'Mali',\n\tMT: 'Malta',\n\tMH: 'Marshall Islands',\n\tMQ: 'Martinique',\n\tMR: 'Mauritania',\n\tMU: 'Mauritius',\n\tYT: 'Mayotte',\n\tMX: 'Mexico',\n\tFM: 'Micronesia',\n\tMD: 'Moldova',\n\tMC: 'Monaco',\n\tMN: 'Mongolia',\n\tME: 'Montenegro',\n\tMS: 'Montserrat',\n\tMA: 'Morocco',\n\tMZ: 'Mozambique',\n\tMM: 'Myanmar',\n\tNA: 'Namibia',\n\tNR: 'Nauru',\n\tNP: 'Nepal',\n\tNL: 'Netherlands',\n\tNC: 'New Caledonia',\n\tNZ: 'New Zealand',\n\tNI: 'Nicaragua',\n\tNE: 'Niger',\n\tNG: 'Nigeria',\n\tNU: 'Niue',\n\tNF: 'Norfolk Island',\n\tMP: 'Northern Mariana Islands',\n\tNO: 'Norway',\n\tOM: 'Oman',\n\tPK: 'Pakistan',\n\tPW: 'Palau',\n\tPS: 'Palestine, State of',\n\tPA: 'Panama',\n\tPG: 'Papua New Guinea',\n\tPY: 'Paraguay',\n\tPE: 'Peru',\n\tPH: 'Philippines',\n\tPN: 'Pitcairn',\n\tPL: 'Poland',\n\tPT: 'Portugal',\n\tPR: 'Puerto Rico',\n\tQA: 'Qatar',\n\tRE: 'Réunion',\n\tRO: 'Romania',\n\tRU: 'Russian Federation',\n\tRW: 'Rwanda',\n\tBL: 'Saint Barthélemy',\n\tSH: 'Saint Helena',\n\tKN: 'Saint Kitts and Nevis',\n\tLC: 'Saint Lucia',\n\tMF: 'Saint Martin',\n\tPM: 'Saint Pierre and Miquelon',\n\tVC: 'Saint Vincent and the Grenadines',\n\tWS: 'Samoa',\n\tSM: 'San Marino',\n\tST: 'Sao Tome and Principe',\n\tSA: 'Saudi Arabia',\n\tSN: 'Senegal',\n\tRS: 'Serbia',\n\tSC: 'Seychelles',\n\tSL: 'Sierra Leone',\n\tSG: 'Singapore',\n\tSX: 'Sint Maarten',\n\tSK: 'Slovakia',\n\tSI: 'Slovenia',\n\tSB: 'Solomon Islands',\n\tSO: 'Somalia',\n\tZA: 'South Africa',\n\tGS: 'South Georgia and the South Sandwich Islands',\n\tSS: 'South Sudan',\n\tES: 'Spain',\n\tLK: 'Sri Lanka',\n\tSD: 'Sudan',\n\tSR: 'Suriname',\n\tSJ: 'Svalbard and Jan Mayen',\n\tSZ: 'Swaziland',\n\tSE: 'Sweden',\n\tCH: 'Switzerland',\n\tSY: 'Syrian Arab Republic',\n\tTW: 'Taiwan',\n\tTJ: 'Tajikistan',\n\tTZ: 'Tanzania',\n\tTH: 'Thailand',\n\tTL: 'Timor-Leste',\n\tTG: 'Togo',\n\tTK: 'Tokelau',\n\tTO: 'Tonga',\n\tTT: 'Trinidad and Tobago',\n\tTN: 'Tunisia',\n\tTR: 'Turkey',\n\tTM: 'Turkmenistan',\n\tTC: 'Turks and Caicos Islands',\n\tTV: 'Tuvalu',\n\tUG: 'Uganda',\n\tUA: 'Ukraine',\n\tAE: 'United Arab Emirates',\n\tGB: 'United Kingdom',\n\tUM: 'United States Minor Outlying Islands',\n\tUY: 'Uruguay',\n\tUZ: 'Uzbekistan',\n\tVU: 'Vanuatu',\n\tVE: 'Venezuela',\n\tVN: 'Viet Nam',\n\tVG: 'British Virgin Islands',\n\tVI: 'US Virgin Islands',\n\tWF: 'Wallis and Futuna',\n\tEH: 'Western Sahara',\n\tYE: 'Yemen',\n\tZM: 'Zambia',\n\tZW: 'Zimbabwe',\n};\n"
  },
  {
    "path": "www/app/Csrf.ts",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport * as License from './License';\nimport * as Theme from './Theme';\n\nexport let token = '';\n\nexport function load(): Promise<void> {\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/csrf')\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\ttoken = res.body.token;\n\n\t\t\t\tLicense.setOracle(!!res.body.oracle_license);\n\n\t\t\t\tlet theme = res.body.theme\n\t\t\t\tif (theme) {\n\t\t\t\t\tlet themeParts = theme.split(\"-\")\n\t\t\t\t\tif (themeParts[1] === \"3\") {\n\t\t\t\t\t\tTheme.themeVer3()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tTheme.themeVer5()\n\t\t\t\t\t}\n\n\t\t\t\t\tif (themeParts[0] === \"light\") {\n\t\t\t\t\t\tTheme.light();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tTheme.dark();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tTheme.dark();\n\t\t\t\t}\n\n\t\t\t\tif (res.body.editor_theme) {\n\t\t\t\t\tTheme.setEditorTheme(res.body.editor_theme);\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n"
  },
  {
    "path": "www/app/EditorThemes.ts",
    "content": "// The MIT License (MIT)\n\n// Copyright (c) Brijesh Bittu\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\nimport * as Monaco from \"monaco-editor\"\n\nlet allHallowsEve = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"000000\",\"token\": \"\"},\n    {\"foreground\": \"ffffff\",\"background\": \"434242\",\"token\": \"text\"},\n    {\"foreground\": \"ffffff\",\"background\": \"000000\",\"token\": \"source\"},\n    {\"foreground\": \"9933cc\",\"token\": \"comment\"},\n    {\"foreground\": \"3387cc\",\"token\": \"constant\"},\n    {\"foreground\": \"cc7833\",\"token\": \"keyword\"},\n    {\"foreground\": \"d0d0ff\",\"token\": \"meta.preprocessor.c\"},\n    {\"fontStyle\": \"italic\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"ffffff\",\"background\": \"9b9b9b\",\"token\": \"source comment.block\"},\n    {\"foreground\": \"66cc33\",\"token\": \"string\"},\n    {\"foreground\": \"aaaaaa\",\"token\": \"string constant.character.escape\"},\n    {\"foreground\": \"000000\",\"background\": \"cccc33\",\"token\": \"string.interpolated\"},\n    {\"foreground\": \"cccc33\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"cccc33\",\"token\": \"string.literal\"},\n    {\"foreground\": \"555555\",\"token\": \"string.interpolated constant.character.escape\"},\n    {\"fontStyle\": \"underline\",\"token\": \"entity.name.type\"},\n    {\"fontStyle\": \"italic underline\",\"token\": \"entity.other.inherited-class\"},\n    {\"fontStyle\": \"underline\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"c83730\",\"token\": \"support.function\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#FFFFFF\",\n\t\t\"editor.background\": \"#000000\",\n\t\t\"editor.selectionBackground\": \"#73597EE0\",\n\t\t\"editor.lineHighlightBackground\": \"#333300\",\n\t\t\"editorCursor.foreground\": \"#FFFFFF\",\n\t\t\"editorWhitespace.foreground\": \"#404040\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet amy = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"200020\",\"token\": \"\"},\n    {\"foreground\": \"404080\",\"background\": \"200020\",\"fontStyle\": \"italic\",\"token\": \"comment.block\"},\n    {\"foreground\": \"999999\",\"token\": \"string\"},\n    {\"foreground\": \"707090\",\"token\": \"constant.language\"},\n    {\"foreground\": \"7090b0\",\"token\": \"constant.numeric\"},\n    {\"fontStyle\": \"bold\",\"token\": \"constant.numeric.integer.int32\"},\n    {\"fontStyle\": \"italic\",\"token\": \"constant.numeric.integer.int64\"},\n    {\"fontStyle\": \"bold italic\",\"token\": \"constant.numeric.integer.nativeint\"},\n    {\"fontStyle\": \"underline\",\"token\": \"constant.numeric.floating-point.ocaml\"},\n    {\"foreground\": \"666666\",\"token\": \"constant.character\"},\n    {\"foreground\": \"8080a0\",\"token\": \"constant.language.boolean\"},\n    {\"foreground\": \"008080\",\"token\": \"variable.language\"},\n    {\"foreground\": \"008080\",\"token\": \"variable.other\"},\n    {\"foreground\": \"a080ff\",\"token\": \"keyword\"},\n    {\"foreground\": \"a0a0ff\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"d0d0ff\",\"token\": \"keyword.other.decorator\"},\n    {\"fontStyle\": \"underline\",\"token\": \"keyword.operator.infix.floating-point.ocaml\"},\n    {\"fontStyle\": \"underline\",\"token\": \"keyword.operator.prefix.floating-point.ocaml\"},\n    {\"foreground\": \"c080c0\",\"token\": \"keyword.other.directive\"},\n    {\"foreground\": \"c080c0\",\"fontStyle\": \"underline\",\"token\": \"keyword.other.directive.line-number\"},\n    {\"foreground\": \"80a0ff\",\"token\": \"keyword.control\"},\n    {\"foreground\": \"b0fff0\",\"token\": \"storage\"},\n    {\"foreground\": \"60b0ff\",\"token\": \"entity.name.type.variant\"},\n    {\"foreground\": \"60b0ff\",\"fontStyle\": \"italic\",\"token\": \"storage.type.variant.polymorphic\"},\n    {\"foreground\": \"60b0ff\",\"fontStyle\": \"italic\",\"token\": \"entity.name.type.variant.polymorphic\"},\n    {\"foreground\": \"b000b0\",\"token\": \"entity.name.type.module\"},\n    {\"foreground\": \"b000b0\",\"fontStyle\": \"underline\",\"token\": \"entity.name.type.module-type.ocaml\"},\n    {\"foreground\": \"a00050\",\"token\": \"support.other\"},\n    {\"foreground\": \"70e080\",\"token\": \"entity.name.type.class\"},\n    {\"foreground\": \"70e0a0\",\"token\": \"entity.name.type.class-type\"},\n    {\"foreground\": \"50a0a0\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"80b0b0\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"3080a0\",\"token\": \"entity.name.type.token\"},\n    {\"foreground\": \"3cb0d0\",\"token\": \"entity.name.type.token.reference\"},\n    {\"foreground\": \"90e0e0\",\"token\": \"entity.name.function.non-terminal\"},\n    {\"foreground\": \"c0f0f0\",\"token\": \"entity.name.function.non-terminal.reference\"},\n    {\"foreground\": \"009090\",\"token\": \"entity.name.tag\"},\n    {\"background\": \"200020\",\"token\": \"support.constant\"},\n    {\"foreground\": \"400080\",\"background\": \"ffff00\",\"fontStyle\": \"bold\",\"token\": \"invalid.illegal\"},\n    {\"foreground\": \"200020\",\"background\": \"cc66ff\",\"token\": \"invalid.deprecated\"},\n    {\"background\": \"40008054\",\"token\": \"source.camlp4.embedded\"},\n    {\"foreground\": \"805080\",\"token\": \"punctuation\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#D0D0FF\",\n\t\t\"editor.background\": \"#200020\",\n\t\t\"editor.selectionBackground\": \"#80000080\",\n\t\t\"editor.lineHighlightBackground\": \"#80000040\",\n\t\t\"editorCursor.foreground\": \"#7070FF\",\n\t\t\"editorWhitespace.foreground\": \"#BFBFBF\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet birdsOfParadise = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"372725\",\"token\": \"\"},\n    {\"foreground\": \"e6e1c4\",\"background\": \"322323\",\"token\": \"source\"},\n    {\"foreground\": \"6b4e32\",\"fontStyle\": \"italic\",\"token\": \"comment\"},\n    {\"foreground\": \"ef5d32\",\"token\": \"keyword\"},\n    {\"foreground\": \"ef5d32\",\"token\": \"storage\"},\n    {\"foreground\": \"efac32\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"efac32\",\"token\": \"keyword.other.name-of-parameter.objc\"},\n    {\"foreground\": \"efac32\",\"fontStyle\": \"bold\",\"token\": \"entity.name\"},\n    {\"foreground\": \"6c99bb\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"7daf9c\",\"token\": \"variable.language\"},\n    {\"foreground\": \"7daf9c\",\"token\": \"variable.other\"},\n    {\"foreground\": \"6c99bb\",\"token\": \"constant\"},\n    {\"foreground\": \"efac32\",\"token\": \"variable.other.constant\"},\n    {\"foreground\": \"6c99bb\",\"token\": \"constant.language\"},\n    {\"foreground\": \"d9d762\",\"token\": \"string\"},\n    {\"foreground\": \"efac32\",\"token\": \"support.function\"},\n    {\"foreground\": \"efac32\",\"token\": \"support.type\"},\n    {\"foreground\": \"6c99bb\",\"token\": \"support.constant\"},\n    {\"foreground\": \"efcb43\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"efcb43\",\"token\": \"declaration.tag\"},\n    {\"foreground\": \"efcb43\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"efcb43\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"ffffff\",\"background\": \"990000\",\"token\": \"invalid\"},\n    {\"foreground\": \"7daf9c\",\"token\": \"constant.character.escaped\"},\n    {\"foreground\": \"7daf9c\",\"token\": \"constant.character.escape\"},\n    {\"foreground\": \"7daf9c\",\"token\": \"string source\"},\n    {\"foreground\": \"7daf9c\",\"token\": \"string source.ruby\"},\n    {\"foreground\": \"e6e1dc\",\"background\": \"144212\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"e6e1dc\",\"background\": \"660000\",\"token\": \"markup.deleted\"},\n    {\"background\": \"2f33ab\",\"token\": \"meta.diff.header\"},\n    {\"background\": \"2f33ab\",\"token\": \"meta.separator.diff\"},\n    {\"background\": \"2f33ab\",\"token\": \"meta.diff.index\"},\n    {\"background\": \"2f33ab\",\"token\": \"meta.diff.range\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#E6E1C4\",\n\t\t\"editor.background\": \"#372725\",\n\t\t\"editor.selectionBackground\": \"#16120E\",\n\t\t\"editor.lineHighlightBackground\": \"#1F1611\",\n\t\t\"editorCursor.foreground\": \"#E6E1C4\",\n\t\t\"editorWhitespace.foreground\": \"#42302D\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet blackboard = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"0C1021\",\"token\": \"\"},\n    {\"foreground\": \"aeaeae\",\"token\": \"comment\"},\n    {\"foreground\": \"d8fa3c\",\"token\": \"constant\"},\n    {\"foreground\": \"ff6400\",\"token\": \"entity\"},\n    {\"foreground\": \"fbde2d\",\"token\": \"keyword\"},\n    {\"foreground\": \"fbde2d\",\"token\": \"storage\"},\n    {\"foreground\": \"61ce3c\",\"token\": \"string\"},\n    {\"foreground\": \"61ce3c\",\"token\": \"meta.verbatim\"},\n    {\"foreground\": \"8da6ce\",\"token\": \"support\"},\n    {\"foreground\": \"ab2a1d\",\"fontStyle\": \"italic\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"9d1e15\",\"token\": \"invalid.illegal\"},\n    {\"foreground\": \"ff6400\",\"fontStyle\": \"italic\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"ff6400\",\"token\": \"string constant.other.placeholder\"},\n    {\"foreground\": \"becde6\",\"token\": \"meta.function-call.py\"},\n    {\"foreground\": \"7f90aa\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"7f90aa\",\"token\": \"meta.tag entity\"},\n    {\"foreground\": \"ffffff\",\"token\": \"entity.name.section\"},\n    {\"foreground\": \"d5e0f3\",\"token\": \"keyword.type.variant\"},\n    {\"foreground\": \"f8f8f8\",\"token\": \"source.ocaml keyword.operator.symbol\"},\n    {\"foreground\": \"8da6ce\",\"token\": \"source.ocaml keyword.operator.symbol.infix\"},\n    {\"foreground\": \"8da6ce\",\"token\": \"source.ocaml keyword.operator.symbol.prefix\"},\n    {\"fontStyle\": \"underline\",\"token\": \"source.ocaml keyword.operator.symbol.infix.floating-point\"},\n    {\"fontStyle\": \"underline\",\"token\": \"source.ocaml keyword.operator.symbol.prefix.floating-point\"},\n    {\"fontStyle\": \"underline\",\"token\": \"source.ocaml constant.numeric.floating-point\"},\n    {\"background\": \"ffffff08\",\"token\": \"text.tex.latex meta.function.environment\"},\n    {\"background\": \"7a96fa08\",\"token\": \"text.tex.latex meta.function.environment meta.function.environment\"},\n    {\"foreground\": \"fbde2d\",\"token\": \"text.tex.latex support.function\"},\n    {\"foreground\": \"ffffff\",\"token\": \"source.plist string.unquoted\"},\n    {\"foreground\": \"ffffff\",\"token\": \"source.plist keyword.operator\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#F8F8F8\",\n\t\t\"editor.background\": \"#0C1021\",\n\t\t\"editor.selectionBackground\": \"#253B76\",\n\t\t\"editor.lineHighlightBackground\": \"#FFFFFF0F\",\n\t\t\"editorCursor.foreground\": \"#FFFFFFA6\",\n\t\t\"editorWhitespace.foreground\": \"#FFFFFF40\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet brillianceBlack = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"0D0D0DFA\",\"token\": \"\"},\n    {\"foreground\": \"000000\",\"background\": \"ffffff\",\"fontStyle\": \"bold\",\"token\": \"meta.thomas_aylott\"},\n    {\"foreground\": \"555555\",\"background\": \"ffffff\",\"fontStyle\": \"underline\",\"token\": \"meta.subtlegradient\"},\n    {\"foreground\": \"fffc80\",\"background\": \"803d0033\",\"token\": \"string -meta.tag -meta.doctype -string.regexp -string.literal -string.interpolated -string.quoted.literal -string.unquoted\"},\n    {\"foreground\": \"fffc80\",\"background\": \"803d0033\",\"token\": \"variable.parameter.misc.css\"},\n    {\"foreground\": \"fffc80\",\"background\": \"803d0033\",\"token\": \"text string source string\"},\n    {\"foreground\": \"fffc80\",\"background\": \"803d0033\",\"token\": \"string.unquoted string\"},\n    {\"foreground\": \"fffc80\",\"background\": \"803d0033\",\"token\": \"string.regexp string\"},\n    {\"foreground\": \"fffc80\",\"background\": \"803d0033\",\"token\": \"string.interpolated string\"},\n    {\"foreground\": \"fffc80\",\"background\": \"803d0033\",\"token\": \"meta.tag source string\"},\n    {\"foreground\": \"803d00\",\"token\": \"punctuation.definition.string -meta.tag\"},\n    {\"foreground\": \"fff80033\",\"token\": \"string.regexp punctuation.definition.string\"},\n    {\"foreground\": \"fff80033\",\"token\": \"string.quoted.literal punctuation.definition.string\"},\n    {\"foreground\": \"fff80033\",\"token\": \"string.quoted.double.ruby.mod punctuation.definition.string\"},\n    {\"foreground\": \"fff800\",\"background\": \"43800033\",\"token\": \"string.quoted.literal\"},\n    {\"foreground\": \"fff800\",\"background\": \"43800033\",\"token\": \"string.quoted.double.ruby.mod\"},\n    {\"foreground\": \"ffbc80\",\"token\": \"string.unquoted -string.unquoted.embedded\"},\n    {\"foreground\": \"ffbc80\",\"token\": \"string.quoted.double.multiline\"},\n    {\"foreground\": \"ffbc80\",\"token\": \"meta.scope.heredoc\"},\n    {\"foreground\": \"fffc80\",\"background\": \"1a1a1a\",\"token\": \"string.interpolated\"},\n    {\"foreground\": \"fff800\",\"background\": \"43800033\",\"token\": \"string.regexp\"},\n    {\"background\": \"43800033\",\"token\": \"string.regexp.group\"},\n    {\"foreground\": \"ffffff66\",\"background\": \"43800033\",\"token\": \"string.regexp.group string.regexp.group\"},\n    {\"foreground\": \"ffffff66\",\"background\": \"43800033\",\"token\": \"string.regexp.group string.regexp.group string.regexp.group\"},\n    {\"foreground\": \"ffffff66\",\"background\": \"43800033\",\"token\": \"string.regexp.group string.regexp.group string.regexp.group string.regexp.group\"},\n    {\"foreground\": \"86ff00\",\"background\": \"43800033\",\"token\": \"string.regexp.character-class\"},\n    {\"foreground\": \"00fff8\",\"background\": \"43800033\",\"token\": \"string.regexp.arbitrary-repitition\"},\n    {\"foreground\": \"803d00\",\"token\": \"string.regexp punctuation.definition.string keyword.other\"},\n    {\"background\": \"0086ff33\",\"token\": \"meta.group.assertion.regexp\"},\n    {\"foreground\": \"0086ff\",\"token\": \"meta.assertion\"},\n    {\"foreground\": \"0086ff\",\"token\": \"meta.group.assertion keyword.control.group.regexp\"},\n    {\"foreground\": \"0086ff\",\"token\": \"meta.group.assertion punctuation.definition.group\"},\n    {\"foreground\": \"c6ff00\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"86ff00\",\"token\": \"constant.character\"},\n    {\"foreground\": \"07ff00\",\"token\": \"constant.language\"},\n    {\"foreground\": \"07ff00\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"07ff00\",\"token\": \"constant.other.java\"},\n    {\"foreground\": \"07ff00\",\"token\": \"constant.other.unit\"},\n    {\"foreground\": \"07ff00\",\"background\": \"04800033\",\"token\": \"constant.language.pseudo-variable\"},\n    {\"foreground\": \"00ff79\",\"token\": \"constant.other\"},\n    {\"foreground\": \"00ff79\",\"token\": \"constant.block\"},\n    {\"foreground\": \"00fff8\",\"token\": \"support.constant\"},\n    {\"foreground\": \"00fff8\",\"token\": \"constant.name\"},\n    {\"foreground\": \"00ff79\",\"background\": \"00807c33\",\"token\": \"variable.other.readwrite.global.pre-defined\"},\n    {\"foreground\": \"00ff79\",\"background\": \"00807c33\",\"token\": \"variable.language\"},\n    {\"foreground\": \"00fff8\",\"token\": \"variable.other.constant\"},\n    {\"foreground\": \"00fff8\",\"background\": \"00807c33\",\"token\": \"support.variable\"},\n    {\"foreground\": \"00807c\",\"background\": \"00438033\",\"token\": \"variable.other.readwrite.global\"},\n    {\"foreground\": \"31a6ff\",\"token\": \"variable.other\"},\n    {\"foreground\": \"31a6ff\",\"token\": \"variable.js\"},\n    {\"foreground\": \"31a6ff\",\"token\": \"punctuation.separator.variable\"},\n    {\"foreground\": \"0086ff\",\"background\": \"0008ff33\",\"token\": \"variable.other.readwrite.class\"},\n    {\"foreground\": \"406180\",\"token\": \"variable.other.readwrite.instance\"},\n    {\"foreground\": \"406180\",\"token\": \"variable.other.php\"},\n    {\"foreground\": \"406180\",\"token\": \"variable.other.normal\"},\n    {\"foreground\": \"00000080\",\"token\": \"punctuation.definition\"},\n    {\"foreground\": \"00000080\",\"token\": \"punctuation.separator.variable\"},\n    {\"foreground\": \"7e0080\",\"token\": \"storage -storage.modifier\"},\n    {\"background\": \"803d0033\",\"token\": \"other.preprocessor\"},\n    {\"background\": \"803d0033\",\"token\": \"entity.name.preprocessor\"},\n    {\"foreground\": \"666666\",\"token\": \"variable.language.this.js\"},\n    {\"foreground\": \"803d00\",\"token\": \"storage.modifier\"},\n    {\"foreground\": \"ff0000\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"ff0000\",\"token\": \"entity.name.type.class\"},\n    {\"foreground\": \"ff0000\",\"token\": \"entity.name.type.module\"},\n    {\"foreground\": \"870000\",\"background\": \"ff000033\",\"token\": \"meta.class -meta.class.instance\"},\n    {\"foreground\": \"870000\",\"background\": \"ff000033\",\"token\": \"declaration.class\"},\n    {\"foreground\": \"870000\",\"background\": \"ff000033\",\"token\": \"meta.definition.class\"},\n    {\"foreground\": \"870000\",\"background\": \"ff000033\",\"token\": \"declaration.module\"},\n    {\"foreground\": \"ff0000\",\"background\": \"87000033\",\"token\": \"support.type\"},\n    {\"foreground\": \"ff0000\",\"background\": \"87000033\",\"token\": \"support.class\"},\n    {\"foreground\": \"ff3d44\",\"token\": \"entity.name.instance\"},\n    {\"foreground\": \"ff3d44\",\"token\": \"entity.name.type.instance\"},\n    {\"background\": \"831e5133\",\"token\": \"meta.class.instance.constructor\"},\n    {\"foreground\": \"ff0086\",\"background\": \"80000433\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"ff0086\",\"background\": \"80000433\",\"token\": \"entity.name.module\"},\n    {\"foreground\": \"ff0086\",\"token\": \"meta.definition.method\"},\n    {\"foreground\": \"ff0086\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"ff0086\",\"token\": \"entity.name.preprocessor\"},\n    {\"foreground\": \"9799ff\",\"token\": \"variable.parameter.function\"},\n    {\"foreground\": \"9799ff\",\"token\": \"variable.parameter -variable.parameter.misc.css\"},\n    {\"foreground\": \"9799ff\",\"token\": \"meta.definition.method  meta.definition.param-list\"},\n    {\"foreground\": \"9799ff\",\"token\": \"meta.function.method.with-arguments variable.parameter.function\"},\n    {\"foreground\": \"800004\",\"token\": \"punctuation.definition.parameters\"},\n    {\"foreground\": \"800004\",\"token\": \"variable.parameter.function punctuation.separator.object\"},\n    {\"foreground\": \"782ec1\",\"token\": \"keyword.other.special-method\"},\n    {\"foreground\": \"782ec1\",\"token\": \"meta.function-call entity.name.function -(meta.function-call meta.function)\"},\n    {\"foreground\": \"782ec1\",\"token\": \"support.function - variable\"},\n    {\"foreground\": \"9d3eff\",\"token\": \"meta.function-call support.function - variable\"},\n    {\"foreground\": \"603f80\",\"background\": \"603f8033\",\"token\": \"support.function\"},\n    {\"foreground\": \"bc80ff\",\"token\": \"punctuation.section.function\"},\n    {\"foreground\": \"bc80ff\",\"token\": \"meta.brace.curly.function\"},\n    {\"foreground\": \"bc80ff\",\"token\": \"meta.function-call punctuation.section.scope.ruby\"},\n    {\"foreground\": \"bc80ff\",\"token\": \"meta.function-call punctuation.separator.object\"},\n    {\"foreground\": \"bc80ff\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.round punctuation.section.scope\"},\n    {\"foreground\": \"bc80ff\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.round meta.delimiter.object.comma\"},\n    {\"foreground\": \"bc80ff\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.curly.function meta.delimiter.object.comma\"},\n    {\"foreground\": \"bc80ff\",\"fontStyle\": \"bold\",\"token\": \"meta.brace.round\"},\n    {\"foreground\": \"a88fc0\",\"token\": \"meta.function-call.method.without-arguments\"},\n    {\"foreground\": \"a88fc0\",\"token\": \"meta.function-call.method.without-arguments entity.name.function\"},\n    {\"foreground\": \"f800ff\",\"token\": \"keyword.control\"},\n    {\"foreground\": \"7900ff\",\"token\": \"keyword.other\"},\n    {\"foreground\": \"0000ce\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"0000ce\",\"token\": \"declaration.function.operator\"},\n    {\"foreground\": \"0000ce\",\"token\": \"meta.preprocessor.c.include\"},\n    {\"foreground\": \"0000ce\",\"token\": \"punctuation.separator.operator\"},\n    {\"foreground\": \"0000ce\",\"background\": \"00009a33\",\"token\": \"keyword.operator.assignment\"},\n    {\"foreground\": \"2136ce\",\"token\": \"keyword.operator.arithmetic\"},\n    {\"foreground\": \"3759ff\",\"background\": \"00009a33\",\"token\": \"keyword.operator.logical\"},\n    {\"foreground\": \"7c88ff\",\"token\": \"keyword.operator.comparison\"},\n    {\"foreground\": \"800043\",\"token\": \"meta.class.instance.constructor keyword.operator.new\"},\n    {\"foreground\": \"cccccc\",\"background\": \"333333\",\"token\": \"meta.doctype\"},\n    {\"foreground\": \"cccccc\",\"background\": \"333333\",\"token\": \"meta.tag.sgml-declaration.doctype\"},\n    {\"foreground\": \"cccccc\",\"background\": \"333333\",\"token\": \"meta.tag.sgml.doctype\"},\n    {\"foreground\": \"333333\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"666666\",\"background\": \"333333bf\",\"token\": \"meta.tag.structure\"},\n    {\"foreground\": \"666666\",\"background\": \"333333bf\",\"token\": \"meta.tag.segment\"},\n    {\"foreground\": \"4c4c4c\",\"background\": \"4c4c4c33\",\"token\": \"meta.tag.block\"},\n    {\"foreground\": \"4c4c4c\",\"background\": \"4c4c4c33\",\"token\": \"meta.tag.xml\"},\n    {\"foreground\": \"4c4c4c\",\"background\": \"4c4c4c33\",\"token\": \"meta.tag.key\"},\n    {\"foreground\": \"ff7900\",\"background\": \"803d0033\",\"token\": \"meta.tag.inline\"},\n    {\"background\": \"803d0033\",\"token\": \"meta.tag.inline source\"},\n    {\"foreground\": \"ff0007\",\"background\": \"80000433\",\"token\": \"meta.tag.other\"},\n    {\"foreground\": \"ff0007\",\"background\": \"80000433\",\"token\": \"entity.name.tag.style\"},\n    {\"foreground\": \"ff0007\",\"background\": \"80000433\",\"token\": \"entity.name.tag.script\"},\n    {\"foreground\": \"ff0007\",\"background\": \"80000433\",\"token\": \"meta.tag.block.script\"},\n    {\"foreground\": \"ff0007\",\"background\": \"80000433\",\"token\": \"source.js.embedded punctuation.definition.tag.html\"},\n    {\"foreground\": \"ff0007\",\"background\": \"80000433\",\"token\": \"source.css.embedded punctuation.definition.tag.html\"},\n    {\"foreground\": \"0086ff\",\"background\": \"00438033\",\"token\": \"meta.tag.form\"},\n    {\"foreground\": \"0086ff\",\"background\": \"00438033\",\"token\": \"meta.tag.block.form\"},\n    {\"foreground\": \"f800ff\",\"background\": \"3c008033\",\"token\": \"meta.tag.meta\"},\n    {\"background\": \"121212\",\"token\": \"meta.section.html.head\"},\n    {\"background\": \"0043801a\",\"token\": \"meta.section.html.form\"},\n    {\"foreground\": \"666666\",\"token\": \"meta.tag.xml\"},\n    {\"foreground\": \"ffffff4d\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"ffffff33\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"ffffff33\",\"token\": \"meta.tag punctuation.definition.string\"},\n    {\"foreground\": \"ffffff66\",\"token\": \"meta.tag string -source -punctuation\"},\n    {\"foreground\": \"ffffff66\",\"token\": \"text source text meta.tag string -punctuation\"},\n    {\"foreground\": \"999999\",\"token\": \"text meta.paragraph\"},\n    {\"foreground\": \"fff800\",\"background\": \"33333333\",\"token\": \"markup markup -(markup meta.paragraph.list)\"},\n    {\"foreground\": \"000000\",\"background\": \"ffffff\",\"token\": \"markup.hr\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"95d4ff80\",\"fontStyle\": \"bold\",\"token\": \"markup.bold\"},\n    {\"fontStyle\": \"italic\",\"token\": \"markup.italic\"},\n    {\"fontStyle\": \"underline\",\"token\": \"markup.underline\"},\n    {\"foreground\": \"0086ff\",\"token\": \"meta.reference\"},\n    {\"foreground\": \"0086ff\",\"token\": \"markup.underline.link\"},\n    {\"foreground\": \"00fff8\",\"background\": \"00438033\",\"token\": \"entity.name.reference\"},\n    {\"foreground\": \"00fff8\",\"fontStyle\": \"underline\",\"token\": \"meta.reference.list markup.underline.link\"},\n    {\"foreground\": \"00fff8\",\"fontStyle\": \"underline\",\"token\": \"text.html.textile markup.underline.link\"},\n    {\"background\": \"80808040\",\"token\": \"markup.raw.block\"},\n    {\"background\": \"ffffff1a\",\"token\": \"markup.quote\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.list meta.paragraph\"},\n    {\"foreground\": \"000000\",\"background\": \"ffffff\",\"token\": \"text.html.markdown\"},\n    {\"foreground\": \"000000\",\"token\": \"text.html.markdown meta.paragraph\"},\n    {\"foreground\": \"555555\",\"token\": \"text.html.markdown markup.list meta.paragraph\"},\n    {\"foreground\": \"000000\",\"fontStyle\": \"bold\",\"token\": \"text.html.markdown markup.heading\"},\n    {\"foreground\": \"8a5420\",\"token\": \"text.html.markdown string\"},\n    {\"foreground\": \"666666\",\"token\": \"meta.selector\"},\n    {\"foreground\": \"006680\",\"token\": \"source.css meta.scope.property-list meta.property-value punctuation.definition.arguments\"},\n    {\"foreground\": \"006680\",\"token\": \"source.css meta.scope.property-list meta.property-value punctuation.separator.arguments\"},\n    {\"foreground\": \"4f00ff\",\"token\": \"entity.other.attribute-name.pseudo-element\"},\n    {\"foreground\": \"7900ff\",\"token\": \"entity.other.attribute-name.pseudo-class\"},\n    {\"foreground\": \"7900ff\",\"token\": \"entity.other.attribute-name.tag.pseudo-class\"},\n    {\"foreground\": \"f800ff\",\"token\": \"meta.selector entity.other.attribute-name.class\"},\n    {\"foreground\": \"ff0086\",\"token\": \"meta.selector entity.other.attribute-name.id\"},\n    {\"foreground\": \"ff0007\",\"token\": \"meta.selector entity.name.tag\"},\n    {\"foreground\": \"ff7900\",\"fontStyle\": \"bold\",\"token\": \"entity.name.tag.wildcard\"},\n    {\"foreground\": \"ff7900\",\"fontStyle\": \"bold\",\"token\": \"entity.other.attribute-name.universal\"},\n    {\"foreground\": \"c25a00\",\"token\": \"source.css entity.other.attribute-name.attribute\"},\n    {\"foreground\": \"673000\",\"token\": \"source.css meta.attribute-selector keyword.operator.comparison\"},\n    {\"foreground\": \"333333\",\"fontStyle\": \"bold\",\"token\": \"meta.scope.property-list\"},\n    {\"foreground\": \"999999\",\"token\": \"meta.property-name\"},\n    {\"foreground\": \"ffffff\",\"background\": \"0d0d0d\",\"token\": \"support.type.property-name\"},\n    {\"foreground\": \"999999\",\"background\": \"19191980\",\"token\": \"meta.property-value\"},\n    {\"background\": \"000000\",\"token\": \"text.latex markup.raw\"},\n    {\"foreground\": \"bc80ff\",\"token\": \"text.latex support.function -support.function.textit -support.function.emph\"},\n    {\"foreground\": \"ffffffbf\",\"token\": \"text.latex support.function.section\"},\n    {\"foreground\": \"000000\",\"background\": \"ffffff\",\"token\": \"text.latex entity.name.section -meta.group -keyword.operator.braces\"},\n    {\"background\": \"00000080\",\"token\": \"text.latex keyword.operator.delimiter\"},\n    {\"foreground\": \"999999\",\"token\": \"text.latex keyword.operator.brackets\"},\n    {\"foreground\": \"666666\",\"token\": \"text.latex keyword.operator.braces\"},\n    {\"foreground\": \"0008ff4d\",\"background\": \"00008033\",\"token\": \"meta.footnote\"},\n    {\"background\": \"ffffff0d\",\"token\": \"text.latex meta.label.reference\"},\n    {\"foreground\": \"ff0007\",\"background\": \"260001\",\"token\": \"text.latex keyword.control.ref\"},\n    {\"foreground\": \"ffbc80\",\"background\": \"400002\",\"token\": \"text.latex variable.parameter.label.reference\"},\n    {\"foreground\": \"ff0086\",\"background\": \"260014\",\"token\": \"text.latex keyword.control.cite\"},\n    {\"foreground\": \"ffbfe1\",\"background\": \"400022\",\"token\": \"variable.parameter.cite\"},\n    {\"foreground\": \"ffffff80\",\"token\": \"text.latex variable.parameter.label\"},\n    {\"foreground\": \"cdcdcd\",\"token\": \"meta.function markup\"},\n    {\"foreground\": \"33333333\",\"token\": \"text.latex meta.group.braces\"},\n    {\"foreground\": \"33333333\",\"background\": \"00000080\",\"token\": \"text.latex meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"background\": \"00000080\",\"token\": \"text.latex meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"background\": \"000000\",\"token\": \"text.latex meta.environment.list meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"token\": \"text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"token\": \"text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"token\": \"text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"000000\",\"background\": \"cccccc\",\"token\": \"text.latex meta.end-document\"},\n    {\"foreground\": \"000000\",\"background\": \"cccccc\",\"token\": \"text.latex meta.begin-document\"},\n    {\"foreground\": \"000000\",\"background\": \"cccccc\",\"token\": \"meta.end-document.latex support.function\"},\n    {\"foreground\": \"000000\",\"background\": \"cccccc\",\"token\": \"meta.end-document.latex variable.parameter\"},\n    {\"foreground\": \"000000\",\"background\": \"cccccc\",\"token\": \"meta.begin-document.latex support.function\"},\n    {\"foreground\": \"000000\",\"background\": \"cccccc\",\"token\": \"meta.begin-document.latex variable.parameter\"},\n    {\"foreground\": \"00ffaa\",\"background\": \"00805533\",\"token\": \"meta.brace.erb.return-value\"},\n    {\"background\": \"8080801a\",\"token\": \"source.ruby.rails.embedded.return-value.one-line\"},\n    {\"foreground\": \"00fff8\",\"background\": \"00fff81a\",\"token\": \"punctuation.section.embedded -(source string source punctuation.section.embedded)\"},\n    {\"foreground\": \"00fff8\",\"background\": \"00fff81a\",\"token\": \"meta.brace.erb.html\"},\n    {\"background\": \"00fff81a\",\"token\": \"source.ruby.rails.embedded.one-line\"},\n    {\"foreground\": \"406180\",\"token\": \"source string source punctuation.section.embedded\"},\n    {\"background\": \"0d0d0d\",\"token\": \"source.js.embedded\"},\n    {\"background\": \"000000\",\"token\": \"meta.brace.erb\"},\n    {\"foreground\": \"ffffff\",\"background\": \"33333380\",\"token\": \"source string source\"},\n    {\"foreground\": \"999999\",\"background\": \"00000099\",\"token\": \"source string.interpolated source\"},\n    {\"background\": \"3333331a\",\"token\": \"source source\"},\n    {\"background\": \"3333331a\",\"token\": \"source.java.embedded\"},\n    {\"foreground\": \"ffffff\",\"token\": \"text -text.xml.strict\"},\n    {\"foreground\": \"cccccc\",\"background\": \"000000\",\"token\": \"text source\"},\n    {\"foreground\": \"cccccc\",\"background\": \"000000\",\"token\": \"meta.scope.django.template\"},\n    {\"foreground\": \"999999\",\"token\": \"text string source\"},\n    {\"foreground\": \"330004\",\"background\": \"ff0007\",\"fontStyle\": \"bold\",\"token\": \"invalid -invalid.SOMETHING\"},\n    {\"foreground\": \"ff3600\",\"fontStyle\": \"underline\",\"token\": \"invalid.SOMETHING\"},\n    {\"foreground\": \"333333\",\"token\": \"meta.syntax\"},\n    {\"foreground\": \"4c4c4c\",\"background\": \"33333333\",\"token\": \"comment -comment.line\"},\n    {\"foreground\": \"4c4c4c\",\"fontStyle\": \"italic\",\"token\": \"comment.line\"},\n    {\"fontStyle\": \"italic\",\"token\": \"text comment.block -source\"},\n    {\"foreground\": \"40ff9a\",\"background\": \"00401e\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"ff40a3\",\"background\": \"400022\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"ffff55\",\"background\": \"803d00\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"ffffff\",\"background\": \"000000\",\"token\": \"text.subversion-commit meta.scope.changed-files\"},\n    {\"foreground\": \"ffffff\",\"background\": \"000000\",\"token\": \"text.subversion-commit meta.scope.changed-files.svn meta.diff.separator\"},\n    {\"foreground\": \"000000\",\"background\": \"ffffff\",\"token\": \"text.subversion-commit\"},\n    {\"foreground\": \"7f7f7f\",\"background\": \"ffffff03\",\"fontStyle\": \"bold\",\"token\": \"punctuation.terminator\"},\n    {\"foreground\": \"7f7f7f\",\"background\": \"ffffff03\",\"fontStyle\": \"bold\",\"token\": \"meta.delimiter\"},\n    {\"foreground\": \"7f7f7f\",\"background\": \"ffffff03\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.method\"},\n    {\"background\": \"00000080\",\"token\": \"punctuation.terminator.statement\"},\n    {\"background\": \"00000080\",\"token\": \"meta.delimiter.statement.js\"},\n    {\"background\": \"00000040\",\"token\": \"meta.delimiter.object.js\"},\n    {\"foreground\": \"803d00\",\"fontStyle\": \"bold\",\"token\": \"string.quoted.single.brace\"},\n    {\"foreground\": \"803d00\",\"fontStyle\": \"bold\",\"token\": \"string.quoted.double.brace\"},\n    {\"foreground\": \"333333\",\"background\": \"dcdcdc\",\"token\": \"text.blog\"},\n    {\"foreground\": \"333333\",\"background\": \"dcdcdc\",\"token\": \"text.mail\"},\n    {\"foreground\": \"cccccc\",\"background\": \"000000\",\"token\": \"text.blog text\"},\n    {\"foreground\": \"cccccc\",\"background\": \"000000\",\"token\": \"text.mail text\"},\n    {\"foreground\": \"06403e\",\"background\": \"00fff81a\",\"token\": \"meta.header.blog keyword.other\"},\n    {\"foreground\": \"06403e\",\"background\": \"00fff81a\",\"token\": \"meta.header.mail keyword.other\"},\n    {\"foreground\": \"803d00\",\"background\": \"ffff551a\",\"token\": \"meta.header.blog string.unquoted.blog\"},\n    {\"foreground\": \"803d00\",\"background\": \"ffff551a\",\"token\": \"meta.header.mail string.unquoted\"},\n    {\"foreground\": \"ff0000\",\"token\": \"source.ocaml entity.name.type.module\"},\n    {\"foreground\": \"ff0000\",\"background\": \"83000033\",\"token\": \"source.ocaml support.other.module\"},\n    {\"foreground\": \"00fff8\",\"token\": \"entity.name.type.variant\"},\n    {\"foreground\": \"00ff79\",\"token\": \"source.ocaml entity.name.tag\"},\n    {\"foreground\": \"00ff79\",\"token\": \"source.ocaml meta.record.definition\"},\n    {\"foreground\": \"ffffff\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.parameters\"},\n    {\"foreground\": \"4c4c4c\",\"background\": \"33333333\",\"token\": \"meta.brace.pipe\"},\n    {\"foreground\": \"666666\",\"fontStyle\": \"bold\",\"token\": \"meta.brace.erb\"},\n    {\"foreground\": \"666666\",\"fontStyle\": \"bold\",\"token\": \"source.ruby.embedded.source.brace\"},\n    {\"foreground\": \"666666\",\"fontStyle\": \"bold\",\"token\": \"punctuation.section.dictionary\"},\n    {\"foreground\": \"666666\",\"fontStyle\": \"bold\",\"token\": \"punctuation.terminator.dictionary\"},\n    {\"foreground\": \"666666\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.object\"},\n    {\"foreground\": \"666666\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.statement\"},\n    {\"foreground\": \"666666\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.key-value.css\"},\n    {\"foreground\": \"999999\",\"fontStyle\": \"bold\",\"token\": \"punctuation.section.scope.curly\"},\n    {\"foreground\": \"999999\",\"fontStyle\": \"bold\",\"token\": \"punctuation.section.scope\"},\n    {\"foreground\": \"0c823b\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.objects\"},\n    {\"foreground\": \"0c823b\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.curly meta.delimiter.object.comma\"},\n    {\"foreground\": \"0c823b\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.key-value -meta.tag\"},\n    {\"foreground\": \"0c823b\",\"fontStyle\": \"bold\",\"token\": \"source.ocaml punctuation.separator.match-definition\"},\n    {\"foreground\": \"800043\",\"token\": \"punctuation.separator.parameters.function.js\"},\n    {\"foreground\": \"800043\",\"token\": \"punctuation.definition.function\"},\n    {\"foreground\": \"800043\",\"token\": \"punctuation.separator.function-return\"},\n    {\"foreground\": \"800043\",\"token\": \"punctuation.separator.function-definition\"},\n    {\"foreground\": \"800043\",\"token\": \"punctuation.definition.arguments\"},\n    {\"foreground\": \"800043\",\"token\": \"punctuation.separator.arguments\"},\n    {\"foreground\": \"7f5e40\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.square punctuation.section.scope\"},\n    {\"foreground\": \"7f5e40\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.square meta.delimiter.object.comma\"},\n    {\"foreground\": \"7f5e40\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"meta.brace.square\"},\n    {\"foreground\": \"7f5e40\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.array\"},\n    {\"foreground\": \"7f5e40\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"punctuation.section.array\"},\n    {\"foreground\": \"7f5e40\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"punctuation.definition.array\"},\n    {\"foreground\": \"7f5e40\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"punctuation.definition.constant.range\"},\n    {\"background\": \"803d001a\",\"token\": \"meta.structure.array -punctuation.definition.array\"},\n    {\"background\": \"803d001a\",\"token\": \"meta.definition.range -punctuation.definition.constant.range\"},\n    {\"background\": \"00000080\",\"token\": \"meta.brace.curly meta.group.css\"},\n    {\"foreground\": \"666666\",\"background\": \"00000080\",\"token\": \"meta.source.embedded\"},\n    {\"foreground\": \"666666\",\"background\": \"00000080\",\"token\": \"entity.other.django.tagbraces\"},\n    {\"background\": \"00000080\",\"token\": \"source.ruby meta.even-tab\"},\n    {\"background\": \"00000080\",\"token\": \"source.ruby meta.even-tab.group2\"},\n    {\"background\": \"00000080\",\"token\": \"source.ruby meta.even-tab.group4\"},\n    {\"background\": \"00000080\",\"token\": \"source.ruby meta.even-tab.group6\"},\n    {\"background\": \"00000080\",\"token\": \"source.ruby meta.even-tab.group8\"},\n    {\"background\": \"00000080\",\"token\": \"source.ruby meta.even-tab.group10\"},\n    {\"background\": \"00000080\",\"token\": \"source.ruby meta.even-tab.group12\"},\n    {\"foreground\": \"666666\",\"token\": \"meta.block.slate\"},\n    {\"foreground\": \"cccccc\",\"token\": \"meta.block.content.slate\"},\n    {\"background\": \"0a0a0a\",\"token\": \"meta.odd-tab.group1\"},\n    {\"background\": \"0a0a0a\",\"token\": \"meta.group.braces\"},\n    {\"background\": \"0a0a0a\",\"token\": \"meta.block.slate\"},\n    {\"background\": \"0a0a0a\",\"token\": \"text.xml.strict meta.tag\"},\n    {\"background\": \"0a0a0a\",\"token\": \"meta.paren-group\"},\n    {\"background\": \"0a0a0a\",\"token\": \"meta.section\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.even-tab.group2\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.group.braces meta.group.braces\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.block.slate meta.block.slate\"},\n    {\"background\": \"0e0e0e\",\"token\": \"text.xml.strict meta.tag meta.tag\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.group.braces meta.group.braces\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.paren-group meta.paren-group\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.section meta.section\"},\n    {\"background\": \"111111\",\"token\": \"meta.odd-tab.group3\"},\n    {\"background\": \"111111\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"111111\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"111111\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag\"},\n    {\"background\": \"111111\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"111111\",\"token\": \"meta.paren-group meta.paren-group meta.paren-group\"},\n    {\"background\": \"111111\",\"token\": \"meta.section meta.section meta.section\"},\n    {\"background\": \"151515\",\"token\": \"meta.even-tab.group4\"},\n    {\"background\": \"151515\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"151515\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"151515\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"151515\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"151515\",\"token\": \"meta.paren-group meta.paren-group meta.paren-group meta.paren-group\"},\n    {\"background\": \"151515\",\"token\": \"meta.section meta.section meta.section meta.section\"},\n    {\"background\": \"191919\",\"token\": \"meta.odd-tab.group5\"},\n    {\"background\": \"191919\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"191919\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"191919\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"191919\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"191919\",\"token\": \"meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group\"},\n    {\"background\": \"191919\",\"token\": \"meta.section meta.section meta.section meta.section meta.section\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.even-tab.group6\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"1c1c1c\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.section meta.section meta.section meta.section meta.section meta.section\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.odd-tab.group7\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"1f1f1f\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.section meta.section meta.section meta.section meta.section meta.section meta.section\"},\n    {\"background\": \"212121\",\"token\": \"meta.even-tab.group8\"},\n    {\"background\": \"212121\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"212121\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"212121\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"212121\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"212121\",\"token\": \"meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group\"},\n    {\"background\": \"212121\",\"token\": \"meta.section meta.section meta.section meta.section meta.section meta.section meta.section meta.section\"},\n    {\"background\": \"242424\",\"token\": \"meta.odd-tab.group9\"},\n    {\"background\": \"242424\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"242424\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"242424\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"242424\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"242424\",\"token\": \"meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group\"},\n    {\"background\": \"242424\",\"token\": \"meta.section meta.section meta.section meta.section meta.section meta.section meta.section meta.section meta.section\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.even-tab.group10\"},\n    {\"background\": \"151515\",\"token\": \"meta.odd-tab.group11\"},\n    {\"foreground\": \"1b95e2\",\"token\": \"meta.property.vendor.microsoft.trident.4\"},\n    {\"foreground\": \"1b95e2\",\"token\": \"meta.property.vendor.microsoft.trident.4 support.type.property-name\"},\n    {\"foreground\": \"1b95e2\",\"token\": \"meta.property.vendor.microsoft.trident.4 punctuation.terminator.rule\"},\n    {\"foreground\": \"f5c034\",\"token\": \"meta.property.vendor.microsoft.trident.5\"},\n    {\"foreground\": \"f5c034\",\"token\": \"meta.property.vendor.microsoft.trident.5 support.type.property-name\"},\n    {\"foreground\": \"f5c034\",\"token\": \"meta.property.vendor.microsoft.trident.5 punctuation.separator.key-value\"},\n    {\"foreground\": \"f5c034\",\"token\": \"meta.property.vendor.microsoft.trident.5 punctuation.terminator.rule\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#EEEEEE\",\n\t\t\"editor.background\": \"#0D0D0DFA\",\n\t\t\"editor.selectionBackground\": \"#0010B499\",\n\t\t\"editor.lineHighlightBackground\": \"#00008033\",\n\t\t\"editorCursor.foreground\": \"#3333FF\",\n\t\t\"editorWhitespace.foreground\": \"#CCCCCC1A\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet brillianceDull = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"050505FA\",\"token\": \"\"},\n    {\"foreground\": \"000000\",\"background\": \"ffffff\",\"fontStyle\": \"bold\",\"token\": \"meta.thomas_aylott\"},\n    {\"foreground\": \"555555\",\"background\": \"ffffff\",\"fontStyle\": \"underline\",\"token\": \"meta.subtlegradient\"},\n    {\"foreground\": \"e6e6e6\",\"background\": \"ffffff\",\"token\": \"meta.subtlegradient\"},\n    {\"foreground\": \"d2d1ab\",\"background\": \"803d0033\",\"token\": \"string -meta.tag -meta.doctype -string.regexp -string.literal -string.interpolated -string.quoted.literal -string.unquoted\"},\n    {\"foreground\": \"d2d1ab\",\"background\": \"803d0033\",\"token\": \"variable.parameter.misc.css\"},\n    {\"foreground\": \"d2d1ab\",\"background\": \"803d0033\",\"token\": \"text string source string\"},\n    {\"foreground\": \"d2d1ab\",\"background\": \"803d0033\",\"token\": \"string.unquoted string\"},\n    {\"foreground\": \"d2d1ab\",\"background\": \"803d0033\",\"token\": \"string.regexp string\"},\n    {\"foreground\": \"533f2c\",\"token\": \"punctuation.definition.string -meta.tag\"},\n    {\"foreground\": \"fff80033\",\"token\": \"string.regexp punctuation.definition.string\"},\n    {\"foreground\": \"fff80033\",\"token\": \"string.quoted.literal punctuation.definition.string\"},\n    {\"foreground\": \"fff80033\",\"token\": \"string.quoted.double.ruby.mod punctuation.definition.string\"},\n    {\"foreground\": \"a6a458\",\"background\": \"43800033\",\"token\": \"string.quoted.literal\"},\n    {\"foreground\": \"a6a458\",\"background\": \"43800033\",\"token\": \"string.quoted.double.ruby.mod\"},\n    {\"foreground\": \"d2beab\",\"token\": \"string.unquoted -string.unquoted.embedded\"},\n    {\"foreground\": \"d2beab\",\"token\": \"string.quoted.double.multiline\"},\n    {\"foreground\": \"d2beab\",\"token\": \"meta.scope.heredoc\"},\n    {\"foreground\": \"d2d1ab\",\"background\": \"1a1a1a\",\"token\": \"string.interpolated\"},\n    {\"foreground\": \"a6a458\",\"background\": \"43800033\",\"token\": \"string.regexp\"},\n    {\"background\": \"43800033\",\"token\": \"string.regexp.group\"},\n    {\"foreground\": \"ffffff66\",\"background\": \"43800033\",\"token\": \"string.regexp.group string.regexp.group\"},\n    {\"foreground\": \"ffffff66\",\"background\": \"43800033\",\"token\": \"string.regexp.group string.regexp.group string.regexp.group\"},\n    {\"foreground\": \"ffffff66\",\"background\": \"43800033\",\"token\": \"string.regexp.group string.regexp.group string.regexp.group string.regexp.group\"},\n    {\"foreground\": \"80a659\",\"background\": \"43800033\",\"token\": \"string.regexp.character-class\"},\n    {\"foreground\": \"56a5a4\",\"background\": \"43800033\",\"token\": \"string.regexp.arbitrary-repitition\"},\n    {\"foreground\": \"a75980\",\"token\": \"source.regexp keyword.operator\"},\n    {\"foreground\": \"ffffff\",\"fontStyle\": \"italic\",\"token\": \"string.regexp comment\"},\n    {\"background\": \"0086ff33\",\"token\": \"meta.group.assertion.regexp\"},\n    {\"foreground\": \"5780a6\",\"token\": \"meta.assertion\"},\n    {\"foreground\": \"5780a6\",\"token\": \"meta.group.assertion keyword.control.group.regexp\"},\n    {\"foreground\": \"95a658\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"80a659\",\"token\": \"constant.character\"},\n    {\"foreground\": \"59a559\",\"token\": \"constant.language\"},\n    {\"foreground\": \"59a559\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"59a559\",\"token\": \"constant.other.java\"},\n    {\"foreground\": \"59a559\",\"token\": \"constant.other.unit\"},\n    {\"foreground\": \"59a559\",\"background\": \"04800033\",\"token\": \"constant.language.pseudo-variable\"},\n    {\"foreground\": \"57a57d\",\"token\": \"constant.other\"},\n    {\"foreground\": \"57a57d\",\"token\": \"constant.block\"},\n    {\"foreground\": \"56a5a4\",\"token\": \"support.constant\"},\n    {\"foreground\": \"56a5a4\",\"token\": \"constant.name\"},\n    {\"foreground\": \"5e6b6b\",\"token\": \"variable.language\"},\n    {\"foreground\": \"5e6b6b\",\"token\": \"variable.other.readwrite.global.pre-defined\"},\n    {\"foreground\": \"56a5a4\",\"token\": \"variable.other.constant\"},\n    {\"foreground\": \"56a5a4\",\"background\": \"00807c33\",\"token\": \"support.variable\"},\n    {\"foreground\": \"2b5252\",\"background\": \"00438033\",\"token\": \"variable.other.readwrite.global\"},\n    {\"foreground\": \"5780a6\",\"token\": \"variable.other\"},\n    {\"foreground\": \"5780a6\",\"token\": \"variable.js\"},\n    {\"foreground\": \"5780a6\",\"background\": \"0007ff33\",\"token\": \"variable.other.readwrite.class\"},\n    {\"foreground\": \"555f69\",\"token\": \"variable.other.readwrite.instance\"},\n    {\"foreground\": \"555f69\",\"token\": \"variable.other.php\"},\n    {\"foreground\": \"555f69\",\"token\": \"variable.other.normal\"},\n    {\"foreground\": \"00000080\",\"token\": \"punctuation.definition -punctuation.definition.comment\"},\n    {\"foreground\": \"00000080\",\"token\": \"punctuation.separator.variable\"},\n    {\"foreground\": \"a77d58\",\"token\": \"storage -storage.modifier\"},\n    {\"background\": \"803d0033\",\"token\": \"other.preprocessor\"},\n    {\"background\": \"803d0033\",\"token\": \"entity.name.preprocessor\"},\n    {\"foreground\": \"666666\",\"token\": \"variable.language.this.js\"},\n    {\"foreground\": \"533f2c\",\"token\": \"storage.modifier\"},\n    {\"foreground\": \"a7595a\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"a7595a\",\"token\": \"entity.name.type.class\"},\n    {\"foreground\": \"a7595a\",\"token\": \"entity.name.type.module\"},\n    {\"foreground\": \"532d2d\",\"background\": \"29161780\",\"token\": \"meta.class -meta.class.instance\"},\n    {\"foreground\": \"532d2d\",\"background\": \"29161780\",\"token\": \"declaration.class\"},\n    {\"foreground\": \"532d2d\",\"background\": \"29161780\",\"token\": \"meta.definition.class\"},\n    {\"foreground\": \"532d2d\",\"background\": \"29161780\",\"token\": \"declaration.module\"},\n    {\"foreground\": \"a7595a\",\"background\": \"80000433\",\"token\": \"support.type\"},\n    {\"foreground\": \"a7595a\",\"background\": \"80000433\",\"token\": \"support.class\"},\n    {\"foreground\": \"a7595a\",\"token\": \"entity.name.instance\"},\n    {\"background\": \"80004333\",\"token\": \"meta.class.instance.constructor\"},\n    {\"foreground\": \"a75980\",\"background\": \"80000433\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"a75980\",\"background\": \"80000433\",\"token\": \"entity.name.module\"},\n    {\"foreground\": \"a75980\",\"token\": \"object.property.function\"},\n    {\"foreground\": \"a75980\",\"token\": \"meta.definition.method\"},\n    {\"foreground\": \"532d40\",\"background\": \"80004333\",\"token\": \"meta.function -(meta.tell-block)\"},\n    {\"foreground\": \"532d40\",\"background\": \"80004333\",\"token\": \"meta.property.function\"},\n    {\"foreground\": \"532d40\",\"background\": \"80004333\",\"token\": \"declaration.function\"},\n    {\"foreground\": \"a75980\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"a75980\",\"token\": \"entity.name.preprocessor\"},\n    {\"foreground\": \"a459a5\",\"token\": \"keyword\"},\n    {\"foreground\": \"a459a5\",\"background\": \"3c008033\",\"token\": \"keyword.control\"},\n    {\"foreground\": \"8d809d\",\"token\": \"keyword.other.special-method\"},\n    {\"foreground\": \"8d809d\",\"token\": \"meta.function-call entity.name.function -(meta.function-call meta.function)\"},\n    {\"foreground\": \"8d809d\",\"token\": \"support.function - variable\"},\n    {\"foreground\": \"634683\",\"token\": \"support.function - variable\"},\n    {\"foreground\": \"7979b7\",\"fontStyle\": \"bold\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"7979b7\",\"fontStyle\": \"bold\",\"token\": \"declaration.function.operator\"},\n    {\"foreground\": \"7979b7\",\"fontStyle\": \"bold\",\"token\": \"meta.preprocessor.c.include\"},\n    {\"foreground\": \"9899c8\",\"token\": \"keyword.operator.comparison\"},\n    {\"foreground\": \"abacd2\",\"background\": \"3c008033\",\"token\": \"variable.parameter -variable.parameter.misc.css\"},\n    {\"foreground\": \"abacd2\",\"background\": \"3c008033\",\"token\": \"meta.definition.method  meta.definition.param-list\"},\n    {\"foreground\": \"abacd2\",\"background\": \"3c008033\",\"token\": \"meta.function.method.with-arguments variable.parameter.function\"},\n    {\"foreground\": \"cdcdcd\",\"background\": \"333333\",\"token\": \"meta.doctype\"},\n    {\"foreground\": \"cdcdcd\",\"background\": \"333333\",\"token\": \"meta.tag.sgml-declaration.doctype\"},\n    {\"foreground\": \"cdcdcd\",\"background\": \"333333\",\"token\": \"meta.tag.sgml.doctype\"},\n    {\"foreground\": \"333333\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"666666\",\"background\": \"333333bf\",\"token\": \"meta.tag.structure\"},\n    {\"foreground\": \"666666\",\"background\": \"333333bf\",\"token\": \"meta.tag.segment\"},\n    {\"foreground\": \"4c4c4c\",\"background\": \"4c4c4c33\",\"token\": \"meta.tag.block\"},\n    {\"foreground\": \"4c4c4c\",\"background\": \"4c4c4c33\",\"token\": \"meta.tag.xml\"},\n    {\"foreground\": \"4c4c4c\",\"background\": \"4c4c4c33\",\"token\": \"meta.tag.key\"},\n    {\"foreground\": \"a77d58\",\"background\": \"803d0033\",\"token\": \"meta.tag.inline\"},\n    {\"background\": \"803d0033\",\"token\": \"meta.tag.inline source\"},\n    {\"foreground\": \"a7595a\",\"background\": \"80000433\",\"token\": \"meta.tag.other\"},\n    {\"foreground\": \"a7595a\",\"background\": \"80000433\",\"token\": \"entity.name.tag.style\"},\n    {\"foreground\": \"a7595a\",\"background\": \"80000433\",\"token\": \"source entity.other.attribute-name -text.html.basic.embedded\"},\n    {\"foreground\": \"a7595a\",\"background\": \"80000433\",\"token\": \"entity.name.tag.script\"},\n    {\"foreground\": \"a7595a\",\"background\": \"80000433\",\"token\": \"meta.tag.block.script\"},\n    {\"foreground\": \"5780a6\",\"background\": \"00438033\",\"token\": \"meta.tag.form\"},\n    {\"foreground\": \"5780a6\",\"background\": \"00438033\",\"token\": \"meta.tag.block.form\"},\n    {\"foreground\": \"a459a5\",\"background\": \"3c008033\",\"token\": \"meta.tag.meta\"},\n    {\"background\": \"121212\",\"token\": \"meta.section.html.head\"},\n    {\"background\": \"0043801a\",\"token\": \"meta.section.html.form\"},\n    {\"foreground\": \"666666\",\"token\": \"meta.tag.xml\"},\n    {\"foreground\": \"ffffff4d\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"ffffff33\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"ffffff33\",\"token\": \"meta.tag punctuation.definition.string\"},\n    {\"foreground\": \"ffffff66\",\"token\": \"meta.tag string -source -punctuation\"},\n    {\"foreground\": \"ffffff66\",\"token\": \"text source text meta.tag string -punctuation\"},\n    {\"foreground\": \"a6a458\",\"background\": \"33333333\",\"token\": \"markup markup -(markup meta.paragraph.list)\"},\n    {\"foreground\": \"000000\",\"background\": \"ffffff\",\"token\": \"markup.hr\"},\n    {\"foreground\": \"666666\",\"background\": \"33333380\",\"token\": \"markup.heading\"},\n    {\"fontStyle\": \"bold\",\"token\": \"markup.bold\"},\n    {\"fontStyle\": \"italic\",\"token\": \"markup.italic\"},\n    {\"fontStyle\": \"underline\",\"token\": \"markup.underline\"},\n    {\"foreground\": \"5780a6\",\"token\": \"meta.reference\"},\n    {\"foreground\": \"5780a6\",\"token\": \"markup.underline.link\"},\n    {\"foreground\": \"56a5a4\",\"background\": \"00438033\",\"token\": \"entity.name.reference\"},\n    {\"foreground\": \"56a5a4\",\"fontStyle\": \"underline\",\"token\": \"meta.reference.list markup.underline.link\"},\n    {\"foreground\": \"56a5a4\",\"fontStyle\": \"underline\",\"token\": \"text.html.textile markup.underline.link\"},\n    {\"foreground\": \"999999\",\"background\": \"000000\",\"token\": \"markup.raw.block\"},\n    {\"background\": \"ffffff1a\",\"token\": \"markup.quote\"},\n    {\"foreground\": \"666666\",\"background\": \"00000080\",\"token\": \"meta.selector\"},\n    {\"foreground\": \"575aa6\",\"background\": \"00048033\",\"token\": \"meta.attribute-match.css\"},\n    {\"foreground\": \"7c58a5\",\"token\": \"entity.other.attribute-name.pseudo-class\"},\n    {\"foreground\": \"7c58a5\",\"token\": \"entity.other.attribute-name.tag.pseudo-class\"},\n    {\"foreground\": \"a459a5\",\"token\": \"meta.selector entity.other.attribute-name.class\"},\n    {\"foreground\": \"a75980\",\"token\": \"meta.selector entity.other.attribute-name.id\"},\n    {\"foreground\": \"a7595a\",\"token\": \"meta.selector entity.name.tag\"},\n    {\"foreground\": \"a77d58\",\"fontStyle\": \"bold\",\"token\": \"entity.name.tag.wildcard\"},\n    {\"foreground\": \"a77d58\",\"fontStyle\": \"bold\",\"token\": \"entity.other.attribute-name.universal\"},\n    {\"foreground\": \"333333\",\"fontStyle\": \"bold\",\"token\": \"meta.scope.property-list\"},\n    {\"foreground\": \"999999\",\"token\": \"meta.property-name\"},\n    {\"foreground\": \"ffffff\",\"background\": \"000000\",\"token\": \"support.type.property-name\"},\n    {\"foreground\": \"999999\",\"background\": \"0d0d0d\",\"token\": \"meta.property-value\"},\n    {\"background\": \"000000\",\"token\": \"text.latex markup.raw\"},\n    {\"foreground\": \"bdabd1\",\"token\": \"text.latex support.function -support.function.textit -support.function.emph\"},\n    {\"foreground\": \"ffffffbf\",\"token\": \"text.latex support.function.section\"},\n    {\"foreground\": \"000000\",\"background\": \"ffffff\",\"token\": \"text.latex entity.name.section -meta.group -keyword.operator.braces\"},\n    {\"background\": \"00000080\",\"token\": \"text.latex keyword.operator.delimiter\"},\n    {\"foreground\": \"999999\",\"token\": \"text.latex keyword.operator.brackets\"},\n    {\"foreground\": \"666666\",\"token\": \"text.latex keyword.operator.braces\"},\n    {\"foreground\": \"0008ff4d\",\"background\": \"00048033\",\"token\": \"meta.footnote\"},\n    {\"background\": \"ffffff0d\",\"token\": \"text.latex meta.label.reference\"},\n    {\"foreground\": \"a7595a\",\"background\": \"180d0c\",\"token\": \"text.latex keyword.control.ref\"},\n    {\"foreground\": \"d2beab\",\"background\": \"291616\",\"token\": \"text.latex variable.parameter.label.reference\"},\n    {\"foreground\": \"a75980\",\"background\": \"180d12\",\"token\": \"text.latex keyword.control.cite\"},\n    {\"foreground\": \"e8d5de\",\"background\": \"29161f\",\"token\": \"variable.parameter.cite\"},\n    {\"foreground\": \"ffffff80\",\"token\": \"text.latex variable.parameter.label\"},\n    {\"foreground\": \"33333333\",\"token\": \"text.latex meta.group.braces\"},\n    {\"foreground\": \"33333333\",\"background\": \"00000080\",\"token\": \"text.latex meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"background\": \"00000080\",\"token\": \"text.latex meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"background\": \"000000\",\"token\": \"text.latex meta.environment.list meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"token\": \"text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"token\": \"text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"33333333\",\"token\": \"text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list\"},\n    {\"foreground\": \"000000\",\"background\": \"cdcdcd\",\"token\": \"text.latex meta.end-document\"},\n    {\"foreground\": \"000000\",\"background\": \"cdcdcd\",\"token\": \"text.latex meta.begin-document\"},\n    {\"foreground\": \"000000\",\"background\": \"cdcdcd\",\"token\": \"meta.end-document.latex support.function\"},\n    {\"foreground\": \"000000\",\"background\": \"cdcdcd\",\"token\": \"meta.end-document.latex variable.parameter\"},\n    {\"foreground\": \"000000\",\"background\": \"cdcdcd\",\"token\": \"meta.begin-document.latex support.function\"},\n    {\"foreground\": \"000000\",\"background\": \"cdcdcd\",\"token\": \"meta.begin-document.latex variable.parameter\"},\n    {\"foreground\": \"596b61\",\"background\": \"45815d33\",\"token\": \"meta.brace.erb.return-value\"},\n    {\"background\": \"66666633\",\"token\": \"source.ruby.rails.embedded.return-value.one-line\"},\n    {\"foreground\": \"56a5a4\",\"background\": \"00fff81a\",\"token\": \"punctuation.section.embedded -(source string source punctuation.section.embedded)\"},\n    {\"foreground\": \"56a5a4\",\"background\": \"00fff81a\",\"token\": \"meta.brace.erb.html\"},\n    {\"background\": \"00fff81a\",\"token\": \"source.ruby.rails.embedded.one-line\"},\n    {\"foreground\": \"555f69\",\"token\": \"source string source punctuation.section.embedded\"},\n    {\"background\": \"000000\",\"token\": \"source\"},\n    {\"background\": \"000000\",\"token\": \"meta.brace.erb\"},\n    {\"foreground\": \"ffffff\",\"background\": \"33333380\",\"token\": \"source string source\"},\n    {\"foreground\": \"999999\",\"background\": \"00000099\",\"token\": \"source string.interpolated source\"},\n    {\"background\": \"3333331a\",\"token\": \"source.java.embedded\"},\n    {\"foreground\": \"ffffff\",\"token\": \"text -text.xml.strict\"},\n    {\"foreground\": \"cccccc\",\"background\": \"000000\",\"token\": \"text source\"},\n    {\"foreground\": \"cccccc\",\"background\": \"000000\",\"token\": \"meta.scope.django.template\"},\n    {\"foreground\": \"999999\",\"token\": \"text string source\"},\n    {\"foreground\": \"333333\",\"token\": \"meta.syntax\"},\n    {\"foreground\": \"211211\",\"background\": \"a7595a\",\"fontStyle\": \"bold\",\"token\": \"invalid\"},\n    {\"foreground\": \"8f8fc3\",\"background\": \"0000ff1a\",\"fontStyle\": \"italic\",\"token\": \"0comment\"},\n    {\"foreground\": \"0000ff1a\",\"fontStyle\": \"bold\",\"token\": \"comment punctuation\"},\n    {\"foreground\": \"333333\",\"token\": \"comment\"},\n    {\"foreground\": \"262626\",\"background\": \"8080800d\",\"fontStyle\": \"bold italic\",\"token\": \"comment punctuation\"},\n    {\"fontStyle\": \"italic\",\"token\": \"text comment.block -source\"},\n    {\"foreground\": \"81bb9e\",\"background\": \"15281f\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"bc839f\",\"background\": \"400021\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"c3c38f\",\"background\": \"533f2c\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"ffffff\",\"background\": \"000000\",\"token\": \"text.subversion-commit meta.scope.changed-files\"},\n    {\"foreground\": \"ffffff\",\"background\": \"000000\",\"token\": \"text.subversion-commit meta.scope.changed-files.svn meta.diff.separator\"},\n    {\"foreground\": \"000000\",\"background\": \"ffffff\",\"token\": \"text.subversion-commit\"},\n    {\"foreground\": \"ffffff\",\"background\": \"ffffff03\",\"fontStyle\": \"bold\",\"token\": \"punctuation.terminator\"},\n    {\"foreground\": \"ffffff\",\"background\": \"ffffff03\",\"fontStyle\": \"bold\",\"token\": \"meta.delimiter\"},\n    {\"foreground\": \"ffffff\",\"background\": \"ffffff03\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.method\"},\n    {\"background\": \"000000bf\",\"token\": \"punctuation.terminator.statement\"},\n    {\"background\": \"000000bf\",\"token\": \"meta.delimiter.statement.js\"},\n    {\"background\": \"00000040\",\"token\": \"meta.delimiter.object.js\"},\n    {\"foreground\": \"533f2c\",\"fontStyle\": \"bold\",\"token\": \"string.quoted.single.brace\"},\n    {\"foreground\": \"533f2c\",\"fontStyle\": \"bold\",\"token\": \"string.quoted.double.brace\"},\n    {\"background\": \"ffffff\",\"token\": \"text.blog -(text.blog text)\"},\n    {\"foreground\": \"666666\",\"background\": \"ffffff\",\"token\": \"meta.headers.blog\"},\n    {\"foreground\": \"192b2a\",\"background\": \"00fff81a\",\"token\": \"meta.headers.blog keyword.other.blog\"},\n    {\"foreground\": \"533f2c\",\"background\": \"ffff551a\",\"token\": \"meta.headers.blog string.unquoted.blog\"},\n    {\"foreground\": \"4c4c4c\",\"background\": \"33333333\",\"token\": \"meta.brace.pipe\"},\n    {\"foreground\": \"4c4c4c\",\"fontStyle\": \"bold\",\"token\": \"meta.brace.erb\"},\n    {\"foreground\": \"4c4c4c\",\"fontStyle\": \"bold\",\"token\": \"source.ruby.embedded.source.brace\"},\n    {\"foreground\": \"4c4c4c\",\"fontStyle\": \"bold\",\"token\": \"punctuation.section.dictionary\"},\n    {\"foreground\": \"4c4c4c\",\"fontStyle\": \"bold\",\"token\": \"punctuation.terminator.dictionary\"},\n    {\"foreground\": \"4c4c4c\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.object\"},\n    {\"foreground\": \"ffffff\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.curly punctuation.section.scope\"},\n    {\"foreground\": \"ffffff\",\"fontStyle\": \"bold\",\"token\": \"meta.brace.curly\"},\n    {\"foreground\": \"345743\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.objects\"},\n    {\"foreground\": \"345743\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.curly meta.delimiter.object.comma\"},\n    {\"foreground\": \"345743\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.key-value -meta.tag\"},\n    {\"foreground\": \"695f55\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.square punctuation.section.scope\"},\n    {\"foreground\": \"695f55\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.square meta.delimiter.object.comma\"},\n    {\"foreground\": \"695f55\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"meta.brace.square\"},\n    {\"foreground\": \"695f55\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"punctuation.separator.array\"},\n    {\"foreground\": \"695f55\",\"background\": \"803d001a\",\"fontStyle\": \"bold\",\"token\": \"punctuation.section.array\"},\n    {\"foreground\": \"cdcdcd\",\"background\": \"00000080\",\"token\": \"meta.brace.curly meta.group\"},\n    {\"foreground\": \"532d40\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.round punctuation.section.scope\"},\n    {\"foreground\": \"532d40\",\"fontStyle\": \"bold\",\"token\": \"meta.group.braces.round meta.delimiter.object.comma\"},\n    {\"foreground\": \"532d40\",\"fontStyle\": \"bold\",\"token\": \"meta.brace.round\"},\n    {\"foreground\": \"abacd2\",\"background\": \"3c008033\",\"token\": \"punctuation.section.function\"},\n    {\"foreground\": \"abacd2\",\"background\": \"3c008033\",\"token\": \"meta.brace.curly.function\"},\n    {\"foreground\": \"abacd2\",\"background\": \"3c008033\",\"token\": \"meta.function-call punctuation.section.scope.ruby\"},\n    {\"foreground\": \"666666\",\"background\": \"00000080\",\"token\": \"meta.source.embedded\"},\n    {\"foreground\": \"666666\",\"background\": \"00000080\",\"token\": \"entity.other.django.tagbraces\"},\n    {\"background\": \"0a0a0a\",\"token\": \"meta.odd-tab.group1\"},\n    {\"background\": \"0a0a0a\",\"token\": \"meta.group.braces\"},\n    {\"background\": \"0a0a0a\",\"token\": \"meta.block.slate\"},\n    {\"background\": \"0a0a0a\",\"token\": \"text.xml.strict meta.tag\"},\n    {\"background\": \"0a0a0a\",\"token\": \"meta.tell-block meta.tell-block\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.even-tab.group2\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.group.braces meta.group.braces\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.block.slate meta.block.slate\"},\n    {\"background\": \"0e0e0e\",\"token\": \"text.xml.strict meta.tag meta.tag\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.group.braces meta.group.braces\"},\n    {\"background\": \"0e0e0e\",\"token\": \"meta.tell-block meta.tell-block\"},\n    {\"background\": \"111111\",\"token\": \"meta.odd-tab.group3\"},\n    {\"background\": \"111111\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"111111\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"111111\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag\"},\n    {\"background\": \"111111\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"111111\",\"token\": \"meta.tell-block meta.tell-block meta.tell-block\"},\n    {\"background\": \"151515\",\"token\": \"meta.even-tab.group4\"},\n    {\"background\": \"151515\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"151515\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"151515\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"151515\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"151515\",\"token\": \"meta.tell-block meta.tell-block meta.tell-block meta.tell-block\"},\n    {\"background\": \"191919\",\"token\": \"meta.odd-tab.group5\"},\n    {\"background\": \"191919\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"191919\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"191919\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"191919\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"191919\",\"token\": \"meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.even-tab.group6\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"1c1c1c\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"1c1c1c\",\"token\": \"meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.odd-tab.group7\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"1f1f1f\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"1f1f1f\",\"token\": \"meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block\"},\n    {\"background\": \"212121\",\"token\": \"meta.even-tab.group8\"},\n    {\"background\": \"212121\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"212121\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"212121\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"212121\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"212121\",\"token\": \"meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block\"},\n    {\"background\": \"242424\",\"token\": \"meta.odd-tab.group11\"},\n    {\"background\": \"242424\",\"token\": \"meta.odd-tab.group10\"},\n    {\"background\": \"242424\",\"token\": \"meta.odd-tab.group9\"},\n    {\"background\": \"242424\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"242424\",\"token\": \"meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate\"},\n    {\"background\": \"242424\",\"token\": \"text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag\"},\n    {\"background\": \"242424\",\"token\": \"meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces\"},\n    {\"background\": \"242424\",\"token\": \"meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block\"},\n    {\"foreground\": \"666666\",\"token\": \"meta.block.slate\"},\n    {\"foreground\": \"cdcdcd\",\"token\": \"meta.block.content.slate\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#CDCDCD\",\n\t\t\"editor.background\": \"#050505FA\",\n\t\t\"editor.selectionBackground\": \"#2E2EE64D\",\n\t\t\"editor.lineHighlightBackground\": \"#0000801A\",\n\t\t\"editorCursor.foreground\": \"#7979B7\",\n\t\t\"editorWhitespace.foreground\": \"#CDCDCD1A\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet chromeDevTools = {\n\t\"base\": \"vs\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"FFFFFF\",\"token\": \"\"},\n    {\"foreground\": \"c41a16\",\"token\": \"string\"},\n    {\"foreground\": \"1c00cf\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"aa0d91\",\"token\": \"keyword\"},\n    {\"foreground\": \"000000\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"aa0d91\",\"token\": \"constant.language\"},\n    {\"foreground\": \"990000\",\"token\": \"support.class.exception\"},\n    {\"foreground\": \"000000\",\"token\": \"entity.name.function\"},\n    {\"fontStyle\": \"bold underline\",\"token\": \"entity.name.type\"},\n    {\"fontStyle\": \"italic\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"007400\",\"token\": \"comment\"},\n    {\"foreground\": \"ff0000\",\"token\": \"invalid\"},\n    {\"background\": \"e71a1100\",\"token\": \"invalid.deprecated.trailing-whitespace\"},\n    {\"foreground\": \"000000\",\"background\": \"fafafafc\",\"token\": \"text source\"},\n    {\"foreground\": \"aa0d91\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"aa0d91\",\"token\": \"declaration.tag\"},\n    {\"foreground\": \"000000\",\"fontStyle\": \"bold\",\"token\": \"support\"},\n    {\"foreground\": \"aa0d91\",\"token\": \"storage\"},\n    {\"fontStyle\": \"bold underline\",\"token\": \"entity.name.section\"},\n    {\"foreground\": \"000000\",\"fontStyle\": \"bold\",\"token\": \"entity.name.function.frame\"},\n    {\"foreground\": \"333333\",\"token\": \"meta.tag.preprocessor.xml\"},\n    {\"foreground\": \"994500\",\"fontStyle\": \"italic\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"881280\",\"token\": \"entity.name.tag\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#000000\",\n\t\t\"editor.background\": \"#FFFFFF\",\n\t\t\"editor.selectionBackground\": \"#BAD6FD\",\n\t\t\"editor.lineHighlightBackground\": \"#0000001A\",\n\t\t\"editorCursor.foreground\": \"#000000\",\n\t\t\"editorWhitespace.foreground\": \"#B3B3B3F4\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet cloudsMidnight = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"191919\",\"token\": \"\"},\n    {\"foreground\": \"3c403b\",\"token\": \"comment\"},\n    {\"foreground\": \"5d90cd\",\"token\": \"string\"},\n    {\"foreground\": \"46a609\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"39946a\",\"token\": \"constant.language\"},\n    {\"foreground\": \"927c5d\",\"token\": \"keyword\"},\n    {\"foreground\": \"927c5d\",\"token\": \"support.constant.property-value\"},\n    {\"foreground\": \"927c5d\",\"token\": \"constant.other.color\"},\n    {\"foreground\": \"366f1a\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"a46763\",\"token\": \"entity.other.attribute-name.html\"},\n    {\"foreground\": \"4b4b4b\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"e92e2e\",\"token\": \"storage\"},\n    {\"foreground\": \"858585\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"606060\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"a165ac\",\"token\": \"constant.character.entity\"},\n    {\"foreground\": \"a165ac\",\"token\": \"support.class.js\"},\n    {\"foreground\": \"606060\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"e92e2e\",\"token\": \"meta.selector.css\"},\n    {\"foreground\": \"e92e2e\",\"token\": \"entity.name.tag.css\"},\n    {\"foreground\": \"e92e2e\",\"token\": \"entity.other.attribute-name.id.css\"},\n    {\"foreground\": \"e92e2e\",\"token\": \"entity.other.attribute-name.class.css\"},\n    {\"foreground\": \"616161\",\"token\": \"meta.property-name.css\"},\n    {\"foreground\": \"e92e2e\",\"token\": \"support.function\"},\n    {\"foreground\": \"ffffff\",\"background\": \"e92e2e\",\"token\": \"invalid\"},\n    {\"foreground\": \"e92e2e\",\"token\": \"punctuation.section.embedded\"},\n    {\"foreground\": \"606060\",\"token\": \"punctuation.definition.tag\"},\n    {\"foreground\": \"a165ac\",\"token\": \"constant.other.color.rgb-value.css\"},\n    {\"foreground\": \"a165ac\",\"token\": \"support.constant.property-value.css\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#929292\",\n\t\t\"editor.background\": \"#191919\",\n\t\t\"editor.selectionBackground\": \"#000000\",\n\t\t\"editor.lineHighlightBackground\": \"#D7D7D708\",\n\t\t\"editorCursor.foreground\": \"#7DA5DC\",\n\t\t\"editorWhitespace.foreground\": \"#BFBFBF\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet clouds = {\n\t\"base\": \"vs\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"FFFFFF\",\"token\": \"\"},\n    {\"foreground\": \"bcc8ba\",\"token\": \"comment\"},\n    {\"foreground\": \"5d90cd\",\"token\": \"string\"},\n    {\"foreground\": \"46a609\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"39946a\",\"token\": \"constant.language\"},\n    {\"foreground\": \"af956f\",\"token\": \"keyword\"},\n    {\"foreground\": \"af956f\",\"token\": \"support.constant.property-value\"},\n    {\"foreground\": \"af956f\",\"token\": \"constant.other.color\"},\n    {\"foreground\": \"96dc5f\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"484848\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"c52727\",\"token\": \"storage\"},\n    {\"foreground\": \"858585\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"606060\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"bf78cc\",\"token\": \"constant.character.entity\"},\n    {\"foreground\": \"bf78cc\",\"token\": \"support.class.js\"},\n    {\"foreground\": \"606060\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"c52727\",\"token\": \"meta.selector.css\"},\n    {\"foreground\": \"c52727\",\"token\": \"entity.name.tag.css\"},\n    {\"foreground\": \"c52727\",\"token\": \"entity.other.attribute-name.id.css\"},\n    {\"foreground\": \"c52727\",\"token\": \"entity.other.attribute-name.class.css\"},\n    {\"foreground\": \"484848\",\"token\": \"meta.property-name.css\"},\n    {\"foreground\": \"c52727\",\"token\": \"support.function\"},\n    {\"background\": \"ff002a\",\"token\": \"invalid\"},\n    {\"foreground\": \"c52727\",\"token\": \"punctuation.section.embedded\"},\n    {\"foreground\": \"606060\",\"token\": \"punctuation.definition.tag\"},\n    {\"foreground\": \"bf78cc\",\"token\": \"constant.other.color.rgb-value.css\"},\n    {\"foreground\": \"bf78cc\",\"token\": \"support.constant.property-value.css\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#000000\",\n\t\t\"editor.background\": \"#FFFFFF\",\n\t\t\"editor.selectionBackground\": \"#BDD5FC\",\n\t\t\"editor.lineHighlightBackground\": \"#FFFBD1\",\n\t\t\"editorCursor.foreground\": \"#000000\",\n\t\t\"editorWhitespace.foreground\": \"#BFBFBF\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet cobalt = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"002240\",\"token\": \"\"},\n    {\"foreground\": \"e1efff\",\"token\": \"punctuation - (punctuation.definition.string || punctuation.definition.comment)\"},\n    {\"foreground\": \"ff628c\",\"token\": \"constant\"},\n    {\"foreground\": \"ffdd00\",\"token\": \"entity\"},\n    {\"foreground\": \"ff9d00\",\"token\": \"keyword\"},\n    {\"foreground\": \"ffee80\",\"token\": \"storage\"},\n    {\"foreground\": \"3ad900\",\"token\": \"string -string.unquoted.old-plist -string.unquoted.heredoc\"},\n    {\"foreground\": \"3ad900\",\"token\": \"string.unquoted.heredoc string\"},\n    {\"foreground\": \"0088ff\",\"fontStyle\": \"italic\",\"token\": \"comment\"},\n    {\"foreground\": \"80ffbb\",\"token\": \"support\"},\n    {\"foreground\": \"cccccc\",\"token\": \"variable\"},\n    {\"foreground\": \"ff80e1\",\"token\": \"variable.language\"},\n    {\"foreground\": \"ffee80\",\"token\": \"meta.function-call\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"800f00\",\"token\": \"invalid\"},\n    {\"foreground\": \"ffffff\",\"background\": \"223545\",\"token\": \"text source\"},\n    {\"foreground\": \"ffffff\",\"background\": \"223545\",\"token\": \"string.unquoted.heredoc\"},\n    {\"foreground\": \"ffffff\",\"background\": \"223545\",\"token\": \"source source\"},\n    {\"foreground\": \"80fcff\",\"fontStyle\": \"italic\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"9eff80\",\"token\": \"string.quoted source\"},\n    {\"foreground\": \"80ff82\",\"token\": \"string constant\"},\n    {\"foreground\": \"80ffc2\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"edef7d\",\"token\": \"string variable\"},\n    {\"foreground\": \"ffb054\",\"token\": \"support.function\"},\n    {\"foreground\": \"eb939a\",\"token\": \"support.constant\"},\n    {\"foreground\": \"ff1e00\",\"token\": \"support.type.exception\"},\n    {\"foreground\": \"8996a8\",\"token\": \"meta.preprocessor.c\"},\n    {\"foreground\": \"afc4db\",\"token\": \"meta.preprocessor.c keyword\"},\n    {\"foreground\": \"73817d\",\"token\": \"meta.sgml.html meta.doctype\"},\n    {\"foreground\": \"73817d\",\"token\": \"meta.sgml.html meta.doctype entity\"},\n    {\"foreground\": \"73817d\",\"token\": \"meta.sgml.html meta.doctype string\"},\n    {\"foreground\": \"73817d\",\"token\": \"meta.xml-processing\"},\n    {\"foreground\": \"73817d\",\"token\": \"meta.xml-processing entity\"},\n    {\"foreground\": \"73817d\",\"token\": \"meta.xml-processing string\"},\n    {\"foreground\": \"9effff\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"9effff\",\"token\": \"meta.tag entity\"},\n    {\"foreground\": \"9effff\",\"token\": \"meta.selector.css entity.name.tag\"},\n    {\"foreground\": \"ffb454\",\"token\": \"meta.selector.css entity.other.attribute-name.id\"},\n    {\"foreground\": \"5fe461\",\"token\": \"meta.selector.css entity.other.attribute-name.class\"},\n    {\"foreground\": \"9df39f\",\"token\": \"support.type.property-name.css\"},\n    {\"foreground\": \"f6f080\",\"token\": \"meta.property-group support.constant.property-value.css\"},\n    {\"foreground\": \"f6f080\",\"token\": \"meta.property-value support.constant.property-value.css\"},\n    {\"foreground\": \"f6aa11\",\"token\": \"meta.preprocessor.at-rule keyword.control.at-rule\"},\n    {\"foreground\": \"edf080\",\"token\": \"meta.property-value support.constant.named-color.css\"},\n    {\"foreground\": \"edf080\",\"token\": \"meta.property-value constant\"},\n    {\"foreground\": \"eb939a\",\"token\": \"meta.constructor.argument.css\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"000e1a\",\"token\": \"meta.diff\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"000e1a\",\"token\": \"meta.diff.header\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"4c0900\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"806f00\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"154f00\",\"token\": \"markup.inserted\"},\n    {\"background\": \"8fddf630\",\"token\": \"markup.raw\"},\n    {\"background\": \"004480\",\"token\": \"markup.quote\"},\n    {\"background\": \"130d26\",\"token\": \"markup.list\"},\n    {\"foreground\": \"c1afff\",\"fontStyle\": \"bold\",\"token\": \"markup.bold\"},\n    {\"foreground\": \"b8ffd9\",\"fontStyle\": \"italic\",\"token\": \"markup.italic\"},\n    {\"foreground\": \"c8e4fd\",\"background\": \"001221\",\"fontStyle\": \"bold\",\"token\": \"markup.heading\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#FFFFFF\",\n\t\t\"editor.background\": \"#002240\",\n\t\t\"editor.selectionBackground\": \"#B36539BF\",\n\t\t\"editor.lineHighlightBackground\": \"#00000059\",\n\t\t\"editorCursor.foreground\": \"#FFFFFF\",\n\t\t\"editorWhitespace.foreground\": \"#FFFFFF26\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet dracula = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"282a36\",\"token\": \"\"},\n    {\"foreground\": \"6272a4\",\"token\": \"comment\"},\n    {\"foreground\": \"f1fa8c\",\"token\": \"string\"},\n    {\"foreground\": \"bd93f9\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"bd93f9\",\"token\": \"constant.language\"},\n    {\"foreground\": \"bd93f9\",\"token\": \"constant.character\"},\n    {\"foreground\": \"bd93f9\",\"token\": \"constant.other\"},\n    {\"foreground\": \"ffb86c\",\"token\": \"variable.other.readwrite.instance\"},\n    {\"foreground\": \"ff79c6\",\"token\": \"constant.character.escaped\"},\n    {\"foreground\": \"ff79c6\",\"token\": \"constant.character.escape\"},\n    {\"foreground\": \"ff79c6\",\"token\": \"string source\"},\n    {\"foreground\": \"ff79c6\",\"token\": \"string source.ruby\"},\n    {\"foreground\": \"ff79c6\",\"token\": \"keyword\"},\n    {\"foreground\": \"ff79c6\",\"token\": \"storage\"},\n    {\"foreground\": \"8be9fd\",\"fontStyle\": \"italic\",\"token\": \"storage.type\"},\n    {\"foreground\": \"50fa7b\",\"fontStyle\": \"underline\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"50fa7b\",\"fontStyle\": \"italic underline\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"50fa7b\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"ffb86c\",\"fontStyle\": \"italic\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"ff79c6\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"50fa7b\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"8be9fd\",\"token\": \"support.function\"},\n    {\"foreground\": \"6be5fd\",\"token\": \"support.constant\"},\n    {\"foreground\": \"66d9ef\",\"fontStyle\": \" italic\",\"token\": \"support.type\"},\n    {\"foreground\": \"66d9ef\",\"fontStyle\": \" italic\",\"token\": \"support.class\"},\n    {\"foreground\": \"f8f8f0\",\"background\": \"ff79c6\",\"token\": \"invalid\"},\n    {\"foreground\": \"f8f8f0\",\"background\": \"bd93f9\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"cfcfc2\",\"token\": \"meta.structure.dictionary.json string.quoted.double.json\"},\n    {\"foreground\": \"6272a4\",\"token\": \"meta.diff\"},\n    {\"foreground\": \"6272a4\",\"token\": \"meta.diff.header\"},\n    {\"foreground\": \"ff79c6\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"50fa7b\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"e6db74\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"bd93f9\",\"token\": \"constant.numeric.line-number.find-in-files - match\"},\n    {\"foreground\": \"e6db74\",\"token\": \"entity.name.filename\"},\n    {\"foreground\": \"f83333\",\"token\": \"message.error\"},\n    {\"foreground\": \"eeeeee\",\"token\": \"punctuation.definition.string.begin.json - meta.structure.dictionary.value.json\"},\n    {\"foreground\": \"eeeeee\",\"token\": \"punctuation.definition.string.end.json - meta.structure.dictionary.value.json\"},\n    {\"foreground\": \"8be9fd\",\"token\": \"meta.structure.dictionary.json string.quoted.double.json\"},\n    {\"foreground\": \"f1fa8c\",\"token\": \"meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"50fa7b\",\"token\": \"meta meta meta meta meta meta meta.structure.dictionary.value string\"},\n    {\"foreground\": \"ffb86c\",\"token\": \"meta meta meta meta meta meta.structure.dictionary.value string\"},\n    {\"foreground\": \"ff79c6\",\"token\": \"meta meta meta meta meta.structure.dictionary.value string\"},\n    {\"foreground\": \"bd93f9\",\"token\": \"meta meta meta meta.structure.dictionary.value string\"},\n    {\"foreground\": \"50fa7b\",\"token\": \"meta meta meta.structure.dictionary.value string\"},\n    {\"foreground\": \"ffb86c\",\"token\": \"meta meta.structure.dictionary.value string\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#f8f8f2\",\n\t\t\"editor.background\": \"#282a36\",\n\t\t\"editor.selectionBackground\": \"#44475a\",\n\t\t\"editor.lineHighlightBackground\": \"#44475a\",\n\t\t\"editorCursor.foreground\": \"#f8f8f0\",\n\t\t\"editorWhitespace.foreground\": \"#3B3A32\",\n\t\t\"editorIndentGuide.activeBackground\": \"#9D550FB0\",\n\t\t\"editor.selectionHighlightBorder\": \"#222218\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet dreamweaver = {\n\t\"base\": \"vs\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"FFFFFF\",\"token\": \"\"},\n    {\"foreground\": \"000000\",\"token\": \"text\"},\n    {\"foreground\": \"ee000b\",\"token\": \"constant.numeric - source.css\"},\n    {\"foreground\": \"9a9a9a\",\"token\": \"comment\"},\n    {\"foreground\": \"00359e\",\"token\": \"text.html meta.tag\"},\n    {\"foreground\": \"001eff\",\"token\": \"text.html.basic meta.tag string.quoted - source\"},\n    {\"foreground\": \"000000\",\"fontStyle\": \"bold\",\"token\": \"text.html.basic constant.character.entity.html\"},\n    {\"foreground\": \"106800\",\"token\": \"text.html meta.tag.a - string\"},\n    {\"foreground\": \"6d232e\",\"token\": \"text.html meta.tag.img - string\"},\n    {\"foreground\": \"ff9700\",\"token\": \"text.html meta.tag.form - string\"},\n    {\"foreground\": \"009079\",\"token\": \"text.html meta.tag.table - string\"},\n    {\"foreground\": \"842b44\",\"token\": \"source.js.embedded.html punctuation.definition.tag - source.php\"},\n    {\"foreground\": \"842b44\",\"token\": \"source.js.embedded.html entity.name.tag.script\"},\n    {\"foreground\": \"842b44\",\"token\": \"source.js.embedded entity.other.attribute-name - source.js string\"},\n    {\"foreground\": \"9a9a9a\",\"token\": \"source.js comment - source.php\"},\n    {\"foreground\": \"000000\",\"token\": \"source.js meta.function - source.php\"},\n    {\"foreground\": \"24c696\",\"token\": \"source.js meta.class - source.php\"},\n    {\"foreground\": \"24c696\",\"token\": \"source.js support.function - source.php\"},\n    {\"foreground\": \"0035ff\",\"token\": \"source.js string - source.php\"},\n    {\"foreground\": \"0035ff\",\"token\": \"source.js keyword.operator\"},\n    {\"foreground\": \"7e00b7\",\"token\": \"source.js support.class\"},\n    {\"foreground\": \"000000\",\"fontStyle\": \"bold\",\"token\": \"source.js storage\"},\n    {\"foreground\": \"05208c\",\"fontStyle\": \"bold\",\"token\": \"source.js storage - storage.type.function - source.php\"},\n    {\"foreground\": \"05208c\",\"fontStyle\": \"bold\",\"token\": \"source.js constant - source.php\"},\n    {\"foreground\": \"05208c\",\"fontStyle\": \"bold\",\"token\": \"source.js keyword - source.php\"},\n    {\"foreground\": \"05208c\",\"fontStyle\": \"bold\",\"token\": \"source.js variable.language\"},\n    {\"foreground\": \"05208c\",\"fontStyle\": \"bold\",\"token\": \"source.js meta.brace\"},\n    {\"foreground\": \"05208c\",\"fontStyle\": \"bold\",\"token\": \"source.js punctuation.definition.parameters.begin\"},\n    {\"foreground\": \"05208c\",\"fontStyle\": \"bold\",\"token\": \"source.js punctuation.definition.parameters.end\"},\n    {\"foreground\": \"106800\",\"token\": \"source.js string.regexp\"},\n    {\"foreground\": \"106800\",\"token\": \"source.js string.regexp constant\"},\n    {\"foreground\": \"8d00b7\",\"token\": \"source.css.embedded.html punctuation.definition.tag\"},\n    {\"foreground\": \"8d00b7\",\"token\": \"source.css.embedded.html entity.name.tag.style\"},\n    {\"foreground\": \"8d00b7\",\"token\": \"source.css.embedded entity.other.attribute-name - meta.selector\"},\n    {\"foreground\": \"009c7f\",\"fontStyle\": \"bold\",\"token\": \"source.css meta.at-rule.import.css\"},\n    {\"foreground\": \"ee000b\",\"fontStyle\": \"bold\",\"token\": \"source.css keyword.other.important\"},\n    {\"foreground\": \"430303\",\"fontStyle\": \"bold\",\"token\": \"source.css meta.at-rule.media\"},\n    {\"foreground\": \"106800\",\"token\": \"source.css string\"},\n    {\"foreground\": \"da29ff\",\"token\": \"source.css meta.selector\"},\n    {\"foreground\": \"da29ff\",\"token\": \"source.css meta.property-list\"},\n    {\"foreground\": \"da29ff\",\"token\": \"source.css meta.at-rule\"},\n    {\"foreground\": \"da29ff\",\"fontStyle\": \"bold\",\"token\": \"source.css punctuation.separator - source.php\"},\n    {\"foreground\": \"da29ff\",\"fontStyle\": \"bold\",\"token\": \"source.css punctuation.terminator - source.php\"},\n    {\"foreground\": \"05208c\",\"token\": \"source.css meta.property-name\"},\n    {\"foreground\": \"0035ff\",\"token\": \"source.css meta.property-value\"},\n    {\"foreground\": \"ee000b\",\"fontStyle\": \"bold\",\"token\": \"source.php punctuation.section.embedded.begin\"},\n    {\"foreground\": \"ee000b\",\"fontStyle\": \"bold\",\"token\": \"source.php punctuation.section.embedded.end\"},\n    {\"foreground\": \"000000\",\"token\": \"source.php - punctuation.section\"},\n    {\"foreground\": \"000000\",\"token\": \"source.php variable\"},\n    {\"foreground\": \"000000\",\"token\": \"source.php meta.function.arguments\"},\n    {\"foreground\": \"05208c\",\"token\": \"source.php punctuation - string - variable - meta.function\"},\n    {\"foreground\": \"24bf96\",\"token\": \"source.php storage.type\"},\n    {\"foreground\": \"009714\",\"token\": \"source.php keyword - comment\"},\n    {\"foreground\": \"009714\",\"token\": \"source.php storage.type.class\"},\n    {\"foreground\": \"009714\",\"token\": \"source.php storage.type.interface\"},\n    {\"foreground\": \"009714\",\"token\": \"source.php storage.modifier\"},\n    {\"foreground\": \"009714\",\"token\": \"source.php constant.language\"},\n    {\"foreground\": \"0035ff\",\"token\": \"source.php support\"},\n    {\"foreground\": \"0035ff\",\"token\": \"source.php storage\"},\n    {\"foreground\": \"0035ff\",\"token\": \"source.php keyword.operator\"},\n    {\"foreground\": \"0035ff\",\"token\": \"source.php storage.type.function\"},\n    {\"foreground\": \"0092f2\",\"token\": \"source.php variable.other.global\"},\n    {\"foreground\": \"551d02\",\"token\": \"source.php support.constant\"},\n    {\"foreground\": \"551d02\",\"token\": \"source.php constant.language.php\"},\n    {\"foreground\": \"e20000\",\"token\": \"source.php string\"},\n    {\"foreground\": \"e20000\",\"token\": \"source.php string keyword.operator\"},\n    {\"foreground\": \"ff6200\",\"token\": \"source.php string.quoted.double variable\"},\n    {\"foreground\": \"ff9404\",\"token\": \"source.php comment\"},\n    {\"foreground\": \"ee000b\",\"background\": \"efff8a\",\"fontStyle\": \"bold\",\"token\": \"invalid\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#000000\",\n\t\t\"editor.background\": \"#FFFFFF\",\n\t\t\"editor.selectionBackground\": \"#5EA0FF\",\n\t\t\"editor.lineHighlightBackground\": \"#00000012\",\n\t\t\"editorCursor.foreground\": \"#000000\",\n\t\t\"editorWhitespace.foreground\": \"#BFBFBF\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet espressoLibre = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"2A211C\",\"token\": \"\"},\n    {\"foreground\": \"0066ff\",\"fontStyle\": \"italic\",\"token\": \"comment\"},\n    {\"foreground\": \"43a8ed\",\"fontStyle\": \"bold\",\"token\": \"keyword\"},\n    {\"foreground\": \"43a8ed\",\"fontStyle\": \"bold\",\"token\": \"storage\"},\n    {\"foreground\": \"44aa43\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"c5656b\",\"fontStyle\": \"bold\",\"token\": \"constant\"},\n    {\"foreground\": \"585cf6\",\"fontStyle\": \"bold\",\"token\": \"constant.language\"},\n    {\"foreground\": \"318495\",\"token\": \"variable.language\"},\n    {\"foreground\": \"318495\",\"token\": \"variable.other\"},\n    {\"foreground\": \"049b0a\",\"token\": \"string\"},\n    {\"foreground\": \"2fe420\",\"token\": \"constant.character.escape\"},\n    {\"foreground\": \"2fe420\",\"token\": \"string source\"},\n    {\"foreground\": \"1a921c\",\"token\": \"meta.preprocessor\"},\n    {\"foreground\": \"9aff87\",\"fontStyle\": \"bold\",\"token\": \"keyword.control.import\"},\n    {\"foreground\": \"ff9358\",\"fontStyle\": \"bold\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"ff9358\",\"fontStyle\": \"bold\",\"token\": \"keyword.other.name-of-parameter.objc\"},\n    {\"fontStyle\": \"underline\",\"token\": \"entity.name.type\"},\n    {\"fontStyle\": \"italic\",\"token\": \"entity.other.inherited-class\"},\n    {\"fontStyle\": \"italic\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"8b8e9c\",\"token\": \"storage.type.method\"},\n    {\"fontStyle\": \"italic\",\"token\": \"meta.section entity.name.section\"},\n    {\"fontStyle\": \"italic\",\"token\": \"declaration.section entity.name.section\"},\n    {\"foreground\": \"7290d9\",\"fontStyle\": \"bold\",\"token\": \"support.function\"},\n    {\"foreground\": \"6d79de\",\"fontStyle\": \"bold\",\"token\": \"support.class\"},\n    {\"foreground\": \"6d79de\",\"fontStyle\": \"bold\",\"token\": \"support.type\"},\n    {\"foreground\": \"00af0e\",\"fontStyle\": \"bold\",\"token\": \"support.constant\"},\n    {\"foreground\": \"2f5fe0\",\"fontStyle\": \"bold\",\"token\": \"support.variable\"},\n    {\"foreground\": \"687687\",\"token\": \"keyword.operator.js\"},\n    {\"foreground\": \"ffffff\",\"background\": \"990000\",\"token\": \"invalid\"},\n    {\"background\": \"ffd0d0\",\"token\": \"invalid.deprecated.trailing-whitespace\"},\n    {\"background\": \"f5aa7730\",\"token\": \"text source\"},\n    {\"background\": \"f5aa7730\",\"token\": \"string.unquoted\"},\n    {\"foreground\": \"8f7e65\",\"token\": \"meta.tag.preprocessor.xml\"},\n    {\"foreground\": \"888888\",\"token\": \"meta.tag.sgml.doctype\"},\n    {\"fontStyle\": \"italic\",\"token\": \"string.quoted.docinfo.doctype.DTD\"},\n    {\"foreground\": \"43a8ed\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"43a8ed\",\"token\": \"declaration.tag\"},\n    {\"fontStyle\": \"bold\",\"token\": \"entity.name.tag\"},\n    {\"fontStyle\": \"italic\",\"token\": \"entity.other.attribute-name\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#BDAE9D\",\n\t\t\"editor.background\": \"#2A211C\",\n\t\t\"editor.selectionBackground\": \"#C3DCFF\",\n\t\t\"editor.lineHighlightBackground\": \"#3A312C\",\n\t\t\"editorCursor.foreground\": \"#889AFF\",\n\t\t\"editorWhitespace.foreground\": \"#BFBFBF\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet githubDark = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"24292e\",\"token\": \"\"},\n    {\"foreground\": \"959da5\",\"token\": \"comment\"},\n    {\"foreground\": \"959da5\",\"token\": \"punctuation.definition.comment\"},\n    {\"foreground\": \"959da5\",\"token\": \"string.comment\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"constant\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"entity.name.constant\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"variable.other.constant\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"variable.language\"},\n    {\"foreground\": \"b392f0\",\"token\": \"entity\"},\n    {\"foreground\": \"b392f0\",\"token\": \"entity.name\"},\n    {\"foreground\": \"f6f8fa\",\"token\": \"variable.parameter.function\"},\n    {\"foreground\": \"7bcc72\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"ea4a5a\",\"token\": \"keyword\"},\n    {\"foreground\": \"ea4a5a\",\"token\": \"storage\"},\n    {\"foreground\": \"ea4a5a\",\"token\": \"storage.type\"},\n    {\"foreground\": \"f6f8fa\",\"token\": \"storage.modifier.package\"},\n    {\"foreground\": \"f6f8fa\",\"token\": \"storage.modifier.import\"},\n    {\"foreground\": \"f6f8fa\",\"token\": \"storage.type.java\"},\n    {\"foreground\": \"79b8ff\",\"token\": \"string\"},\n    {\"foreground\": \"79b8ff\",\"token\": \"punctuation.definition.string\"},\n    {\"foreground\": \"79b8ff\",\"token\": \"string punctuation.section.embedded source\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"support\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"meta.property-name\"},\n    {\"foreground\": \"fb8532\",\"token\": \"variable\"},\n    {\"foreground\": \"f6f8fa\",\"token\": \"variable.other\"},\n    {\"foreground\": \"d73a49\",\"fontStyle\": \"bold italic underline\",\"token\": \"invalid.broken\"},\n    {\"foreground\": \"d73a49\",\"fontStyle\": \"bold italic underline\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"fafbfc\",\"background\": \"d73a49\",\"fontStyle\": \"italic underline\",\"token\": \"invalid.illegal\"},\n    {\"foreground\": \"fafbfc\",\"background\": \"d73a49\",\"fontStyle\": \"italic underline\",\"token\": \"carriage-return\"},\n    {\"foreground\": \"d73a49\",\"fontStyle\": \"bold italic underline\",\"token\": \"invalid.unimplemented\"},\n    {\"foreground\": \"d73a49\",\"token\": \"message.error\"},\n    {\"foreground\": \"f6f8fa\",\"token\": \"string source\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"string variable\"},\n    {\"foreground\": \"79b8ff\",\"token\": \"source.regexp\"},\n    {\"foreground\": \"79b8ff\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"79b8ff\",\"token\": \"string.regexp.character-class\"},\n    {\"foreground\": \"79b8ff\",\"token\": \"string.regexp constant.character.escape\"},\n    {\"foreground\": \"79b8ff\",\"token\": \"string.regexp source.ruby.embedded\"},\n    {\"foreground\": \"79b8ff\",\"token\": \"string.regexp string.regexp.arbitrary-repitition\"},\n    {\"foreground\": \"7bcc72\",\"fontStyle\": \"bold\",\"token\": \"string.regexp constant.character.escape\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"support.constant\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"support.variable\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"meta.module-reference\"},\n    {\"foreground\": \"fb8532\",\"token\": \"markup.list\"},\n    {\"foreground\": \"0366d6\",\"fontStyle\": \"bold\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"0366d6\",\"fontStyle\": \"bold\",\"token\": \"markup.heading entity.name\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"markup.quote\"},\n    {\"foreground\": \"f6f8fa\",\"fontStyle\": \"italic\",\"token\": \"markup.italic\"},\n    {\"foreground\": \"f6f8fa\",\"fontStyle\": \"bold\",\"token\": \"markup.bold\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"markup.raw\"},\n    {\"foreground\": \"b31d28\",\"background\": \"ffeef0\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"b31d28\",\"background\": \"ffeef0\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"b31d28\",\"background\": \"ffeef0\",\"token\": \"punctuation.definition.deleted\"},\n    {\"foreground\": \"176f2c\",\"background\": \"f0fff4\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"176f2c\",\"background\": \"f0fff4\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"176f2c\",\"background\": \"f0fff4\",\"token\": \"punctuation.definition.inserted\"},\n    {\"foreground\": \"b08800\",\"background\": \"fffdef\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"b08800\",\"background\": \"fffdef\",\"token\": \"punctuation.definition.changed\"},\n    {\"foreground\": \"2f363d\",\"background\": \"959da5\",\"token\": \"markup.ignored\"},\n    {\"foreground\": \"2f363d\",\"background\": \"959da5\",\"token\": \"markup.untracked\"},\n    {\"foreground\": \"b392f0\",\"fontStyle\": \"bold\",\"token\": \"meta.diff.range\"},\n    {\"foreground\": \"c8e1ff\",\"token\": \"meta.diff.header\"},\n    {\"foreground\": \"0366d6\",\"fontStyle\": \"bold\",\"token\": \"meta.separator\"},\n    {\"foreground\": \"0366d6\",\"token\": \"meta.output\"},\n    {\"foreground\": \"ffeef0\",\"token\": \"brackethighlighter.tag\"},\n    {\"foreground\": \"ffeef0\",\"token\": \"brackethighlighter.curly\"},\n    {\"foreground\": \"ffeef0\",\"token\": \"brackethighlighter.round\"},\n    {\"foreground\": \"ffeef0\",\"token\": \"brackethighlighter.square\"},\n    {\"foreground\": \"ffeef0\",\"token\": \"brackethighlighter.angle\"},\n    {\"foreground\": \"ffeef0\",\"token\": \"brackethighlighter.quote\"},\n    {\"foreground\": \"d73a49\",\"token\": \"brackethighlighter.unmatched\"},\n    {\"foreground\": \"d73a49\",\"token\": \"sublimelinter.mark.error\"},\n    {\"foreground\": \"fb8532\",\"token\": \"sublimelinter.mark.warning\"},\n    {\"foreground\": \"6a737d\",\"token\": \"sublimelinter.gutter-mark\"},\n    {\"foreground\": \"79b8ff\",\"fontStyle\": \"underline\",\"token\": \"constant.other.reference.link\"},\n    {\"foreground\": \"79b8ff\",\"fontStyle\": \"underline\",\"token\": \"string.other.link\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#c9d1d9\",\n\t\t\"editor.background\": \"#0d1117\",\n\t\t\"editor.selectionBackground\": \"#4c2889\",\n\t\t\"editor.inactiveSelectionBackground\": \"#444d56\",\n\t\t\"editor.lineHighlightBackground\": \"#444d56\",\n\t\t\"editorCursor.foreground\": \"#c9d1d9\",\n\t\t\"editorWhitespace.foreground\": \"#6a737d\",\n\t\t\"editorIndentGuide.background\": \"#6a737d\",\n\t\t\"editorIndentGuide.activeBackground\": \"#f6f8fa\",\n\t\t\"editor.selectionHighlightBorder\": \"#444d56\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet githubLight = {\n\t\"base\": \"vs\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"ffffff\",\"token\": \"\"},\n    {\"foreground\": \"6a737d\",\"token\": \"comment\"},\n    {\"foreground\": \"6a737d\",\"token\": \"punctuation.definition.comment\"},\n    {\"foreground\": \"6a737d\",\"token\": \"string.comment\"},\n    {\"foreground\": \"005cc5\",\"token\": \"constant\"},\n    {\"foreground\": \"005cc5\",\"token\": \"entity.name.constant\"},\n    {\"foreground\": \"005cc5\",\"token\": \"variable.other.constant\"},\n    {\"foreground\": \"005cc5\",\"token\": \"variable.language\"},\n    {\"foreground\": \"6f42c1\",\"token\": \"entity\"},\n    {\"foreground\": \"6f42c1\",\"token\": \"entity.name\"},\n    {\"foreground\": \"24292e\",\"token\": \"variable.parameter.function\"},\n    {\"foreground\": \"22863a\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"d73a49\",\"token\": \"keyword\"},\n    {\"foreground\": \"d73a49\",\"token\": \"storage\"},\n    {\"foreground\": \"d73a49\",\"token\": \"storage.type\"},\n    {\"foreground\": \"24292e\",\"token\": \"storage.modifier.package\"},\n    {\"foreground\": \"24292e\",\"token\": \"storage.modifier.import\"},\n    {\"foreground\": \"24292e\",\"token\": \"storage.type.java\"},\n    {\"foreground\": \"032f62\",\"token\": \"string\"},\n    {\"foreground\": \"032f62\",\"token\": \"punctuation.definition.string\"},\n    {\"foreground\": \"032f62\",\"token\": \"string punctuation.section.embedded source\"},\n    {\"foreground\": \"005cc5\",\"token\": \"support\"},\n    {\"foreground\": \"005cc5\",\"token\": \"meta.property-name\"},\n    {\"foreground\": \"e36209\",\"token\": \"variable\"},\n    {\"foreground\": \"24292e\",\"token\": \"variable.other\"},\n    {\"foreground\": \"b31d28\",\"fontStyle\": \"bold italic underline\",\"token\": \"invalid.broken\"},\n    {\"foreground\": \"b31d28\",\"fontStyle\": \"bold italic underline\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"fafbfc\",\"background\": \"b31d28\",\"fontStyle\": \"italic underline\",\"token\": \"invalid.illegal\"},\n    {\"foreground\": \"fafbfc\",\"background\": \"d73a49\",\"fontStyle\": \"italic underline\",\"token\": \"carriage-return\"},\n    {\"foreground\": \"b31d28\",\"fontStyle\": \"bold italic underline\",\"token\": \"invalid.unimplemented\"},\n    {\"foreground\": \"b31d28\",\"token\": \"message.error\"},\n    {\"foreground\": \"24292e\",\"token\": \"string source\"},\n    {\"foreground\": \"005cc5\",\"token\": \"string variable\"},\n    {\"foreground\": \"032f62\",\"token\": \"source.regexp\"},\n    {\"foreground\": \"032f62\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"032f62\",\"token\": \"string.regexp.character-class\"},\n    {\"foreground\": \"032f62\",\"token\": \"string.regexp constant.character.escape\"},\n    {\"foreground\": \"032f62\",\"token\": \"string.regexp source.ruby.embedded\"},\n    {\"foreground\": \"032f62\",\"token\": \"string.regexp string.regexp.arbitrary-repitition\"},\n    {\"foreground\": \"22863a\",\"fontStyle\": \"bold\",\"token\": \"string.regexp constant.character.escape\"},\n    {\"foreground\": \"005cc5\",\"token\": \"support.constant\"},\n    {\"foreground\": \"005cc5\",\"token\": \"support.variable\"},\n    {\"foreground\": \"005cc5\",\"token\": \"meta.module-reference\"},\n    {\"foreground\": \"735c0f\",\"token\": \"markup.list\"},\n    {\"foreground\": \"005cc5\",\"fontStyle\": \"bold\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"005cc5\",\"fontStyle\": \"bold\",\"token\": \"markup.heading entity.name\"},\n    {\"foreground\": \"22863a\",\"token\": \"markup.quote\"},\n    {\"foreground\": \"24292e\",\"fontStyle\": \"italic\",\"token\": \"markup.italic\"},\n    {\"foreground\": \"24292e\",\"fontStyle\": \"bold\",\"token\": \"markup.bold\"},\n    {\"foreground\": \"005cc5\",\"token\": \"markup.raw\"},\n    {\"foreground\": \"b31d28\",\"background\": \"ffeef0\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"b31d28\",\"background\": \"ffeef0\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"b31d28\",\"background\": \"ffeef0\",\"token\": \"punctuation.definition.deleted\"},\n    {\"foreground\": \"22863a\",\"background\": \"f0fff4\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"22863a\",\"background\": \"f0fff4\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"22863a\",\"background\": \"f0fff4\",\"token\": \"punctuation.definition.inserted\"},\n    {\"foreground\": \"e36209\",\"background\": \"ffebda\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"e36209\",\"background\": \"ffebda\",\"token\": \"punctuation.definition.changed\"},\n    {\"foreground\": \"f6f8fa\",\"background\": \"005cc5\",\"token\": \"markup.ignored\"},\n    {\"foreground\": \"f6f8fa\",\"background\": \"005cc5\",\"token\": \"markup.untracked\"},\n    {\"foreground\": \"6f42c1\",\"fontStyle\": \"bold\",\"token\": \"meta.diff.range\"},\n    {\"foreground\": \"005cc5\",\"token\": \"meta.diff.header\"},\n    {\"foreground\": \"005cc5\",\"fontStyle\": \"bold\",\"token\": \"meta.separator\"},\n    {\"foreground\": \"005cc5\",\"token\": \"meta.output\"},\n    {\"foreground\": \"586069\",\"token\": \"brackethighlighter.tag\"},\n    {\"foreground\": \"586069\",\"token\": \"brackethighlighter.curly\"},\n    {\"foreground\": \"586069\",\"token\": \"brackethighlighter.round\"},\n    {\"foreground\": \"586069\",\"token\": \"brackethighlighter.square\"},\n    {\"foreground\": \"586069\",\"token\": \"brackethighlighter.angle\"},\n    {\"foreground\": \"586069\",\"token\": \"brackethighlighter.quote\"},\n    {\"foreground\": \"b31d28\",\"token\": \"brackethighlighter.unmatched\"},\n    {\"foreground\": \"b31d28\",\"token\": \"sublimelinter.mark.error\"},\n    {\"foreground\": \"e36209\",\"token\": \"sublimelinter.mark.warning\"},\n    {\"foreground\": \"959da5\",\"token\": \"sublimelinter.gutter-mark\"},\n    {\"foreground\": \"032f62\",\"fontStyle\": \"underline\",\"token\": \"constant.other.reference.link\"},\n    {\"foreground\": \"032f62\",\"fontStyle\": \"underline\",\"token\": \"string.other.link\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#24292e\",\n\t\t\"editor.background\": \"#ffffff\",\n\t\t\"editor.selectionBackground\": \"#c8c8fa\",\n\t\t\"editor.inactiveSelectionBackground\": \"#fafbfc\",\n\t\t\"editor.lineHighlightBackground\": \"#fafbfc\",\n\t\t\"editorCursor.foreground\": \"#24292e\",\n\t\t\"editorWhitespace.foreground\": \"#959da5\",\n\t\t\"editorIndentGuide.background\": \"#959da5\",\n\t\t\"editorIndentGuide.activeBackground\": \"#24292e\",\n\t\t\"editor.selectionHighlightBorder\": \"#fafbfc\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet github = {\n\t\"base\": \"vs\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"F8F8FF\",\"token\": \"\"},\n    {\"foreground\": \"999988\",\"fontStyle\": \"italic\",\"token\": \"comment\"},\n    {\"foreground\": \"999999\",\"fontStyle\": \"bold\",\"token\": \"comment.block.preprocessor\"},\n    {\"foreground\": \"999999\",\"fontStyle\": \"bold italic\",\"token\": \"comment.documentation\"},\n    {\"foreground\": \"999999\",\"fontStyle\": \"bold italic\",\"token\": \"comment.block.documentation\"},\n    {\"foreground\": \"a61717\",\"background\": \"e3d2d2\",\"token\": \"invalid.illegal\"},\n    {\"fontStyle\": \"bold\",\"token\": \"keyword\"},\n    {\"fontStyle\": \"bold\",\"token\": \"storage\"},\n    {\"fontStyle\": \"bold\",\"token\": \"keyword.operator\"},\n    {\"fontStyle\": \"bold\",\"token\": \"constant.language\"},\n    {\"fontStyle\": \"bold\",\"token\": \"support.constant\"},\n    {\"foreground\": \"445588\",\"fontStyle\": \"bold\",\"token\": \"storage.type\"},\n    {\"foreground\": \"445588\",\"fontStyle\": \"bold\",\"token\": \"support.type\"},\n    {\"foreground\": \"008080\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"0086b3\",\"token\": \"variable.other\"},\n    {\"foreground\": \"999999\",\"token\": \"variable.language\"},\n    {\"foreground\": \"445588\",\"fontStyle\": \"bold\",\"token\": \"entity.name.type\"},\n    {\"foreground\": \"445588\",\"fontStyle\": \"bold\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"445588\",\"fontStyle\": \"bold\",\"token\": \"support.class\"},\n    {\"foreground\": \"008080\",\"token\": \"variable.other.constant\"},\n    {\"foreground\": \"800080\",\"token\": \"constant.character.entity\"},\n    {\"foreground\": \"990000\",\"token\": \"entity.name.exception\"},\n    {\"foreground\": \"990000\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"990000\",\"token\": \"support.function\"},\n    {\"foreground\": \"990000\",\"token\": \"keyword.other.name-of-parameter\"},\n    {\"foreground\": \"555555\",\"token\": \"entity.name.section\"},\n    {\"foreground\": \"000080\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"008080\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"008080\",\"token\": \"support.variable\"},\n    {\"foreground\": \"009999\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"009999\",\"token\": \"constant.other\"},\n    {\"foreground\": \"dd1144\",\"token\": \"string - string source\"},\n    {\"foreground\": \"dd1144\",\"token\": \"constant.character\"},\n    {\"foreground\": \"009926\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"990073\",\"token\": \"constant.other.symbol\"},\n    {\"fontStyle\": \"bold\",\"token\": \"punctuation\"},\n    {\"foreground\": \"000000\",\"background\": \"ffdddd\",\"token\": \"markup.deleted\"},\n    {\"fontStyle\": \"italic\",\"token\": \"markup.italic\"},\n    {\"foreground\": \"aa0000\",\"token\": \"markup.error\"},\n    {\"foreground\": \"999999\",\"token\": \"markup.heading.1\"},\n    {\"foreground\": \"000000\",\"background\": \"ddffdd\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"888888\",\"token\": \"markup.output\"},\n    {\"foreground\": \"888888\",\"token\": \"markup.raw\"},\n    {\"foreground\": \"555555\",\"token\": \"markup.prompt\"},\n    {\"fontStyle\": \"bold\",\"token\": \"markup.bold\"},\n    {\"foreground\": \"aaaaaa\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"aa0000\",\"token\": \"markup.traceback\"},\n    {\"fontStyle\": \"underline\",\"token\": \"markup.underline\"},\n    {\"foreground\": \"999999\",\"background\": \"eaf2f5\",\"token\": \"meta.diff.range\"},\n    {\"foreground\": \"999999\",\"background\": \"eaf2f5\",\"token\": \"meta.diff.index\"},\n    {\"foreground\": \"999999\",\"background\": \"eaf2f5\",\"token\": \"meta.separator\"},\n    {\"foreground\": \"999999\",\"background\": \"ffdddd\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"999999\",\"background\": \"ddffdd\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"4183c4\",\"token\": \"meta.link\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#000000\",\n\t\t\"editor.background\": \"#F8F8FF\",\n\t\t\"editor.selectionBackground\": \"#B4D5FE\",\n\t\t\"editor.lineHighlightBackground\": \"#FFFEEB\",\n\t\t\"editorCursor.foreground\": \"#666666\",\n\t\t\"editorWhitespace.foreground\": \"#BBBBBB\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet merbivoreSoft = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"161616\",\"token\": \"\"},\n    {\"foreground\": \"ad2ea4\",\"fontStyle\": \"italic\",\"token\": \"comment\"},\n    {\"foreground\": \"fc6f09\",\"token\": \"keyword\"},\n    {\"foreground\": \"fc6f09\",\"token\": \"storage\"},\n    {\"foreground\": \"fc83ff\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"58c554\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"1edafb\",\"token\": \"constant\"},\n    {\"foreground\": \"8dff0a\",\"token\": \"constant.library\"},\n    {\"foreground\": \"fc6f09\",\"token\": \"support.function\"},\n    {\"foreground\": \"fdc251\",\"token\": \"constant.language\"},\n    {\"foreground\": \"8dff0a\",\"token\": \"string\"},\n    {\"foreground\": \"1edafb\",\"token\": \"support.type\"},\n    {\"foreground\": \"8dff0a\",\"token\": \"support.constant\"},\n    {\"foreground\": \"fc6f09\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"fc6f09\",\"token\": \"declaration.tag\"},\n    {\"foreground\": \"fc6f09\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"ffff89\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"ffffff\",\"background\": \"990000\",\"token\": \"invalid\"},\n    {\"foreground\": \"519f50\",\"token\": \"constant.character.escaped\"},\n    {\"foreground\": \"519f50\",\"token\": \"constant.character.escape\"},\n    {\"foreground\": \"519f50\",\"token\": \"string source\"},\n    {\"foreground\": \"519f50\",\"token\": \"string source.ruby\"},\n    {\"foreground\": \"e6e1dc\",\"background\": \"144212\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"e6e1dc\",\"background\": \"660000\",\"token\": \"markup.deleted\"},\n    {\"background\": \"2f33ab\",\"token\": \"meta.diff.header\"},\n    {\"background\": \"2f33ab\",\"token\": \"meta.separator.diff\"},\n    {\"background\": \"2f33ab\",\"token\": \"meta.diff.index\"},\n    {\"background\": \"2f33ab\",\"token\": \"meta.diff.range\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#E6E1DC\",\n\t\t\"editor.background\": \"#161616\",\n\t\t\"editor.selectionBackground\": \"#454545\",\n\t\t\"editor.lineHighlightBackground\": \"#333435\",\n\t\t\"editorCursor.foreground\": \"#FFFFFF\",\n\t\t\"editorWhitespace.foreground\": \"#404040\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet monokai = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"272822\",\"token\": \"\"},\n    {\"foreground\": \"75715e\",\"token\": \"comment\"},\n    {\"foreground\": \"e6db74\",\"token\": \"string\"},\n    {\"foreground\": \"ae81ff\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"ae81ff\",\"token\": \"constant.language\"},\n    {\"foreground\": \"ae81ff\",\"token\": \"constant.character\"},\n    {\"foreground\": \"ae81ff\",\"token\": \"constant.other\"},\n    {\"foreground\": \"f92672\",\"token\": \"keyword\"},\n    {\"foreground\": \"f92672\",\"token\": \"storage\"},\n    {\"foreground\": \"66d9ef\",\"fontStyle\": \"italic\",\"token\": \"storage.type\"},\n    {\"foreground\": \"a6e22e\",\"fontStyle\": \"underline\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"a6e22e\",\"fontStyle\": \"italic underline\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"a6e22e\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"fd971f\",\"fontStyle\": \"italic\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"f92672\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"a6e22e\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"66d9ef\",\"token\": \"support.function\"},\n    {\"foreground\": \"66d9ef\",\"token\": \"support.constant\"},\n    {\"foreground\": \"66d9ef\",\"fontStyle\": \"italic\",\"token\": \"support.type\"},\n    {\"foreground\": \"66d9ef\",\"fontStyle\": \"italic\",\"token\": \"support.class\"},\n    {\"foreground\": \"f8f8f0\",\"background\": \"f92672\",\"token\": \"invalid\"},\n    {\"foreground\": \"f8f8f0\",\"background\": \"ae81ff\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"cfcfc2\",\"token\": \"meta.structure.dictionary.json string.quoted.double.json\"},\n    {\"foreground\": \"75715e\",\"token\": \"meta.diff\"},\n    {\"foreground\": \"75715e\",\"token\": \"meta.diff.header\"},\n    {\"foreground\": \"f92672\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"a6e22e\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"e6db74\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"ae81ffa0\",\"token\": \"constant.numeric.line-number.find-in-files - match\"},\n    {\"foreground\": \"e6db74\",\"token\": \"entity.name.filename.find-in-files\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#F8F8F2\",\n\t\t\"editor.background\": \"#272822\",\n\t\t\"editor.selectionBackground\": \"#49483E\",\n\t\t\"editor.lineHighlightBackground\": \"#3E3D32\",\n\t\t\"editorCursor.foreground\": \"#F8F8F0\",\n\t\t\"editorWhitespace.foreground\": \"#3B3A32\",\n\t\t\"editorIndentGuide.activeBackground\": \"#9D550FB0\",\n\t\t\"editor.selectionHighlightBorder\": \"#222218\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet nightOwl = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"011627\",\"token\": \"\"},\n    {\"foreground\": \"637777\",\"token\": \"comment\"},\n    {\"foreground\": \"addb67\",\"token\": \"string\"},\n    {\"foreground\": \"ecc48d\",\"token\": \"vstring.quoted\"},\n    {\"foreground\": \"ecc48d\",\"token\": \"variable.other.readwrite.js\"},\n    {\"foreground\": \"5ca7e4\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"5ca7e4\",\"token\": \"string.regexp keyword.other\"},\n    {\"foreground\": \"5f7e97\",\"token\": \"meta.function punctuation.separator.comma\"},\n    {\"foreground\": \"f78c6c\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"f78c6c\",\"token\": \"constant.character.numeric\"},\n    {\"foreground\": \"addb67\",\"token\": \"variable\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword\"},\n    {\"foreground\": \"c792ea\",\"token\": \"punctuation.accessor\"},\n    {\"foreground\": \"c792ea\",\"token\": \"storage\"},\n    {\"foreground\": \"c792ea\",\"token\": \"meta.var.expr\"},\n    {\"foreground\": \"c792ea\",\"token\": \"meta.class meta.method.declaration meta.var.expr storage.type.jsm\"},\n    {\"foreground\": \"c792ea\",\"token\": \"storage.type.property.js\"},\n    {\"foreground\": \"c792ea\",\"token\": \"storage.type.property.ts\"},\n    {\"foreground\": \"c792ea\",\"token\": \"storage.type.property.tsx\"},\n    {\"foreground\": \"82aaff\",\"token\": \"storage.type\"},\n    {\"foreground\": \"ffcb8b\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"ffcb8b\",\"token\": \"meta.class entity.name.type.class\"},\n    {\"foreground\": \"addb67\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"82aaff\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"addb67\",\"token\": \"punctuation.definition.variable\"},\n    {\"foreground\": \"d3423e\",\"token\": \"punctuation.section.embedded\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"punctuation.terminator.expression\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"punctuation.definition.arguments\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"punctuation.definition.array\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"punctuation.section.array\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"meta.array\"},\n    {\"foreground\": \"d9f5dd\",\"token\": \"punctuation.definition.list.begin\"},\n    {\"foreground\": \"d9f5dd\",\"token\": \"punctuation.definition.list.end\"},\n    {\"foreground\": \"d9f5dd\",\"token\": \"punctuation.separator.arguments\"},\n    {\"foreground\": \"d9f5dd\",\"token\": \"punctuation.definition.list\"},\n    {\"foreground\": \"d3423e\",\"token\": \"string.template meta.template.expression\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"string.template punctuation.definition.string\"},\n    {\"foreground\": \"c792ea\",\"fontStyle\": \"italic\",\"token\": \"italic\"},\n    {\"foreground\": \"addb67\",\"fontStyle\": \"bold\",\"token\": \"bold\"},\n    {\"foreground\": \"82aaff\",\"token\": \"constant.language\"},\n    {\"foreground\": \"82aaff\",\"token\": \"punctuation.definition.constant\"},\n    {\"foreground\": \"82aaff\",\"token\": \"variable.other.constant\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"support.function.construct\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"keyword.other.new\"},\n    {\"foreground\": \"82aaff\",\"token\": \"constant.character\"},\n    {\"foreground\": \"82aaff\",\"token\": \"constant.other\"},\n    {\"foreground\": \"f78c6c\",\"token\": \"constant.character.escape\"},\n    {\"foreground\": \"addb67\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"d7dbe0\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"cc2996\",\"token\": \"punctuation.definition.tag.html\"},\n    {\"foreground\": \"cc2996\",\"token\": \"punctuation.definition.tag.begin\"},\n    {\"foreground\": \"cc2996\",\"token\": \"punctuation.definition.tag.end\"},\n    {\"foreground\": \"addb67\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"addb67\",\"token\": \"entity.name.tag.custom\"},\n    {\"foreground\": \"82aaff\",\"token\": \"support.function\"},\n    {\"foreground\": \"82aaff\",\"token\": \"support.constant\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"upport.constant.meta.property-value\"},\n    {\"foreground\": \"addb67\",\"token\": \"support.type\"},\n    {\"foreground\": \"addb67\",\"token\": \"support.class\"},\n    {\"foreground\": \"addb67\",\"token\": \"support.variable.dom\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"support.constant\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"keyword.other.special-method\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"keyword.other.new\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"keyword.other.debugger\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"keyword.control\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.operator.comparison\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.flow.js\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.flow.ts\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.flow.tsx\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.ruby\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.module.ruby\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.class.ruby\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.def.ruby\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.loop.js\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.loop.ts\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.import.js\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.import.ts\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.import.tsx\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.from.js\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.from.ts\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.control.from.tsx\"},\n    {\"foreground\": \"ffffff\",\"background\": \"ff2c83\",\"token\": \"invalid\"},\n    {\"foreground\": \"ffffff\",\"background\": \"d3423e\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.operator.relational\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.operator.assignement\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.operator.arithmetic\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.operator.bitwise\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.operator.increment\"},\n    {\"foreground\": \"c792ea\",\"token\": \"keyword.operator.ternary\"},\n    {\"foreground\": \"637777\",\"token\": \"comment.line.double-slash\"},\n    {\"foreground\": \"cdebf7\",\"token\": \"object\"},\n    {\"foreground\": \"ff5874\",\"token\": \"constant.language.null\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"meta.brace\"},\n    {\"foreground\": \"c792ea\",\"token\": \"meta.delimiter.period\"},\n    {\"foreground\": \"d9f5dd\",\"token\": \"punctuation.definition.string\"},\n    {\"foreground\": \"ff5874\",\"token\": \"constant.language.boolean\"},\n    {\"foreground\": \"ffffff\",\"token\": \"object.comma\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"variable.parameter.function\"},\n    {\"foreground\": \"80cbc4\",\"token\": \"support.type.vendor.property-name\"},\n    {\"foreground\": \"80cbc4\",\"token\": \"support.constant.vendor.property-value\"},\n    {\"foreground\": \"80cbc4\",\"token\": \"support.type.property-name\"},\n    {\"foreground\": \"80cbc4\",\"token\": \"meta.property-list entity.name.tag\"},\n    {\"foreground\": \"57eaf1\",\"token\": \"meta.property-list entity.name.tag.reference\"},\n    {\"foreground\": \"f78c6c\",\"token\": \"constant.other.color.rgb-value punctuation.definition.constant\"},\n    {\"foreground\": \"ffeb95\",\"token\": \"constant.other.color\"},\n    {\"foreground\": \"ffeb95\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"c792ea\",\"token\": \"meta.selector\"},\n    {\"foreground\": \"fad430\",\"token\": \"entity.other.attribute-name.id\"},\n    {\"foreground\": \"80cbc4\",\"token\": \"meta.property-name\"},\n    {\"foreground\": \"c792ea\",\"token\": \"entity.name.tag.doctype\"},\n    {\"foreground\": \"c792ea\",\"token\": \"meta.tag.sgml.doctype\"},\n    {\"foreground\": \"d9f5dd\",\"token\": \"punctuation.definition.parameters\"},\n    {\"foreground\": \"ecc48d\",\"token\": \"string.quoted\"},\n    {\"foreground\": \"ecc48d\",\"token\": \"string.quoted.double\"},\n    {\"foreground\": \"ecc48d\",\"token\": \"string.quoted.single\"},\n    {\"foreground\": \"addb67\",\"token\": \"support.constant.math\"},\n    {\"foreground\": \"addb67\",\"token\": \"support.type.property-name.json\"},\n    {\"foreground\": \"addb67\",\"token\": \"support.constant.json\"},\n    {\"foreground\": \"c789d6\",\"token\": \"meta.structure.dictionary.value.json string.quoted.double\"},\n    {\"foreground\": \"80cbc4\",\"token\": \"string.quoted.double.json punctuation.definition.string.json\"},\n    {\"foreground\": \"ff5874\",\"token\": \"meta.structure.dictionary.json meta.structure.dictionary.value constant.language\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"variable.other.ruby\"},\n    {\"foreground\": \"ecc48d\",\"token\": \"entity.name.type.class.ruby\"},\n    {\"foreground\": \"ecc48d\",\"token\": \"keyword.control.class.ruby\"},\n    {\"foreground\": \"ecc48d\",\"token\": \"meta.class.ruby\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"constant.language.symbol.hashkey.ruby\"},\n    {\"foreground\": \"e0eddd\",\"background\": \"a57706\",\"fontStyle\": \"italic\",\"token\": \"meta.diff\"},\n    {\"foreground\": \"e0eddd\",\"background\": \"a57706\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.header\"},\n    {\"foreground\": \"ef535090\",\"fontStyle\": \"italic\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"a2bffc\",\"fontStyle\": \"italic\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"a2bffc\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.header.git\"},\n    {\"foreground\": \"a2bffc\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"a2bffc\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"219186\",\"background\": \"eae3ca\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"d3201f\",\"token\": \"other.package.exclude\"},\n    {\"foreground\": \"d3201f\",\"token\": \"other.remove\"},\n    {\"foreground\": \"269186\",\"token\": \"other.add\"},\n    {\"foreground\": \"ff5874\",\"token\": \"constant.language.python\"},\n    {\"foreground\": \"82aaff\",\"token\": \"variable.parameter.function.python\"},\n    {\"foreground\": \"82aaff\",\"token\": \"meta.function-call.arguments.python\"},\n    {\"foreground\": \"b2ccd6\",\"token\": \"meta.function-call.python\"},\n    {\"foreground\": \"b2ccd6\",\"token\": \"meta.function-call.generic.python\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"punctuation.python\"},\n    {\"foreground\": \"addb67\",\"token\": \"entity.name.function.decorator.python\"},\n    {\"foreground\": \"8eace3\",\"token\": \"source.python variable.language.special\"},\n    {\"foreground\": \"82b1ff\",\"token\": \"markup.heading.markdown\"},\n    {\"foreground\": \"c792ea\",\"fontStyle\": \"italic\",\"token\": \"markup.italic.markdown\"},\n    {\"foreground\": \"addb67\",\"fontStyle\": \"bold\",\"token\": \"markup.bold.markdown\"},\n    {\"foreground\": \"697098\",\"token\": \"markup.quote.markdown\"},\n    {\"foreground\": \"80cbc4\",\"token\": \"markup.inline.raw.markdown\"},\n    {\"foreground\": \"ff869a\",\"token\": \"markup.underline.link.markdown\"},\n    {\"foreground\": \"ff869a\",\"token\": \"markup.underline.link.image.markdown\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"string.other.link.title.markdown\"},\n    {\"foreground\": \"d6deeb\",\"token\": \"string.other.link.description.markdown\"},\n    {\"foreground\": \"82b1ff\",\"token\": \"punctuation.definition.string.markdown\"},\n    {\"foreground\": \"82b1ff\",\"token\": \"punctuation.definition.string.begin.markdown\"},\n    {\"foreground\": \"82b1ff\",\"token\": \"punctuation.definition.string.end.markdown\"},\n    {\"foreground\": \"82b1ff\",\"token\": \"meta.link.inline.markdown punctuation.definition.string\"},\n    {\"foreground\": \"7fdbca\",\"token\": \"punctuation.definition.metadata.markdown\"},\n    {\"foreground\": \"82b1ff\",\"token\": \"beginning.punctuation.definition.list.markdown\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#d6deeb\",\n\t\t\"editor.background\": \"#011627\",\n\t\t\"editor.selectionBackground\": \"#5f7e9779\",\n\t\t\"editor.lineHighlightBackground\": \"#010E17\",\n\t\t\"editorCursor.foreground\": \"#80a4c2\",\n\t\t\"editorWhitespace.foreground\": \"#2e2040\",\n\t\t\"editorIndentGuide.background\": \"#5e81ce52\",\n\t\t\"editor.selectionHighlightBorder\": \"#122d42\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet nord = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"2E3440\",\"token\": \"\"},\n    {\"foreground\": \"616e88\",\"token\": \"comment\"},\n    {\"foreground\": \"a3be8c\",\"token\": \"string\"},\n    {\"foreground\": \"b48ead\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"81a1c1\",\"token\": \"constant.language\"},\n    {\"foreground\": \"81a1c1\",\"token\": \"keyword\"},\n    {\"foreground\": \"81a1c1\",\"token\": \"storage\"},\n    {\"foreground\": \"81a1c1\",\"token\": \"storage.type\"},\n    {\"foreground\": \"8fbcbb\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"8fbcbb\",\"fontStyle\": \"  bold\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"88c0d0\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"81a1c1\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"8fbcbb\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"88c0d0\",\"token\": \"support.function\"},\n    {\"foreground\": \"f8f8f0\",\"background\": \"f92672\",\"token\": \"invalid\"},\n    {\"foreground\": \"f8f8f0\",\"background\": \"ae81ff\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"b48ead\",\"token\": \"constant.color.other.rgb-value\"},\n    {\"foreground\": \"ebcb8b\",\"token\": \"constant.character.escape\"},\n    {\"foreground\": \"8fbcbb\",\"token\": \"variable.other.constant\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#D8DEE9\",\n\t\t\"editor.background\": \"#2E3440\",\n\t\t\"editor.selectionBackground\": \"#434C5ECC\",\n\t\t\"editor.lineHighlightBackground\": \"#3B4252\",\n\t\t\"editorCursor.foreground\": \"#D8DEE9\",\n\t\t\"editorWhitespace.foreground\": \"#434C5ECC\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet oceanicNext = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"1B2B34\",\"token\": \"\"},\n    {\"foreground\": \"65737e\",\"token\": \"comment\"},\n    {\"foreground\": \"65737e\",\"token\": \"punctuation.definition.comment\"},\n    {\"foreground\": \"cdd3de\",\"token\": \"variable\"},\n    {\"foreground\": \"c594c5\",\"token\": \"keyword\"},\n    {\"foreground\": \"c594c5\",\"token\": \"storage.type\"},\n    {\"foreground\": \"c594c5\",\"token\": \"storage.modifier\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"constant.other.color\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"punctuation\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"punctuation.definition.tag\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"punctuation.separator.inheritance.php\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"punctuation.definition.tag.html\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"punctuation.definition.tag.begin.html\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"punctuation.definition.tag.end.html\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"punctuation.section.embedded\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"keyword.other.template\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"keyword.other.substitution\"},\n    {\"foreground\": \"eb606b\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"eb606b\",\"token\": \"meta.tag.sgml\"},\n    {\"foreground\": \"eb606b\",\"token\": \"markup.deleted.git_gutter\"},\n    {\"foreground\": \"6699cc\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"6699cc\",\"token\": \"meta.function-call\"},\n    {\"foreground\": \"6699cc\",\"token\": \"variable.function\"},\n    {\"foreground\": \"6699cc\",\"token\": \"support.function\"},\n    {\"foreground\": \"6699cc\",\"token\": \"keyword.other.special-method\"},\n    {\"foreground\": \"6699cc\",\"token\": \"meta.block-level\"},\n    {\"foreground\": \"f2777a\",\"token\": \"support.other.variable\"},\n    {\"foreground\": \"f2777a\",\"token\": \"string.other.link\"},\n    {\"foreground\": \"f99157\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"f99157\",\"token\": \"constant.language\"},\n    {\"foreground\": \"f99157\",\"token\": \"support.constant\"},\n    {\"foreground\": \"f99157\",\"token\": \"constant.character\"},\n    {\"foreground\": \"f99157\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"f99157\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"99c794\",\"fontStyle\": \"normal\",\"token\": \"string\"},\n    {\"foreground\": \"99c794\",\"fontStyle\": \"normal\",\"token\": \"constant.other.symbol\"},\n    {\"foreground\": \"99c794\",\"fontStyle\": \"normal\",\"token\": \"constant.other.key\"},\n    {\"foreground\": \"99c794\",\"fontStyle\": \"normal\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"99c794\",\"fontStyle\": \"normal\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"99c794\",\"fontStyle\": \"normal\",\"token\": \"markup.inserted.git_gutter\"},\n    {\"foreground\": \"99c794\",\"fontStyle\": \"normal\",\"token\": \"meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js\"},\n    {\"foreground\": \"fac863\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"fac863\",\"token\": \"entity.name.type.class\"},\n    {\"foreground\": \"fac863\",\"token\": \"support.type\"},\n    {\"foreground\": \"fac863\",\"token\": \"support.class\"},\n    {\"foreground\": \"fac863\",\"token\": \"support.orther.namespace.use.php\"},\n    {\"foreground\": \"fac863\",\"token\": \"meta.use.php\"},\n    {\"foreground\": \"fac863\",\"token\": \"support.other.namespace.php\"},\n    {\"foreground\": \"fac863\",\"token\": \"markup.changed.git_gutter\"},\n    {\"foreground\": \"ec5f67\",\"token\": \"entity.name.module.js\"},\n    {\"foreground\": \"ec5f67\",\"token\": \"variable.import.parameter.js\"},\n    {\"foreground\": \"ec5f67\",\"token\": \"variable.other.class.js\"},\n    {\"foreground\": \"ec5f67\",\"fontStyle\": \"italic\",\"token\": \"variable.language\"},\n    {\"foreground\": \"cdd3de\",\"token\": \"meta.group.braces.curly.js constant.other.object.key.js string.unquoted.label.js\"},\n    {\"foreground\": \"d8dee9\",\"token\": \"meta.class-method.js entity.name.function.js\"},\n    {\"foreground\": \"d8dee9\",\"token\": \"variable.function.constructor\"},\n    {\"foreground\": \"d8dee9\",\"token\": \"meta.class.js meta.class.property.js meta.method.js string.unquoted.js entity.name.function.js\"},\n    {\"foreground\": \"bb80b3\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"99c794\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"ec5f67\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"bb80b3\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"5fb3b3\",\"token\": \"constant.character.escape\"},\n    {\"fontStyle\": \"underline\",\"token\": \"*url*\"},\n    {\"fontStyle\": \"underline\",\"token\": \"*link*\"},\n    {\"fontStyle\": \"underline\",\"token\": \"*uri*\"},\n    {\"foreground\": \"ab7967\",\"token\": \"constant.numeric.line-number.find-in-files - match\"},\n    {\"foreground\": \"99c794\",\"token\": \"entity.name.filename.find-in-files\"},\n    {\"foreground\": \"6699cc\",\"fontStyle\": \"italic\",\"token\": \"tag.decorator.js entity.name.tag.js\"},\n    {\"foreground\": \"6699cc\",\"fontStyle\": \"italic\",\"token\": \"tag.decorator.js punctuation.definition.tag.js\"},\n    {\"foreground\": \"ec5f67\",\"fontStyle\": \"italic\",\"token\": \"source.js constant.other.object.key.js string.unquoted.label.js\"},\n    {\"foreground\": \"fac863\",\"token\": \"source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"fac863\",\"token\": \"source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string\"},\n    {\"foreground\": \"c594c5\",\"token\": \"source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"c594c5\",\"token\": \"source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string\"},\n    {\"foreground\": \"d8dee9\",\"token\": \"source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"d8dee9\",\"token\": \"source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string\"},\n    {\"foreground\": \"6699cc\",\"token\": \"source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"6699cc\",\"token\": \"source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string\"},\n    {\"foreground\": \"ab7967\",\"token\": \"source.json meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"ab7967\",\"token\": \"source.json meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string\"},\n    {\"foreground\": \"ec5f67\",\"token\": \"source.json meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"ec5f67\",\"token\": \"source.json meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string\"},\n    {\"foreground\": \"f99157\",\"token\": \"source.json meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"f99157\",\"token\": \"source.json meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string\"},\n    {\"foreground\": \"fac863\",\"token\": \"source.json meta meta.structure.dictionary.json string.quoted.double.json - meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"fac863\",\"token\": \"source.json meta meta.structure.dictionary.json punctuation.definition.string - meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string\"},\n    {\"foreground\": \"c594c5\",\"token\": \"source.json meta.structure.dictionary.json string.quoted.double.json - meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json\"},\n    {\"foreground\": \"c594c5\",\"token\": \"source.json meta.structure.dictionary.json punctuation.definition.string - meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#CDD3DE\",\n\t\t\"editor.background\": \"#1B2B34\",\n\t\t\"editor.selectionBackground\": \"#4f5b66\",\n\t\t\"editor.lineHighlightBackground\": \"#65737e55\",\n\t\t\"editorCursor.foreground\": \"#c0c5ce\",\n\t\t\"editorWhitespace.foreground\": \"#65737e\",\n\t\t\"editorIndentGuide.background\": \"#65737F\",\n\t\t\"editorIndentGuide.activeBackground\": \"#FBC95A\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet pastelsOnDark = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"211E1E\",\"token\": \"\"},\n    {\"foreground\": \"555555\",\"token\": \"comment\"},\n    {\"foreground\": \"555555\",\"token\": \"comment.block\"},\n    {\"foreground\": \"ad9361\",\"token\": \"string\"},\n    {\"foreground\": \"cccccc\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"a1a1ff\",\"token\": \"keyword\"},\n    {\"foreground\": \"2f006e\",\"token\": \"meta.preprocessor\"},\n    {\"fontStyle\": \"bold\",\"token\": \"keyword.control.import\"},\n    {\"foreground\": \"a1a1ff\",\"token\": \"support.function\"},\n    {\"foreground\": \"0000ff\",\"token\": \"declaration.function function-result\"},\n    {\"fontStyle\": \"bold\",\"token\": \"declaration.function function-name\"},\n    {\"fontStyle\": \"bold\",\"token\": \"declaration.function argument-name\"},\n    {\"foreground\": \"0000ff\",\"token\": \"declaration.function function-arg-type\"},\n    {\"fontStyle\": \"italic\",\"token\": \"declaration.function function-argument\"},\n    {\"fontStyle\": \"underline\",\"token\": \"declaration.class class-name\"},\n    {\"fontStyle\": \"italic underline\",\"token\": \"declaration.class class-inheritance\"},\n    {\"foreground\": \"fff9f9\",\"background\": \"ff0000\",\"fontStyle\": \"bold\",\"token\": \"invalid\"},\n    {\"background\": \"ffd0d0\",\"token\": \"invalid.deprecated.trailing-whitespace\"},\n    {\"fontStyle\": \"italic\",\"token\": \"declaration.section section-name\"},\n    {\"foreground\": \"c10006\",\"token\": \"string.interpolation\"},\n    {\"foreground\": \"666666\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"c1c144\",\"token\": \"variable\"},\n    {\"foreground\": \"6782d3\",\"token\": \"constant\"},\n    {\"foreground\": \"afa472\",\"token\": \"constant.character\"},\n    {\"foreground\": \"de8e30\",\"fontStyle\": \"bold\",\"token\": \"constant.language\"},\n    {\"fontStyle\": \"underline\",\"token\": \"embedded\"},\n    {\"foreground\": \"858ef4\",\"token\": \"keyword.markup.element-name\"},\n    {\"foreground\": \"9b456f\",\"token\": \"keyword.markup.attribute-name\"},\n    {\"foreground\": \"9b456f\",\"token\": \"meta.attribute-with-value\"},\n    {\"foreground\": \"c82255\",\"fontStyle\": \"bold\",\"token\": \"keyword.exception\"},\n    {\"foreground\": \"47b8d6\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"6969fa\",\"fontStyle\": \"bold\",\"token\": \"keyword.control\"},\n    {\"foreground\": \"68685b\",\"token\": \"meta.tag.preprocessor.xml\"},\n    {\"foreground\": \"888888\",\"token\": \"meta.tag.sgml.doctype\"},\n    {\"fontStyle\": \"italic\",\"token\": \"string.quoted.docinfo.doctype.DTD\"},\n    {\"foreground\": \"909090\",\"token\": \"comment.other.server-side-include.xhtml\"},\n    {\"foreground\": \"909090\",\"token\": \"comment.other.server-side-include.html\"},\n    {\"foreground\": \"858ef4\",\"token\": \"text.html declaration.tag\"},\n    {\"foreground\": \"858ef4\",\"token\": \"text.html meta.tag\"},\n    {\"foreground\": \"858ef4\",\"token\": \"text.html entity.name.tag.xhtml\"},\n    {\"foreground\": \"9b456f\",\"token\": \"keyword.markup.attribute-name\"},\n    {\"foreground\": \"777777\",\"token\": \"keyword.other.phpdoc.php\"},\n    {\"foreground\": \"c82255\",\"token\": \"keyword.other.include.php\"},\n    {\"foreground\": \"de8e20\",\"fontStyle\": \"bold\",\"token\": \"support.constant.core.php\"},\n    {\"foreground\": \"de8e10\",\"fontStyle\": \"bold\",\"token\": \"support.constant.std.php\"},\n    {\"foreground\": \"b72e1d\",\"token\": \"variable.other.global.php\"},\n    {\"foreground\": \"00ff00\",\"token\": \"variable.other.global.safer.php\"},\n    {\"foreground\": \"bfa36d\",\"token\": \"string.quoted.single.php\"},\n    {\"foreground\": \"6969fa\",\"token\": \"keyword.storage.php\"},\n    {\"foreground\": \"ad9361\",\"token\": \"string.quoted.double.php\"},\n    {\"foreground\": \"ec9e00\",\"token\": \"entity.other.attribute-name.id.css\"},\n    {\"foreground\": \"b8cd06\",\"fontStyle\": \"bold\",\"token\": \"entity.name.tag.css\"},\n    {\"foreground\": \"edca06\",\"token\": \"entity.other.attribute-name.class.css\"},\n    {\"foreground\": \"2e759c\",\"token\": \"entity.other.attribute-name.pseudo-class.css\"},\n    {\"foreground\": \"ffffff\",\"background\": \"ff0000\",\"token\": \"invalid.bad-comma.css\"},\n    {\"foreground\": \"9b2e4d\",\"token\": \"support.constant.property-value.css\"},\n    {\"foreground\": \"e1c96b\",\"token\": \"support.type.property-name.css\"},\n    {\"foreground\": \"666633\",\"token\": \"constant.other.rgb-value.css\"},\n    {\"foreground\": \"666633\",\"token\": \"support.constant.font-name.css\"},\n    {\"foreground\": \"7171f3\",\"token\": \"support.constant.tm-language-def\"},\n    {\"foreground\": \"7171f3\",\"token\": \"support.constant.name.tm-language-def\"},\n    {\"foreground\": \"6969fa\",\"token\": \"keyword.other.unit.css\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#DADADA\",\n\t\t\"editor.background\": \"#211E1E\",\n\t\t\"editor.selectionBackground\": \"#73597E80\",\n\t\t\"editor.lineHighlightBackground\": \"#353030\",\n\t\t\"editorCursor.foreground\": \"#FFFFFF\",\n\t\t\"editorWhitespace.foreground\": \"#4F4D4D\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet sunburst = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"000000\",\"token\": \"\"},\n    {\"foreground\": \"aeaeae\",\"fontStyle\": \"italic\",\"token\": \"comment\"},\n    {\"foreground\": \"3387cc\",\"token\": \"constant\"},\n    {\"foreground\": \"89bdff\",\"token\": \"entity\"},\n    {\"foreground\": \"e28964\",\"token\": \"keyword\"},\n    {\"foreground\": \"99cf50\",\"token\": \"storage\"},\n    {\"foreground\": \"65b042\",\"token\": \"string\"},\n    {\"foreground\": \"9b859d\",\"token\": \"support\"},\n    {\"foreground\": \"3e87e3\",\"token\": \"variable\"},\n    {\"foreground\": \"fd5ff1\",\"fontStyle\": \"italic underline\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"fd5ff1\",\"background\": \"562d56bf\",\"token\": \"invalid.illegal\"},\n    {\"background\": \"b1b3ba08\",\"token\": \"text source\"},\n    {\"foreground\": \"9b5c2e\",\"fontStyle\": \"italic\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"daefa3\",\"token\": \"string.quoted source\"},\n    {\"foreground\": \"ddf2a4\",\"token\": \"string constant\"},\n    {\"foreground\": \"e9c062\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"cf7d34\",\"token\": \"string.regexp constant.character.escape\"},\n    {\"foreground\": \"cf7d34\",\"token\": \"string.regexp source.ruby.embedded\"},\n    {\"foreground\": \"cf7d34\",\"token\": \"string.regexp string.regexp.arbitrary-repitition\"},\n    {\"foreground\": \"8a9a95\",\"token\": \"string variable\"},\n    {\"foreground\": \"dad085\",\"token\": \"support.function\"},\n    {\"foreground\": \"cf6a4c\",\"token\": \"support.constant\"},\n    {\"foreground\": \"8996a8\",\"token\": \"meta.preprocessor.c\"},\n    {\"foreground\": \"afc4db\",\"token\": \"meta.preprocessor.c keyword\"},\n    {\"fontStyle\": \"underline\",\"token\": \"entity.name.type\"},\n    {\"foreground\": \"676767\",\"fontStyle\": \"italic\",\"token\": \"meta.cast\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.sgml.html meta.doctype\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.sgml.html meta.doctype entity\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.sgml.html meta.doctype string\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.xml-processing\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.xml-processing entity\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.xml-processing string\"},\n    {\"foreground\": \"89bdff\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"89bdff\",\"token\": \"meta.tag entity\"},\n    {\"foreground\": \"e0c589\",\"token\": \"source entity.name.tag\"},\n    {\"foreground\": \"e0c589\",\"token\": \"source entity.other.attribute-name\"},\n    {\"foreground\": \"e0c589\",\"token\": \"meta.tag.inline\"},\n    {\"foreground\": \"e0c589\",\"token\": \"meta.tag.inline entity\"},\n    {\"foreground\": \"e18964\",\"token\": \"entity.name.tag.namespace\"},\n    {\"foreground\": \"e18964\",\"token\": \"entity.other.attribute-name.namespace\"},\n    {\"foreground\": \"cda869\",\"token\": \"meta.selector.css entity.name.tag\"},\n    {\"foreground\": \"8f9d6a\",\"token\": \"meta.selector.css entity.other.attribute-name.tag.pseudo-class\"},\n    {\"foreground\": \"8b98ab\",\"token\": \"meta.selector.css entity.other.attribute-name.id\"},\n    {\"foreground\": \"9b703f\",\"token\": \"meta.selector.css entity.other.attribute-name.class\"},\n    {\"foreground\": \"c5af75\",\"token\": \"support.type.property-name.css\"},\n    {\"foreground\": \"f9ee98\",\"token\": \"meta.property-group support.constant.property-value.css\"},\n    {\"foreground\": \"f9ee98\",\"token\": \"meta.property-value support.constant.property-value.css\"},\n    {\"foreground\": \"8693a5\",\"token\": \"meta.preprocessor.at-rule keyword.control.at-rule\"},\n    {\"foreground\": \"dd7b3b\",\"token\": \"meta.property-value support.constant.named-color.css\"},\n    {\"foreground\": \"dd7b3b\",\"token\": \"meta.property-value constant\"},\n    {\"foreground\": \"8f9d6a\",\"token\": \"meta.constructor.argument.css\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"0e2231\",\"fontStyle\": \"italic\",\"token\": \"meta.diff\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"0e2231\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.header\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"420e09\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"4a410d\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"253b22\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"e9c062\",\"fontStyle\": \"italic\",\"token\": \"markup.italic\"},\n    {\"foreground\": \"e9c062\",\"fontStyle\": \"bold\",\"token\": \"markup.bold\"},\n    {\"foreground\": \"e18964\",\"fontStyle\": \"underline\",\"token\": \"markup.underline\"},\n    {\"foreground\": \"e1d4b9\",\"background\": \"fee09c12\",\"fontStyle\": \"italic\",\"token\": \"markup.quote\"},\n    {\"foreground\": \"fedcc5\",\"background\": \"632d04\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"fedcc5\",\"background\": \"632d04\",\"token\": \"markup.heading entity\"},\n    {\"foreground\": \"e1d4b9\",\"token\": \"markup.list\"},\n    {\"foreground\": \"578bb3\",\"background\": \"b1b3ba08\",\"token\": \"markup.raw\"},\n    {\"foreground\": \"f67b37\",\"fontStyle\": \"italic\",\"token\": \"markup comment\"},\n    {\"foreground\": \"60a633\",\"background\": \"242424\",\"token\": \"meta.separator\"},\n    {\"background\": \"eeeeee29\",\"token\": \"meta.line.entry.logfile\"},\n    {\"background\": \"eeeeee29\",\"token\": \"meta.line.exit.logfile\"},\n    {\"background\": \"751012\",\"token\": \"meta.line.error.logfile\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#F8F8F8\",\n\t\t\"editor.background\": \"#000000\",\n\t\t\"editor.selectionBackground\": \"#DDF0FF33\",\n\t\t\"editor.lineHighlightBackground\": \"#FFFFFF0D\",\n\t\t\"editorCursor.foreground\": \"#A7A7A7\",\n\t\t\"editorWhitespace.foreground\": \"#CAE2FB3D\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet tomorrowNightBlue = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"002451\",\"token\": \"\"},\n    {\"foreground\": \"7285b7\",\"token\": \"comment\"},\n    {\"foreground\": \"ffffff\",\"token\": \"keyword.operator.class\"},\n    {\"foreground\": \"ffffff\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"ffffff\",\"token\": \"constant.other\"},\n    {\"foreground\": \"ffffff\",\"token\": \"source.php.embedded.line\"},\n    {\"foreground\": \"ff9da4\",\"token\": \"variable\"},\n    {\"foreground\": \"ff9da4\",\"token\": \"support.other.variable\"},\n    {\"foreground\": \"ff9da4\",\"token\": \"string.other.link\"},\n    {\"foreground\": \"ff9da4\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"ff9da4\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"ff9da4\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"ff9da4\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"ff9da4\",\"token\": \"declaration.tag\"},\n    {\"foreground\": \"ff9da4\",\"token\": \"markup.deleted.git_gutter\"},\n    {\"foreground\": \"ffc58f\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"ffc58f\",\"token\": \"constant.language\"},\n    {\"foreground\": \"ffc58f\",\"token\": \"support.constant\"},\n    {\"foreground\": \"ffc58f\",\"token\": \"constant.character\"},\n    {\"foreground\": \"ffc58f\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"ffc58f\",\"token\": \"punctuation.section.embedded\"},\n    {\"foreground\": \"ffc58f\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"ffeead\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"ffeead\",\"token\": \"entity.name.type.class\"},\n    {\"foreground\": \"ffeead\",\"token\": \"support.type\"},\n    {\"foreground\": \"ffeead\",\"token\": \"support.class\"},\n    {\"foreground\": \"d1f1a9\",\"token\": \"string\"},\n    {\"foreground\": \"d1f1a9\",\"token\": \"constant.other.symbol\"},\n    {\"foreground\": \"d1f1a9\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"d1f1a9\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"d1f1a9\",\"token\": \"markup.inserted.git_gutter\"},\n    {\"foreground\": \"99ffff\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"99ffff\",\"token\": \"constant.other.color\"},\n    {\"foreground\": \"bbdaff\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"bbdaff\",\"token\": \"meta.function-call\"},\n    {\"foreground\": \"bbdaff\",\"token\": \"support.function\"},\n    {\"foreground\": \"bbdaff\",\"token\": \"keyword.other.special-method\"},\n    {\"foreground\": \"bbdaff\",\"token\": \"meta.block-level\"},\n    {\"foreground\": \"bbdaff\",\"token\": \"markup.changed.git_gutter\"},\n    {\"foreground\": \"ebbbff\",\"token\": \"keyword\"},\n    {\"foreground\": \"ebbbff\",\"token\": \"storage\"},\n    {\"foreground\": \"ebbbff\",\"token\": \"storage.type\"},\n    {\"foreground\": \"ebbbff\",\"token\": \"entity.name.tag.css\"},\n    {\"foreground\": \"ffffff\",\"background\": \"f99da5\",\"token\": \"invalid\"},\n    {\"foreground\": \"ffffff\",\"background\": \"bbdafe\",\"token\": \"meta.separator\"},\n    {\"foreground\": \"ffffff\",\"background\": \"ebbbff\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.inserted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.deleted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"718c00\",\"token\": \"markup.inserted.diff\"},\n    {\"foreground\": \"718c00\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"c82829\",\"token\": \"markup.deleted.diff\"},\n    {\"foreground\": \"c82829\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"3e999f\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.range\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#FFFFFF\",\n\t\t\"editor.background\": \"#002451\",\n\t\t\"editor.selectionBackground\": \"#003F8E\",\n\t\t\"editor.lineHighlightBackground\": \"#00346E\",\n\t\t\"editorCursor.foreground\": \"#FFFFFF\",\n\t\t\"editorWhitespace.foreground\": \"#404F7D\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet tomorrowNightBright = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"000000\",\"token\": \"\"},\n    {\"foreground\": \"969896\",\"token\": \"comment\"},\n    {\"foreground\": \"eeeeee\",\"token\": \"keyword.operator.class\"},\n    {\"foreground\": \"eeeeee\",\"token\": \"constant.other\"},\n    {\"foreground\": \"eeeeee\",\"token\": \"source.php.embedded.line\"},\n    {\"foreground\": \"d54e53\",\"token\": \"variable\"},\n    {\"foreground\": \"d54e53\",\"token\": \"support.other.variable\"},\n    {\"foreground\": \"d54e53\",\"token\": \"string.other.link\"},\n    {\"foreground\": \"d54e53\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"d54e53\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"d54e53\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"d54e53\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"d54e53\",\"token\": \"declaration.tag\"},\n    {\"foreground\": \"d54e53\",\"token\": \"markup.deleted.git_gutter\"},\n    {\"foreground\": \"e78c45\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"e78c45\",\"token\": \"constant.language\"},\n    {\"foreground\": \"e78c45\",\"token\": \"support.constant\"},\n    {\"foreground\": \"e78c45\",\"token\": \"constant.character\"},\n    {\"foreground\": \"e78c45\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"e78c45\",\"token\": \"punctuation.section.embedded\"},\n    {\"foreground\": \"e78c45\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"e7c547\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"e7c547\",\"token\": \"entity.name.type.class\"},\n    {\"foreground\": \"e7c547\",\"token\": \"support.type\"},\n    {\"foreground\": \"e7c547\",\"token\": \"support.class\"},\n    {\"foreground\": \"b9ca4a\",\"token\": \"string\"},\n    {\"foreground\": \"b9ca4a\",\"token\": \"constant.other.symbol\"},\n    {\"foreground\": \"b9ca4a\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"b9ca4a\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"b9ca4a\",\"token\": \"markup.inserted.git_gutter\"},\n    {\"foreground\": \"70c0b1\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"70c0b1\",\"token\": \"constant.other.color\"},\n    {\"foreground\": \"7aa6da\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"7aa6da\",\"token\": \"meta.function-call\"},\n    {\"foreground\": \"7aa6da\",\"token\": \"support.function\"},\n    {\"foreground\": \"7aa6da\",\"token\": \"keyword.other.special-method\"},\n    {\"foreground\": \"7aa6da\",\"token\": \"meta.block-level\"},\n    {\"foreground\": \"7aa6da\",\"token\": \"markup.changed.git_gutter\"},\n    {\"foreground\": \"c397d8\",\"token\": \"keyword\"},\n    {\"foreground\": \"c397d8\",\"token\": \"storage\"},\n    {\"foreground\": \"c397d8\",\"token\": \"storage.type\"},\n    {\"foreground\": \"c397d8\",\"token\": \"entity.name.tag.css\"},\n    {\"foreground\": \"ced2cf\",\"background\": \"df5f5f\",\"token\": \"invalid\"},\n    {\"foreground\": \"ced2cf\",\"background\": \"82a3bf\",\"token\": \"meta.separator\"},\n    {\"foreground\": \"ced2cf\",\"background\": \"b798bf\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.inserted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.deleted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"718c00\",\"token\": \"markup.inserted.diff\"},\n    {\"foreground\": \"718c00\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"c82829\",\"token\": \"markup.deleted.diff\"},\n    {\"foreground\": \"c82829\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"3e999f\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.range\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#DEDEDE\",\n\t\t\"editor.background\": \"#000000\",\n\t\t\"editor.selectionBackground\": \"#424242\",\n\t\t\"editor.lineHighlightBackground\": \"#2A2A2A\",\n\t\t\"editorCursor.foreground\": \"#9F9F9F\",\n\t\t\"editorWhitespace.foreground\": \"#343434\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet tomorrowNightEighties = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"2D2D2D\",\"token\": \"\"},\n    {\"foreground\": \"999999\",\"token\": \"comment\"},\n    {\"foreground\": \"cccccc\",\"token\": \"keyword.operator.class\"},\n    {\"foreground\": \"cccccc\",\"token\": \"constant.other\"},\n    {\"foreground\": \"cccccc\",\"token\": \"source.php.embedded.line\"},\n    {\"foreground\": \"f2777a\",\"token\": \"variable\"},\n    {\"foreground\": \"f2777a\",\"token\": \"support.other.variable\"},\n    {\"foreground\": \"f2777a\",\"token\": \"string.other.link\"},\n    {\"foreground\": \"f2777a\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"f2777a\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"f2777a\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"f2777a\",\"token\": \"declaration.tag\"},\n    {\"foreground\": \"f2777a\",\"token\": \"markup.deleted.git_gutter\"},\n    {\"foreground\": \"f99157\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"f99157\",\"token\": \"constant.language\"},\n    {\"foreground\": \"f99157\",\"token\": \"support.constant\"},\n    {\"foreground\": \"f99157\",\"token\": \"constant.character\"},\n    {\"foreground\": \"f99157\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"f99157\",\"token\": \"punctuation.section.embedded\"},\n    {\"foreground\": \"f99157\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"ffcc66\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"ffcc66\",\"token\": \"entity.name.type.class\"},\n    {\"foreground\": \"ffcc66\",\"token\": \"support.type\"},\n    {\"foreground\": \"ffcc66\",\"token\": \"support.class\"},\n    {\"foreground\": \"99cc99\",\"token\": \"string\"},\n    {\"foreground\": \"99cc99\",\"token\": \"constant.other.symbol\"},\n    {\"foreground\": \"99cc99\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"99cc99\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"99cc99\",\"token\": \"markup.inserted.git_gutter\"},\n    {\"foreground\": \"66cccc\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"66cccc\",\"token\": \"constant.other.color\"},\n    {\"foreground\": \"6699cc\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"6699cc\",\"token\": \"meta.function-call\"},\n    {\"foreground\": \"6699cc\",\"token\": \"support.function\"},\n    {\"foreground\": \"6699cc\",\"token\": \"keyword.other.special-method\"},\n    {\"foreground\": \"6699cc\",\"token\": \"meta.block-level\"},\n    {\"foreground\": \"6699cc\",\"token\": \"markup.changed.git_gutter\"},\n    {\"foreground\": \"cc99cc\",\"token\": \"keyword\"},\n    {\"foreground\": \"cc99cc\",\"token\": \"storage\"},\n    {\"foreground\": \"cc99cc\",\"token\": \"storage.type\"},\n    {\"foreground\": \"cc99cc\",\"token\": \"entity.name.tag.css\"},\n    {\"foreground\": \"cdcdcd\",\"background\": \"f2777a\",\"token\": \"invalid\"},\n    {\"foreground\": \"cdcdcd\",\"background\": \"99cccc\",\"token\": \"meta.separator\"},\n    {\"foreground\": \"cdcdcd\",\"background\": \"cc99cc\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.inserted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.deleted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"718c00\",\"token\": \"markup.inserted.diff\"},\n    {\"foreground\": \"718c00\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"c82829\",\"token\": \"markup.deleted.diff\"},\n    {\"foreground\": \"c82829\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"3e999f\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.range\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#CCCCCC\",\n\t\t\"editor.background\": \"#2D2D2D\",\n\t\t\"editor.selectionBackground\": \"#515151\",\n\t\t\"editor.lineHighlightBackground\": \"#393939\",\n\t\t\"editorCursor.foreground\": \"#CCCCCC\",\n\t\t\"editorWhitespace.foreground\": \"#6A6A6A\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet tomorrowNight = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"1D1F21\",\"token\": \"\"},\n    {\"foreground\": \"969896\",\"token\": \"comment\"},\n    {\"foreground\": \"ced1cf\",\"token\": \"keyword.operator.class\"},\n    {\"foreground\": \"ced1cf\",\"token\": \"constant.other\"},\n    {\"foreground\": \"ced1cf\",\"token\": \"source.php.embedded.line\"},\n    {\"foreground\": \"cc6666\",\"token\": \"variable\"},\n    {\"foreground\": \"cc6666\",\"token\": \"support.other.variable\"},\n    {\"foreground\": \"cc6666\",\"token\": \"string.other.link\"},\n    {\"foreground\": \"cc6666\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"cc6666\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"cc6666\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"cc6666\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"cc6666\",\"token\": \"declaration.tag\"},\n    {\"foreground\": \"cc6666\",\"token\": \"markup.deleted.git_gutter\"},\n    {\"foreground\": \"de935f\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"de935f\",\"token\": \"constant.language\"},\n    {\"foreground\": \"de935f\",\"token\": \"support.constant\"},\n    {\"foreground\": \"de935f\",\"token\": \"constant.character\"},\n    {\"foreground\": \"de935f\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"de935f\",\"token\": \"punctuation.section.embedded\"},\n    {\"foreground\": \"de935f\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"f0c674\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"f0c674\",\"token\": \"entity.name.type.class\"},\n    {\"foreground\": \"f0c674\",\"token\": \"support.type\"},\n    {\"foreground\": \"f0c674\",\"token\": \"support.class\"},\n    {\"foreground\": \"b5bd68\",\"token\": \"string\"},\n    {\"foreground\": \"b5bd68\",\"token\": \"constant.other.symbol\"},\n    {\"foreground\": \"b5bd68\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"b5bd68\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"b5bd68\",\"token\": \"markup.inserted.git_gutter\"},\n    {\"foreground\": \"8abeb7\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"8abeb7\",\"token\": \"constant.other.color\"},\n    {\"foreground\": \"81a2be\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"81a2be\",\"token\": \"meta.function-call\"},\n    {\"foreground\": \"81a2be\",\"token\": \"support.function\"},\n    {\"foreground\": \"81a2be\",\"token\": \"keyword.other.special-method\"},\n    {\"foreground\": \"81a2be\",\"token\": \"meta.block-level\"},\n    {\"foreground\": \"81a2be\",\"token\": \"markup.changed.git_gutter\"},\n    {\"foreground\": \"b294bb\",\"token\": \"keyword\"},\n    {\"foreground\": \"b294bb\",\"token\": \"storage\"},\n    {\"foreground\": \"b294bb\",\"token\": \"storage.type\"},\n    {\"foreground\": \"b294bb\",\"token\": \"entity.name.tag.css\"},\n    {\"foreground\": \"ced2cf\",\"background\": \"df5f5f\",\"token\": \"invalid\"},\n    {\"foreground\": \"ced2cf\",\"background\": \"82a3bf\",\"token\": \"meta.separator\"},\n    {\"foreground\": \"ced2cf\",\"background\": \"b798bf\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.inserted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.deleted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"718c00\",\"token\": \"markup.inserted.diff\"},\n    {\"foreground\": \"718c00\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"c82829\",\"token\": \"markup.deleted.diff\"},\n    {\"foreground\": \"c82829\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"3e999f\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.range\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#C5C8C6\",\n\t\t\"editor.background\": \"#1D1F21\",\n\t\t\"editor.selectionBackground\": \"#3074B8\",\n\t\t\"editor.lineHighlightBackground\": \"#282A2E\",\n\t\t\"editorCursor.foreground\": \"#AEAFAD\",\n\t\t\"editorWhitespace.foreground\": \"#4B4E55\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet tomorrow = {\n\t\"base\": \"vs\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"FFFFFF\",\"token\": \"\"},\n    {\"foreground\": \"8e908c\",\"token\": \"comment\"},\n    {\"foreground\": \"666969\",\"token\": \"keyword.operator.class\"},\n    {\"foreground\": \"666969\",\"token\": \"constant.other\"},\n    {\"foreground\": \"666969\",\"token\": \"source.php.embedded.line\"},\n    {\"foreground\": \"c82829\",\"token\": \"variable\"},\n    {\"foreground\": \"c82829\",\"token\": \"support.other.variable\"},\n    {\"foreground\": \"c82829\",\"token\": \"string.other.link\"},\n    {\"foreground\": \"c82829\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"c82829\",\"token\": \"entity.name.tag\"},\n    {\"foreground\": \"c82829\",\"token\": \"entity.other.attribute-name\"},\n    {\"foreground\": \"c82829\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"c82829\",\"token\": \"declaration.tag\"},\n    {\"foreground\": \"c82829\",\"token\": \"markup.deleted.git_gutter\"},\n    {\"foreground\": \"f5871f\",\"token\": \"constant.numeric\"},\n    {\"foreground\": \"f5871f\",\"token\": \"constant.language\"},\n    {\"foreground\": \"f5871f\",\"token\": \"support.constant\"},\n    {\"foreground\": \"f5871f\",\"token\": \"constant.character\"},\n    {\"foreground\": \"f5871f\",\"token\": \"variable.parameter\"},\n    {\"foreground\": \"f5871f\",\"token\": \"punctuation.section.embedded\"},\n    {\"foreground\": \"f5871f\",\"token\": \"keyword.other.unit\"},\n    {\"foreground\": \"c99e00\",\"token\": \"entity.name.class\"},\n    {\"foreground\": \"c99e00\",\"token\": \"entity.name.type.class\"},\n    {\"foreground\": \"c99e00\",\"token\": \"support.type\"},\n    {\"foreground\": \"c99e00\",\"token\": \"support.class\"},\n    {\"foreground\": \"718c00\",\"token\": \"string\"},\n    {\"foreground\": \"718c00\",\"token\": \"constant.other.symbol\"},\n    {\"foreground\": \"718c00\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"718c00\",\"token\": \"markup.heading\"},\n    {\"foreground\": \"718c00\",\"token\": \"markup.inserted.git_gutter\"},\n    {\"foreground\": \"3e999f\",\"token\": \"keyword.operator\"},\n    {\"foreground\": \"3e999f\",\"token\": \"constant.other.color\"},\n    {\"foreground\": \"4271ae\",\"token\": \"entity.name.function\"},\n    {\"foreground\": \"4271ae\",\"token\": \"meta.function-call\"},\n    {\"foreground\": \"4271ae\",\"token\": \"support.function\"},\n    {\"foreground\": \"4271ae\",\"token\": \"keyword.other.special-method\"},\n    {\"foreground\": \"4271ae\",\"token\": \"meta.block-level\"},\n    {\"foreground\": \"4271ae\",\"token\": \"markup.changed.git_gutter\"},\n    {\"foreground\": \"8959a8\",\"token\": \"keyword\"},\n    {\"foreground\": \"8959a8\",\"token\": \"storage\"},\n    {\"foreground\": \"8959a8\",\"token\": \"storage.type\"},\n    {\"foreground\": \"ffffff\",\"background\": \"c82829\",\"token\": \"invalid\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.separator\"},\n    {\"foreground\": \"ffffff\",\"background\": \"8959a8\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.inserted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"markup.deleted.diff\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"ffffff\",\"token\": \"meta.diff.header.from-file\"},\n    {\"background\": \"718c00\",\"token\": \"markup.inserted.diff\"},\n    {\"background\": \"718c00\",\"token\": \"meta.diff.header.to-file\"},\n    {\"background\": \"c82829\",\"token\": \"markup.deleted.diff\"},\n    {\"background\": \"c82829\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.from-file\"},\n    {\"foreground\": \"ffffff\",\"background\": \"4271ae\",\"token\": \"meta.diff.header.to-file\"},\n    {\"foreground\": \"3e999f\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.range\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#4D4D4C\",\n\t\t\"editor.background\": \"#FFFFFF\",\n\t\t\"editor.selectionBackground\": \"#D6D6D6\",\n\t\t\"editor.lineHighlightBackground\": \"#EFEFEF\",\n\t\t\"editorCursor.foreground\": \"#AEAFAD\",\n\t\t\"editorWhitespace.foreground\": \"#D1D1D1\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nlet twilight = {\n\t\"base\": \"vs-dark\",\n\t\"inherit\": true,\n\t\"rules\": [\n    {\"background\": \"141414\",\"token\": \"\"},\n    {\"foreground\": \"5f5a60\",\"fontStyle\": \"italic\",\"token\": \"comment\"},\n    {\"foreground\": \"cf6a4c\",\"token\": \"constant\"},\n    {\"foreground\": \"9b703f\",\"token\": \"entity\"},\n    {\"foreground\": \"cda869\",\"token\": \"keyword\"},\n    {\"foreground\": \"f9ee98\",\"token\": \"storage\"},\n    {\"foreground\": \"8f9d6a\",\"token\": \"string\"},\n    {\"foreground\": \"9b859d\",\"token\": \"support\"},\n    {\"foreground\": \"7587a6\",\"token\": \"variable\"},\n    {\"foreground\": \"d2a8a1\",\"fontStyle\": \"italic underline\",\"token\": \"invalid.deprecated\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"562d56bf\",\"token\": \"invalid.illegal\"},\n    {\"background\": \"b0b3ba14\",\"token\": \"text source\"},\n    {\"background\": \"b1b3ba21\",\"token\": \"text.html.ruby source\"},\n    {\"foreground\": \"9b5c2e\",\"fontStyle\": \"italic\",\"token\": \"entity.other.inherited-class\"},\n    {\"foreground\": \"daefa3\",\"token\": \"string source\"},\n    {\"foreground\": \"ddf2a4\",\"token\": \"string constant\"},\n    {\"foreground\": \"e9c062\",\"token\": \"string.regexp\"},\n    {\"foreground\": \"cf7d34\",\"token\": \"string.regexp constant.character.escape\"},\n    {\"foreground\": \"cf7d34\",\"token\": \"string.regexp source.ruby.embedded\"},\n    {\"foreground\": \"cf7d34\",\"token\": \"string.regexp string.regexp.arbitrary-repitition\"},\n    {\"foreground\": \"8a9a95\",\"token\": \"string variable\"},\n    {\"foreground\": \"dad085\",\"token\": \"support.function\"},\n    {\"foreground\": \"cf6a4c\",\"token\": \"support.constant\"},\n    {\"foreground\": \"8996a8\",\"token\": \"meta.preprocessor.c\"},\n    {\"foreground\": \"afc4db\",\"token\": \"meta.preprocessor.c keyword\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.tag.sgml.doctype\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.tag.sgml.doctype entity\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.tag.sgml.doctype string\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.tag.preprocessor.xml\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.tag.preprocessor.xml entity\"},\n    {\"foreground\": \"494949\",\"token\": \"meta.tag.preprocessor.xml string\"},\n    {\"foreground\": \"ac885b\",\"token\": \"declaration.tag\"},\n    {\"foreground\": \"ac885b\",\"token\": \"declaration.tag entity\"},\n    {\"foreground\": \"ac885b\",\"token\": \"meta.tag\"},\n    {\"foreground\": \"ac885b\",\"token\": \"meta.tag entity\"},\n    {\"foreground\": \"e0c589\",\"token\": \"declaration.tag.inline\"},\n    {\"foreground\": \"e0c589\",\"token\": \"declaration.tag.inline entity\"},\n    {\"foreground\": \"e0c589\",\"token\": \"source entity.name.tag\"},\n    {\"foreground\": \"e0c589\",\"token\": \"source entity.other.attribute-name\"},\n    {\"foreground\": \"e0c589\",\"token\": \"meta.tag.inline\"},\n    {\"foreground\": \"e0c589\",\"token\": \"meta.tag.inline entity\"},\n    {\"foreground\": \"cda869\",\"token\": \"meta.selector.css entity.name.tag\"},\n    {\"foreground\": \"8f9d6a\",\"token\": \"meta.selector.css entity.other.attribute-name.tag.pseudo-class\"},\n    {\"foreground\": \"8b98ab\",\"token\": \"meta.selector.css entity.other.attribute-name.id\"},\n    {\"foreground\": \"9b703f\",\"token\": \"meta.selector.css entity.other.attribute-name.class\"},\n    {\"foreground\": \"c5af75\",\"token\": \"support.type.property-name.css\"},\n    {\"foreground\": \"f9ee98\",\"token\": \"meta.property-group support.constant.property-value.css\"},\n    {\"foreground\": \"f9ee98\",\"token\": \"meta.property-value support.constant.property-value.css\"},\n    {\"foreground\": \"8693a5\",\"token\": \"meta.preprocessor.at-rule keyword.control.at-rule\"},\n    {\"foreground\": \"ca7840\",\"token\": \"meta.property-value support.constant.named-color.css\"},\n    {\"foreground\": \"ca7840\",\"token\": \"meta.property-value constant\"},\n    {\"foreground\": \"8f9d6a\",\"token\": \"meta.constructor.argument.css\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"0e2231\",\"fontStyle\": \"italic\",\"token\": \"meta.diff\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"0e2231\",\"fontStyle\": \"italic\",\"token\": \"meta.diff.header\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"0e2231\",\"fontStyle\": \"italic\",\"token\": \"meta.separator\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"420e09\",\"token\": \"markup.deleted\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"4a410d\",\"token\": \"markup.changed\"},\n    {\"foreground\": \"f8f8f8\",\"background\": \"253b22\",\"token\": \"markup.inserted\"},\n    {\"foreground\": \"f9ee98\",\"token\": \"markup.list\"},\n    {\"foreground\": \"cf6a4c\",\"token\": \"markup.heading\"}\n  ],\n\t\"colors\": {\n\t\t\"editor.foreground\": \"#F8F8F8\",\n\t\t\"editor.background\": \"#141414\",\n\t\t\"editor.selectionBackground\": \"#DDF0FF33\",\n\t\t\"editor.lineHighlightBackground\": \"#FFFFFF08\",\n\t\t\"editorCursor.foreground\": \"#A7A7A7\",\n\t\t\"editorWhitespace.foreground\": \"#FFFFFF40\",\n\n\t\t'diffEditor.insertedTextBackground': '#2ea04320',\n\t\t'diffEditor.insertedLineBackground': '#2ea04326',\n\t\t'diffEditor.removedTextBackground': '#f8514920',\n\t\t'diffEditor.removedLineBackground': '#f8514920',\n\t\t'diffEditor.insertedTextBorder': '#2ea04300',\n\t\t'diffEditor.removedTextBorder': '#f8514900',\n\t}\n} as Monaco.editor.IStandaloneThemeData\n\nexport const editorThemes: Record<string, Monaco.editor.IStandaloneThemeData> = {\n\t\"all-hallows-eve\": allHallowsEve,\n\t\"amy\": amy,\n\t\"birds-of-paradise\": birdsOfParadise,\n\t\"blackboard\": blackboard,\n\t\"brilliance-black\": brillianceBlack,\n\t\"brilliance-dull\": brillianceDull,\n\t\"chrome-dev-tools\": chromeDevTools,\n\t\"clouds-midnight\": cloudsMidnight,\n\t\"clouds\": clouds,\n\t\"cobalt\": cobalt,\n\t\"dracula\": dracula,\n\t\"dreamweaver\": dreamweaver,\n\t\"espresso-libre\": espressoLibre,\n\t\"github-dark\": githubDark,\n\t\"github-light\": githubLight,\n\t\"github\": github,\n\t\"merbivore-soft\": merbivoreSoft,\n\t\"monokai\": monokai,\n\t\"night-owl\": nightOwl,\n\t\"nord\": nord,\n\t\"oceanic-next\": oceanicNext,\n\t\"pastels-on-dark\": pastelsOnDark,\n\t\"sunburst\": sunburst,\n\t\"tomorrow-night-blue\": tomorrowNightBlue,\n\t\"tomorrow-night-bright\": tomorrowNightBright,\n\t\"tomorrow-night-eighties\": tomorrowNightEighties,\n\t\"tomorrow-night\": tomorrowNight,\n\t\"tomorrow\": tomorrow,\n\t\"twilight\": twilight,\n}\n"
  },
  {
    "path": "www/app/Event.ts",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport EventDispatcher from './dispatcher/EventDispatcher';\nimport * as Csrf from './Csrf';\n\nlet connected = false;\nconst pendingEvents: Record<string, any> = {};\n\nfunction connect(): void {\n\tlet url = '';\n\tlet location = window.location;\n\n\tif (location.protocol === 'https:') {\n\t\turl += 'wss';\n\t} else {\n\t\turl += 'ws';\n\t}\n\n\turl += '://' + location.host + '/event?csrf_token=' + Csrf.token;\n\n\tlet socket = new WebSocket(url);\n\n\tsocket.addEventListener('close', () => {\n\t\tsetTimeout(() => {\n\t\t\tconnect();\n\t\t}, 500);\n\t});\n\n\tsocket.addEventListener('message', (evt) => {\n\t\tconst eventData = JSON.parse(evt.data).data;\n\t\tconst eventId = JSON.stringify(eventData);\n\n\t\tif (pendingEvents[eventId]) {\n\t\t\treturn;\n\t\t}\n\n\t\tpendingEvents[eventId] = eventData;\n\n\t\tsetTimeout(() => {\n\t\t\tif (pendingEvents[eventId]) {\n\t\t\t\tconsole.log(eventData);\n\t\t\t\tEventDispatcher.dispatch(eventData);\n\n\t\t\t\tdelete pendingEvents[eventId];\n\t\t\t}\n\t\t}, 300);\n\t});\n}\n\nexport function init() {\n\tif (connected) {\n\t\treturn;\n\t}\n\tconnected = true;\n\n\tconnect();\n}\n"
  },
  {
    "path": "www/app/EventEmitter.ts",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport * as Events from 'events';\n\nexport default class EventEmitter extends Events.EventEmitter {\n\temitDefer(event: string | symbol, ...args: any[]): void {\n\t\tsetTimeout((): void => {\n\t\t\tthis.emit(event, ...args);\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "www/app/License.ts",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport * as Alert from './Alert';\nimport * as Csrf from './Csrf';\n\nexport let oracle = false;\n\nexport function save(): Promise<void> {\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/license')\n\t\t\t.send({\n\t\t\t\toracle: oracle,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save license state');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function setOracle(state: boolean): void {\n\toracle = state;\n}\n"
  },
  {
    "path": "www/app/Loader.ts",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport Dispatcher from './dispatcher/Dispatcher';\nimport * as LoadingTypes from './types/LoadingTypes';\nimport * as MiscUtils from './utils/MiscUtils';\n\nexport default class Loader {\n\t_id: string;\n\n\tconstructor() {\n\t\tthis._id = MiscUtils.uuid();\n\t}\n\n\tloading(): Loader {\n\t\tDispatcher.dispatch({\n\t\t\ttype: LoadingTypes.ADD,\n\t\t\tdata: {\n\t\t\t\tid: this._id,\n\t\t\t},\n\t\t});\n\t\treturn this;\n\t}\n\n\tdone(): Loader {\n\t\tDispatcher.dispatch({\n\t\t\ttype: LoadingTypes.DONE,\n\t\t\tdata: {\n\t\t\t\tid: this._id,\n\t\t\t},\n\t\t});\n\t\treturn this;\n\t}\n}\n"
  },
  {
    "path": "www/app/References.d.ts",
    "content": "declare module '@novnc/novnc' {\n\texport default class RFB {\n\t\tconstructor(target: HTMLDivElement, url: string, options?: any);\n\t\t[key:string]: any;\n\t}\n}\n"
  },
  {
    "path": "www/app/Router.ts",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport * as CompletionActions from './actions/CompletionActions';\nimport * as UserActions from './actions/UserActions';\nimport * as SessionActions from './actions/SessionActions';\nimport * as AuditActions from './actions/AuditActions';\nimport * as NodeActions from './actions/NodeActions';\nimport * as PolicyActions from './actions/PolicyActions';\nimport * as CertificateActions from './actions/CertificateActions';\nimport * as SecretActions from './actions/SecretActions';\nimport * as OrganizationActions from './actions/OrganizationActions';\nimport * as DatacenterActions from './actions/DatacenterActions';\nimport * as AlertActions from './actions/AlertActions';\nimport * as ZoneActions from './actions/ZoneActions';\nimport * as ShapeActions from './actions/ShapeActions';\nimport * as BlockActions from './actions/BlockActions';\nimport * as VpcActions from './actions/VpcActions';\nimport * as DomainActions from './actions/DomainActions';\nimport * as PlanActions from './actions/PlanActions';\nimport * as BalancerActions from './actions/BalancerActions';\nimport * as StorageActions from './actions/StorageActions';\nimport * as ImageActions from './actions/ImageActions';\nimport * as PoolActions from './actions/PoolActions';\nimport * as DiskActions from './actions/DiskActions';\nimport * as InstanceActions from './actions/InstanceActions';\nimport * as PodActions from './actions/PodActions';\nimport * as FirewallActions from './actions/FirewallActions';\nimport * as AuthorityActions from './actions/AuthorityActions';\nimport * as LogActions from './actions/LogActions';\nimport * as SettingsActions from './actions/SettingsActions';\nimport * as SubscriptionActions from './actions/SubscriptionActions';\n\nexport function setLocation(location: string) {\n\twindow.location.hash = location\n\tlet evt = new Event(\"router_update\")\n\twindow.dispatchEvent(evt)\n}\n\nexport function reload() {\n\tlet evt = new Event(\"router_update\")\n\twindow.dispatchEvent(evt)\n}\n\nexport function refresh(callback?: () => void) {\n\tlet pathname = window.location.hash.replace(/^#/, '');\n\tCompletionActions.sync();\n\n\tif (pathname === '/users') {\n\t\tUserActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname.startsWith('/user/')) {\n\t\tUserActions.reload().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t\tSessionActions.reload().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t\tAuditActions.reload().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/nodes') {\n\t\tNodeActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/policies') {\n\t\tSettingsActions.sync();\n\t\tPolicyActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/certificates') {\n\t\tCertificateActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/secrets') {\n\t\tSecretActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/organizations') {\n\t\tOrganizationActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/datacenters') {\n\t\tDatacenterActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/zones') {\n\t\tZoneActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/shapes') {\n\t\tShapeActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/blocks') {\n\t\tBlockActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/vpcs') {\n\t\tVpcActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/domains') {\n\t\tDomainActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/plans') {\n\t\tPlanActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/balancers') {\n\t\tBalancerActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/storages') {\n\t\tStorageActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/images') {\n\t\tImageActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/pools') {\n\t\tPoolActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/disks') {\n\t\tDiskActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/instances') {\n\t\tInstanceActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/pods') {\n\t\tPodActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/firewalls') {\n\t\tFirewallActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/authorities') {\n\t\tAuthorityActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/alerts') {\n\t\tAlertActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/logs') {\n\t\tLogActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/settings') {\n\t\tSettingsActions.sync().then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else if (pathname === '/subscription') {\n\t\tSubscriptionActions.sync(true).then((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (callback) {\n\t\t\t\tcallback()\n\t\t\t}\n\t\t});\n\t} else {\n\t\tconsole.log(`Failed to match refresh ${pathname}`)\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: false,\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "www/app/Styles.tsx",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport * as React from 'react';\nimport * as Theme from './Theme';\nimport * as Blueprint from '@blueprintjs/core';\n\ninterface Colors {\n\twhite: string;\n\tblack: string;\n\tblue1: string;\n\tblue2: string;\n\tblue3: string;\n\tblue4: string;\n\tblue5: string;\n\tdarkGray1: string;\n\tdarkGray2: string;\n\tdarkGray3: string;\n\tdarkGray4: string;\n\tdarkGray5: string;\n\tforest1: string;\n\tforest2: string;\n\tforest3: string;\n\tforest4: string;\n\tforest5: string;\n\tgold1: string;\n\tgold2: string;\n\tgold3: string;\n\tgold4: string;\n\tgold5: string;\n\tgray1: string;\n\tgray2: string;\n\tgray3: string;\n\tgray4: string;\n\tgray5: string;\n\tgreen1: string;\n\tgreen2: string;\n\tgreen3: string;\n\tgreen4: string;\n\tgreen5: string;\n\tindigo1: string;\n\tindigo2: string;\n\tindigo3: string;\n\tindigo4: string;\n\tindigo5: string;\n\tlightGray1: string;\n\tlightGray2: string;\n\tlightGray3: string;\n\tlightGray4: string;\n\tlightGray5: string;\n\tlime1: string;\n\tlime2: string;\n\tlime3: string;\n\tlime4: string;\n\tlime5: string;\n\torange1: string;\n\torange2: string;\n\torange3: string;\n\torange4: string;\n\torange5: string;\n\tred1: string;\n\tred2: string;\n\tred3: string;\n\tred4: string;\n\tred5: string;\n\trose1: string;\n\trose2: string;\n\trose3: string;\n\trose4: string;\n\trose5: string;\n\tsepia1: string;\n\tsepia2: string;\n\tsepia3: string;\n\tsepia4: string;\n\tsepia5: string;\n\tturquoise1: string;\n\tturquoise2: string;\n\tturquoise3: string;\n\tturquoise4: string;\n\tturquoise5: string;\n\tvermilion1: string;\n\tvermilion2: string;\n\tvermilion3: string;\n\tvermilion4: string;\n\tvermilion5: string;\n\tviolet1: string;\n\tviolet2: string;\n\tviolet3: string;\n\tviolet4: string;\n\tviolet5: string;\n}\n\ninterface Styles {\n\tcolors: Colors;\n}\n\nexport const colors = {\n\twhite: Blueprint.Colors.WHITE,\n\tblack: Blueprint.Colors.BLACK,\n\tblue1: Blueprint.Colors.BLUE1,\n\tblue2: Blueprint.Colors.BLUE2,\n\tblue3: Blueprint.Colors.BLUE3,\n\tblue4: Blueprint.Colors.BLUE4,\n\tblue5: Blueprint.Colors.BLUE5,\n\tdarkGray1: Blueprint.Colors.DARK_GRAY1,\n\tdarkGray2: Blueprint.Colors.DARK_GRAY2,\n\tdarkGray3: Blueprint.Colors.DARK_GRAY3,\n\tdarkGray4: Blueprint.Colors.DARK_GRAY4,\n\tdarkGray5: Blueprint.Colors.DARK_GRAY5,\n\tforest1: Blueprint.Colors.FOREST1,\n\tforest2: Blueprint.Colors.FOREST2,\n\tforest3: Blueprint.Colors.FOREST3,\n\tforest4: Blueprint.Colors.FOREST4,\n\tforest5: Blueprint.Colors.FOREST5,\n\tgold1: Blueprint.Colors.GOLD1,\n\tgold2: Blueprint.Colors.GOLD2,\n\tgold3: Blueprint.Colors.GOLD3,\n\tgold4: Blueprint.Colors.GOLD4,\n\tgold5: Blueprint.Colors.GOLD5,\n\tgray1: Blueprint.Colors.GRAY1,\n\tgray2: Blueprint.Colors.GRAY2,\n\tgray3: Blueprint.Colors.GRAY3,\n\tgray4: Blueprint.Colors.GRAY4,\n\tgray5: Blueprint.Colors.GRAY5,\n\tgreen1: Blueprint.Colors.GREEN1,\n\tgreen2: Blueprint.Colors.GREEN2,\n\tgreen3: Blueprint.Colors.GREEN3,\n\tgreen4: Blueprint.Colors.GREEN4,\n\tgreen5: Blueprint.Colors.GREEN5,\n\tindigo1: Blueprint.Colors.INDIGO1,\n\tindigo2: Blueprint.Colors.INDIGO2,\n\tindigo3: Blueprint.Colors.INDIGO3,\n\tindigo4: Blueprint.Colors.INDIGO4,\n\tindigo5: Blueprint.Colors.INDIGO5,\n\tlightGray1: Blueprint.Colors.LIGHT_GRAY1,\n\tlightGray2: Blueprint.Colors.LIGHT_GRAY2,\n\tlightGray3: Blueprint.Colors.LIGHT_GRAY3,\n\tlightGray4: Blueprint.Colors.LIGHT_GRAY4,\n\tlightGray5: Blueprint.Colors.LIGHT_GRAY5,\n\tlime1: Blueprint.Colors.LIME1,\n\tlime2: Blueprint.Colors.LIME2,\n\tlime3: Blueprint.Colors.LIME3,\n\tlime4: Blueprint.Colors.LIME4,\n\tlime5: Blueprint.Colors.LIME5,\n\torange1: Blueprint.Colors.ORANGE1,\n\torange2: Blueprint.Colors.ORANGE2,\n\torange3: Blueprint.Colors.ORANGE3,\n\torange4: Blueprint.Colors.ORANGE4,\n\torange5: Blueprint.Colors.ORANGE5,\n\tred1: Blueprint.Colors.RED1,\n\tred2: Blueprint.Colors.RED2,\n\tred3: Blueprint.Colors.RED3,\n\tred4: Blueprint.Colors.RED4,\n\tred5: Blueprint.Colors.RED5,\n\trose1: Blueprint.Colors.ROSE1,\n\trose2: Blueprint.Colors.ROSE2,\n\trose3: Blueprint.Colors.ROSE3,\n\trose4: Blueprint.Colors.ROSE4,\n\trose5: Blueprint.Colors.ROSE5,\n\tsepia1: Blueprint.Colors.SEPIA1,\n\tsepia2: Blueprint.Colors.SEPIA2,\n\tsepia3: Blueprint.Colors.SEPIA3,\n\tsepia4: Blueprint.Colors.SEPIA4,\n\tsepia5: Blueprint.Colors.SEPIA5,\n\tturquoise1: Blueprint.Colors.TURQUOISE1,\n\tturquoise2: Blueprint.Colors.TURQUOISE2,\n\tturquoise3: Blueprint.Colors.TURQUOISE3,\n\tturquoise4: Blueprint.Colors.TURQUOISE4,\n\tturquoise5: Blueprint.Colors.TURQUOISE5,\n\tvermilion1: Blueprint.Colors.VERMILION1,\n\tvermilion2: Blueprint.Colors.VERMILION2,\n\tvermilion3: Blueprint.Colors.VERMILION3,\n\tvermilion4: Blueprint.Colors.VERMILION4,\n\tvermilion5: Blueprint.Colors.VERMILION5,\n\tviolet1: Blueprint.Colors.VIOLET1,\n\tviolet2: Blueprint.Colors.VIOLET2,\n\tviolet3: Blueprint.Colors.VIOLET3,\n\tviolet4: Blueprint.Colors.VIOLET4,\n\tviolet5: Blueprint.Colors.VIOLET5,\n};\n\nexport const fixedHeight = 1000\n\nexport const headerShift = (): string => {\n\tif (Theme.theme === \"dark\") {\n\t\treturn \"rgba(0, 0, 0, 0.13)\"\n\t}\n\treturn \"rgba(0, 0, 0, 0.04)\"\n}\n"
  },
  {
    "path": "www/app/Theme.ts",
    "content": "/// <reference path=\"./References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport * as Alert from './Alert';\nimport * as Csrf from './Csrf';\nimport * as MiscUtils from './utils/MiscUtils';\nimport * as EditorThemes from './EditorThemes';\nimport * as Monaco from \"monaco-editor\"\n\nexport interface Callback {\n\t(): void;\n}\n\nlet callbacks: Set<Callback> = new Set<Callback>();\nexport let theme = 'dark';\nexport let themeVer = 5;\nlet editorThemeName = '';\nexport const monospaceSize = \"12px\"\nexport const monospaceFont = \"Consolas, Menlo, 'Roboto Mono', 'DejaVu Sans Mono'\"\nexport const monospaceWeight = \"500\"\n\nexport function save(): Promise<void> {\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/theme')\n\t\t\t.send({\n\t\t\t\ttheme: theme + `-${themeVer}`,\n\t\t\t\teditor_theme: editorThemeName,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save theme');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function themeVer3(): void {\n\tconst blueprintTheme3 = document.getElementById(\n\t\t\"blueprint3-theme\") as HTMLLinkElement\n\tconst blueprintTheme5 = document.getElementById(\n\t\t\"blueprint5-theme\") as HTMLLinkElement\n\tblueprintTheme3.disabled = false;\n\tblueprintTheme5.disabled = true;\n\tif (theme === \"dark\") {\n\t\tdocument.body.className = 'bp3-theme bp5-dark';\n\t\tdocument.documentElement.className = 'dark3-scroll bp5-focus-disabled';\n\t} else {\n\t\tdocument.body.className = 'bp3-theme';\n\t\tdocument.documentElement.className = 'bp5-focus-disabled';\n\t}\n\tthemeVer = 3;\n}\n\nexport function themeVer5(): void {\n\tconst blueprintTheme3 = document.getElementById(\n\t\t\"blueprint3-theme\") as HTMLLinkElement\n\tconst blueprintTheme5 = document.getElementById(\n\t\t\"blueprint5-theme\") as HTMLLinkElement\n\tblueprintTheme3.disabled = true;\n\tblueprintTheme5.disabled = false;\n\tif (theme === \"dark\") {\n\t\tdocument.body.className = 'bp5-dark';\n\t\tdocument.documentElement.className = 'dark5-scroll bp5-focus-disabled';\n\t} else {\n\t\tdocument.body.className = '';\n\t\tdocument.documentElement.className = 'bp5-focus-disabled';\n\t}\n\tthemeVer = 5;\n}\n\nexport function light(): void {\n\ttheme = 'light';\n\tif (themeVer === 3) {\n\t\tdocument.body.className = 'bp3-theme';\n\t\tdocument.documentElement.className = 'bp5-focus-disabled';\n\t} else {\n\t\tdocument.body.className = '';\n\t\tdocument.documentElement.className = 'bp5-focus-disabled';\n\t}\n\tcallbacks.forEach((callback: Callback): void => {\n\t\tcallback();\n\t});\n}\n\nexport function dark(): void {\n\ttheme = 'dark';\n\tif (themeVer === 3) {\n\t\tdocument.body.className = 'bp3-theme bp5-dark';\n\t\tdocument.documentElement.className = 'dark3-scroll bp5-focus-disabled';\n\t} else {\n\t\tdocument.body.className = 'bp5-dark';\n\t\tdocument.documentElement.className = 'dark5-scroll bp5-focus-disabled';\n\t}\n\tcallbacks.forEach((callback: Callback): void => {\n\t\tcallback();\n\t});\n}\n\nexport function toggle(ver3: boolean): void {\n\tif (theme === \"dark\") {\n\t\tlight();\n\t\tif (ver3) {\n\t\t\tthemeVer3();\n\t\t} else {\n\t\t\tthemeVer5();\n\t\t}\n\t} else if (theme === \"light\") {\n\t\tdark();\n\t\tif (ver3) {\n\t\t\tthemeVer3();\n\t\t} else {\n\t\t\tthemeVer5();\n\t\t}\n\t}\n}\n\nexport function getEditorTheme(): string {\n\tif (!editorThemeName) {\n\t\tif (theme === \"light\") {\n\t\t\treturn \"github-light\";\n\t\t} else {\n\t\t\treturn \"github-dark\";\n\t\t}\n\t}\n\treturn editorThemeName\n}\n\nexport function setEditorTheme(name: string) {\n\teditorThemeName = name\n\tcallbacks.forEach((callback: Callback): void => {\n\t\tcallback();\n\t});\n}\n\nexport function addChangeListener(callback: Callback): void {\n\tcallbacks.add(callback);\n}\n\nexport function removeChangeListener(callback: () => void): void {\n\tcallbacks.delete(callback);\n}\n\nexport let editorThemeNames: Record<string, string> = {}\n\nfor (let themeName in EditorThemes.editorThemes) {\n\tlet editorTheme = EditorThemes.editorThemes[themeName]\n\tMonaco.editor.defineTheme(themeName, editorTheme)\n\n\tlet formattedThemeName = MiscUtils.titleCase(\n\tthemeName.replaceAll(\"-\", \" \"))\n\teditorThemeNames[themeName] = formattedThemeName\n}\n"
  },
  {
    "path": "www/app/actions/AlertActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport AlertsStore from '../stores/AlertsStore';\nimport * as AlertTypes from '../types/AlertTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport CompletionStore from \"../stores/CompletionStore\";\n\nlet syncId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/alert')\n\t\t\t.query({\n\t\t\t\t...AlertsStore.filter,\n\t\t\t\tpage: AlertsStore.page,\n\t\t\t\tpage_count: AlertsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load alerts');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: AlertTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\talerts: res.body.alerts,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: AlertTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: AlertTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: AlertTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(alert: AlertTypes.Alert): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/alert/' + alert.id)\n\t\t\t.send(alert)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save alert');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(alert: AlertTypes.Alert): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/alert')\n\t\t\t.send(alert)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create alert');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(alertId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/alert/' + alertId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete alerts');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(alertIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/alert')\n\t\t\t.send(alertIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete alerts');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: AlertTypes.AlertDispatch) => {\n\tswitch (action.type) {\n\t\tcase AlertTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/AuditActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport * as Constants from '../Constants';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as AuditTypes from '../types/AuditTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport AuditsStore from '../stores/AuditsStore';\n\nlet syncId: string;\n\nexport function load(userId: string): Promise<void> {\n\tif (!userId) {\n\t\treturn Promise.resolve();\n\t}\n\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/audit/' + userId)\n\t\t\t.query({\n\t\t\t\tpage: AuditsStore.page,\n\t\t\t\tpage_count: AuditsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load audits');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: AuditTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tuserId: userId,\n\t\t\t\t\t\taudits: res.body.audits,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function reload(): Promise<void> {\n\treturn load(AuditsStore.userId);\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: AuditTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn reload();\n}\n\nEventDispatcher.register((action: AuditTypes.AuditDispatch) => {\n\tswitch (action.type) {\n\t\tcase AuditTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\treload();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/AuthorityActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as AuthorityTypes from '../types/AuthorityTypes';\nimport AuthoritiesStore from '../stores/AuthoritiesStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\nlet syncNamesId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/authority')\n\t\t\t.query({\n\t\t\t\t...AuthoritiesStore.filter,\n\t\t\t\tpage: AuthoritiesStore.page,\n\t\t\t\tpage_count: AuthoritiesStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load authorities');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: AuthorityTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tauthorities: res.body.authorities,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncNames(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncNamesId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/authority')\n\t\t\t.query({\n\t\t\t\tnames: true,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncNamesId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load authority names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: AuthorityTypes.SYNC_NAMES,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tauthorities: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: AuthorityTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: AuthorityTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: AuthorityTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(authority: AuthorityTypes.Authority): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/authority/' + authority.id)\n\t\t\t.send(authority)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save authority');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(authority: AuthorityTypes.Authority): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/authority')\n\t\t\t.send(authority)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create authority');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(authorityId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/authority/' + authorityId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete authority');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(authorityIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/authority')\n\t\t\t.send(authorityIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete authorities');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: AuthorityTypes.AuthorityDispatch) => {\n\tswitch (action.type) {\n\t\tcase AuthorityTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/BalancerActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as BalancerTypes from '../types/BalancerTypes';\nimport BalancersStore from '../stores/BalancersStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/balancer')\n\t\t\t.query({\n\t\t\t\t...BalancersStore.filter,\n\t\t\t\tpage: BalancersStore.page,\n\t\t\t\tpage_count: BalancersStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load balancers');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: BalancerTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tbalancers: res.body.balancers,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: BalancerTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: BalancerTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: BalancerTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(balancer: BalancerTypes.Balancer): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/balancer/' + balancer.id)\n\t\t\t.send(balancer)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save balancer');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(balancer: BalancerTypes.Balancer): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/balancer')\n\t\t\t.send(balancer)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create balancer');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(balancerId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/balancer/' + balancerId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete balancer');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(balancerIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/balancer')\n\t\t\t.send(balancerIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete balancers');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: BalancerTypes.BalancerDispatch) => {\n\tswitch (action.type) {\n\t\tcase BalancerTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/BlockActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as BlockTypes from '../types/BlockTypes';\nimport BlocksStore from '../stores/BlocksStore';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/block')\n\t\t\t.query({\n\t\t\t\t...BlocksStore.filter,\n\t\t\t\tpage: BlocksStore.page,\n\t\t\t\tpage_count: BlocksStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load blocks');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: BlockTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tblocks: res.body.blocks,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: BlockTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: BlockTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: BlockTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(block: BlockTypes.Block): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/block/' + block.id)\n\t\t\t.send(block)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save block');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(block: BlockTypes.Block): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/block')\n\t\t\t.send(block)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create block');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(blockId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/block/' + blockId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete blocks');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(blockIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/block')\n\t\t\t.send(blockIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete blocks');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: BlockTypes.BlockDispatch) => {\n\tswitch (action.type) {\n\t\tcase BlockTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/CertificateActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport CertificatesStore from '../stores/CertificatesStore';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport CompletionStore from \"../stores/CompletionStore\";\n\nlet syncId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/certificate')\n\t\t\t.query({\n\t\t\t\t...CertificatesStore.filter,\n\t\t\t\tpage: CertificatesStore.page,\n\t\t\t\tpage_count: CertificatesStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load certificates');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: CertificateTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tcertificates: res.body.certificates,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: CertificateTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function filter(filt: CertificateTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: CertificateTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function commit(cert: CertificateTypes.Certificate): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/certificate/' + cert.id)\n\t\t\t.send(cert)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save certificate');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(cert: CertificateTypes.Certificate): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/certificate')\n\t\t\t.send(cert)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create certificate');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(certId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/certificate/' + certId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete certificates');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(certificateIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/certificate')\n\t\t\t.send(certificateIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete certificates');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: CertificateTypes.CertificateDispatch) => {\n\tswitch (action.type) {\n\t\tcase CertificateTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/CompletionActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport * as Constants from '../Constants';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as GlobalTypes from '../types/GlobalTypes';\nimport * as CompletionTypes from '../types/CompletionTypes';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\nlet lastSyncTime: number | null = null;\nlet syncInProgress: boolean = false;\n\nexport function sync(): Promise<void> {\n\tif (syncInProgress) {\n\t\treturn Promise.resolve();\n\t}\n\n\tsyncInProgress = true;\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\ttry {\n\t\t\tSuperAgent\n\t\t\t\t.get('/completion')\n\t\t\t\t.query({\n\t\t\t\t\t...CompletionStore.filter,\n\t\t\t\t})\n\t\t\t\t.set('Accept', 'application/json')\n\t\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t\t.set('Organization', CompletionStore.userOrganization || \"\")\n\t\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\t\tsyncInProgress = false;\n\n\t\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tAlert.errorRes(res, 'Failed to load completion data');\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\t\ttype: CompletionTypes.SYNC,\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tcompletion: res.body,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tlastSyncTime = Date.now();\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t} catch (e) {\n\t\t\tsyncInProgress = false;\n\t\t\treject(e);\n\t\t}\n\t});\n}\n\nexport function lastSync(): number | null {\n\treturn lastSyncTime;\n}\n\nexport function filter(filt: CompletionTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: CompletionTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function setUserOrganization(userOrg: string): void {\n\tDispatcher.dispatch({\n\t\ttype: GlobalTypes.RESET,\n\t\tdata: {\n\t\t\torganization: userOrg,\n\t\t},\n\t});\n\tDispatcher.dispatch({\n\t\ttype: GlobalTypes.RELOAD,\n\t\tdata: {\n\t\t\torganization: userOrg,\n\t\t},\n\t});\n}\n\nEventDispatcher.register((action: CompletionTypes.CompletionDispatch) => {\n\tswitch (action.type) {\n\t\tcase CompletionTypes.CHANGE:\n\t\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/DatacenterActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport DatacentersStore from '../stores/DatacentersStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\nlet syncNamesId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/datacenter')\n\t\t\t.query({\n\t\t\t\t...DatacentersStore.filter,\n\t\t\t\tpage: DatacentersStore.page,\n\t\t\t\tpage_count: DatacentersStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load datacenters');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: DatacenterTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tdatacenters: res.body.datacenters,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncNames(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncNamesId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/datacenter')\n\t\t\t.query({\n\t\t\t\tnames: true,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncNamesId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load datacenter names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: DatacenterTypes.SYNC_NAMES,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tsecrets: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: DatacenterTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function filter(filt: DatacenterTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: DatacenterTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function commit(datacenter: DatacenterTypes.Datacenter): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/datacenter/' + datacenter.id)\n\t\t\t.send(datacenter)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save datacenter');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(datacenter: DatacenterTypes.Datacenter): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/datacenter')\n\t\t\t.send(datacenter)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create datacenter');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(datacenterId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/datacenter/' + datacenterId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete datacenters');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(datacenterIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/datacenter')\n\t\t\t.send(datacenterIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete datacenters');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: DatacenterTypes.DatacenterDispatch) => {\n\tswitch (action.type) {\n\t\tcase DatacenterTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/DeviceActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as DeviceTypes from '../types/DeviceTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport DevicesStore from '../stores/DevicesStore';\nimport * as PolicyTypes from \"../types/PolicyTypes\";\n\nlet syncId: string;\n\nexport function load(userId: string): Promise<void> {\n\tif (!userId) {\n\t\treturn Promise.resolve();\n\t}\n\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/device/' + userId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load devices');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: DeviceTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tuserId: userId,\n\t\t\t\t\t\tdevices: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function reload(): Promise<void> {\n\treturn load(DevicesStore.userId);\n}\n\nexport function create(device: DeviceTypes.Device): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/device')\n\t\t\t.send(device)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create device');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function testAlert(deviceId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/device/' + deviceId + '/alert')\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to send test alert');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function commit(device: DeviceTypes.Device): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/device/' + device.id)\n\t\t\t.send(device)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save device');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(deviceId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/device/' + deviceId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete device');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: DeviceTypes.DeviceDispatch) => {\n\tswitch (action.type) {\n\t\tcase DeviceTypes.CHANGE:\n\t\t\treload();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/DiskActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as DiskTypes from '../types/DiskTypes';\nimport DisksStore from '../stores/DisksStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/disk')\n\t\t\t.query({\n\t\t\t\t...DisksStore.filter,\n\t\t\t\tpage: DisksStore.page,\n\t\t\t\tpage_count: DisksStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load disks');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: DiskTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tdisks: res.body.disks,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: DiskTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: DiskTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: DiskTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(disk: DiskTypes.Disk): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/disk/' + disk.id)\n\t\t\t.send(disk)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save disk');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(disk: DiskTypes.Disk): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/disk')\n\t\t\t.send(disk)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create disk');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(diskId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/disk/' + diskId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete disk');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(diskIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/disk')\n\t\t\t.send(diskIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete disks');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function forceRemoveMulti(diskIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/disk')\n\t\t\t.query({\n\t\t\t\tforce: true,\n\t\t\t})\n\t\t\t.send(diskIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete disks');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function updateMulti(diskIds: string[],\n\t\taction: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/disk')\n\t\t\t.send({\n\t\t\t\t\"ids\": diskIds,\n\t\t\t\t\"action\": action,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to update disks');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: DiskTypes.DiskDispatch) => {\n\tswitch (action.type) {\n\t\tcase DiskTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/DomainActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as DomainTypes from '../types/DomainTypes';\nimport DomainsStore from '../stores/DomainsStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\nlet syncNamesId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/domain')\n\t\t\t.query({\n\t\t\t\t...DomainsStore.filter,\n\t\t\t\tpage: DomainsStore.page,\n\t\t\t\tpage_count: DomainsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load domains');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: DomainTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tdomains: res.body.domains,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncName(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncNamesId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/domain')\n\t\t\t.query({\n\t\t\t\tnames: true,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncNamesId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load domain names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: DomainTypes.SYNC_NAME,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tdomains: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: DomainTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: DomainTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: DomainTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(domain: DomainTypes.Domain): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/domain/' + domain.id)\n\t\t\t.send(domain)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save domain');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(domain: DomainTypes.Domain): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/domain')\n\t\t\t.send(domain)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create domain');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(domainId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/domain/' + domainId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete domain');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(domainIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/domain')\n\t\t\t.send(domainIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete domains');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: DomainTypes.DomainDispatch) => {\n\tswitch (action.type) {\n\t\tcase DomainTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/FirewallActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as FirewallTypes from '../types/FirewallTypes';\nimport FirewallsStore from '../stores/FirewallsStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/firewall')\n\t\t\t.query({\n\t\t\t\t...FirewallsStore.filter,\n\t\t\t\tpage: FirewallsStore.page,\n\t\t\t\tpage_count: FirewallsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load firewalls');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: FirewallTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tfirewalls: res.body.firewalls,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: FirewallTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: FirewallTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: FirewallTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(firewall: FirewallTypes.Firewall): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/firewall/' + firewall.id)\n\t\t\t.send(firewall)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save firewall');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(firewall: FirewallTypes.Firewall): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/firewall')\n\t\t\t.send(firewall)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create firewall');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(firewallId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/firewall/' + firewallId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete firewall');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(firewallIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/firewall')\n\t\t\t.send(firewallIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete firewalls');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: FirewallTypes.FirewallDispatch) => {\n\tswitch (action.type) {\n\t\tcase FirewallTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/ImageActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as ImageTypes from '../types/ImageTypes';\nimport ImagesStore from '../stores/ImagesStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/image')\n\t\t\t.query({\n\t\t\t\t...ImagesStore.filter,\n\t\t\t\tpage: ImagesStore.page,\n\t\t\t\tpage_count: ImagesStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load images');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: ImageTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\timages: res.body.images,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncDatacenter(datacenter: string): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tif (!datacenter) {\n\t\tDispatcher.dispatch({\n\t\t\ttype: ImageTypes.SYNC_DATACENTER,\n\t\t\tdata: {\n\t\t\t\timages: [],\n\t\t\t},\n\t\t});\n\t\treturn Promise.resolve();\n\t}\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/image')\n\t\t\t.query({\n\t\t\t\tdatacenter: datacenter,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load images names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: ImageTypes.SYNC_DATACENTER,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\timages: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: ImageTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: ImageTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: ImageTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(image: ImageTypes.Image): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/image/' + image.id)\n\t\t\t.send(image)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save image');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(image: ImageTypes.Image): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/image')\n\t\t\t.send(image)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create image');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(imageId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/image/' + imageId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete image');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(imageIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/image')\n\t\t\t.send(imageIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete images');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: ImageTypes.ImageDispatch) => {\n\tswitch (action.type) {\n\t\tcase ImageTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/InstanceActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport InstancesStore from '../stores/InstancesStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/instance')\n\t\t\t.query({\n\t\t\t\t...InstancesStore.filter,\n\t\t\t\tpage: InstancesStore.page,\n\t\t\t\tpage_count: InstancesStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load instances');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: InstanceTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tinstances: res.body.instances,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: InstanceTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: InstanceTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: InstanceTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(instance: InstanceTypes.Instance): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/instance/' + instance.id)\n\t\t\t.send(instance)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save instance');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(instance: InstanceTypes.Instance): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/instance')\n\t\t\t.send(instance)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create instance');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(instanceId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/instance/' + instanceId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete instance');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(instanceIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/instance')\n\t\t\t.send(instanceIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete instances');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function forceRemoveMulti(instanceIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/instance')\n\t\t\t.query({\n\t\t\t\tforce: true,\n\t\t\t})\n\t\t\t.send(instanceIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to force delete instances');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function updateMulti(instanceIds: string[],\n\t\taction: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/instance')\n\t\t\t.send({\n\t\t\t\t\"ids\": instanceIds,\n\t\t\t\t\"action\": action,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to update instances');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncNode(node: string, pool: string): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet scope: string;\n\tlet query: {[key: string]: string};\n\tif (node) {\n\t\tscope = node;\n\t\tquery = {\n\t\t\tnode_names: node,\n\t\t};\n\t} else {\n\t\tscope = pool;\n\t\tquery = {\n\t\t\tpool_names: pool,\n\t\t};\n\t}\n\n\tif (!scope) {\n\t\treturn Promise.resolve();\n\t}\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/instance')\n\t\t\t.query(query)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load instance names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: InstanceTypes.SYNC_NODE,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tscope: scope,\n\t\t\t\t\t\tinstances: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: InstanceTypes.InstanceDispatch) => {\n\tswitch (action.type) {\n\t\tcase InstanceTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/LogActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport * as Constants from '../Constants';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as LogTypes from '../types/LogTypes';\nimport LogsStore from '../stores/LogsStore';\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/log')\n\t\t\t.query({\n\t\t\t\t...LogsStore.filter,\n\t\t\t\tpage: LogsStore.page,\n\t\t\t\tpage_count: LogsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load logs');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: LogTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tlogs: res.body.logs,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: LogTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: LogTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: LogTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nEventDispatcher.register((action: LogTypes.LogDispatch) => {\n\tswitch (action.type) {\n\t\tcase LogTypes.CHANGE:\n\t\t\tif (!Constants.user && window.location.hash.indexOf('/logs') !== -1) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/NodeActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as NodeTypes from '../types/NodeTypes';\nimport NodesStore from '../stores/NodesStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\nlet syncZonesId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/node')\n\t\t\t.query({\n\t\t\t\t...NodesStore.filter,\n\t\t\t\tpage: NodesStore.page,\n\t\t\t\tpage_count: NodesStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load nodes');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: NodeTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tnodes: res.body.nodes,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncZone(zone: string): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncZonesId = curSyncId;\n\n\tif (!zone) {\n\t\tDispatcher.dispatch({\n\t\t\ttype: NodeTypes.SYNC_ZONE,\n\t\t\tdata: {\n\t\t\t\tnodes: [],\n\t\t\t},\n\t\t});\n\t\treturn Promise.resolve();\n\t}\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/node')\n\t\t\t.query({\n\t\t\t\tnames: true,\n\t\t\t\tzone: zone,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncZonesId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load nodes names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: NodeTypes.SYNC_ZONE,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tnodes: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: NodeTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: NodeTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: NodeTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(node: NodeTypes.Node): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/node/' + node.id)\n\t\t\t.send(node)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save node');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function operation(nodeId: string, operation: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/node/' + nodeId + '/' + operation)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to update node');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function init(nodeId: string,\n\t\tdata: NodeTypes.NodeInit): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/node/' + nodeId + '/init')\n\t\t\t.send(data)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to update node');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(node: NodeTypes.Node): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/node')\n\t\t\t.send(node)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create node');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(nodeId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/node/' + nodeId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete nodes');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: NodeTypes.NodeDispatch) => {\n\tswitch (action.type) {\n\t\tcase NodeTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/OrganizationActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport OrganizationsStore from '../stores/OrganizationsStore';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/organization')\n\t\t\t.query({\n\t\t\t\t...OrganizationsStore.filter,\n\t\t\t\tpage: OrganizationsStore.page,\n\t\t\t\tpage_count: OrganizationsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load organizations');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: OrganizationTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\torganizations: res.body.organizations,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: OrganizationTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function filter(filt: OrganizationTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: OrganizationTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function commit(org: OrganizationTypes.Organization): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/organization/' + org.id)\n\t\t\t.send(org)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save organization');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(org: OrganizationTypes.Organization): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/organization')\n\t\t\t.send(org)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create organization');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(orgId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/organization/' + orgId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete organizations');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(organizationIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/organization')\n\t\t\t.send(organizationIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete organizations');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: OrganizationTypes.OrganizationDispatch) => {\n\tswitch (action.type) {\n\t\tcase OrganizationTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/PlanActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as PlanTypes from '../types/PlanTypes';\nimport PlansStore from '../stores/PlansStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\nlet syncNamesId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/plan')\n\t\t\t.query({\n\t\t\t\t...PlansStore.filter,\n\t\t\t\tpage: PlansStore.page,\n\t\t\t\tpage_count: PlansStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load plans');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: PlanTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tplans: res.body.plans,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncName(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncNamesId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/plan')\n\t\t\t.query({\n\t\t\t\tnames: true,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncNamesId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load plan names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: PlanTypes.SYNC_NAME,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tplans: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: PlanTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: PlanTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: PlanTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(plan: PlanTypes.Plan): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/plan/' + plan.id)\n\t\t\t.send(plan)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save plan');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(plan: PlanTypes.Plan): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/plan')\n\t\t\t.send(plan)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create plan');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(planId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/plan/' + planId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete plan');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(planIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/plan')\n\t\t\t.send(planIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete plans');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: PlanTypes.PlanDispatch) => {\n\tswitch (action.type) {\n\t\tcase PlanTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/PodActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as PodTypes from '../types/PodTypes';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport PodsStore from '../stores/PodsStore';\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\nlet syncUnitId: string;\nlet lastPodId: string;\nlet lastUnitId: string\nlet dataSyncReqs: {[key: string]: SuperAgent.Request} = {};\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/pod')\n\t\t\t.query({\n\t\t\t\t...PodsStore.filter,\n\t\t\t\tpage: PodsStore.page,\n\t\t\t\tpage_count: PodsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load pods');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: PodTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tpods: res.body.pods,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: PodTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: PodTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: PodTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(pod: PodTypes.Pod): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/pod/' + pod.id)\n\t\t\t.send(pod)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save pod');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function commitDeploy(pod: PodTypes.Pod,\n\tresync?: boolean): Promise<void> {\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/pod/' + pod.id + \"/deploy\")\n\t\t\t.send(pod)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save pod');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (resync) {\n\t\t\t\t\tsync(true)\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function commitDrafts(pod: PodTypes.Pod,\n\tresync?: boolean): Promise<void> {\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/pod/' + pod.id + \"/drafts\")\n\t\t\t.send(pod)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save pod');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (resync) {\n\t\t\t\t\tsync(true)\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(pod: PodTypes.Pod): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/pod')\n\t\t\t.send(pod)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create pod');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(podId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/pod/' + podId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete pod');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(podIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/pod')\n\t\t\t.send(podIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete pods');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncUnit(podId?: string, unitId?: string): Promise<void> {\n\tif (!podId) {\n\t\tpodId = lastPodId\n\t} else {\n\t\tlastPodId = podId\n\t}\n\n\tif (!unitId) {\n\t\tunitId = lastUnitId\n\t} else {\n\t\tlastUnitId = unitId\n\t}\n\n\tif (!podId || !unitId) {\n\t\treturn Promise.resolve();\n\t}\n\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncUnitId = curSyncId;\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/pod/' + podId + \"/unit/\" + unitId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncUnitId || (res && res.status === 404)) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load pod unit');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: PodTypes.SYNC_UNIT,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tunit: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function deployUnit(podId: string, unitId: string,\n\tspecId: string, count: number): Promise<void> {\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/pod/' + podId + \"/unit/\" + unitId + \"/deployment\")\n\t\t\t.send({\n\t\t\t\tcount: count,\n\t\t\t\tspec: specId,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create deployments');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function updateMultiUnitAction(podId: string, unitId: string,\n\tdeploymentIds: string[], action: string, commit?: string): Promise<void> {\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/pod/' + podId + \"/unit/\" + unitId + \"/deployment\")\n\t\t\t.query({\n\t\t\t\taction: action,\n\t\t\t\tcommit: commit,\n\t\t\t})\n\t\t\t.send(deploymentIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to modify deployments');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function commitDeployment(deply: PodTypes.Deployment): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/pod/' + deply.pod + \"/unit/\" + deply.unit +\n\t\t\t\t\"/deployment/\" + deply.id)\n\t\t\t.send(deply)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save deployment');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function log(deply: PodTypes.Deployment,\n\tresource: string, noLoading?: boolean): Promise<any> {\n\n\tlet curDataSyncId = MiscUtils.uuid();\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<any>((resolve, reject): void => {\n\t\tlet req = SuperAgent.get('/pod/' + deply.pod +\n\t\t\t\t\"/unit/\" + deply.unit + \"/deployment/\" + deply.id + \"/log\")\n\t\t\t.query({\n\t\t\t\tresource: resource,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.on('abort', () => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\t\t\t\tresolve(null);\n\t\t\t});\n\t\tdataSyncReqs[curDataSyncId] = req;\n\n\t\treq.end((err: any, res: SuperAgent.Response): void => {\n\t\t\tdelete dataSyncReqs[curDataSyncId];\n\t\t\tif (loader) {\n\t\t\t\tloader.done();\n\t\t\t}\n\n\t\t\tif (res && res.status === 401) {\n\t\t\t\twindow.location.href = '/login';\n\t\t\t\tresolve(null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (err) {\n\t\t\t\tAlert.errorRes(res, 'Failed to load check log');\n\t\t\t\treject(err);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tresolve(res.body);\n\t\t});\n\t});\n}\n\nexport function syncSpecs(podId: string, unitId: string, page: number,\n\tnoLoading?: boolean): Promise<PodTypes.CommitData> {\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<PodTypes.CommitData>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get(\"/pod/\" + podId + \"/unit/\" + unitId + \"/spec\")\n\t\t\t.query({\n\t\t\t\tpage: page,\n\t\t\t\tpage_count: 100,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load unit commits');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tres.body.unit = unitId\n\t\t\t\tres.body.page = page\n\t\t\t\tres.body.page_count = 100\n\t\t\t\tresolve(res.body as PodTypes.CommitData);\n\t\t\t});\n\t});\n}\n\nexport function spec(podId: string, unitId: string,\n\tspecId: string): Promise<PodTypes.Commit> {\n\n\treturn new Promise<PodTypes.Commit>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get(\"/pod/\" + podId + \"/unit/\" + unitId + \"/spec/\" + specId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load unit commits');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve(res.body as PodTypes.Commit);\n\t\t\t});\n\t});\n}\n\nexport function dataCancel(): void {\n\tfor (let [key, val] of Object.entries(dataSyncReqs)) {\n\t\tval.abort();\n\t}\n}\n\nEventDispatcher.register((action: PodTypes.PodDispatch) => {\n\tswitch (action.type) {\n\t\tcase PodTypes.CHANGE:\n\t\t\tsync();\n\t\t\tsyncUnit();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/PolicyActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as PolicyTypes from '../types/PolicyTypes';\nimport PoliciesStore from '../stores/PoliciesStore';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/policy')\n\t\t\t.query({\n\t\t\t\t...PoliciesStore.filter,\n\t\t\t\tpage: PoliciesStore.page,\n\t\t\t\tpage_count: PoliciesStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load policies');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: PolicyTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tpolicies: res.body.policies,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: PolicyTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function filter(filt: PolicyTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: PolicyTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function commit(policy: PolicyTypes.Policy): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/policy/' + policy.id)\n\t\t\t.send(policy)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save policy');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(policy: PolicyTypes.Policy): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/policy')\n\t\t\t.send(policy)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create policy');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(policyId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/policy/' + policyId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete policies');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(policyIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/policy')\n\t\t\t.send(policyIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete policies');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: PolicyTypes.PolicyDispatch) => {\n\tswitch (action.type) {\n\t\tcase PolicyTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/PoolActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as PoolTypes from '../types/PoolTypes';\nimport PoolsStore from '../stores/PoolsStore';\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/pool')\n\t\t\t.query({\n\t\t\t\t...PoolsStore.filter,\n\t\t\t\tpage: PoolsStore.page,\n\t\t\t\tpage_count: PoolsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load pools');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: PoolTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tpools: res.body.pools,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: PoolTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: PoolTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: PoolTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(pool: PoolTypes.Pool): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/pool/' + pool.id)\n\t\t\t.send(pool)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save pool');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(pool: PoolTypes.Pool): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/pool')\n\t\t\t.send(pool)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create pool');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(poolId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/pool/' + poolId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete pool');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(poolIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/pool')\n\t\t\t.send(poolIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete pools');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: PoolTypes.PoolDispatch) => {\n\tswitch (action.type) {\n\t\tcase PoolTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/RelationsActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport * as RelationTypes from '../types/RelationTypes';\nimport CompletionStore from \"../stores/CompletionStore\";\n\nexport function load(kind: string,\n\tid: string): Promise<RelationTypes.Relation> {\n\n\treturn new Promise<RelationTypes.Relation>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get(\"/relations/\" + kind + \"/\" + id)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load resource overview');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve(res.body as RelationTypes.Relation);\n\t\t\t});\n\t});\n}\n"
  },
  {
    "path": "www/app/actions/SecretActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as SecretTypes from '../types/SecretTypes';\nimport SecretsStore from '../stores/SecretsStore';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport CompletionStore from \"../stores/CompletionStore\";\n\nlet syncId: string;\nlet syncNamesId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/secret')\n\t\t\t.query({\n\t\t\t\t...SecretsStore.filter,\n\t\t\t\tpage: SecretsStore.page,\n\t\t\t\tpage_count: SecretsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load secrets');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: SecretTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tsecrets: res.body.secrets,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncNames(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncNamesId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/secret')\n\t\t\t.query({\n\t\t\t\tnames: true,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncNamesId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load secret names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: SecretTypes.SYNC_NAMES,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tsecrets: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: SecretTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function filter(filt: SecretTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: SecretTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function commit(secr: SecretTypes.Secret): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/secret/' + secr.id)\n\t\t\t.send(secr)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save secret');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(secr: SecretTypes.Secret): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/secret')\n\t\t\t.send(secr)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create secret');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(secrId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/secret/' + secrId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete secrets');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(secretIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/secret')\n\t\t\t.send(secretIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete secrets');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: SecretTypes.SecretDispatch) => {\n\tswitch (action.type) {\n\t\tcase SecretTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/SessionActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport * as Constants from \"../Constants\";\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as SessionTypes from '../types/SessionTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport SessionsStore from '../stores/SessionsStore';\n\nlet syncId: string;\n\nexport function _load(userId: string): Promise<void> {\n\tif (!userId) {\n\t\treturn Promise.resolve();\n\t}\n\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/session/' + userId)\n\t\t\t.query({\n\t\t\t\tshow_removed: SessionsStore.showRemoved,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load sessions');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: SessionTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tuserId: userId,\n\t\t\t\t\t\tsessions: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function load(userId: string): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: SessionTypes.SHOW_REMOVED,\n\t\tdata: {\n\t\t\tshowRemoved: false,\n\t\t},\n\t});\n\n\treturn _load(userId);\n}\n\nexport function reload(): Promise<void> {\n\treturn _load(SessionsStore.userId);\n}\n\nexport function showRemoved(state: boolean): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: SessionTypes.SHOW_REMOVED,\n\t\tdata: {\n\t\t\tshowRemoved: state,\n\t\t},\n\t});\n\n\treturn reload();\n}\n\nexport function remove(sessionId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/session/' + sessionId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete session');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: SessionTypes.SessionDispatch) => {\n\tswitch (action.type) {\n\t\tcase SessionTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\treload();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/SettingsActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as SettingsTypes from '../types/SettingsTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/settings')\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to sync builds');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: SettingsTypes.SYNC,\n\t\t\t\t\tdata: res.body,\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function commit(\n\t\tsettings: SettingsTypes.Settings): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/settings')\n\t\t\t.send(settings)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to commit settings');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: SettingsTypes.SYNC,\n\t\t\t\t\tdata: res.body,\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: SettingsTypes.SettingsDispatch) => {\n\tswitch (action.type) {\n\t\tcase SettingsTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/ShapeActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as ShapeTypes from '../types/ShapeTypes';\nimport ShapesStore from '../stores/ShapesStore';\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/shape')\n\t\t\t.query({\n\t\t\t\t...ShapesStore.filter,\n\t\t\t\tpage: ShapesStore.page,\n\t\t\t\tpage_count: ShapesStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load shapes');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: ShapeTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tshapes: res.body.shapes,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: ShapeTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: ShapeTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: ShapeTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(shape: ShapeTypes.Shape): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/shape/' + shape.id)\n\t\t\t.send(shape)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save shape');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(shape: ShapeTypes.Shape): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/shape')\n\t\t\t.send(shape)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create shape');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(shapeId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/shape/' + shapeId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete shape');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(shapeIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/shape')\n\t\t\t.send(shapeIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete shapes');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: ShapeTypes.ShapeDispatch) => {\n\tswitch (action.type) {\n\t\tcase ShapeTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/StorageActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as StorageTypes from '../types/StorageTypes';\nimport StoragesStore from '../stores/StoragesStore';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/storage')\n\t\t\t.query({\n\t\t\t\t...StoragesStore.filter,\n\t\t\t\tpage: StoragesStore.page,\n\t\t\t\tpage_count: StoragesStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load storages');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: StorageTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tstorages: res.body.storages,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: StorageTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function filter(filt: StorageTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: StorageTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function commit(storage: StorageTypes.Storage): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/storage/' + storage.id)\n\t\t\t.send(storage)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save storage');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(storage: StorageTypes.Storage): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/storage')\n\t\t\t.send(storage)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create storage');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(storageId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/storage/' + storageId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete storages');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(storageIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/storage')\n\t\t\t.send(storageIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete storages');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: StorageTypes.StorageDispatch) => {\n\tswitch (action.type) {\n\t\tcase StorageTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/SubscriptionActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as SubscriptionTypes from '../types/SubscriptionTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\n\nexport function sync(update: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/subscription' + (update ? '/update' : ''))\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to sync subscription');\n\t\t\t\t\treject(err);\n\n\t\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\t\ttype: SubscriptionTypes.SYNC,\n\t\t\t\t\t\tdata: {},\n\t\t\t\t\t});\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: SubscriptionTypes.SYNC,\n\t\t\t\t\tdata: res.body,\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function activate(license: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/subscription')\n\t\t\t.send({\n\t\t\t\tlicense: license,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to activate subscription');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: SubscriptionTypes.SYNC,\n\t\t\t\t\tdata: res.body,\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function cancel(key: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('https://app.pritunl.com/subscription')\n\t\t\t.send({\n\t\t\t\tkey: key,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to cancel subscription');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\n\t\t\t\tsync(true);\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: SubscriptionTypes.SubscriptionDispatch) => {\n\tswitch (action.type) {\n\t\tcase SubscriptionTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync(false);\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/UserActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as UserTypes from '../types/UserTypes';\nimport UserStore from '../stores/UserStore';\nimport UsersStore from '../stores/UsersStore';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\n\nexport function load(userId: string): Promise<void> {\n\tif (!userId) {\n\t\tlet user: UserTypes.User = {\n\t\t\tid: null,\n\t\t\ttype: 'local',\n\t\t\troles: [],\n\t\t\tpermissions: [],\n\t\t};\n\n\t\tDispatcher.dispatch({\n\t\t\ttype: UserTypes.LOAD,\n\t\t\tdata: {\n\t\t\t\tuser: user,\n\t\t\t},\n\t\t});\n\n\t\treturn Promise.resolve();\n\t}\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/user/' + userId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load user');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: UserTypes.LOAD,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tuser: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function reload(): Promise<void> {\n\treturn load(UserStore.user ? UserStore.user.id : null);\n}\n\nexport function unload(): void {\n\tDispatcher.dispatch({\n\t\ttype: UserTypes.UNLOAD,\n\t});\n}\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/user')\n\t\t\t.query({\n\t\t\t\t...UsersStore.filter,\n\t\t\t\tpage: UsersStore.page,\n\t\t\t\tpage_count: UsersStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load users');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: UserTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tusers: res.body.users,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: UserTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: UserTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: UserTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(user: UserTypes.User): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/user/' + user.id)\n\t\t\t.send(user)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save user');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: UserTypes.LOAD,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tuser: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(user: UserTypes.User): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/user')\n\t\t\t.send(user)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create user');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(userIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/user')\n\t\t\t.send(userIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete users');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: UserTypes.UserDispatch) => {\n\tswitch (action.type) {\n\t\tcase UserTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/VpcActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as VpcTypes from '../types/VpcTypes';\nimport VpcsStore from '../stores/VpcsStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\n\nlet syncId: string;\nlet syncNamesId: string;\n\nexport function sync(noLoading?: boolean): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader: Loader;\n\tif (!noLoading) {\n\t\tloader = new Loader().loading();\n\t}\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/vpc')\n\t\t\t.query({\n\t\t\t\t...VpcsStore.filter,\n\t\t\t\tpage: VpcsStore.page,\n\t\t\t\tpage_count: VpcsStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tif (loader) {\n\t\t\t\t\tloader.done();\n\t\t\t\t}\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load vpcs');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: VpcTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tvpcs: res.body.vpcs,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncNames(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncNamesId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/vpc')\n\t\t\t.query({\n\t\t\t\tnames: \"true\",\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncNamesId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load vpcs names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: VpcTypes.SYNC_NAMES,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tvpcs: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: VpcTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function filter(filt: VpcTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: VpcTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\n\treturn sync();\n}\n\nexport function commit(vpc: VpcTypes.Vpc): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/vpc/' + vpc.id)\n\t\t\t.send(vpc)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save vpc');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(vpc: VpcTypes.Vpc): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/vpc')\n\t\t\t.send(vpc)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create vpc');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(vpcId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/vpc/' + vpcId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete vpc');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(vpcIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/vpc')\n\t\t\t.send(vpcIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete vpcs');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: VpcTypes.VpcDispatch) => {\n\tswitch (action.type) {\n\t\tcase VpcTypes.CHANGE:\n\t\t\tsync();\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/actions/ZoneActions.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as SuperAgent from 'superagent';\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Alert from '../Alert';\nimport * as Csrf from '../Csrf';\nimport Loader from '../Loader';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport ZonesStore from '../stores/ZonesStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\n\nlet syncId: string;\nlet syncNamesId: string;\n\nexport function sync(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/zone')\n\t\t\t.query({\n\t\t\t\t...ZonesStore.filter,\n\t\t\t\tpage: ZonesStore.page,\n\t\t\t\tpage_count: ZonesStore.pageCount,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load zones');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: ZoneTypes.SYNC,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tzones: res.body.zones,\n\t\t\t\t\t\tcount: res.body.count,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function syncNames(): Promise<void> {\n\tlet curSyncId = MiscUtils.uuid();\n\tsyncNamesId = curSyncId;\n\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.get('/zone')\n\t\t\t.query({\n\t\t\t\tnames: true,\n\t\t\t})\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (curSyncId !== syncNamesId) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to load zone names');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tDispatcher.dispatch({\n\t\t\t\t\ttype: ZoneTypes.SYNC_NAMES,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tsecrets: res.body,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function traverse(page: number): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: ZoneTypes.TRAVERSE,\n\t\tdata: {\n\t\t\tpage: page,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function filter(filt: ZoneTypes.Filter): Promise<void> {\n\tDispatcher.dispatch({\n\t\ttype: ZoneTypes.FILTER,\n\t\tdata: {\n\t\t\tfilter: filt,\n\t\t},\n\t});\n\treturn sync();\n}\n\nexport function commit(zone: ZoneTypes.Zone): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.put('/zone/' + zone.id)\n\t\t\t.send(zone)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to save zone');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function create(zone: ZoneTypes.Zone): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.post('/zone')\n\t\t\t.send(zone)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to create zone');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function remove(zoneId: string): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/zone/' + zoneId)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.set('Organization', CompletionStore.userOrganization)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete zones');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nexport function removeMulti(zoneIds: string[]): Promise<void> {\n\tlet loader = new Loader().loading();\n\n\treturn new Promise<void>((resolve, reject): void => {\n\t\tSuperAgent\n\t\t\t.delete('/zone')\n\t\t\t.send(zoneIds)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (res && res.status === 401) {\n\t\t\t\t\twindow.location.href = '/login';\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to delete zones');\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\t});\n}\n\nEventDispatcher.register((action: ZoneTypes.ZoneDispatch) => {\n\tswitch (action.type) {\n\t\tcase ZoneTypes.CHANGE:\n\t\t\tif (!Constants.user) {\n\t\t\t\tsync();\n\t\t\t}\n\t\t\tbreak;\n\t}\n});\n"
  },
  {
    "path": "www/app/completion/Cache.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher'\nimport EventEmitter from \"../EventEmitter\"\nimport * as CompletionTypes from \"../types/CompletionTypes\"\nimport * as GlobalTypes from \"../types/GlobalTypes\"\n\nexport interface Kind {\n\tname: string\n\tlabel: string\n\ttitle: string\n}\n\nexport interface Resource {\n\tid: string\n\tname: string\n\tlabel?: string\n\tinfo: ResourceInfo[]\n\ttags?: Tag[]\n}\n\nexport interface ResourceInfo {\n\tlabel: string\n\tvalue: string | number\n}\n\nexport interface Tag {\n\tname: string\n\tlabel: string\n}\n\nexport interface Dispatch {\n\ttype: string\n}\n\nclass CompletionCache extends EventEmitter {\n\t_kindMap: Record<string, number> = {}\n\t_kinds: Kind[] = []\n\t_resourceMap: Record<string, Record<string, number>> = {}\n\t_resources: Record<string, Resource[]> = {}\n\t_token = Dispatcher.register((this._callback).bind(this))\n\n\tconstructor() {\n\t\tsuper()\n\t}\n\n\tget kinds(): Kind[] {\n\t\treturn this._kinds\n\t}\n\n\tkind(name: string): Kind {\n\t\tconst i = this._kindMap[name]\n\t\tif (i === undefined) {\n\t\t\treturn null\n\t\t}\n\n\t\treturn this._kinds[i]\n\t}\n\n\tresource(kindName: string, name: string): Resource {\n\t\tconst kindResourceMap = this._resourceMap[kindName]\n\t\tif (!kindResourceMap) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst i = kindResourceMap[name]\n\t\tif (i === undefined) {\n\t\t\treturn null\n\t\t}\n\n\t\treturn this._resources[kindName][i]\n\t}\n\n\tresources(kind: string): Resource[] {\n\t\treturn (this._resources[kind] || [])\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback)\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback)\n\t}\n\n\t_reset(): void {\n\t\tthis._kinds = []\n\t\tthis._kindMap = {}\n\t\tthis._resources = {}\n\t\tthis._resourceMap = {}\n\t}\n\n\t_callback(action: Dispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset()\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\tupdate(resources: CompletionTypes.Completion): void {\n\t\tthis._kinds = []\n\t\tthis._resources = {}\n\t\tlet resourceList: Resource[]\n\t\tlet subResourceList: Resource[]\n\n\t\tthis._kinds.push({\n\t\t\tname: \"organization\",\n\t\t\tlabel: \"Organization\",\n\t\t\ttitle: \"**Organization**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.organizations ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"organization\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"domain\",\n\t\t\tlabel: \"Domain\",\n\t\t\ttitle: \"**Domain**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.domains ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"domain\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"vpc\",\n\t\t\tlabel: \"VPC\",\n\t\t\ttitle: \"**VPC**\",\n\t\t})\n\t\tthis._kinds.push({\n\t\t\tname: \"subnet\",\n\t\t\tlabel: \"Subnet\",\n\t\t\ttitle: \"**Subnet**\",\n\t\t})\n\t\tresourceList = []\n\t\tsubResourceList = []\n\t\tfor (let item of (resources.vpcs ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\n\t\t\tfor (let subItem of (item.subnets || [])) {\n\t\t\t\tsubResourceList.push({\n\t\t\t\t\tid: subItem.id,\n\t\t\t\t\tname: subItem.name,\n\t\t\t\t\tlabel: \"[\" + item.name + \"] \" + subItem.name,\n\t\t\t\t\tinfo: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\t\tvalue: subItem.name,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"**VPC**\",\n\t\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tthis._resources[\"vpc\"] = resourceList\n\t\tthis._resources[\"subnet\"] = subResourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"datacenter\",\n\t\t\tlabel: \"Datacenter\",\n\t\t\ttitle: \"**Datacenter**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.datacenters ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"datacenter\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"node\",\n\t\t\tlabel: \"Node\",\n\t\t\ttitle: \"**Node**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.nodes ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"node\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"pool\",\n\t\t\tlabel: \"Pool\",\n\t\t\ttitle: \"**Pool**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.pools ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"pool\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"zone\",\n\t\t\tlabel: \"Zone\",\n\t\t\ttitle: \"**Zone**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.zones ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"zone\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"shape\",\n\t\t\tlabel: \"Shapes\",\n\t\t\ttitle: \"**Shapes**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.shapes ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"shape\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"image\",\n\t\t\tlabel: \"Image\",\n\t\t\ttitle: \"**Image**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.images ||[])) {\n\t\t\tlet tags: Tag[] = []\n\t\t\tfor (let tag of (item.tags || [])) {\n\t\t\t\ttags.push({\n\t\t\t\t\tname: tag,\n\t\t\t\t\tlabel: tag,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\ttags: tags,\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"image\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"build\",\n\t\t\tlabel: \"Build\",\n\t\t\ttitle: \"**Build**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.builds ||[])) {\n\t\t\tlet tags: Tag[] = []\n\t\t\tfor (let tag of (item.tags || [])) {\n\t\t\t\ttags.push({\n\t\t\t\t\tname: tag.tag,\n\t\t\t\t\tlabel: tag.tag,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\ttags: tags,\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"build\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"instance\",\n\t\t\tlabel: \"Instance\",\n\t\t\ttitle: \"**Instance**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.instances ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Memory**\",\n\t\t\t\t\t\tvalue: item.memory,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Processors**\",\n\t\t\t\t\t\tvalue: item.processors,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"instance\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"plan\",\n\t\t\tlabel: \"Plan\",\n\t\t\ttitle: \"**Plan**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.plans ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"plan\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"certificate\",\n\t\t\tlabel: \"Certificate\",\n\t\t\ttitle: \"**Certificate**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.certificates ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"certificate\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"secret\",\n\t\t\tlabel: \"Secret\",\n\t\t\ttitle: \"**Secret**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.secrets ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"secret\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"pod\",\n\t\t\tlabel: \"Pod\",\n\t\t\ttitle: \"**Pod**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.pods ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"pod\"] = resourceList\n\n\t\tthis._kinds.push({\n\t\t\tname: \"unit\",\n\t\t\tlabel: \"Unit\",\n\t\t\ttitle: \"**Unit**\",\n\t\t})\n\t\tresourceList = []\n\t\tfor (let item of (resources.units ||[])) {\n\t\t\tresourceList.push({\n\t\t\t\tid: item.id,\n\t\t\t\tname: item.name,\n\t\t\t\tinfo: [\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"**Name**\",\n\t\t\t\t\t\tvalue: item.name,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\tthis._resources[\"unit\"] = resourceList\n\n\t\tthis._kindMap = {}\n\t\tfor (let i = 0; i < this._kinds.length; i++) {\n\t\t\tthis._kindMap[this._kinds[i].name] = i\n\t\t}\n\n\t\tthis._resourceMap = {}\n\t\tObject.entries(this._resources).forEach(([kindName, resources]) => {\n\t\t\tlet kindResourceMap: Record<string, number> = {}\n\t\t\tfor (let i = 0; i < resources.length; i++) {\n\t\t\t\tkindResourceMap[resources[i].name] = i\n\t\t\t}\n\t\t\tthis._resourceMap[kindName] = kindResourceMap\n\t\t})\n\t}\n}\n\nexport default new CompletionCache()\n"
  },
  {
    "path": "www/app/completion/Engine.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as Monaco from \"monaco-editor\"\nimport * as MonacoEditor from \"@monaco-editor/react\"\nimport * as MonacoYaml from \"monaco-yaml\"\nimport CompletionCache from \"./Cache\"\nimport * as Types from \"./Types\"\n\nlet registered = false\n\nexport type Match = Monaco.languages.ProviderResult<\n\tMonaco.languages.CompletionList>\n\nconst noMatch: Match = {\n\tsuggestions: []\n}\n\nexport enum CompletionItemKind {\n\tMethod = 0,\n\tFunction = 1,\n\tConstructor = 2,\n\tField = 3,\n\tVariable = 4,\n\tClass = 5,\n\tStruct = 6,\n\tInterface = 7,\n\tModule = 8,\n\tProperty = 9,\n\tEvent = 10,\n\tOperator = 11,\n\tUnit = 12,\n\tValue = 13,\n\tConstant = 14,\n\tEnum = 15,\n\tEnumMember = 16,\n\tKeyword = 17,\n\tText = 18,\n\tColor = 19,\n\tFile = 20,\n\tReference = 21,\n\tCustomcolor = 22,\n\tFolder = 23,\n\tTypeParameter = 24,\n\tUser = 25,\n\tIssue = 26,\n\tSnippet = 27\n}\n\nexport enum CompletionItemInsertTextRule {\n\tNone = 0,\n\t/**\n\t * Adjust whitespace/indentation of multiline insert texts to\n\t * match the current line indentation.\n\t */\n\tKeepWhitespace = 1,\n\t/**\n\t * `insertText` is a snippet.\n\t */\n\tInsertAsSnippet = 4\n}\n\nexport function handleBeforeMount(\n\t\tmonaco: MonacoEditor.Monaco): void {\n\n\tMonacoYaml.configureMonacoYaml(monaco, {\n\t\tenableSchemaRequest: false,\n\t\tschemas: [\n\t\t\t{\n\t\t\t\tfileMatch: [\"instance.yaml\"],\n\t\t\t\tschema: {\n\t\t\t\t\ttype: \"object\",\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"Instance name\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tkind: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tenum: [\"instance\"],\n\t\t\t\t\t\t\tdescription: \"Resource kind\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcount: {\n\t\t\t\t\t\t\ttype: \"integer\",\n\t\t\t\t\t\t\tdescription: \"Number of instances\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tplan: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"Instance plan\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tzone: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"Availability zone\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnode: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"Specific node for the instance\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tshape: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"Instance shape specification\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tvpc: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"VPC identifier\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsubnet: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"Subnet identifier\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\troles: {\n\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tdescription: \"List of roles assigned to the instance\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tprocessors: {\n\t\t\t\t\t\t\ttype: \"integer\",\n\t\t\t\t\t\t\tdescription: \"Number of processors allocated\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmemory: {\n\t\t\t\t\t\t\ttype: \"integer\",\n\t\t\t\t\t\t\tdescription: \"Memory allocated in MB\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tuefi: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Enable UEFI boot\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsecureBoot: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Enable secure boot\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcloudType: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"Cloud provider type\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttpm: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Enable Trusted Platform Module\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tvnc: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Enable VNC access\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdeleteProtection: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Enable deletion protection\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tskipSourceDestCheck: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Skip source/destination check\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tgui: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Desktop GUI\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\thostAddress: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Allocate host address\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tpublicAddress: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Allocate public IPv4 address\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tpublicAddress6: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Allocate public IPv6 address\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdhcpServer: {\n\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\tdescription: \"Enable DHCP server\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\timage: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"Image identifier\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmounts: {\n\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\tdescription: \"Mount name\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\ttype: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\tenum: [\"host_path\", \"disk\"],\n\t\t\t\t\t\t\t\t\t\tdescription: \"Mount type\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\tdescription: \"Mount path\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\thostPath: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\tdescription: \"Host mount path\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tdisks: {\n\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tdescription: \"List of disk identifiers\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\trequired: [\"path\"],\n\t\t\t\t\t\t\t\tdescription: \"Disk mount configuration\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tdescription: \"Disk mounts\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnodePorts: {\n\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\tprotocol: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\tenum: [\"tcp\", \"udp\"],\n\t\t\t\t\t\t\t\t\t\tdescription: \"Network protocol\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\texternalPort: {\n\t\t\t\t\t\t\t\t\t\ttype: \"integer\",\n\t\t\t\t\t\t\t\t\t\tminimum: 1,\n\t\t\t\t\t\t\t\t\t\tmaximum: 65535,\n\t\t\t\t\t\t\t\t\t\tdescription: \"External port number\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tinternalPort: {\n\t\t\t\t\t\t\t\t\t\ttype: \"integer\",\n\t\t\t\t\t\t\t\t\t\tminimum: 1,\n\t\t\t\t\t\t\t\t\t\tmaximum: 65535,\n\t\t\t\t\t\t\t\t\t\tdescription: \"Internal port number\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\trequired: [\"protocol\", \"externalPort\", \"internalPort\"],\n\t\t\t\t\t\t\t\tdescription: \"Node port mapping\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tdescription: \"Node port configurations\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcertificates: {\n\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tdescription: \"List of certificate identifiers\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsecrets: {\n\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tdescription: \"List of secret identifiers\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tpods: {\n\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tdescription: \"List of pod identifiers\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdiskSize: {\n\t\t\t\t\t\t\ttype: \"integer\",\n\t\t\t\t\t\t\tdescription: \"Size of disk in GB\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\trequired: [\"name\", \"kind\", \"zone\", \"vpc\", \"subnet\", \"image\"],\n\t\t\t\t\tdescription: \"Instance configuration\",\n\t\t\t\t},\n\t\t\t\turi: \"https://todo.pritunl.com/instance-schema.json\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tfileMatch: [\"domain.yaml\"],\n\t\t\t\tschema: {\n\t\t\t\t\ttype: \"object\",\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"Domain name identifier\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tkind: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tenum: [\"domain\"],\n\t\t\t\t\t\t\tdescription: \"Resource kind\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\trecords: {\n\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\tdescription: \"Record name (subdomain or label)\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tdomain: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\tdescription: \"Domain name for this record\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\ttype: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\tenum: [\n\t\t\t\t\t\t\t\t\t\t\t\"host\",\n\t\t\t\t\t\t\t\t\t\t\t\"private\",\n\t\t\t\t\t\t\t\t\t\t\t\"private6\",\n\t\t\t\t\t\t\t\t\t\t\t\"public\",\n\t\t\t\t\t\t\t\t\t\t\t\"public6\",\n\t\t\t\t\t\t\t\t\t\t\t\"cloud_public\",\n\t\t\t\t\t\t\t\t\t\t\t\"cloud_public6\",\n\t\t\t\t\t\t\t\t\t\t\t\"cloud_private\",\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\tdescription: \"Record type\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\trequired: [\"name\", \"domain\", \"type\"],\n\t\t\t\t\t\t\t\tdescription: \"DNS record configuration\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tdescription: \"List of DNS records\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\trequired: [\"name\", \"kind\", \"records\"],\n\t\t\t\t\tdescription: \"Domain and DNS records configuration\",\n\t\t\t\t},\n\t\t\t\turi: \"https://todo.pritunl.com/domain-schema.json\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tfileMatch: [\"firewall.yaml\"],\n\t\t\t\tschema: {\n\t\t\t\t\ttype: \"object\",\n\t\t\t\t\trequired: [\"name\", \"kind\", \"ingress\"],\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tdescription: \"The name of the firewall rule\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tkind: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tenum: [\"firewall\"],\n\t\t\t\t\t\t\tdescription: \"Resource kind\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tingress: {\n\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\tdescription: \"Ingress rules for the firewall\",\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\trequired: [\"protocol\", \"port\", \"source\"],\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\tprotocol: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\tenum: [\"all\", \"icmp\", \"tcp\", \"udp\",\n\t\t\t\t\t\t\t\t\t\t\t\"multicast\", \"broadcast\"],\n\t\t\t\t\t\t\t\t\t\tdescription: \"The protocol for this rule\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tport: {\n\t\t\t\t\t\t\t\t\t\ttype: [\"number\", \"string\"],\n\t\t\t\t\t\t\t\t\t\tminimum: 1,\n\t\t\t\t\t\t\t\t\t\tmaximum: 65535,\n\t\t\t\t\t\t\t\t\t\tdescription: \"Port number or range \" +\n\t\t\t\t\t\t\t\t\t\t\t\"(e.g. \\\"80\\\" or \\\"80-443\\\")\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tsource: {\n\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\tdescription: \"Source addresses or networks\",\n\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\turi: \"https://todo.pritunl.com/firewall-schema.json\",\n\t\t\t},\n\t\t],\n\t});\n}\n\nexport function handleAfterMount(\n\t\teditor: Monaco.editor.IStandaloneCodeEditor,\n\t\tmonaco: MonacoEditor.Monaco): void {\n\n\tif (registered) {\n\t\treturn\n\t}\n\tregistered = true\n\n\tmonaco.languages.registerHoverProvider(\"markdown\", {\n\t\tprovideHover: (model, position, token) => {\n\t\t\tconst lineContent = model.getLineContent(position.lineNumber)\n\n\t\t\tconst match = lineContent.match(\n\t\t\t\t/\\+\\/([a-zA-Z0-9-]*)\\/([a-zA-Z0-9-]*)/)\n\t\t\tif (!match) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tlet kindName = match[1]\n\t\t\tlet resourceName = match[2]\n\t\t\tlet kind = CompletionCache.kind(kindName)\n\t\t\tlet resource = CompletionCache.resource(kindName, resourceName)\n\n\t\t\tif (kind && resource) {\n\t\t\t\tlet contents = [\n\t\t\t\t\t{value: kind.title},\n\t\t\t\t]\n\n\t\t\t\tlet data: string[] = []\n\t\t\t\tfor (let item of resource.info) {\n\t\t\t\t\tdata.push(item.label + \":  \" + item.value)\n\t\t\t\t}\n\n\t\t\t\tcontents.push({\n\t\t\t\t\tvalue: data.join(\"  \\n\"),\n\t\t\t\t})\n\n\t\t\t\treturn {\n\t\t\t\t\trange: {\n\t\t\t\t\t\tstartLineNumber: position.lineNumber,\n\t\t\t\t\t\tendLineNumber: position.lineNumber,\n\t\t\t\t\t\tstartColumn: match.index + 1,\n\t\t\t\t\t\tendColumn: match.index + kindName.length + resourceName.length + 5,\n\t\t\t\t\t},\n\t\t\t\t\tcontents: contents,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null\n\t\t}\n\t})\n\n\teditor.updateOptions({\n    suggest: {\n        preview: true,\n    }\n\t});\n\n\tmonaco.languages.setLanguageConfiguration(\"markdown\", {\n\t\twordPattern: /[^+\\/:]+/g,\n\t});\n\n\tmonaco.languages.registerCompletionItemProvider(\"markdown\", {\n\t\ttriggerCharacters: [\"+\", \"/\", \":\"],\n\t\tprovideCompletionItems: (model, position) => {\n\t\t\tconst textBeforeCursor = model.getValueInRange({\n\t\t\t\tstartLineNumber: position.lineNumber,\n\t\t\t\tstartColumn: 1,\n\t\t\t\tendLineNumber: position.lineNumber,\n\t\t\t\tendColumn: position.column,\n\t\t\t})\n\n\t\t\tconst selectorWithTagMatch = textBeforeCursor.match(\n\t\t\t\t/\\+\\/([a-zA-Z0-9-]*)\\/([a-zA-Z0-9-]*):([a-zA-Z0-9-]*)\\/$/);\n\t\t\tconst selectorDirectMatch = textBeforeCursor.match(\n\t\t\t\t/\\+\\/([a-zA-Z0-9-]*)\\/([a-zA-Z0-9-]*)\\/$/);\n\n\t\t\tif (selectorWithTagMatch || selectorDirectMatch) {\n\t\t\t\tconst match = selectorWithTagMatch || selectorDirectMatch;\n\t\t\t\tconst kindName = match[1];\n\t\t\t\tconst resourceName = match[2];\n\n\t\t\t\tlet selectorKey = \"\";\n\t\t\t\tswitch (kindName) {\n\t\t\t\t\tcase \"instance\":\n\t\t\t\t\t\tselectorKey = \"instance\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"vpc\":\n\t\t\t\t\t\tselectorKey = \"vpc\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"subnet\":\n\t\t\t\t\t\tselectorKey = \"subnet\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"certificate\":\n\t\t\t\t\t\tselectorKey = \"certificate\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"secret\":\n\t\t\t\t\t\tselectorKey = \"secret\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"unit\":\n\t\t\t\t\t\tselectorKey = \"unit\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn noMatch;\n\t\t\t\t}\n\n\t\t\t\tconst selectors = Types.Selectors[selectorKey];\n\t\t\t\tif (!selectors) {\n\t\t\t\t\treturn noMatch;\n\t\t\t\t}\n\n\t\t\t\tconst range = {\n\t\t\t\t\tstartLineNumber: position.lineNumber,\n\t\t\t\t\tendLineNumber: position.lineNumber,\n\t\t\t\t\tstartColumn: position.column,\n\t\t\t\t\tendColumn: position.column,\n\t\t\t\t}\n\n\t\t\t\tlet suggestions: Monaco.languages.CompletionItem[] = [];\n\n\t\t\t\tfor (const [key, info] of Object.entries(selectors)) {\n\t\t\t\t\tsuggestions.push({\n\t\t\t\t\t\tlabel: key,\n\t\t\t\t\t\tkind: CompletionItemKind.Value,\n\t\t\t\t\t\tinsertText: key,\n\t\t\t\t\t\tinsertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,\n\t\t\t\t\t\tdocumentation: info.tooltip,\n\t\t\t\t\t\tdetail: info.tooltip,\n\t\t\t\t\t\trange: range,\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tsuggestions: suggestions,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst tagMatch = textBeforeCursor.match(\n\t\t\t\t/\\+\\/([a-zA-Z0-9-]*)\\/([a-zA-Z0-9-]*):$/);\n\t\t\tif (tagMatch) {\n\t\t\t\tlet kindName = tagMatch[1]\n\t\t\t\tlet resourceName = tagMatch[2]\n\t\t\t\tlet resource = CompletionCache.resource(kindName, resourceName)\n\t\t\t\tif (!resource || !resource.tags) {\n\t\t\t\t\treturn noMatch\n\t\t\t\t}\n\n\t\t\t\tconst range = {\n\t\t\t\t\tstartLineNumber: position.lineNumber,\n\t\t\t\t\tendLineNumber: position.lineNumber,\n\t\t\t\t\tstartColumn: position.column,\n\t\t\t\t\tendColumn: position.column,\n\t\t\t\t}\n\n\t\t\t\tlet suggestions: Monaco.languages.CompletionItem[] = []\n\n\t\t\t\tfor (const tag of resource.tags) {\n\t\t\t\t\tsuggestions.push({\n\t\t\t\t\t\tlabel: tag.name,\n\t\t\t\t\t\tkind: CompletionItemKind.Field,\n\t\t\t\t\t\tinsertText: tag.name,\n\t\t\t\t\t\tinsertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,\n\t\t\t\t\t\tdocumentation: \"Tag\",\n\t\t\t\t\t\trange: range,\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tsuggestions: suggestions,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst resourceMatch = textBeforeCursor.match(/\\+\\/([a-zA-Z0-9-]*)\\/$/)\n\t\t\tif (resourceMatch) {\n\t\t\t\tlet kind = CompletionCache.kind(resourceMatch[1])\n\t\t\t\tif (!kind) {\n\t\t\t\t\treturn noMatch\n\t\t\t\t}\n\n\t\t\t\tconst range = {\n\t\t\t\t\tstartLineNumber: position.lineNumber,\n\t\t\t\t\tendLineNumber: position.lineNumber,\n\t\t\t\t\tstartColumn: position.column,\n\t\t\t\t\tendColumn: position.column,\n\t\t\t\t}\n\n\t\t\t\tlet suggestions: Monaco.languages.CompletionItem[] = []\n\n\t\t\t\tCompletionCache.resources(kind.name).forEach((resource, index) => {\n\t\t\t\t\tsuggestions.push({\n\t\t\t\t\t\tlabel: resource.label || resource.name,\n\t\t\t\t\t\tkind: CompletionItemKind.Property,\n\t\t\t\t\t\tinsertText: resource.name,\n\t\t\t\t\t\tinsertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,\n\t\t\t\t\t\tdocumentation: kind.title,\n\t\t\t\t\t\trange: range,\n\t\t\t\t\t\tsortText: index.toString().padStart(3, \"0\"),\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\treturn {\n\t\t\t\t\tsuggestions: suggestions,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst kindMatch = textBeforeCursor.match(/\\+\\/$/)\n\t\t\tif (kindMatch) {\n\t\t\t\tconst range = {\n\t\t\t\t\tstartLineNumber: position.lineNumber,\n\t\t\t\t\tendLineNumber: position.lineNumber,\n\t\t\t\t\tstartColumn: position.column,\n\t\t\t\t\tendColumn: position.column,\n\t\t\t\t}\n\n\t\t\t\tlet suggestions: Monaco.languages.CompletionItem[] = []\n\n\t\t\t\tfor (const kind of CompletionCache.kinds) {\n\t\t\t\t\tsuggestions.push({\n\t\t\t\t\t\tlabel: kind.name,\n\t\t\t\t\t\tkind: CompletionItemKind.Class,\n\t\t\t\t\t\tinsertText: kind.name,\n\t\t\t\t\t\tinsertTextRules: CompletionItemInsertTextRule.InsertAsSnippet,\n\t\t\t\t\t\tdocumentation: kind.title,\n\t\t\t\t\t\trange: range,\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tsuggestions: suggestions,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn noMatch\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "www/app/completion/Types.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as CompletionTypes from \"../types/CompletionTypes\";\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as DomainTypes from \"../types/DomainTypes\";\nimport * as VpcTypes from \"../types/VpcTypes\";\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\nimport * as NodeTypes from \"../types/NodeTypes\";\nimport * as PoolTypes from \"../types/PoolTypes\";\nimport * as ZoneTypes from \"../types/ZoneTypes\";\nimport * as ShapeTypes from \"../types/ShapeTypes\";\nimport * as ImageTypes from \"../types/ImageTypes\";\nimport * as InstanceTypes from \"../types/InstanceTypes\";\nimport * as PlanTypes from \"../types/PlanTypes\";\nimport * as CertificateTypes from \"../types/CertificateTypes\";\nimport * as SecretTypes from \"../types/SecretTypes\";\nimport * as PodTypes from \"../types/PodTypes\";\n\nexport interface Resources {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tdomains: DomainTypes.DomainsRo;\n\tvpcs: VpcTypes.VpcsRo;\n\tsubnets: VpcTypes.Subnet[];\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tnodes: NodeTypes.NodesRo;\n\tpools: PoolTypes.PoolsRo;\n\tzones: ZoneTypes.ZonesRo;\n\tshapes: ShapeTypes.ShapesRo;\n\timages: ImageTypes.ImagesRo;\n\tbuilds: CompletionTypes.Build[];\n\tinstances: InstanceTypes.InstancesRo;\n\tplans: PlanTypes.PlansRo;\n\tcertificates: CertificateTypes.CertificatesRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tpods: PodTypes.PodsRo;\n\tunits: PodTypes.UnitsRo;\n}\n\nexport interface Kind {\n\tname: string\n\tlabel: string\n\ttitle: string\n}\n\nexport interface Resource {\n\tid: string\n\tname: string\n\tinfo: ResourceInfo[]\n}\n\nexport interface ResourceInfo {\n\tlabel: string\n\tvalue: string | number\n}\n\nexport interface Dispatch {\n\ttype: string\n}\n\nexport type SelectorInfo = {\n\tlabel: string;\n\ttooltip: string;\n}\n\nexport const Selectors: Record<string, Record<string, SelectorInfo>> = {\n\t\"instance\": {\n\t\t\"id\": {\n\t\t\tlabel: \"ID\",\n\t\t\ttooltip: \"Unique identifier of the instance\"\n\t\t},\n\t\t\"organization\": {\n\t\t\tlabel: \"Organization\",\n\t\t\ttooltip: \"Organization the instance belongs to\"\n\t\t},\n\t\t\"zone\": {\n\t\t\tlabel: \"Zone\",\n\t\t\ttooltip: \"Availability zone where the instance is deployed\"\n\t\t},\n\t\t\"vpc\": {\n\t\t\tlabel: \"VPC\",\n\t\t\ttooltip: \"Virtual Private Cloud network the instance is connected to\"\n\t\t},\n\t\t\"subnet\": {\n\t\t\tlabel: \"Subnet\",\n\t\t\ttooltip: \"Subnet within the VPC where the instance resides\"\n\t\t},\n\t\t\"cloud_subnet\": {\n\t\t\tlabel: \"Cloud Subnet\",\n\t\t\ttooltip: \"Cloud Cloud subnet configuration\"\n\t\t},\n\t\t\"cloud_vnic\": {\n\t\t\tlabel: \"Cloud VNIC\",\n\t\t\ttooltip: \"Cloud virtual network interface\"\n\t\t},\n\t\t\"image\": {\n\t\t\tlabel: \"Image\",\n\t\t\ttooltip: \"Base image used for the instance\"\n\t\t},\n\t\t\"state\": {\n\t\t\tlabel: \"State\",\n\t\t\ttooltip: \"Current operational state of the instance\"\n\t\t},\n\t\t\"uefi\": {\n\t\t\tlabel: \"UEFI\",\n\t\t\ttooltip: \"Unified Extensible Firmware Interface status\"\n\t\t},\n\t\t\"secure_boot\": {\n\t\t\tlabel: \"Secure Boot\",\n\t\t\ttooltip: \"Status of secure boot feature\"\n\t\t},\n\t\t\"tpm\": {\n\t\t\tlabel: \"TPM\",\n\t\t\ttooltip: \"Trusted Platform Module status\"\n\t\t},\n\t\t\"dhcp_server\": {\n\t\t\tlabel: \"DHCP Server\",\n\t\t\ttooltip: \"Dynamic Host Configuration Protocol server status\"\n\t\t},\n\t\t\"cloud_type\": {\n\t\t\tlabel: \"Cloud Type\",\n\t\t\ttooltip: \"Type of cloud infrastructure being used\"\n\t\t},\n\t\t\"delete_protection\": {\n\t\t\tlabel: \"Delete Protection\",\n\t\t\ttooltip: \"Status of deletion protection feature\"\n\t\t},\n\t\t\"skip_source_dest_check\": {\n\t\t\tlabel: \"Skip Source/Dest Check\",\n\t\t\ttooltip: \"Status of source/destination checking\"\n\t\t},\n\t\t\"qemu_version\": {\n\t\t\tlabel: \"QEMU Version\",\n\t\t\ttooltip: \"Version of QEMU virtualization software\"\n\t\t},\n\t\t\"public_ips\": {\n\t\t\tlabel: \"Public IPs\",\n\t\t\ttooltip: \"List of public IPv4 addresses\"\n\t\t},\n\t\t\"public_ips6\": {\n\t\t\tlabel: \"Public IPv6\",\n\t\t\ttooltip: \"List of public IPv6 addresses\"\n\t\t},\n\t\t\"private_ips\": {\n\t\t\tlabel: \"Private IPs\",\n\t\t\ttooltip: \"List of private IPv4 addresses\"\n\t\t},\n\t\t\"private_ips6\": {\n\t\t\tlabel: \"Private IPv6\",\n\t\t\ttooltip: \"List of private IPv6 addresses\"\n\t\t},\n\t\t\"gateway_ips\": {\n\t\t\tlabel: \"Gateway IPs\",\n\t\t\ttooltip: \"IPv4 gateway addresses\"\n\t\t},\n\t\t\"gateway_ips6\": {\n\t\t\tlabel: \"Gateway IPv6\",\n\t\t\ttooltip: \"IPv6 gateway addresses\"\n\t\t},\n\t\t\"cloud_private_ips\": {\n\t\t\tlabel: \"Cloud Private IPs\",\n\t\t\ttooltip: \"Cloud private IP addresses\"\n\t\t},\n\t\t\"cloud_public_ips\": {\n\t\t\tlabel: \"Cloud Public IPs\",\n\t\t\ttooltip: \"Cloud public IP addresses\"\n\t\t},\n\t\t\"host_ips\": {\n\t\t\tlabel: \"Host IPs\",\n\t\t\ttooltip: \"IP addresses of the host machine\"\n\t\t},\n\t\t\"network_namespace\": {\n\t\t\tlabel: \"Network Namespace\",\n\t\t\ttooltip: \"Network namespace configuration\"\n\t\t},\n\t\t\"no_public_address\": {\n\t\t\tlabel: \"No Public Address\",\n\t\t\ttooltip: \"Indicates if public IPv4 addressing is disabled\"\n\t\t},\n\t\t\"no_public_address6\": {\n\t\t\tlabel: \"No Public IPv6\",\n\t\t\ttooltip: \"Indicates if public IPv6 addressing is disabled\"\n\t\t},\n\t\t\"no_host_address\": {\n\t\t\tlabel: \"No Host Address\",\n\t\t\ttooltip: \"Indicates if host addressing is disabled\"\n\t\t},\n\t\t\"node\": {\n\t\t\tlabel: \"Node\",\n\t\t\ttooltip: \"Physical or virtual node where the instance runs\"\n\t\t},\n\t\t\"shape\": {\n\t\t\tlabel: \"Shape\",\n\t\t\ttooltip: \"Instance type and size configuration\"\n\t\t},\n\t\t\"name\": {\n\t\t\tlabel: \"Name\",\n\t\t\ttooltip: \"Display name of the instance\"\n\t\t},\n\t\t\"root_enabled\": {\n\t\t\tlabel: \"Root Enabled\",\n\t\t\ttooltip: \"Status of root access\"\n\t\t},\n\t\t\"memory\": {\n\t\t\tlabel: \"Memory\",\n\t\t\ttooltip: \"Allocated RAM\"\n\t\t},\n\t\t\"processors\": {\n\t\t\tlabel: \"Processors\",\n\t\t\ttooltip: \"Number of allocated CPU cores\"\n\t\t},\n\t\t\"roles\": {\n\t\t\tlabel: \"Roles\",\n\t\t\ttooltip: \"Access roles assigned to the instance\"\n\t\t},\n\t\t\"vnc\": {\n\t\t\tlabel: \"VNC\",\n\t\t\ttooltip: \"Virtual Network Computing status\"\n\t\t},\n\t\t\"spice\": {\n\t\t\tlabel: \"SPICE\",\n\t\t\ttooltip: \"Simple Protocol for Independent Computing Environments status\"\n\t\t},\n\t\t\"gui\": {\n\t\t\tlabel: \"GUI\",\n\t\t\ttooltip: \"Graphical User Interface status\"\n\t\t},\n\t\t\"deployment\": {\n\t\t\tlabel: \"Deployment\",\n\t\t\ttooltip: \"Deployment configuration details\"\n\t\t}\n\t},\n\t\"vpc\": {\n\t\t\"id\": {\n\t\t\tlabel: \"ID\",\n\t\t\ttooltip: \"Unique identifier of the VPC\"\n\t\t},\n\t\t\"name\": {\n\t\t\tlabel: \"Name\",\n\t\t\ttooltip: \"Display name of the VPC\"\n\t\t},\n\t\t\"vpc_id\": {\n\t\t\tlabel: \"VPC ID\",\n\t\t\ttooltip: \"Cloud provider's VPC identifier\"\n\t\t},\n\t\t\"network\": {\n\t\t\tlabel: \"Network\",\n\t\t\ttooltip: \"IPv4 network configuration\"\n\t\t},\n\t\t\"network6\": {\n\t\t\tlabel: \"Network IPv6\",\n\t\t\ttooltip: \"IPv6 network configuration\"\n\t\t}\n\t},\n\t\"subnet\": {\n\t\t\"id\": {\n\t\t\tlabel: \"ID\",\n\t\t\ttooltip: \"Unique identifier of the subnet\"\n\t\t},\n\t\t\"name\": {\n\t\t\tlabel: \"Name\",\n\t\t\ttooltip: \"Display name of the subnet\"\n\t\t},\n\t\t\"network\": {\n\t\t\tlabel: \"Network\",\n\t\t\ttooltip: \"Network address range of the subnet\"\n\t\t}\n\t},\n\t\"certificate\": {\n\t\t\"id\": {\n\t\t\tlabel: \"ID\",\n\t\t\ttooltip: \"Unique identifier of the certificate\"\n\t\t},\n\t\t\"name\": {\n\t\t\tlabel: \"Name\",\n\t\t\ttooltip: \"Display name of the certificate\"\n\t\t},\n\t\t\"type\": {\n\t\t\tlabel: \"Type\",\n\t\t\ttooltip: \"Type of certificate\"\n\t\t},\n\t\t\"key\": {\n\t\t\tlabel: \"Key\",\n\t\t\ttooltip: \"Certificate key information\"\n\t\t},\n\t\t\"certificate\": {\n\t\t\tlabel: \"Certificate\",\n\t\t\ttooltip: \"Certificate content\"\n\t\t}\n\t},\n\t\"secret\": {\n\t\t\"id\": {\n\t\t\tlabel: \"ID\",\n\t\t\ttooltip: \"Unique identifier of the secret\"\n\t\t},\n\t\t\"name\": {\n\t\t\tlabel: \"Name\",\n\t\t\ttooltip: \"Display name of the secret\"\n\t\t},\n\t\t\"type\": {\n\t\t\tlabel: \"Type\",\n\t\t\ttooltip: \"Type of secret\"\n\t\t},\n\t\t\"key\": {\n\t\t\tlabel: \"Key\",\n\t\t\ttooltip: \"Secret key identifier\"\n\t\t},\n\t\t\"value\": {\n\t\t\tlabel: \"Value\",\n\t\t\ttooltip: \"Protected secret value\"\n\t\t},\n\t\t\"region\": {\n\t\t\tlabel: \"Region\",\n\t\t\ttooltip: \"Region where the secret is stored\"\n\t\t},\n\t\t\"public_key\": {\n\t\t\tlabel: \"Public Key\",\n\t\t\ttooltip: \"Public key component\"\n\t\t},\n\t\t\"private_key\": {\n\t\t\tlabel: \"Private Key\",\n\t\t\ttooltip: \"Private key component\"\n\t\t}\n\t},\n\t\"unit\": {\n\t\t\"id\": {\n\t\t\tlabel: \"ID\",\n\t\t\ttooltip: \"Unique identifier of the unit\"\n\t\t},\n\t\t\"name\": {\n\t\t\tlabel: \"Name\",\n\t\t\ttooltip: \"Display name of the unit\"\n\t\t},\n\t\t\"kind\": {\n\t\t\tlabel: \"Kind\",\n\t\t\ttooltip: \"Type of unit\"\n\t\t},\n\t\t\"count\": {\n\t\t\tlabel: \"Count\",\n\t\t\ttooltip: \"Number of instances in the unit\"\n\t\t},\n\t\t\"public_ips\": {\n\t\t\tlabel: \"Public IPs\",\n\t\t\ttooltip: \"List of public IPv4 addresses\"\n\t\t},\n\t\t\"public_ips6\": {\n\t\t\tlabel: \"Public IPv6\",\n\t\t\ttooltip: \"List of public IPv6 addresses\"\n\t\t},\n\t\t\"healthy_public_ips\": {\n\t\t\tlabel: \"Healthy Public IPs\",\n\t\t\ttooltip: \"List of healthy public IPv4 addresses\"\n\t\t},\n\t\t\"healthy_public_ips6\": {\n\t\t\tlabel: \"Healthy Public IPv6\",\n\t\t\ttooltip: \"List of healthy public IPv6 addresses\"\n\t\t},\n\t\t\"unhealthy_public_ips\": {\n\t\t\tlabel: \"Unhealthy Public IPs\",\n\t\t\ttooltip: \"List of unhealthy public IPv4 addresses\"\n\t\t},\n\t\t\"unhealthy_public_ips6\": {\n\t\t\tlabel: \"Unhealthy Public IPv6\",\n\t\t\ttooltip: \"List of unhealthy public IPv6 addresses\"\n\t\t},\n\t\t\"private_ips\": {\n\t\t\tlabel: \"Private IPs\",\n\t\t\ttooltip: \"List of private IPv4 addresses\"\n\t\t},\n\t\t\"private_ips6\": {\n\t\t\tlabel: \"Private IPv6\",\n\t\t\ttooltip: \"List of private IPv6 addresses\"\n\t\t},\n\t\t\"healthy_private_ips\": {\n\t\t\tlabel: \"Healthy Private IPs\",\n\t\t\ttooltip: \"List of healthy private IPv4 addresses\"\n\t\t},\n\t\t\"healthy_private_ips6\": {\n\t\t\tlabel: \"Healthy Private IPv6\",\n\t\t\ttooltip: \"List of healthy private IPv6 addresses\"\n\t\t},\n\t\t\"unhealthy_private_ips\": {\n\t\t\tlabel: \"Unhealthy Private IPs\",\n\t\t\ttooltip: \"List of unhealthy private IPv4 addresses\"\n\t\t},\n\t\t\"unhealthy_private_ips6\": {\n\t\t\tlabel: \"Unhealthy Private IPv6\",\n\t\t\ttooltip: \"List of unhealthy private IPv6 addresses\"\n\t\t},\n\t\t\"cloud_private_ips\": {\n\t\t\tlabel: \"Cloud Private IPs\",\n\t\t\ttooltip: \"List of cloud private IP addresses\"\n\t\t},\n\t\t\"cloud_public_ips\": {\n\t\t\tlabel: \"Cloud Public IPs\",\n\t\t\ttooltip: \"List of cloud public IP addresses\"\n\t\t},\n\t\t\"healthy_cloud_public_ips\": {\n\t\t\tlabel: \"Healthy Cloud Public IPs\",\n\t\t\ttooltip: \"List of healthy cloud public IP addresses\"\n\t\t},\n\t\t\"healthy_cloud_private_ips\": {\n\t\t\tlabel: \"Healthy Cloud Private IPs\",\n\t\t\ttooltip: \"List of healthy cloud private IP addresses\"\n\t\t},\n\t\t\"unhealthy_cloud_public_ips\": {\n\t\t\tlabel: \"Unhealthy Cloud Public IPs\",\n\t\t\ttooltip: \"List of unhealthy cloud public IP addresses\"\n\t\t},\n\t\t\"unhealthy_cloud_private_ips\": {\n\t\t\tlabel: \"Unhealthy Cloud Private IPs\",\n\t\t\ttooltip: \"List of unhealthy cloud private IP addresses\"\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "www/app/components/AdvisoryDialog.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from \"react\";\nimport * as Blueprint from \"@blueprintjs/core\";\nimport * as InstanceTypes from \"../types/InstanceTypes\";\nimport * as MiscUtils from \"../utils/MiscUtils\";\n\ninterface CveDetail {\n\tcve: string;\n\tdetail: InstanceTypes.Advisory;\n}\n\ninterface UpdateEntry {\n\tupdate: InstanceTypes.Update;\n\tcves: CveDetail[];\n\timportantCves: CveDetail[];\n\tworstScore: number;\n\tworstSeverity: string;\n\tlink?: string;\n}\n\ninterface State {\n\topen: boolean;\n\tshowLowSeverity: boolean;\n\texpanded: {[key: string]: boolean};\n\texpandedCves: {[advisory: string]: boolean};\n}\n\ninterface Props {\n\tupdates: InstanceTypes.Update[];\n}\n\nconst css = {\n\tdialog: {\n\t\twidth: \"90%\",\n\t\tmaxWidth: \"720px\",\n\t} as React.CSSProperties,\n\tbody: {\n\t\tpadding: \"12px 16px\",\n\t\tmaxHeight: \"70vh\",\n\t\toverflow: \"auto\",\n\t} as React.CSSProperties,\n\theader: {\n\t\tmargin: \"0 0 10px 0\",\n\t\tfontWeight: 600,\n\t} as React.CSSProperties,\n\tcount: {\n\t\tmarginLeft: \"6px\",\n\t\topacity: 0.7,\n\t} as React.CSSProperties,\n\tsection: {\n\t\tdisplay: \"flex\",\n\t\talignItems: \"center\",\n\t\tmargin: \"14px 0 8px 0\",\n\t\tfontWeight: 600,\n\t} as React.CSSProperties,\n\tupdateCard: {\n\t\tpadding: \"12px\",\n\t\tmarginBottom: \"12px\",\n\t} as React.CSSProperties,\n\tcveCard: {\n\t\tpadding: \"10px\",\n\t\tmarginTop: \"8px\",\n\t\tbackground: \"rgba(138, 155, 168, 0.06)\",\n\t\tborderRadius: \"3px\",\n\t} as React.CSSProperties,\n\theaderRow: {\n\t\talignItems: \"center\",\n\t\tmarginBottom: \"8px\",\n\t\tgap: \"8px\",\n\t} as React.CSSProperties,\n\theaderTag: {\n\t\tpaddingTop: \"3px\",\n\t\tpaddingBottom: \"3px\",\n\t\tmarginRight: \"6px\",\n\t} as React.CSSProperties,\n\ttitle: {\n\t\tfontFamily: \"monospace\",\n\t\tfontSize: \"14px\",\n\t\tfontWeight: 600,\n\t} as React.CSSProperties,\n\tcveTitle: {\n\t\tfontFamily: \"monospace\",\n\t\tfontSize: \"13px\",\n\t\tfontWeight: 600,\n\t} as React.CSSProperties,\n\ttagRow: {\n\t\tmarginBottom: \"8px\",\n\t\tgap: \"6px\",\n\t} as React.CSSProperties,\n\ttag: {\n\t\tpaddingTop: \"3px\",\n\t\tpaddingBottom: \"3px\",\n\t\tmarginRight: \"6px\",\n\t\tmarginBottom: \"4px\",\n\t} as React.CSSProperties,\n\tpackages: {\n\t\tfontSize: \"11px\",\n\t\tcolor: \"var(--bp5-text-color-muted, #5f6b7c)\",\n\t\tmarginBottom: \"6px\",\n\t\twordBreak: \"break-all\",\n\t} as React.CSSProperties,\n\tpackageName: {\n\t\tfontFamily: \"monospace\",\n\t\tfontSize: \"14px\",\n\t\tfontWeight: 600,\n\t\tpadding: \"3px 8px\",\n\t\tbackground: \"rgba(138, 155, 168, 0.15)\",\n\t\tborderRadius: \"3px\",\n\t\twordBreak: \"break-all\",\n\t} as React.CSSProperties,\n\tpackageList: {\n\t\tfontSize: \"11px\",\n\t\tfontFamily: \"monospace\",\n\t\tcolor: \"var(--bp5-text-color-muted, #5f6b7c)\",\n\t\tmarginBottom: \"8px\",\n\t\tpadding: \"6px 8px\",\n\t\tbackground: \"rgba(138, 155, 168, 0.08)\",\n\t\tborderRadius: \"3px\",\n\t\twordBreak: \"break-all\",\n\t\twhiteSpace: \"pre-wrap\",\n\t} as React.CSSProperties,\n\tdescription: {\n\t\tfontSize: \"12px\",\n\t\twhiteSpace: \"pre-wrap\",\n\t\twordBreak: \"break-word\",\n\t\tpadding: \"6px 8px\",\n\t\tbackground: \"rgba(138, 155, 168, 0.1)\",\n\t\tborderRadius: \"3px\",\n\t} as React.CSSProperties,\n\tdescriptionLimited: {\n\t\tdisplay: \"-webkit-box\",\n\t\tWebkitLineClamp: 6,\n\t\tWebkitBoxOrient: \"vertical\",\n\t\toverflow: \"hidden\",\n\t} as React.CSSProperties,\n\tdescriptionToggle: {\n\t\tmarginTop: \"4px\",\n\t\tpadding: \"0\",\n\t\tminHeight: \"0\",\n\t\tfontSize: \"11px\",\n\t} as React.CSSProperties,\n\thiddenToggle: {\n\t\tmarginTop: \"8px\",\n\t\tpadding: \"2px 6px\",\n\t\tminHeight: \"0\",\n\t\tfontSize: \"11px\",\n\t} as React.CSSProperties,\n}\n\nexport default class AdvisoryDialog extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\topen: false,\n\t\t\tshowLowSeverity: false,\n\t\t\texpanded: {},\n\t\t\texpandedCves: {},\n\t\t}\n\t}\n\n\trpmName(pkg: string): string {\n\t\tif (!pkg) {\n\t\t\treturn pkg\n\t\t}\n\t\tlet s = pkg\n\t\tlet lastDot = s.lastIndexOf('.')\n\t\tif (lastDot > 0) {\n\t\t\ts = s.slice(0, lastDot)\n\t\t}\n\t\tlet lastDash = s.lastIndexOf('-')\n\t\tif (lastDash > 0) {\n\t\t\ts = s.slice(0, lastDash)\n\t\t}\n\t\tlastDash = s.lastIndexOf('-')\n\t\tif (lastDash > 0) {\n\t\t\ts = s.slice(0, lastDash)\n\t\t}\n\t\treturn s\n\t}\n\n\tadvisoryLink(advisoryRaw: string): string {\n\t\tlet advisory = (advisoryRaw || \"\").replace(/[^a-zA-Z0-9:-]/g, '')\n\t\tif (advisory.startsWith('ALSA') || advisory.startsWith('RLSA') ||\n\t\t\t\tadvisory.startsWith('RHSA')) {\n\t\t\treturn `https://access.redhat.com/errata/RH${advisory.slice(2)}`\n\t\t} else if (advisory.startsWith('ELSA')) {\n\t\t\treturn `https://linux.oracle.com/errata/${advisory}.html`\n\t\t} else if (advisory.startsWith('FEDORA')) {\n\t\t\treturn `https://bodhi.fedoraproject.org/updates/${advisory}`\n\t\t}\n\t\treturn \"\"\n\t}\n\n\tseverityIntent(severity: string): Blueprint.Intent {\n\t\tswitch ((severity || \"\").toLowerCase()) {\n\t\t\tcase \"critical\":\n\t\t\t\treturn Blueprint.Intent.DANGER;\n\t\t\tcase \"high\":\n\t\t\t\treturn Blueprint.Intent.WARNING;\n\t\t\tcase \"medium\":\n\t\t\t\treturn Blueprint.Intent.PRIMARY;\n\t\t\tdefault:\n\t\t\t\treturn Blueprint.Intent.NONE;\n\t\t}\n\t}\n\n\tisImportantCve(detail: InstanceTypes.Advisory): boolean {\n\t\tif (!detail) {\n\t\t\treturn false;\n\t\t}\n\t\tif (detail.severity === \"critical\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (detail.vector === \"network\" &&\n\t\t\t\t(detail.severity === \"high\" || (detail.score || 0) >= 7)) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tcveSortScore(detail: InstanceTypes.Advisory): number {\n\t\tlet s = detail?.score || 0;\n\t\tif (detail?.vector === \"network\") s += 100;\n\t\tif (detail?.severity === \"critical\") s += 50;\n\t\tif (detail?.privileges === \"none\") s += 10;\n\t\tif (detail?.interaction === \"none\") s += 5;\n\t\treturn s;\n\t}\n\n\tbuildEntries(): UpdateEntry[] {\n\t\tlet updates = this.props.updates;\n\t\tif (!updates) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet entries: UpdateEntry[] = [];\n\t\tfor (let update of updates) {\n\t\t\tlet cves = update.cves || [];\n\t\t\tlet details = update.details || [];\n\t\t\tlet pairs: CveDetail[] = [];\n\t\t\tlet seen = new Set<string>();\n\t\t\tfor (let i = 0; i < cves.length; i++) {\n\t\t\t\tlet cve = cves[i];\n\t\t\t\tlet detail = details[i];\n\t\t\t\tif (!cve || !detail || seen.has(cve)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tseen.add(cve);\n\t\t\t\tpairs.push({cve: cve, detail: detail});\n\t\t\t}\n\n\t\t\tpairs.sort((a, b) =>\n\t\t\t\tthis.cveSortScore(b.detail) - this.cveSortScore(a.detail));\n\n\t\t\tlet importantCves = pairs.filter(\n\t\t\t\t(p): boolean => this.isImportantCve(p.detail));\n\n\t\t\tlet worstScore = 0;\n\t\t\tlet worstSeverity = \"\";\n\t\t\tlet severityRank: {[key: string]: number} = {\n\t\t\t\t\"critical\": 4,\n\t\t\t\t\"high\": 3,\n\t\t\t\t\"medium\": 2,\n\t\t\t\t\"low\": 1,\n\t\t\t};\n\t\t\tlet worstRank = 0;\n\t\t\tfor (let p of pairs) {\n\t\t\t\tlet score = this.cveSortScore(p.detail);\n\t\t\t\tif (score > worstScore) {\n\t\t\t\t\tworstScore = score;\n\t\t\t\t}\n\t\t\t\tlet sev = (p.detail.severity || \"\").toLowerCase();\n\t\t\t\tlet rank = severityRank[sev] || 0;\n\t\t\t\tif (rank > worstRank) {\n\t\t\t\t\tworstRank = rank;\n\t\t\t\t\tworstSeverity = sev;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tentries.push({\n\t\t\t\tupdate: update,\n\t\t\t\tcves: pairs,\n\t\t\t\timportantCves: importantCves,\n\t\t\t\tworstScore: worstScore,\n\t\t\t\tworstSeverity: worstSeverity,\n\t\t\t\tlink: this.advisoryLink(update.advisory || \"\"),\n\t\t\t});\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\tbuttonIntent(entries: UpdateEntry[]): string {\n\t\tif (entries.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tlet hasHigh = false;\n\t\tfor (let entry of entries) {\n\t\t\tif (entry.worstSeverity === \"critical\") {\n\t\t\t\treturn \"bp5-intent-danger\";\n\t\t\t}\n\t\t\tif (entry.worstSeverity === \"high\") {\n\t\t\t\thasHigh = true;\n\t\t\t}\n\t\t}\n\n\t\treturn hasHigh ? \"bp5-intent-warning\" : \"bp5-intent-primary\";\n\t}\n\n\trenderCveCard(entry: UpdateEntry, pair: CveDetail): JSX.Element {\n\t\tlet d = pair.detail;\n\t\tlet key = (entry.update.advisory || \"\") + \"|\" + pair.cve;\n\t\tlet nvdUrl = `https://access.redhat.com/security/cve/${pair.cve}`;\n\n\t\tlet tags: JSX.Element[] = [];\n\t\tif (d.vector === \"network\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"vec\"\n\t\t\t\tintent=\"danger\"\n\t\t\t\ticon=\"globe-network\"\n\t\t\t\tstyle={css.tag}>Network</Blueprint.Tag>);\n\t\t} else if (d.vector === \"adjacent\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"vec\"\n\t\t\t\tintent=\"warning\"\n\t\t\t\ticon=\"globe-network\"\n\t\t\t\tstyle={css.tag}>Adjacent</Blueprint.Tag>);\n\t\t} else if (d.vector === \"local\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"vec\"\n\t\t\t\tintent=\"success\"\n\t\t\t\ticon=\"globe-network\"\n\t\t\t\tstyle={css.tag}>Local</Blueprint.Tag>);\n\t\t} else if (d.vector === \"physical\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"vec\"\n\t\t\t\tintent=\"success\"\n\t\t\t\ticon=\"globe-network\"\n\t\t\t\tstyle={css.tag}>Physical</Blueprint.Tag>);\n\t\t}\n\t\tif (d.privileges === \"none\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"priv\"\n\t\t\t\tintent=\"danger\"\n\t\t\t\ticon=\"shield\"\n\t\t\t\tstyle={css.tag}>Unauthenticated</Blueprint.Tag>);\n\t\t} else if (d.privileges === \"low\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"priv\"\n\t\t\t\tintent=\"warning\"\n\t\t\t\ticon=\"shield\"\n\t\t\t\tstyle={css.tag}>Low Privileged</Blueprint.Tag>);\n\t\t} else if (d.privileges === \"high\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"priv\"\n\t\t\t\tintent=\"success\"\n\t\t\t\ticon=\"shield\"\n\t\t\t\tstyle={css.tag}>High Privileged</Blueprint.Tag>);\n\t\t}\n\t\tif (d.interaction === \"none\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"int\"\n\t\t\t\tintent=\"danger\"\n\t\t\t\ticon=\"console\"\n\t\t\t\tstyle={css.tag}>No Interaction</Blueprint.Tag>);\n\t\t} else if (d.interaction === \"required\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"int\"\n\t\t\t\tintent=\"success\"\n\t\t\t\ticon=\"console\"\n\t\t\t\tstyle={css.tag}>User Interaction</Blueprint.Tag>);\n\t\t}\n\t\tif (d.scope === \"changed\") {\n\t\t\ttags.push(<Blueprint.Tag key=\"scope\"\n\t\t\t\tintent=\"warning\"\n\t\t\t\ticon=\"route\"\n\t\t\t\tstyle={css.tag}>Scope Changed</Blueprint.Tag>);\n\t\t}\n\n\t\tlet sevIntent = this.severityIntent(d.severity || \"\");\n\t\tlet sevLabel = MiscUtils.capitalize(d.severity || \"Unknown\");\n\t\tlet scoreLabel = d.score ? ` ${d.score.toFixed(1)}` : \"\";\n\n\t\treturn <div key={pair.cve} style={css.cveCard}>\n\t\t\t<div className=\"layout horizontal\" style={css.headerRow}>\n\t\t\t\t<Blueprint.Tag\n\t\t\t\t\tintent={sevIntent}\n\t\t\t\t\ticon=\"shield\"\n\t\t\t\t\tstyle={css.headerTag}>{sevLabel}{scoreLabel}</Blueprint.Tag>\n\t\t\t\t<a\n\t\t\t\t\thref={nvdUrl}\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\tstyle={css.cveTitle}\n\t\t\t\t>{pair.cve}</a>\n\t\t\t</div>\n\t\t\t{tags.length > 0 && <div className=\"layout horizontal wrap\"\n\t\t\t\tstyle={css.tagRow}>\n\t\t\t\t{tags}\n\t\t\t</div>}\n\t\t\t{d.description && this.renderDescription(key, d.description)}\n\t\t</div>;\n\t}\n\n\trenderDescription(key: string, text: string): JSX.Element {\n\t\tlet expanded = !!this.state.expanded[key];\n\t\tlet style = expanded ? css.description : {\n\t\t\t...css.description,\n\t\t\t...css.descriptionLimited,\n\t\t};\n\n\t\treturn <>\n\t\t\t<div style={style}>\n\t\t\t\t{text}\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-small\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tstyle={css.descriptionToggle}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\texpanded: {\n\t\t\t\t\t\t\t...this.state.expanded,\n\t\t\t\t\t\t\t[key]: !expanded,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t>{expanded ? \"Show less\" : \"Show more\"}</button>\n\t\t</>;\n\t}\n\n\trenderUpdateCard(entry: UpdateEntry): JSX.Element {\n\t\tlet update = entry.update;\n\t\tlet sevIntent = this.severityIntent(entry.worstSeverity);\n\t\tlet sevLabel = entry.worstSeverity ?\n\t\t\tMiscUtils.capitalize(entry.worstSeverity) : \"Unknown\";\n\n\t\tlet advisoryKey = update.advisory || \"\";\n\t\tlet cvesExpanded = !!this.state.expandedCves[advisoryKey];\n\t\tlet cvesToShow = cvesExpanded ? entry.cves : entry.importantCves;\n\t\tlet hiddenCount = entry.cves.length - entry.importantCves.length;\n\n\t\tlet packages = update.packages || [];\n\t\tlet primaryName = packages.length > 0 ? this.rpmName(packages[0]) : \"\";\n\t\tlet hasFullVersionInfo = packages.length > 1 ||\n\t\t\t(packages.length === 1 && packages[0] !== primaryName);\n\n\t\tlet detailsKey = advisoryKey + \"|details\";\n\t\tlet detailsExpanded = !!this.state.expanded[detailsKey];\n\t\tlet hasDescription = !!update.description;\n\t\tlet showDetailsToggle = hasDescription || hasFullVersionInfo;\n\t\tlet descriptionStyle = detailsExpanded ? css.description : {\n\t\t\t...css.description,\n\t\t\t...css.descriptionLimited,\n\t\t};\n\n\t\treturn <div key={update.advisory}\n\t\t\tclassName=\"bp5-card bp5-elevation-0\"\n\t\t\tstyle={css.updateCard}>\n\t\t\t<div className=\"layout horizontal\" style={css.headerRow}>\n\t\t\t\t<Blueprint.Tag\n\t\t\t\t\tlarge={true}\n\t\t\t\t\tintent={sevIntent}\n\t\t\t\t\ticon=\"shield\"\n\t\t\t\t\tstyle={css.headerTag}>{sevLabel}</Blueprint.Tag>\n\t\t\t\t{primaryName && <span style={css.packageName}>\n\t\t\t\t\t{primaryName}\n\t\t\t\t</span>}\n\t\t\t\t{entry.link ? <a\n\t\t\t\t\thref={entry.link}\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\tstyle={css.title}\n\t\t\t\t>{update.advisory}</a> : <span style={css.title}>\n\t\t\t\t\t{update.advisory}\n\t\t\t\t</span>}\n\t\t\t</div>\n\t\t\t{hasDescription && <div style={descriptionStyle}>\n\t\t\t\t{update.description}\n\t\t\t</div>}\n\t\t\t{detailsExpanded && hasFullVersionInfo &&\n\t\t\t\t<div style={css.packageList}>\n\t\t\t\t\t{packages.join(\"\\n\")}\n\t\t\t\t</div>}\n\t\t\t{showDetailsToggle && <button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-small\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tstyle={css.descriptionToggle}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\texpanded: {\n\t\t\t\t\t\t\t...this.state.expanded,\n\t\t\t\t\t\t\t[detailsKey]: !detailsExpanded,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t>{detailsExpanded ? \"Show less\" : \"Show more\"}</button>}\n\t\t\t{cvesToShow.map((p): JSX.Element =>\n\t\t\t\tthis.renderCveCard(entry, p))}\n\t\t\t{hiddenCount > 0 && <button\n\t\t\t\tclassName={\"bp5-button bp5-minimal bp5-small \" +\n\t\t\t\t\t(cvesExpanded ?\n\t\t\t\t\t\t\"bp5-icon-chevron-up\" :\n\t\t\t\t\t\t\"bp5-icon-chevron-down\")}\n\t\t\t\ttype=\"button\"\n\t\t\t\tstyle={css.hiddenToggle}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\texpandedCves: {\n\t\t\t\t\t\t\t...this.state.expandedCves,\n\t\t\t\t\t\t\t[advisoryKey]: !cvesExpanded,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t>{cvesExpanded ?\n\t\t\t\t`Hide ${hiddenCount} lower risk CVE${hiddenCount === 1 ? \"\" : \"s\"}` :\n\t\t\t\t(entry.importantCves.length === 0 ?\n\t\t\t\t\t`Show ${hiddenCount} CVE${hiddenCount === 1 ? \"\" : \"s\"} (none rated high risk)` :\n\t\t\t\t\t`Show ${hiddenCount} additional CVE${hiddenCount === 1 ? \"\" : \"s\"}`)}</button>}\n\t\t</div>;\n\t}\n\n\trenderBody(entries: UpdateEntry[]): JSX.Element {\n\t\tif (entries.length === 0) {\n\t\t\treturn <div style={css.body}>\n\t\t\t\t<div className=\"bp5-callout bp5-intent-success\"\n\t\t\t\t\tstyle={{padding: \"12px\"}}>\n\t\t\t\t\t<h5 className=\"bp5-heading\">No security advisories</h5>\n\t\t\t\t\tNo outstanding security advisories reported by the guest agent.\n\t\t\t\t</div>\n\t\t\t</div>;\n\t\t}\n\n\t\tentries.sort((a, b) => b.worstScore - a.worstScore);\n\n\t\tlet important: UpdateEntry[] = [];\n\t\tlet other: UpdateEntry[] = [];\n\t\tfor (let entry of entries) {\n\t\t\tif (entry.importantCves.length > 0) {\n\t\t\t\timportant.push(entry);\n\t\t\t} else {\n\t\t\t\tother.push(entry);\n\t\t\t}\n\t\t}\n\n\t\treturn <div style={css.body}>\n\t\t\t{important.length > 0 ? <>\n\t\t\t\t<div style={css.section}>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName=\"bp5-icon-standard bp5-icon-warning-sign bp5-text-intent-danger\"\n\t\t\t\t\t\tstyle={{marginRight: \"6px\"}}\n\t\t\t\t\t/>\n\t\t\t\t\tHigh Risk ({important.length})\n\t\t\t\t</div>\n\t\t\t\t{important.map((e): JSX.Element => this.renderUpdateCard(e))}\n\t\t\t</> : <div className=\"bp5-callout bp5-intent-success\"\n\t\t\t\tstyle={{padding: \"10px\", marginBottom: \"10px\"}}>\n\t\t\t\tNo remotely exploitable or critical advisories.\n\t\t\t</div>}\n\t\t\t{other.length > 0 ? <>\n\t\t\t\t<button\n\t\t\t\t\tclassName={\"bp5-button bp5-minimal \" +\n\t\t\t\t\t\t(this.state.showLowSeverity ?\n\t\t\t\t\t\t\t\"bp5-icon-chevron-down\" :\n\t\t\t\t\t\t\t\"bp5-icon-chevron-right\")}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tstyle={{margin: \"8px 0\"}}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tshowLowSeverity: !this.state.showLowSeverity,\n\t\t\t\t\t\t});\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tLower Risk ({other.length})\n\t\t\t\t</button>\n\t\t\t\t{this.state.showLowSeverity ? <div>\n\t\t\t\t\t{other.map((e): JSX.Element => this.renderUpdateCard(e))}\n\t\t\t\t</div> : null}\n\t\t\t</> : null}\n\t\t</div>;\n\t}\n\n\trender(): JSX.Element {\n\t\tlet entries = this.buildEntries();\n\n\t\tlet dialog: JSX.Element\n\t\tif (this.state.open) {\n\t\t\tdialog = <Blueprint.Dialog\n\t\t\t\ttitle={\n\t\t\t\t\t<div>\n\t\t\t\t\t\tSecurity Advisories\n\t\t\t\t\t\t<span style={css.count}>\n\t\t\t\t\t\t\t({entries.length} advisor{entries.length === 1 ? \"y\" : \"ies\"})\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\tstyle={css.dialog}\n\t\t\t\tisOpen={this.state.open}\n\t\t\t\tusePortal={true}\n\t\t\t\tportalContainer={document.body}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topen: false,\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{this.renderBody(entries)}\n\t\t\t\t<div className=\"bp5-dialog-footer\">\n\t\t\t\t\t<div className=\"bp5-dialog-footer-actions\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-danger\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topen: false,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>Close</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</Blueprint.Dialog>\n\t\t}\n\n\t\treturn <div>\n\t\t\t<button\n\t\t\t\tclassName={\"bp5-button bp5-minimal bp5-icon-shield \" +\n\t\t\t\t\tthis.buttonIntent(entries)}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topen: true,\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>Security Advisories</button>\n\t\t\t{dialog}\n\t\t</div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Alert.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AlertTypes from '../types/AlertTypes';\nimport * as AuthorityTypes from '../types/AuthorityTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport AlertDetailed from './AlertDetailed';\n\ninterface Props {\n\talert: AlertTypes.AlertRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tauthorities: AuthorityTypes.AuthoritiesRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n};\n\nexport default class Alert extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet alert = this.props.alert;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<AlertDetailed\n\t\t\t\t\talert={this.props.alert}\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tauthorities={this.props.authorities}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet roles: JSX.Element[] = [];\n\n\t\tfor (let role of alert.roles) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-intent-primary\"\n\t\t\t\t\tstyle={css.tag}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{alert.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"flex bp5-cell\" style={css.roles}>\n\t\t\t\t{roles}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/AlertDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AlertTypes from '../types/AlertTypes';\nimport * as AuthorityTypes from \"../types/AuthorityTypes\";\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as AlertActions from '../actions/AlertActions';\nimport * as PageInfos from './PageInfo';\nimport PageInput from './PageInput';\nimport PageSave from './PageSave';\nimport PageInfo from './PageInfo';\nimport PageTextArea from './PageTextArea';\nimport ConfirmButton from './ConfirmButton';\nimport PageInputButton from './PageInputButton';\nimport Help from './Help';\nimport PageSwitch from \"./PageSwitch\";\nimport PageSelect from \"./PageSelect\";\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\talert: AlertTypes.AlertRo;\n\tauthorities: AuthorityTypes.AuthoritiesRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddRole: string;\n\taddIgnore: string;\n\talert: AlertTypes.Alert;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '250px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tcontrolButton: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\theader: {\n\t\tfontSize: '20px',\n\t\tmarginTop: '-10px',\n\t\tpaddingBottom: '2px',\n\t\tmarginBottom: '10px',\n\t\tborderBottomStyle: 'solid',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\talertsButtons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n\talertsAdd: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n};\n\nexport default class AlertDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\taddIgnore: '',\n\t\t\talert: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet alert: any;\n\n\t\tif (this.state.changed) {\n\t\t\talert = {\n\t\t\t\t...this.state.alert,\n\t\t\t};\n\t\t} else {\n\t\t\talert = {\n\t\t\t\t...this.props.alert,\n\t\t\t};\n\t\t}\n\n\t\talert[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tAlertActions.commit(this.state.alert).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\talert: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tAlertActions.remove(this.props.alert.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet alert: AlertTypes.Alert;\n\n\t\tif (this.state.changed) {\n\t\t\talert = {\n\t\t\t\t...this.state.alert,\n\t\t\t};\n\t\t} else {\n\t\t\talert = {\n\t\t\t\t...this.props.alert,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...alert.roles,\n\t\t];\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\n\t\talert.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\tonRemoveRole(role: string): void {\n\t\tlet alert: AlertTypes.Alert;\n\n\t\tif (this.state.changed) {\n\t\t\talert = {\n\t\t\t\t...this.state.alert,\n\t\t\t};\n\t\t} else {\n\t\t\talert = {\n\t\t\t\t...this.props.alert,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...alert.roles,\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\n\t\talert.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\tonAddIgnore = (): void => {\n\t\tlet alert: AlertTypes.Alert;\n\n\t\tif (this.state.changed) {\n\t\t\talert = {\n\t\t\t\t...this.state.alert,\n\t\t\t};\n\t\t} else {\n\t\t\talert = {\n\t\t\t\t...this.props.alert,\n\t\t\t};\n\t\t}\n\n\t\tlet ignores = [\n\t\t\t...(alert.ignores || []),\n\t\t];\n\n\t\tif (!this.state.addIgnore) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (ignores.indexOf(this.state.addIgnore) === -1) {\n\t\t\tignores.push(this.state.addIgnore);\n\t\t}\n\n\t\tignores.sort();\n\n\t\talert.ignores = ignores;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddIgnore: '',\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\tonRemoveIgnore(ignore: string): void {\n\t\tlet alert: AlertTypes.Alert;\n\n\t\tif (this.state.changed) {\n\t\t\talert = {\n\t\t\t\t...this.state.alert,\n\t\t\t};\n\t\t} else {\n\t\t\talert = {\n\t\t\t\t...this.props.alert,\n\t\t\t};\n\t\t}\n\n\t\tlet ignores = [\n\t\t\t...(alert.ignores || []),\n\t\t];\n\n\t\tlet i = ignores.indexOf(ignore);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tignores.splice(i, 1);\n\n\t\talert.ignores = ignores;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddIgnore: '',\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet alert: AlertTypes.Alert = this.state.alert ||\n\t\t\tthis.props.alert;\n\n\t\tlet hasOrganizations = !!this.props.organizations.length;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Organization</option>);\n\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet fields: PageInfos.Field[] = [\n\t\t\t{\n\t\t\t\tlabel: 'ID',\n\t\t\t\tvalue: this.props.alert.id || 'None',\n\t\t\t},\n\t\t];\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of alert.roles) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet ignores: JSX.Element[] = [];\n\t\tfor (let ignore of (alert.ignores || [])) {\n\t\t\tignores.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={ignore}\n\t\t\t\t>\n\t\t\t\t\t{ignore}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(ignore);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet valueInt = false;\n\t\tlet valueStr = false;\n\t\tlet valueLabel = '';\n\t\tlet valueHelp = '';\n\t\tlet ignoreShow = false;\n\t\tlet ignoreLabel = '';\n\t\tlet ignoreTitle = '';\n\t\tlet ignoreHelp = '';\n\t\tswitch (alert.resource) {\n\t\t\tcase \"system_cpu_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent CPU usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"system_memory_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent memory usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"system_swap_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent swap usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"system_hugepages_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent hugepages usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"disk_usage_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent disk space usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"kmsg_keyword\":\n\t\t\t\tvalueStr = true;\n\t\t\t\tvalueLabel = 'Dmesg Keyword Match';\n\t\t\t\tvalueHelp = 'Case insensitive dmesg match string to trigger alert.';\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n            <div>\n              <label\n                className=\"bp5-control bp5-checkbox\"\n                style={css.select}\n              >\n                <input\n                  type=\"checkbox\"\n                  checked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n                  onClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n                />\n                <span className=\"bp5-control-indicator\"/>\n              </label>\n            </div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Alert\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this alert\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[alert.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of alert\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={alert.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Alert comment.\"\n\t\t\t\t\t\tplaceholder=\"Alert comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={alert.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\tRoles\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\tcontent=\"The user roles that will be allowed access to this alert. At least one role must match for the user to access the alert.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Alert Type\"\n\t\t\t\t\t\thelp=\"Type of alert\"\n\t\t\t\t\t\tvalue={alert.resource}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('resource', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tvalue=\"instance_offline\"\n\t\t\t\t\t\t>Instance Offline</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<label className=\"bp5-label\" hidden={!ignoreShow}>\n\t\t\t\t\t\t{ignoreLabel}\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle={ignoreTitle}\n\t\t\t\t\t\t\tcontent={ignoreHelp}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{ignores}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add ignore\"\n\t\t\t\t\t\tvalue={this.state.addIgnore}\n\t\t\t\t\t\thidden={!ignoreShow}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddIgnore: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddIgnore}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={fields}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\thelp=\"Organization for alert, both the organaization and role must match. Select node firewall to match node network roles.\"\n\t\t\t\t\t\tvalue={alert.organization}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel={valueLabel}\n\t\t\t\t\t\thelp={valueHelp}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Default\"\n\t\t\t\t\t\tvalue={alert.value_int}\n\t\t\t\t\t\thidden={!valueInt}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('value_int', parseInt(val, 10));\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel={valueLabel}\n\t\t\t\t\t\thelp={valueHelp}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Default\"\n\t\t\t\t\t\tvalue={alert.value_str}\n\t\t\t\t\t\thidden={!valueStr}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('value_str', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Alert Level\"\n\t\t\t\t\t\thelp=\"Level of alert, used for matching device notifications. An endpoint role must also match a user role for ntofications.\"\n\t\t\t\t\t\tvalue={(alert.level || 0).toString()}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('level', parseInt(val, 10));\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"1\">Low</option>\n\t\t\t\t\t\t<option value=\"5\">Medium</option>\n\t\t\t\t\t\t<option value=\"10\">High</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Alert Frequency\"\n\t\t\t\t\t\thelp=\"Minimum duration in seconds between repeat alerts.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter frequency\"\n\t\t\t\t\t\tvalue={alert.frequency}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('frequency', parseInt(val, 10));\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.alert && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\talert: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/AlertNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AlertTypes from '../types/AlertTypes';\nimport * as AuthorityTypes from \"../types/AuthorityTypes\";\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as AlertActions from '../actions/AlertActions';\nimport * as PageInfos from './PageInfo';\nimport PageInput from './PageInput';\nimport PageCreate from './PageCreate';\nimport PageInfo from './PageInfo';\nimport PageTextArea from './PageTextArea';\nimport ConfirmButton from './ConfirmButton';\nimport PageInputButton from './PageInputButton';\nimport Help from './Help';\nimport PageSwitch from \"./PageSwitch\";\nimport PageSelect from \"./PageSelect\";\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\tauthorities: AuthorityTypes.AuthoritiesRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddRole: string;\n\taddIgnore: string;\n\talert: AlertTypes.Alert;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '250px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tcontrolButton: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\theader: {\n\t\tfontSize: '20px',\n\t\tmarginTop: '-10px',\n\t\tpaddingBottom: '2px',\n\t\tmarginBottom: '10px',\n\t\tborderBottomStyle: 'solid',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\talertsButtons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n\talertsAdd: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n};\n\nexport default class AlertDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\taddIgnore: '',\n\t\t\talert: {\n\t\t\t\tname:\"new-alert\",\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet alert: any = {\n\t\t\t...this.state.alert,\n\t\t};\n\n\t\talert[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet alert: any = {\n\t\t\t...this.state.alert,\n\t\t};\n\n\t\tAlertActions.create(alert).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Alert created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet alert: AlertTypes.Alert;\n\n\t\talert = {\n\t\t\t...this.state.alert,\n\t\t};\n\n\t\tlet roles = [\n\t\t\t...(alert.roles || []),\n\t\t];\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\n\t\talert.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\tonRemoveRole(role: string): void {\n\t\tlet alert: AlertTypes.Alert;\n\n\t\talert = {\n\t\t\t...this.state.alert,\n\t\t};\n\n\t\tlet roles = [\n\t\t\t...alert.roles,\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\n\t\talert.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\tonAddIgnore = (): void => {\n\t\tlet alert: AlertTypes.Alert;\n\n\t\talert = {\n\t\t\t...this.state.alert,\n\t\t};\n\n\t\tlet ignores = [\n\t\t\t...(alert.ignores || []),\n\t\t];\n\n\t\tif (!this.state.addIgnore) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (ignores.indexOf(this.state.addIgnore) === -1) {\n\t\t\tignores.push(this.state.addIgnore);\n\t\t}\n\n\t\tignores.sort();\n\n\t\talert.ignores = ignores;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddIgnore: '',\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\tonRemoveIgnore(ignore: string): void {\n\t\tlet alert: AlertTypes.Alert;\n\n\t\talert = {\n\t\t\t...this.state.alert,\n\t\t};\n\n\t\tlet ignores = [\n\t\t\t...(alert.ignores || []),\n\t\t];\n\n\t\tlet i = ignores.indexOf(ignore);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tignores.splice(i, 1);\n\n\t\talert.ignores = ignores;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddIgnore: '',\n\t\t\talert: alert,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet alert: AlertTypes.Alert = this.state.alert;\n\n\t\tlet hasOrganizations = !!this.props.organizations.length;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Organization</option>);\n\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of (alert.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet ignores: JSX.Element[] = [];\n\t\tfor (let ignore of (alert.ignores || [])) {\n\t\t\tignores.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={ignore}\n\t\t\t\t>\n\t\t\t\t\t{ignore}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(ignore);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet valueInt = false;\n\t\tlet valueStr = false;\n\t\tlet valueLabel = '';\n\t\tlet valueHelp = '';\n\t\tlet ignoreShow = false;\n\t\tlet ignoreLabel = '';\n\t\tlet ignoreTitle = '';\n\t\tlet ignoreHelp = '';\n\t\tswitch (alert.resource) {\n\t\t\tcase \"system_cpu_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent CPU usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"system_memory_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent memory usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"system_swap_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent swap usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"system_hugepages_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent hugepages usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"disk_usage_level\":\n\t\t\t\tvalueInt = true;\n\t\t\t\tvalueLabel = 'Usage Threshold';\n\t\t\t\tvalueHelp = 'Maximum percent disk space usage as integer ' +\n\t\t\t\t\t'before alert is triggered.';\n\t\t\t\tbreak;\n\t\t\tcase \"kmsg_keyword\":\n\t\t\t\tvalueStr = true;\n\t\t\t\tvalueLabel = 'Dmesg Keyword Match';\n\t\t\t\tvalueHelp = 'Case insensitive dmesg match string to trigger alert.';\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of alert\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={alert.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Alert comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Alert comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={alert.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\t\tRoles\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\t\tcontent=\"The user roles that will be allowed access to this alert. At least one role must match for the user to access the alert.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Alert Type\"\n\t\t\t\t\t\t\thelp=\"Type of alert\"\n\t\t\t\t\t\t\tvalue={alert.resource}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('resource', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\tvalue=\"instance_offline\"\n\t\t\t\t\t\t\t>Instance Offline</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<label className=\"bp5-label\" hidden={!ignoreShow}>\n\t\t\t\t\t\t\t{ignoreLabel}\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle={ignoreTitle}\n\t\t\t\t\t\t\t\tcontent={ignoreHelp}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{ignores}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Add ignore\"\n\t\t\t\t\t\t\tvalue={this.state.addIgnore}\n\t\t\t\t\t\t\thidden={!ignoreShow}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddIgnore: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddIgnore}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for alert, both the organaization and role must match. Select node firewall to match node network roles.\"\n\t\t\t\t\t\t\tvalue={alert.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel={valueLabel}\n\t\t\t\t\t\t\thelp={valueHelp}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Default\"\n\t\t\t\t\t\t\tvalue={alert.value_int}\n\t\t\t\t\t\t\thidden={!valueInt}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('value_int', parseInt(val, 10));\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel={valueLabel}\n\t\t\t\t\t\t\thelp={valueHelp}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Default\"\n\t\t\t\t\t\t\tvalue={alert.value_str}\n\t\t\t\t\t\t\thidden={!valueStr}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('value_str', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Alert Level\"\n\t\t\t\t\t\t\thelp=\"Level of alert, used for matching device notifications. An endpoint role must also match a user role for ntofications.\"\n\t\t\t\t\t\t\tvalue={(alert.level || 0).toString()}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('level', parseInt(val, 10));\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"1\">Low</option>\n\t\t\t\t\t\t\t<option value=\"5\">Medium</option>\n\t\t\t\t\t\t\t<option value=\"10\">High</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Alert Frequency\"\n\t\t\t\t\t\t\thelp=\"Minimum duration in seconds between repeat alerts.\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter frequency\"\n\t\t\t\t\t\t\tvalue={alert.frequency}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('frequency', parseInt(val, 10));\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.alert}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Alerts.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AlertTypes from '../types/AlertTypes';\nimport * as AuthorityTypes from '../types/AuthorityTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport AlertsStore from '../stores/AlertsStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as AlertActions from '../actions/AlertActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Alert from './Alert';\nimport AlertNew from './AlertNew';\nimport AlertsFilter from './AlertsFilter';\nimport AlertsPage from './AlertsPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\talerts: AlertTypes.AlertsRo;\n\tfilter: AlertTypes.Filter;\n\tauthorities: AuthorityTypes.AuthoritiesRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Alerts extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\talerts: AlertsStore.alerts,\n\t\t\tfilter: AlertsStore.filter,\n\t\t\tauthorities: CompletionStore.authorities,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tAlertsStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tAlertActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tAlertsStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet alerts = AlertsStore.alerts;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\talerts.forEach((alert: AlertTypes.Alert): void => {\n\t\t\tif (curSelected[alert.id]) {\n\t\t\t\tselected[alert.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[alert.id]) {\n\t\t\t\topened[alert.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\talerts: alerts,\n\t\t\tfilter: AlertsStore.filter,\n\t\t\tauthorities: CompletionStore.authorities,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tAlertActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet alertsDom: JSX.Element[] = [];\n\n\t\tthis.state.alerts.forEach((\n\t\t\t\talert: AlertTypes.AlertRo): void => {\n\t\t\talertsDom.push(<Alert\n\t\t\t\tkey={alert.id}\n\t\t\t\talert={alert}\n\t\t\t\tauthorities={this.state.authorities}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tselected={!!this.state.selected[alert.id]}\n\t\t\t\topen={!!this.state.opened[alert.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet alerts = this.state.alerts;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < alerts.length; i++) {\n\t\t\t\t\t\t\tlet usr = alerts[i];\n\n\t\t\t\t\t\t\tif (usr.id === alert.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[alerts[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: alert.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[alert.id]) {\n\t\t\t\t\t\tdelete selected[alert.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[alert.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: alert.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[alert.id]) {\n\t\t\t\t\t\tdelete opened[alert.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[alert.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = AlertsStore.alert(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newAlertDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewAlertDom = <AlertNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tauthorities={this.state.authorities}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Alerts</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tAlertActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tAlertActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected alerts\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<AlertsFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tAlertActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\tauthorities={this.state.authorities}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newAlertDom}\n\t\t\t\t\t{alertsDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!alertsDom.length}\n\t\t\t\ticonClass=\"bp5-icon-notifications\"\n\t\t\t\ttitle=\"No alerts\"\n\t\t\t\tdescription=\"Add a new alert to get started.\"\n\t\t\t/>\n\t\t\t<AlertsPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/AlertsFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AlertTypes from '../types/AlertTypes';\nimport SearchInput from './SearchInput';\nimport * as AuthorityTypes from \"../types/AuthorityTypes\";\n\ninterface Props {\n\tfilter: AlertTypes.Filter;\n\tonFilter: (filter: AlertTypes.Filter) => void;\n\tauthorities: AuthorityTypes.AuthoritiesRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class AlertsFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Alert ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.role}\n\t\t\t\tplaceholder=\"Role\"\n\t\t\t\tvalue={this.props.filter.role}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.role = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.role;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/AlertsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport AlertsStore from '../stores/AlertsStore';\nimport * as AlertActions from '../actions/AlertActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class AlertsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: AlertsStore.page,\n\t\t\tpageCount: AlertsStore.pageCount,\n\t\t\tpages: AlertsStore.pages,\n\t\t\tcount: AlertsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tAlertsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tAlertsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: AlertsStore.page,\n\t\t\tpageCount: AlertsStore.pageCount,\n\t\t\tpages: AlertsStore.pages,\n\t\t\tcount: AlertsStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAlertActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAlertActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAlertActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAlertActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAlertActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Audit.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AuditTypes from '../types/AuditTypes';\nimport * as Constants from '../Constants';\nimport * as AgentUtils from '../utils/AgentUtils';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport PageInfo from './PageInfo';\n\ninterface Props {\n\taudit: AuditTypes.AuditRo;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px',\n\t\tmarginBottom: '5px',\n\t} as React.CSSProperties,\n\tinfo: {\n\t\tmarginBottom: '-5px',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '290px',\n\t} as React.CSSProperties,\n};\n\nexport default class Audit extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet audit = this.props.audit;\n\t\tlet agent = audit.agent || {};\n\n\t\tlet fields: string[] = [];\n\t\tfor (let key in audit.fields) {\n\t\t\tif (!audit.fields.hasOwnProperty(key)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfields.push(key + ': ' + audit.fields[key]);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card\"\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: audit.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Timestamp',\n\t\t\t\t\t\t\t\tvalue: MiscUtils.formatDate(audit.timestamp) || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Fields',\n\t\t\t\t\t\t\t\tvalue: fields,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Type',\n\t\t\t\t\t\t\t\tvalue: audit.type,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Operating System',\n\t\t\t\t\t\t\t\tvalue: Constants.operatingSystems[agent.operating_system] ||\n\t\t\t\t\t\t\t\t'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Browser',\n\t\t\t\t\t\t\t\tvalue: Constants.browsers[agent.browser] || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ISP',\n\t\t\t\t\t\t\t\tvalue: agent.isp || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Location',\n\t\t\t\t\t\t\t\tvalue: [\n\t\t\t\t\t\t\t\t\tAgentUtils.formatLocation(agent),\n\t\t\t\t\t\t\t\t\tAgentUtils.formatCountry(agent),\n\t\t\t\t\t\t\t\t\tAgentUtils.formatContinent(agent),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Coordinates',\n\t\t\t\t\t\t\t\tvalue: AgentUtils.formatCoordinates(agent),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Audits.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AuditTypes from '../types/AuditTypes';\nimport AuditsStore from '../stores/AuditsStore';\nimport * as AuditActions from '../actions/AuditActions';\nimport NonState from './NonState';\nimport Audit from './Audit';\nimport PageHeader from './PageHeader';\nimport AuditsPage from './AuditsPage';\n\ninterface Props {\n\tuserId: string;\n}\n\ninterface State {\n\taudits: AuditTypes.AuditsRo;\n\tdisabled: boolean;\n}\n\nconst css = {\n\theader: {\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n};\n\nexport default class Audits extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\taudits: AuditsStore.audits,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tAuditsStore.addChangeListener(this.onChange);\n\t\tif (this.props.userId) {\n\t\t\tAuditActions.load(this.props.userId);\n\t\t}\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tAuditsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\taudits: AuditsStore.audits,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tif (!this.props.userId) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet audits: JSX.Element[] = [];\n\n\t\tthis.state.audits.forEach((\n\t\t\t\taudit: AuditTypes.AuditRo): void => {\n\t\t\taudits.push(<Audit\n\t\t\t\tkey={audit.id}\n\t\t\t\taudit={audit}\n\t\t\t/>);\n\t\t});\n\n\t\treturn <div>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>User Audit Events</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<div>\n\t\t\t\t{audits}\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!audits.length}\n\t\t\t\ticonClass=\"bp5-icon-search-template\"\n\t\t\t\ttitle=\"No audit events\"\n\t\t\t/>\n\t\t\t<AuditsPage/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/AuditsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport AuditsStore from '../stores/AuditsStore';\nimport * as AuditActions from '../actions/AuditActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class AuditsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: AuditsStore.page,\n\t\t\tpageCount: AuditsStore.pageCount,\n\t\t\tpages: AuditsStore.pages,\n\t\t\tcount: AuditsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tAuditsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tAuditsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: AuditsStore.page,\n\t\t\tpageCount: AuditsStore.pageCount,\n\t\t\tpages: AuditsStore.pages,\n\t\t\tcount: AuditsStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuditActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuditActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuditActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuditActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuditActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Authorities.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AuthorityTypes from '../types/AuthorityTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport AuthoritiesStore from '../stores/AuthoritiesStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as AuthorityActions from '../actions/AuthorityActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Authority from './Authority';\nimport AuthorityNew from './AuthorityNew';\nimport AuthoritiesFilter from './AuthoritiesFilter';\nimport AuthoritiesPage from './AuthoritiesPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tauthorities: AuthorityTypes.AuthoritiesRo;\n\tfilter: AuthorityTypes.Filter;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Authorities extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tauthorities: AuthoritiesStore.authorities,\n\t\t\tfilter: AuthoritiesStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tAuthoritiesStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tAuthorityActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tAuthoritiesStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet authorities = AuthoritiesStore.authorities;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tauthorities.forEach((authority: AuthorityTypes.Authority): void => {\n\t\t\tif (curSelected[authority.id]) {\n\t\t\t\tselected[authority.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[authority.id]) {\n\t\t\t\topened[authority.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tauthorities: authorities,\n\t\t\tfilter: AuthoritiesStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tAuthorityActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet authoritiesDom: JSX.Element[] = [];\n\n\t\tthis.state.authorities.forEach((\n\t\t\t\tauthority: AuthorityTypes.AuthorityRo): void => {\n\t\t\tauthoritiesDom.push(<Authority\n\t\t\t\tkey={authority.id}\n\t\t\t\tauthority={authority}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tselected={!!this.state.selected[authority.id]}\n\t\t\t\topen={!!this.state.opened[authority.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet authorities = this.state.authorities;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < authorities.length; i++) {\n\t\t\t\t\t\t\tlet usr = authorities[i];\n\n\t\t\t\t\t\t\tif (usr.id === authority.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[authorities[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: authority.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[authority.id]) {\n\t\t\t\t\t\tdelete selected[authority.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[authority.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: authority.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[authority.id]) {\n\t\t\t\t\t\tdelete opened[authority.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[authority.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let authrId of Object.keys(this.state.selected)) {\n\t\t\tlet authr = AuthoritiesStore.authority(authrId);\n\t\t\tif (authr) {\n\t\t\t\tselectedNames.push(authr.name || authrId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(authrId);\n\t\t\t}\n\t\t}\n\n\t\tlet newAuthorityDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewAuthorityDom = <AuthorityNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Authorities</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tAuthorityActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tAuthorityActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected authorities\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<AuthoritiesFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tAuthorityActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newAuthorityDom}\n\t\t\t\t\t{authoritiesDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={3} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!authoritiesDom.length}\n\t\t\t\ticonClass=\"bp5-icon-office\"\n\t\t\t\ttitle=\"No authorities\"\n\t\t\t\tdescription=\"Add a new authority to get started.\"\n\t\t\t/>\n\t\t\t<AuthoritiesPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/AuthoritiesFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as AuthorityTypes from '../types/AuthorityTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: AuthorityTypes.Filter;\n\tonFilter: (filter: AuthorityTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class AuthoritiesFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Authority ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.role}\n\t\t\t\tplaceholder=\"Role\"\n\t\t\t\tvalue={this.props.filter.role}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.role = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.role;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.role}\n\t\t\t\tplaceholder=\"Principal\"\n\t\t\t\tvalue={this.props.filter.principal}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.principal = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.principal;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/AuthoritiesPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport AuthoritiesStore from '../stores/AuthoritiesStore';\nimport * as AuthorityActions from '../actions/AuthorityActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class AuthoritiesPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: AuthoritiesStore.page,\n\t\t\tpageCount: AuthoritiesStore.pageCount,\n\t\t\tpages: AuthoritiesStore.pages,\n\t\t\tcount: AuthoritiesStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tAuthoritiesStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tAuthoritiesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: AuthoritiesStore.page,\n\t\t\tpageCount: AuthoritiesStore.pageCount,\n\t\t\tpages: AuthoritiesStore.pages,\n\t\t\tcount: AuthoritiesStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuthorityActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuthorityActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuthorityActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuthorityActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tAuthorityActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Authority.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AuthorityTypes from '../types/AuthorityTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\nimport AuthorityDetailed from './AuthorityDetailed';\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tauthority: AuthorityTypes.AuthorityRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Authority extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet authority = this.props.authority;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<AuthorityDetailed\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tauthority={this.props.authority}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet networkRoles: JSX.Element[] = [];\n\t\tfor (let networkRole of (authority.roles || [])) {\n\t\t\tnetworkRoles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-intent-primary\"\n\t\t\t\t\tstyle={css.tag}\n\t\t\t\t\tkey={networkRole}\n\t\t\t\t>\n\t\t\t\t\t{networkRole}\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet orgName = '';\n\t\tif (authority.organization) {\n\t\t\tlet org = CompletionStore.organization(authority.organization);\n\t\t\torgName = org ? org.name : authority.organization;\n\t\t} else {\n\t\t\torgName = 'No Organization';\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{authority.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-people\"\n\t\t\t\t/>\n\t\t\t\t{orgName}\n\t\t\t</div>\n\t\t\t<div className=\"flex bp5-cell\" style={css.roles}>\n\t\t\t\t{networkRoles}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/AuthorityDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AuthorityTypes from '../types/AuthorityTypes';\nimport * as AuthorityActions from '../actions/AuthorityActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageInputButton from './PageInputButton';\nimport PageTextArea from './PageTextArea';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tauthority: AuthorityTypes.AuthorityRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddRole: string;\n\taddNetworkRole: string;\n\tauthority: AuthorityTypes.Authority;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class AuthorityDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddRole: null,\n\t\t\taddNetworkRole: null,\n\t\t\tauthority: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet authority: any;\n\n\t\tif (this.state.changed) {\n\t\t\tauthority = {\n\t\t\t\t...this.state.authority,\n\t\t\t};\n\t\t} else {\n\t\t\tauthority = {\n\t\t\t\t...this.props.authority,\n\t\t\t};\n\t\t}\n\n\t\tauthority[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet authority: AuthorityTypes.Authority;\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tauthority = {\n\t\t\t\t...this.state.authority,\n\t\t\t};\n\t\t} else {\n\t\t\tauthority = {\n\t\t\t\t...this.props.authority,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(authority.principals || []),\n\t\t];\n\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\t\tauthority.principals = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonRemoveRole = (role: string): void => {\n\t\tlet authority: AuthorityTypes.Authority;\n\n\t\tif (this.state.changed) {\n\t\t\tauthority = {\n\t\t\t\t...this.state.authority,\n\t\t\t};\n\t\t} else {\n\t\t\tauthority = {\n\t\t\t\t...this.props.authority,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(authority.principals || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\t\tauthority.principals = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonAddNetworkRole = (): void => {\n\t\tlet authority: AuthorityTypes.Authority;\n\n\t\tif (!this.state.addNetworkRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tauthority = {\n\t\t\t\t...this.state.authority,\n\t\t\t};\n\t\t} else {\n\t\t\tauthority = {\n\t\t\t\t...this.props.authority,\n\t\t\t};\n\t\t}\n\n\t\tlet networkRoles = [\n\t\t\t...(authority.roles || []),\n\t\t];\n\n\n\t\tif (networkRoles.indexOf(this.state.addNetworkRole) === -1) {\n\t\t\tnetworkRoles.push(this.state.addNetworkRole);\n\t\t}\n\n\t\tnetworkRoles.sort();\n\t\tauthority.roles = networkRoles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonRemoveNetworkRole = (networkRole: string): void => {\n\t\tlet authority: AuthorityTypes.Authority;\n\n\t\tif (this.state.changed) {\n\t\t\tauthority = {\n\t\t\t\t...this.state.authority,\n\t\t\t};\n\t\t} else {\n\t\t\tauthority = {\n\t\t\t\t...this.props.authority,\n\t\t\t};\n\t\t}\n\n\t\tlet networkRoles = [\n\t\t\t...(authority.roles || []),\n\t\t];\n\n\t\tlet i = networkRoles.indexOf(networkRole);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tnetworkRoles.splice(i, 1);\n\t\tauthority.roles = networkRoles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\n\t\tlet authority: any = {\n\t\t\t...this.state.authority,\n\t\t};\n\n\t\tif (this.props.organizations.length && !authority.organization) {\n\t\t\tauthority.organization = this.props.organizations[0].id;\n\t\t}\n\n\t\tAuthorityActions.commit(authority).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tauthority: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tAuthorityActions.remove(this.props.authority.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet authority: AuthorityTypes.Authority = this.state.authority ||\n\t\t\tthis.props.authority;\n\n\t\tlet hasOrganizations = !!this.props.organizations.length;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\t(this.props.organizations || []).forEach((org) => {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey={org.id}\n\t\t\t\t\tvalue={org.id}\n\t\t\t\t>{org.name}</option>,\n\t\t\t);\n\t\t})\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet roles: JSX.Element[] = [];\n\t\t(authority.principals || []).forEach((role) => {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t})\n\n\t\tlet networkRoles: JSX.Element[] = [];\n\t\t(authority.roles || []).forEach((role) => {\n\t\t\tnetworkRoles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveNetworkRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t})\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={3}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n            <div>\n              <label\n                className=\"bp5-control bp5-checkbox\"\n                style={css.select}\n              >\n                <input\n                  type=\"checkbox\"\n                  checked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n                  onClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n                />\n                <span className=\"bp5-control-indicator\"/>\n              </label>\n            </div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Authority\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this authority\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[authority.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of authority\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={authority.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Authority comment.\"\n\t\t\t\t\t\tplaceholder=\"Authority comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={authority.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\thelp=\"Authority type. SSH keys will be saved to ~/.ssh/authorized_keys. SSH certificates will be saved to the SSH server configuration.\"\n\t\t\t\t\t\tvalue={authority.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"ssh_key\">SSH Key</option>\n\t\t\t\t\t\t<option value=\"ssh_certificate\">SSH Certificate</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\thidden={authority.type !== 'ssh_key'}\n\t\t\t\t\t\tlabel=\"SSH Key\"\n\t\t\t\t\t\thelp=\"SSH authorized public key in PEM format.\"\n\t\t\t\t\t\tplaceholder=\"Public key\"\n\t\t\t\t\t\trows={6}\n\t\t\t\t\t\tvalue={authority.key}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('key', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\thidden={authority.type !== 'ssh_certificate'}\n\t\t\t\t\t\tlabel=\"SSH Certificate\"\n\t\t\t\t\t\thelp=\"SSH certificate authority in PEM format.\"\n\t\t\t\t\t\tplaceholder=\"Certificate authority\"\n\t\t\t\t\t\trows={6}\n\t\t\t\t\t\tvalue={authority.certificate}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('certificate', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={authority.type !== 'ssh_certificate'}\n\t\t\t\t\t>\n\t\t\t\t\t\tPrincipals\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Principals\"\n\t\t\t\t\t\t\tcontent=\"Principals that will be matched with SSH certificate authority principles. Principal are case-sensitive.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\thidden={authority.type !== 'ssh_certificate'}\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add principal\"\n\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.authority.id || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasOrganizations}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\thelp=\"Organization for authority, both the organaization and role must match. Select node authority to match node network roles.\"\n\t\t\t\t\t\tvalue={authority.organization}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\tRoles\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\tcontent=\"Roles that will be matched with authorities. Roles are case-sensitive.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{networkRoles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\tvalue={this.state.addNetworkRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddNetworkRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddNetworkRole}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.authority && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tauthority: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/AuthorityNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as AuthorityTypes from '../types/AuthorityTypes';\nimport * as AuthorityActions from '../actions/AuthorityActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageInputButton from './PageInputButton';\nimport PageTextArea from './PageTextArea';\nimport PageCreate from './PageCreate';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddRole: string;\n\taddNetworkRole: string;\n\tauthority: AuthorityTypes.Authority;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class AuthorityNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddRole: null,\n\t\t\taddNetworkRole: null,\n\t\t\tauthority: {\n\t\t\t\tname: \"new-authority\",\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet authority: any = {\n\t\t\t...this.state.authority,\n\t\t};\n\n\t\tauthority[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet authority: AuthorityTypes.Authority;\n\n\t\tauthority = {\n\t\t\t...this.state.authority,\n\t\t};\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(authority.principals || []),\n\t\t];\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\t\tauthority.principals = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonRemoveRole = (role: string): void => {\n\t\tlet authority: AuthorityTypes.Authority;\n\n\t\tauthority = {\n\t\t\t...this.state.authority,\n\t\t};\n\n\t\tlet roles = [\n\t\t\t...(authority.principals || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\t\tauthority.principals = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonAddNetworkRole = (): void => {\n\t\tlet authority: AuthorityTypes.Authority;\n\n\t\tauthority = {\n\t\t\t...this.state.authority,\n\t\t};\n\n\t\tif (!this.state.addNetworkRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet networkRoles = [\n\t\t\t...(authority.roles || []),\n\t\t];\n\n\n\t\tif (networkRoles.indexOf(this.state.addNetworkRole) === -1) {\n\t\t\tnetworkRoles.push(this.state.addNetworkRole);\n\t\t}\n\n\t\tnetworkRoles.sort();\n\t\tauthority.roles = networkRoles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonRemoveNetworkRole = (networkRole: string): void => {\n\t\tlet authority: AuthorityTypes.Authority;\n\n\t\tauthority = {\n\t\t\t...this.state.authority,\n\t\t};\n\n\t\tlet networkRoles = [\n\t\t\t...(authority.roles || []),\n\t\t];\n\n\t\tlet i = networkRoles.indexOf(networkRole);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tnetworkRoles.splice(i, 1);\n\t\tauthority.roles = networkRoles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tauthority: authority,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet authority: any = {\n\t\t\t...this.state.authority,\n\t\t};\n\n\t\tif (this.props.organizations.length && !authority.organization) {\n\t\t\tauthority.organization = this.props.organizations[0].id;\n\t\t}\n\n\t\tAuthorityActions.create(authority).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Authority created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet authority: AuthorityTypes.Authority = this.state.authority;\n\n\t\tlet hasOrganizations = !!this.props.organizations.length;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\t(this.props.organizations || []).forEach((org) => {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey={org.id}\n\t\t\t\t\tvalue={org.id}\n\t\t\t\t>{org.name}</option>,\n\t\t\t);\n\t\t})\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet roles: JSX.Element[] = [];\n\t\t(authority.principals || []).forEach((role) => {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t})\n\n\t\tlet networkRoles: JSX.Element[] = [];\n\t\t(authority.roles || []).forEach((role) => {\n\t\t\tnetworkRoles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveNetworkRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t})\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={3}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of authority\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={authority.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Authority comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Authority comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={authority.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\t\thelp=\"Authority type. SSH keys will be saved to ~/.ssh/authorized_keys. SSH certificates will be saved to the SSH server configuration.\"\n\t\t\t\t\t\t\tvalue={authority.type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"ssh_key\">SSH Key</option>\n\t\t\t\t\t\t\t<option value=\"ssh_certificate\">SSH Certificate</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\thidden={authority.type !== 'ssh_key' && !!authority.type}\n\t\t\t\t\t\t\tlabel=\"SSH Key\"\n\t\t\t\t\t\t\thelp=\"SSH authorized public key in PEM format.\"\n\t\t\t\t\t\t\tplaceholder=\"Public key\"\n\t\t\t\t\t\t\trows={6}\n\t\t\t\t\t\t\tvalue={authority.key}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('key', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\thidden={authority.type !== 'ssh_certificate'}\n\t\t\t\t\t\t\tlabel=\"SSH Certificate\"\n\t\t\t\t\t\t\thelp=\"SSH certificate authority in PEM format.\"\n\t\t\t\t\t\t\tplaceholder=\"Certificate authority\"\n\t\t\t\t\t\t\trows={6}\n\t\t\t\t\t\t\tvalue={authority.certificate}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('certificate', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\t\thidden={authority.type !== 'ssh_certificate'}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tPrincipals\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Principals\"\n\t\t\t\t\t\t\t\tcontent=\"Principals that will be matched with SSH certificate authority principles. Principal are case-sensitive.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\thidden={authority.type !== 'ssh_certificate'}\n\t\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Add principal\"\n\t\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasOrganizations}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for authority, both the organaization and role must match. Select node authority to match node network roles.\"\n\t\t\t\t\t\t\tvalue={authority.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\t\tRoles\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\t\tcontent=\"Roles that will be matched with authorities. Roles are case-sensitive.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{networkRoles}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\t\tvalue={this.state.addNetworkRole}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddNetworkRole: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddNetworkRole}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.authority}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Balancer.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as BalancerTypes from '../types/BalancerTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport CompletionStore from '../stores/CompletionStore';\nimport BalancerDetailed from './BalancerDetailed';\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tcertificates: CertificateTypes.CertificatesRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tbalancer: BalancerTypes.BalancerRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '2px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Balancer extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet balancer = this.props.balancer;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<BalancerDetailed\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tcertificates={this.props.certificates}\n\t\t\t\t\tdatacenters={this.props.datacenters}\n\t\t\t\t\tbalancer={this.props.balancer}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet orgName = '';\n\t\tif (!MiscUtils.objectIdNil(balancer.organization)) {\n\t\t\tlet org = CompletionStore.organization(balancer.organization);\n\t\t\torgName = org ? org.name : balancer.organization;\n\t\t}\n\n\t\tlet datacenter = CompletionStore.datacenter(\n\t\t\tthis.props.balancer.datacenter);\n\t\tlet datacenterName = datacenter ? datacenter.name : null;\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{balancer.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\thidden={!orgName}\n\t\t\t\t\tclassName={'bp5-icon-standard bp5-text-muted bp5-icon-people'}\n\t\t\t\t/>\n\t\t\t\t{orgName}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\thidden={!datacenterName}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-cloud\"\n\t\t\t\t/>\n\t\t\t\t{datacenterName}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BalancerBackend.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as BalancerTypes from '../types/BalancerTypes';\n\ninterface Props {\n\tbackend: BalancerTypes.Backend;\n\tonChange: (state: BalancerTypes.Backend) => void;\n\tonRemove: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\thostname: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\thostnameBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '0 1 auto',\n\t\twidth: '52px',\n\t\tborderRadius: '0 3px 3px 0',\n\t} as React.CSSProperties,\n};\n\nexport default class BalancerBackend extends React.Component<Props, {}> {\n\tclone(): BalancerTypes.Backend {\n\t\treturn {\n\t\t\t...this.props.backend,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet backend = this.props.backend;\n\n\t\treturn <div className=\"bp5-control-group\" style={css.group}>\n\t\t\t<div className=\"bp5-select\" style={css.protocol}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={backend.protocol}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.protocol = evt.target.value;\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<option value=\"http\">HTTP</option>\n\t\t\t\t\t<option value=\"https\">HTTPS</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div style={css.hostnameBox}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.hostname}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Hostname\"\n\t\t\t\t\tvalue={backend.hostname || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.hostname = evt.target.value;\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<input\n\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\tstyle={css.port}\n\t\t\t\ttype=\"text\"\n\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\tspellCheck={false}\n\t\t\t\tplaceholder=\"Port\"\n\t\t\t\tvalue={backend.port}\n\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.port = parseInt(evt.target.value, 10);\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BalancerDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as BalancerTypes from '../types/BalancerTypes';\nimport * as BalancerActions from '../actions/BalancerActions';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport BalancerDomain from './BalancerDomain';\nimport BalancerBackend from './BalancerBackend';\nimport CompletionStore from '../stores/CompletionStore';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageSelectButton from \"./PageSelectButton\";\nimport PageSwitch from \"./PageSwitch\";\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tcertificates: CertificateTypes.CertificatesRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tbalancer: BalancerTypes.BalancerRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tbalancer: BalancerTypes.Balancer;\n\taddCert: string;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class BalancerDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tbalancer: null,\n\t\t\taddCert: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet balancer: any;\n\n\t\tif (this.state.changed) {\n\t\t\tbalancer = {\n\t\t\t\t...this.state.balancer,\n\t\t\t};\n\t\t} else {\n\t\t\tbalancer = {\n\t\t\t\t...this.props.balancer,\n\t\t\t};\n\t\t}\n\n\t\tbalancer[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonAddBackend = (): void => {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tif (this.state.changed) {\n\t\t\tbalancer = {\n\t\t\t\t...this.state.balancer,\n\t\t\t};\n\t\t} else {\n\t\t\tbalancer = {\n\t\t\t\t...this.props.balancer,\n\t\t\t};\n\t\t}\n\n\t\tlet backends = [\n\t\t\t...balancer.backends,\n\t\t\t{\n\t\t\t\tprotocol: 'http',\n\t\t\t\thostname: '',\n\t\t\t\tport: 80,\n\t\t\t},\n\t\t];\n\n\t\tbalancer.backends = backends;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonChangeBackend(i: number, state: BalancerTypes.Backend): void {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tif (this.state.changed) {\n\t\t\tbalancer = {\n\t\t\t\t...this.state.balancer,\n\t\t\t};\n\t\t} else {\n\t\t\tbalancer = {\n\t\t\t\t...this.props.balancer,\n\t\t\t};\n\t\t}\n\n\t\tlet backends = [\n\t\t\t...balancer.backends,\n\t\t];\n\n\t\tbackends[i] = state;\n\n\t\tbalancer.backends = backends;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonRemoveBackend(i: number): void {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tif (this.state.changed) {\n\t\t\tbalancer = {\n\t\t\t\t...this.state.balancer,\n\t\t\t};\n\t\t} else {\n\t\t\tbalancer = {\n\t\t\t\t...this.props.balancer,\n\t\t\t};\n\t\t}\n\n\t\tlet backends = [\n\t\t\t...balancer.backends,\n\t\t];\n\n\t\tbackends.splice(i, 1);\n\n\t\tbalancer.backends = backends;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonAddCert = (): void => {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tif (!this.state.addCert && !this.props.certificates.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tbalancer = {\n\t\t\t\t...this.state.balancer,\n\t\t\t};\n\t\t} else {\n\t\t\tbalancer = {\n\t\t\t\t...this.props.balancer,\n\t\t\t};\n\t\t}\n\n\t\tlet certId = this.state.addCert;\n\t\tif (!certId) {\n\t\t\tfor (let certificate of this.props.certificates) {\n\t\t\t\tif (certificate.organization != balancer.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tcertId = certificate.id;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tlet certificates = [\n\t\t\t...(balancer.certificates || []),\n\t\t];\n\n\t\tif (certificates.indexOf(certId) === -1) {\n\t\t\tcertificates.push(certId);\n\t\t}\n\n\t\tcertificates.sort();\n\n\t\tbalancer.certificates = certificates;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonRemoveCert = (certId: string): void => {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tif (this.state.changed) {\n\t\t\tbalancer = {\n\t\t\t\t...this.state.balancer,\n\t\t\t};\n\t\t} else {\n\t\t\tbalancer = {\n\t\t\t\t...this.props.balancer,\n\t\t\t};\n\t\t}\n\n\t\tlet certificates = [\n\t\t\t...(balancer.certificates || []),\n\t\t];\n\n\t\tlet i = certificates.indexOf(certId);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tcertificates.splice(i, 1);\n\n\t\tbalancer.certificates = certificates;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonAddDomain = (): void => {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tif (this.state.changed) {\n\t\t\tbalancer = {\n\t\t\t\t...this.state.balancer,\n\t\t\t};\n\t\t} else {\n\t\t\tbalancer = {\n\t\t\t\t...this.props.balancer,\n\t\t\t};\n\t\t}\n\n\t\tlet domains = [\n\t\t\t...balancer.domains,\n\t\t\t{},\n\t\t];\n\n\t\tbalancer.domains = domains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonChangeDomain(i: number, state: BalancerTypes.Domain): void {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tif (this.state.changed) {\n\t\t\tbalancer = {\n\t\t\t\t...this.state.balancer,\n\t\t\t};\n\t\t} else {\n\t\t\tbalancer = {\n\t\t\t\t...this.props.balancer,\n\t\t\t};\n\t\t}\n\n\t\tlet domains = [\n\t\t\t...balancer.domains,\n\t\t];\n\n\t\tdomains[i] = state;\n\n\t\tbalancer.domains = domains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonRemoveDomain(i: number): void {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tif (this.state.changed) {\n\t\t\tbalancer = {\n\t\t\t\t...this.state.balancer,\n\t\t\t};\n\t\t} else {\n\t\t\tbalancer = {\n\t\t\t\t...this.props.balancer,\n\t\t\t};\n\t\t}\n\n\t\tlet domains = [\n\t\t\t...balancer.domains,\n\t\t];\n\n\t\tdomains.splice(i, 1);\n\n\t\tbalancer.domains = domains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tBalancerActions.commit(this.state.balancer).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tbalancer: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tBalancerActions.remove(this.props.balancer.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet balancer: BalancerTypes.Balancer = this.state.balancer ||\n\t\t\tthis.props.balancer;\n\n\t\tlet hasOrganizations = false;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">\n\t\t\t\t\tSelect Organization\n\t\t\t\t</option>,\n\t\t\t);\n\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\thasOrganizations = true;\n\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet domains: JSX.Element[] = [];\n\t\tfor (let i = 0; i < balancer.domains.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tdomains.push(\n\t\t\t\t<BalancerDomain\n\t\t\t\t\tkey={index}\n\t\t\t\t\tdomain={balancer.domains[index]}\n\t\t\t\t\tonChange={(state: BalancerTypes.Domain): void => {\n\t\t\t\t\t\tthis.onChangeDomain(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveDomain(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet backends: JSX.Element[] = [];\n\t\tfor (let i = 0; i < balancer.backends.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tbackends.push(\n\t\t\t\t<BalancerBackend\n\t\t\t\t\tkey={index}\n\t\t\t\t\tbackend={balancer.backends[index]}\n\t\t\t\t\tonChange={(state: BalancerTypes.Backend): void => {\n\t\t\t\t\t\tthis.onChangeBackend(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveBackend(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet certificates: JSX.Element[] = [];\n\t\tfor (let certId of (balancer.certificates || [])) {\n\t\t\tlet cert = CompletionStore.certificate(certId);\n\t\t\tif (!cert) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcertificates.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={cert.id}\n\t\t\t\t>\n\t\t\t\t\t{cert.name}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveCert(cert.id);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet hasCertificates = false;\n\t\tlet certificatesSelect: JSX.Element[] = [];\n\t\tif (this.props.certificates) {\n\t\t\tfor (let certificate of this.props.certificates) {\n\t\t\t\tif (certificate.organization != balancer.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasCertificates = true;\n\n\t\t\t\tcertificatesSelect.push(\n\t\t\t\t\t<option key={certificate.id} value={certificate.id}>\n\t\t\t\t\t\t{certificate.name}\n\t\t\t\t\t</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasCertificates) {\n\t\t\tcertificatesSelect = [\n\t\t\t\t<option key=\"null\" value=\"\">\n\t\t\t\t\tNo Certificates\n\t\t\t\t</option>,\n\t\t\t];\n\t\t}\n\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters.length) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Datacenter</option>);\n\n\t\t\thasDatacenters = true;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\tlet requests = 0;\n\t\tlet retries = 0;\n\t\tlet websockets = 0;\n\t\tlet states: string[] = [];\n\t\tlet statesMap: {[index: string]: number} = {};\n\t\tlet online: string[] = [];\n\t\tlet unknownHigh: string[] = [];\n\t\tlet unknownMid: string[] = [];\n\t\tlet unknownLow: string[] = [];\n\t\tlet offline: string[] = [];\n\t\tlet backendsClasses: string[] = [];\n\n\t\tif (this.props.balancer.state && balancer.states) {\n\t\t\tfor (let key in balancer.states) {\n\t\t\t\tif (!balancer.states.hasOwnProperty(key)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tlet state = balancer.states[key];\n\n\t\t\t\trequests += state.requests || 0;\n\t\t\t\tretries += state.retries || 0;\n\t\t\t\twebsockets += state.websockets || 0;\n\n\t\t\t\tfor (let backend of state.offline) {\n\t\t\t\t\tlet curState = statesMap[backend];\n\t\t\t\t\tif (curState === undefined || curState > 1) {\n\t\t\t\t\t\tstatesMap[backend] = 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (let backend of state.unknown_low) {\n\t\t\t\t\tlet curState = statesMap[backend];\n\t\t\t\t\tif (curState === undefined || curState > 2) {\n\t\t\t\t\t\tstatesMap[backend] = 2;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (let backend of state.unknown_mid) {\n\t\t\t\t\tlet curState = statesMap[backend];\n\t\t\t\t\tif (curState === undefined || curState > 3) {\n\t\t\t\t\t\tstatesMap[backend] = 3;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (let backend of state.unknown_high) {\n\t\t\t\t\tlet curState = statesMap[backend];\n\t\t\t\t\tif (curState === undefined || curState > 4) {\n\t\t\t\t\t\tstatesMap[backend] = 4;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (let backend of state.online) {\n\t\t\t\t\tlet curState = statesMap[backend];\n\t\t\t\t\tif (curState === undefined) {\n\t\t\t\t\t\tstatesMap[backend] = 5;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (let backend in statesMap) {\n\t\t\t\tif (!statesMap.hasOwnProperty(backend)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tlet state = statesMap[backend];\n\n\t\t\t\tswitch (state) {\n\t\t\t\t\tcase 5:\n\t\t\t\t\t\tonline.push(backend);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\tunknownHigh.push(backend);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\tunknownMid.push(backend);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tunknownLow.push(backend);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\toffline.push(backend);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tonline.sort();\n\t\t\tfor (let backend of online) {\n\t\t\t\tstates.push(backend + ' - Online');\n\t\t\t\tbackendsClasses.push('bp5-text-intent-success');\n\t\t\t}\n\t\t\tunknownHigh.sort();\n\t\t\tfor (let backend of unknownHigh) {\n\t\t\t\tstates.push(backend + ' - Unknown High');\n\t\t\t\tbackendsClasses.push('bp5-text-intent-warning');\n\t\t\t}\n\t\t\tunknownMid.sort();\n\t\t\tfor (let backend of unknownMid) {\n\t\t\t\tstates.push(backend + ' - Unknown Mid');\n\t\t\t\tbackendsClasses.push('bp5-text-intent-warning');\n\t\t\t}\n\t\t\tunknownLow.sort();\n\t\t\tfor (let backend of unknownLow) {\n\t\t\t\tstates.push(backend + ' - Unknown Low');\n\t\t\t\tbackendsClasses.push('bp5-text-intent-warning');\n\t\t\t}\n\t\t\toffline.sort();\n\t\t\tfor (let backend of offline) {\n\t\t\t\tstates.push(backend + ' - Offline');\n\t\t\t\tbackendsClasses.push('bp5-text-intent-danger');\n\t\t\t}\n\t\t}\n\n\t\tif (!states.length) {\n\t\t\tstates = ['-'];\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={3}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n            <div>\n              <label\n                className=\"bp5-control bp5-checkbox bp5-checkbox\"\n                style={css.select}\n              >\n                <input\n                  type=\"checkbox\"\n                  checked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n                  onClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n                />\n                <span className=\"bp5-control-indicator\"/>\n              </label>\n            </div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Balancer\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this balancer\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[balancer.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of load balancer\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={balancer.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Load balancer comment.\"\n\t\t\t\t\t\tplaceholder=\"Load balancer comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={balancer.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Active\"\n\t\t\t\t\t\thelp=\"Enable or disable load balancer.\"\n\t\t\t\t\t\tchecked={balancer.state}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('state', !balancer.state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\thelp=\"Load balancer type\"\n\t\t\t\t\t\tvalue={balancer.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"http\">HTTP</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\thelp=\"Load balancer datacenter.\"\n\t\t\t\t\t\tvalue={balancer.datacenter}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('datacenter', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\tExternal Domains\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"External Domains\"\n\t\t\t\t\t\t\tcontent=\"When a request comes into a node the requests host will be used to match the request with the domain of a load balancer. Some internal services will be expecting a specific host such as a web server that serves mutliple websites that is also matching the requests host to one of the mutliple websites. If the internal service is expecting a different host set the host field, otherwise leave it blank. Load balancers that are associated with the same datacenter should not also have the same domains.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t{domains}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tstyle={css.itemsAdd}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={this.onAddDomain}\n\t\t\t\t\t>\n\t\t\t\t\t\tAdd Domain\n\t\t\t\t\t</button>\n\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\tInternal Backends\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Internal Backends\"\n\t\t\t\t\t\t\tcontent=\"After a node receives a request it will be forwarded to the internal servers and the response will be sent back to the user. Multiple internal servers can be added to balance the requests between the servers. Hostname must be an IP address of an existing instance.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t{backends}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tstyle={css.itemsAdd}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={this.onAddBackend}\n\t\t\t\t\t>\n\t\t\t\t\t\tAdd Backend\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.balancer.id || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Requests',\n\t\t\t\t\t\t\t\tvalue: requests + '/min',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Retries',\n\t\t\t\t\t\t\t\tvalue: retries + '/min',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'WebSockets',\n\t\t\t\t\t\t\t\tvalue: websockets,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Backends',\n\t\t\t\t\t\t\t\tvalue: states,\n\t\t\t\t\t\t\t\tvalueClasses: backendsClasses,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"WebSockets\"\n\t\t\t\t\t\thelp=\"Enable or disable WebSocket support on balancer.\"\n\t\t\t\t\t\tchecked={balancer.websockets}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('websockets', !balancer.websockets);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasOrganizations}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\thelp=\"Organization for balancer, both the organaization and role must match. Select balancer balancer to match balancer network roles.\"\n\t\t\t\t\t\tvalue={balancer.organization}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t>\n\t\t\t\t\t\tCertificates\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Certificates\"\n\t\t\t\t\t\t\tcontent=\"The certificates to use for this load balancer. The certificates must be valid for all the domains that this load balancer provides access to.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{certificates}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\tlabel=\"Add Certificate\"\n\t\t\t\t\t\tvalue={this.state.addCert}\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasCertificates}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddCert: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddCert}\n\t\t\t\t\t>\n\t\t\t\t\t\t{certificatesSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Health Check Path\"\n\t\t\t\t\t\thelp=\"Path to check status of backend servers. Path must return 200-299 status code.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter path\"\n\t\t\t\t\t\tvalue={balancer.check_path}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('check_path', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.balancer && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tbalancer: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BalancerDomain.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as BalancerTypes from '../types/BalancerTypes';\n\ninterface Props {\n\tdomain: BalancerTypes.Domain;\n\tonChange: (state: BalancerTypes.Domain) => void;\n\tonRemove: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tdomain: {\n\t\twidth: '100%',\n\t\tborderRadius: '0 3px 3px 0',\n\t} as React.CSSProperties,\n\tdomainBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class BalancerDomain extends React.Component<Props, {}> {\n\tclone(): BalancerTypes.Domain {\n\t\treturn {\n\t\t\t...this.props.domain,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet domain = this.props.domain;\n\n\t\treturn <div className=\"bp5-control-group\" style={css.group}>\n\t\t\t<div style={css.domainBox}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.domain}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Domain\"\n\t\t\t\t\tvalue={domain.domain || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.domain = evt.target.value;\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div style={css.domainBox}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.domain}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Host\"\n\t\t\t\t\tvalue={domain.host || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.host = evt.target.value;\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BalancerNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as BalancerTypes from '../types/BalancerTypes';\nimport * as BalancerActions from '../actions/BalancerActions';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport BalancerDomain from './BalancerDomain';\nimport BalancerBackend from './BalancerBackend';\nimport CompletionStore from '../stores/CompletionStore';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageCreate from './PageCreate';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageSelectButton from \"./PageSelectButton\";\nimport PageSwitch from \"./PageSwitch\";\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tcertificates: CertificateTypes.CertificatesRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tbalancer: BalancerTypes.Balancer;\n\taddCert: string;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class BalancerNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddCert: null,\n\t\t\tbalancer: {\n\t\t\t\tname: 'new-balancer',\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet balancer: any = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tbalancer[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonAddBackend = (): void => {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tbalancer = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tlet backends = [\n\t\t\t...(balancer.backends || []),\n\t\t\t{\n\t\t\t\tprotocol: 'http',\n\t\t\t\thostname: '',\n\t\t\t\tport: 80,\n\t\t\t},\n\t\t];\n\n\t\tbalancer.backends = backends;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonChangeBackend(i: number, state: BalancerTypes.Backend): void {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tbalancer = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tlet backends = [\n\t\t\t...(balancer.backends || []),\n\t\t];\n\n\t\tbackends[i] = state;\n\n\t\tbalancer.backends = backends;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonRemoveBackend(i: number): void {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tbalancer = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tlet backends = [\n\t\t\t...(balancer.backends || []),\n\t\t];\n\n\t\tbackends.splice(i, 1);\n\n\t\tbalancer.backends = backends;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonAddCert = (): void => {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tbalancer = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tif (!this.state.addCert && !this.props.certificates.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet certId = this.state.addCert;\n\t\tif (!certId) {\n\t\t\tfor (let certificate of this.props.certificates) {\n\t\t\t\tif (certificate.organization != balancer.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tcertId = certificate.id;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tlet certificates = [\n\t\t\t...(balancer.certificates || []),\n\t\t];\n\n\t\tif (certificates.indexOf(certId) === -1) {\n\t\t\tcertificates.push(certId);\n\t\t}\n\n\t\tcertificates.sort();\n\n\t\tbalancer.certificates = certificates;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonRemoveCert = (certId: string): void => {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tbalancer = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tlet certificates = [\n\t\t\t...(balancer.certificates || []),\n\t\t];\n\n\t\tlet i = certificates.indexOf(certId);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tcertificates.splice(i, 1);\n\n\t\tbalancer.certificates = certificates;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonAddDomain = (): void => {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tbalancer = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tlet domains = [\n\t\t\t...(balancer.domains || []),\n\t\t\t{},\n\t\t];\n\n\t\tbalancer.domains = domains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonChangeDomain(i: number, state: BalancerTypes.Domain): void {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tbalancer = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tlet domains = [\n\t\t\t...(balancer.domains || []),\n\t\t];\n\n\t\tdomains[i] = state;\n\n\t\tbalancer.domains = domains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonRemoveDomain(i: number): void {\n\t\tlet balancer: BalancerTypes.Balancer;\n\n\t\tbalancer = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tlet domains = [\n\t\t\t...(balancer.domains || []),\n\t\t];\n\n\t\tdomains.splice(i, 1);\n\n\t\tbalancer.domains = domains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tbalancer: balancer,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet balancer: any = {\n\t\t\t...this.state.balancer,\n\t\t};\n\n\t\tBalancerActions.create(balancer).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Balancer created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet balancer: BalancerTypes.Balancer = this.state.balancer;\n\n\t\tlet hasOrganizations = false;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">\n\t\t\t\t\tSelect Organization\n\t\t\t\t</option>,\n\t\t\t);\n\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\thasOrganizations = true;\n\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet domains: JSX.Element[] = [];\n\t\t(balancer.domains || []).forEach((domn, index) => {\n\t\t\tdomains.push(\n\t\t\t\t<BalancerDomain\n\t\t\t\t\tkey={index}\n\t\t\t\t\tdomain={domn}\n\t\t\t\t\tonChange={(state: BalancerTypes.Domain): void => {\n\t\t\t\t\t\tthis.onChangeDomain(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveDomain(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t})\n\n\t\tlet backends: JSX.Element[] = [];\n\t\t(balancer.backends || []).forEach((backend, index) => {\n\t\t\tbackends.push(\n\t\t\t\t<BalancerBackend\n\t\t\t\t\tkey={index}\n\t\t\t\t\tbackend={backend}\n\t\t\t\t\tonChange={(state: BalancerTypes.Backend): void => {\n\t\t\t\t\t\tthis.onChangeBackend(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveBackend(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t})\n\n\t\tlet certificates: JSX.Element[] = [];\n\t\tfor (let certId of (balancer.certificates || [])) {\n\t\t\tlet cert = CompletionStore.certificate(certId);\n\t\t\tif (!cert) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcertificates.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={cert.id}\n\t\t\t\t>\n\t\t\t\t\t{cert.name}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveCert(cert.id);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet hasCertificates = false;\n\t\tlet certificatesSelect: JSX.Element[] = [];\n\t\tif (this.props.certificates) {\n\t\t\tfor (let certificate of this.props.certificates) {\n\t\t\t\tif (certificate.organization != balancer.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasCertificates = true;\n\n\t\t\t\tcertificatesSelect.push(\n\t\t\t\t\t<option key={certificate.id} value={certificate.id}>\n\t\t\t\t\t\t{certificate.name}\n\t\t\t\t\t</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasCertificates) {\n\t\t\tcertificatesSelect = [\n\t\t\t\t<option key=\"null\" value=\"\">\n\t\t\t\t\tNo Certificates\n\t\t\t\t</option>,\n\t\t\t];\n\t\t}\n\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters.length) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Datacenter</option>);\n\n\t\t\thasDatacenters = true;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={3}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of load balancer\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={balancer.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Load balancer comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Load balancer comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={balancer.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Active\"\n\t\t\t\t\t\t\thelp=\"Enable or disable load balancer.\"\n\t\t\t\t\t\t\tchecked={balancer.state}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('state', !balancer.state);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\t\thelp=\"Load balancer type\"\n\t\t\t\t\t\t\tvalue={balancer.type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"http\">HTTP</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\t\thelp=\"Load balancer datacenter.\"\n\t\t\t\t\t\t\tvalue={balancer.datacenter}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('datacenter', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\t\tExternal Domains\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"External Domains\"\n\t\t\t\t\t\t\t\tcontent=\"When a request comes into a node the requests host will be used to match the request with the domain of a load balancer. Some internal services will be expecting a specific host such as a web server that serves mutliple websites that is also matching the requests host to one of the mutliple websites. If the internal service is expecting a different host set the host field, otherwise leave it blank. Load balancers that are associated with the same datacenter should not also have the same domains.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t{domains}\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.itemsAdd}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={this.onAddDomain}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tAdd Domain\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\t\tInternal Backends\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Internal Backends\"\n\t\t\t\t\t\t\t\tcontent=\"After a node receives a request it will be forwarded to the internal servers and the response will be sent back to the user. Multiple internal servers can be added to balance the requests between the servers. Hostname must be an IP address of an existing instance.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t{backends}\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.itemsAdd}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={this.onAddBackend}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tAdd Backend\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"WebSockets\"\n\t\t\t\t\t\t\thelp=\"Enable or disable WebSocket support on balancer.\"\n\t\t\t\t\t\t\tchecked={balancer.websockets}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('websockets', !balancer.websockets);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasOrganizations}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for balancer, both the organaization and role must match. Select balancer balancer to match balancer network roles.\"\n\t\t\t\t\t\t\tvalue={balancer.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCertificates\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Certificates\"\n\t\t\t\t\t\t\t\tcontent=\"The certificates to use for this load balancer. The certificates must be valid for all the domains that this load balancer provides access to.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{certificates}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\t\tlabel=\"Add Certificate\"\n\t\t\t\t\t\t\tvalue={this.state.addCert}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasCertificates}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddCert: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddCert}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{certificatesSelect}\n\t\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Health Check Path\"\n\t\t\t\t\t\t\thelp=\"Path to check status of backend servers. Path must return 200-299 status code.\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter path\"\n\t\t\t\t\t\t\tvalue={balancer.check_path}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('check_path', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.balancer}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Balancers.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as BalancerTypes from '../types/BalancerTypes';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport BalancersStore from '../stores/BalancersStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as BalancerActions from '../actions/BalancerActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Balancer from './Balancer';\nimport BalancerNew from './BalancerNew';\nimport BalancersPage from './BalancersPage';\nimport BalancersFilter from './BalancersFilter';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\nimport CertificatesStore from \"../stores/CertificatesStore\";\nimport * as CertificateActions from \"../actions/CertificateActions\";\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tbalancers: BalancerTypes.BalancersRo;\n\tfilter: BalancerTypes.Filter;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tcertificates: CertificateTypes.CertificatesRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Balancers extends React.Component<{}, State> {\n\tsync: MiscUtils.SyncInterval;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tbalancers: BalancersStore.balancers,\n\t\t\tfilter: BalancersStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tcertificates: CertificatesStore.certificates,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tBalancersStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tBalancerActions.sync();\n\t\tCompletionActions.sync();\n\n\t\tthis.sync = new MiscUtils.SyncInterval(\n\t\t\t() => BalancerActions.sync(true),\n\t\t\t5000,\n\t\t)\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tBalancersStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\n\t\tthis.sync?.stop()\n\t}\n\n\tonChange = (): void => {\n\t\tlet balancers = BalancersStore.balancers;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tbalancers.forEach((balancer: BalancerTypes.Balancer): void => {\n\t\t\tif (curSelected[balancer.id]) {\n\t\t\t\tselected[balancer.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[balancer.id]) {\n\t\t\t\topened[balancer.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tbalancers: balancers,\n\t\t\tfilter: BalancersStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tcertificates: CompletionStore.certificates,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tBalancerActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet balancersDom: JSX.Element[] = [];\n\n\t\tthis.state.balancers.forEach((\n\t\t\t\tbalancer: BalancerTypes.BalancerRo): void => {\n\t\t\tbalancersDom.push(<Balancer\n\t\t\t\tkey={balancer.id}\n\t\t\t\tbalancer={balancer}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tcertificates={this.state.certificates}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tselected={!!this.state.selected[balancer.id]}\n\t\t\t\topen={!!this.state.opened[balancer.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet balancers = this.state.balancers;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < balancers.length; i++) {\n\t\t\t\t\t\t\tlet usr = balancers[i];\n\n\t\t\t\t\t\t\tif (usr.id === balancer.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[balancers[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: balancer.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[balancer.id]) {\n\t\t\t\t\t\tdelete selected[balancer.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[balancer.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: balancer.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[balancer.id]) {\n\t\t\t\t\t\tdelete opened[balancer.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[balancer.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = BalancersStore.balancer(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newBalancerDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewBalancerDom = <BalancerNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tcertificates={this.state.certificates}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Load Balancers</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tBalancerActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tBalancerActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected balancers\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<BalancersFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tBalancerActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newBalancerDom}\n\t\t\t\t\t{balancersDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={3} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!balancersDom.length}\n\t\t\t\ticonClass=\"bp5-icon-random\"\n\t\t\t\ttitle=\"No balancers\"\n\t\t\t\tdescription=\"Add a new balancer to get started.\"\n\t\t\t/>\n\t\t\t<BalancersPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BalancersFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as BalancerTypes from '../types/BalancerTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: BalancerTypes.Filter;\n\tonFilter: (filter: BalancerTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class BalancersFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Balancer ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BalancersPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport BalancersStore from '../stores/BalancersStore';\nimport * as BalancerActions from '../actions/BalancerActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class BalancersPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: BalancersStore.page,\n\t\t\tpageCount: BalancersStore.pageCount,\n\t\t\tpages: BalancersStore.pages,\n\t\t\tcount: BalancersStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tBalancersStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tBalancersStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: BalancersStore.page,\n\t\t\tpageCount: BalancersStore.pageCount,\n\t\t\tpages: BalancersStore.pages,\n\t\t\tcount: BalancersStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBalancerActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBalancerActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBalancerActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBalancerActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBalancerActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Block.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as BlockTypes from '../types/BlockTypes';\nimport BlockDetailed from './BlockDetailed';\n\ninterface Props {\n\tblock: BlockTypes.BlockRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Block extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet block = this.props.block;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<BlockDetailed\n\t\t\t\t\tblock={this.props.block}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{block.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-ip-address\"\n\t\t\t\t/>\n\t\t\t\t{block.gateway || block.gateway6}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BlockDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as BlockTypes from '../types/BlockTypes';\nimport * as BlockActions from '../actions/BlockActions';\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport PageInputButton from './PageInputButton';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport * as Alert from \"../Alert\";\nimport Help from './Help';\nimport PageSelect from \"./PageSelect\";\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tblock: BlockTypes.BlockRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddSubnet: string,\n\taddSubnet6: string,\n\taddExclude: string,\n\tblock: BlockTypes.Block;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tcontrolButton: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class BlockDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddSubnet: '',\n\t\t\taddSubnet6: '',\n\t\t\taddExclude: '',\n\t\t\tblock: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet block: any;\n\n\t\tif (this.state.changed) {\n\t\t\tblock = {\n\t\t\t\t...this.state.block,\n\t\t\t};\n\t\t} else {\n\t\t\tblock = {\n\t\t\t\t...this.props.block,\n\t\t\t};\n\t\t}\n\n\t\tblock[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tBlockActions.commit(this.state.block).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tblock: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonSync = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tBlockActions.commit(this.props.block).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tAlert.success('Block sync started');\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tBlockActions.remove(this.props.block.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddSubnet = (): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tif (!this.state.addSubnet) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tblock = {\n\t\t\t\t...this.state.block,\n\t\t\t};\n\t\t} else {\n\t\t\tblock = {\n\t\t\t\t...this.props.block,\n\t\t\t};\n\t\t}\n\n\t\tlet subnets = [\n\t\t\t...(block.subnets || []),\n\t\t];\n\n\t\tlet addSubnet = this.state.addSubnet.trim();\n\t\tif (subnets.indexOf(addSubnet) === -1) {\n\t\t\tsubnets.push(addSubnet);\n\t\t}\n\n\t\tsubnets.sort();\n\t\tblock.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddSubnet: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonRemoveSubnet = (subnet: string): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tif (this.state.changed) {\n\t\t\tblock = {\n\t\t\t\t...this.state.block,\n\t\t\t};\n\t\t} else {\n\t\t\tblock = {\n\t\t\t\t...this.props.block,\n\t\t\t};\n\t\t}\n\n\t\tlet subnets = [\n\t\t\t...(block.subnets || []),\n\t\t];\n\n\t\tlet i = subnets.indexOf(subnet);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tsubnets.splice(i, 1);\n\t\tblock.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonAddSubnet6 = (): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tif (!this.state.addSubnet6) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tblock = {\n\t\t\t\t...this.state.block,\n\t\t\t};\n\t\t} else {\n\t\t\tblock = {\n\t\t\t\t...this.props.block,\n\t\t\t};\n\t\t}\n\n\t\tlet subnets6 = [\n\t\t\t...(block.subnets6 || []),\n\t\t];\n\n\t\tlet addSubnet6 = this.state.addSubnet6.trim();\n\t\tif (subnets6.indexOf(addSubnet6) === -1) {\n\t\t\tsubnets6.push(addSubnet6);\n\t\t}\n\n\t\tsubnets6.sort();\n\t\tblock.subnets6 = subnets6;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddSubnet6: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonRemoveSubnet6 = (subnet: string): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tif (this.state.changed) {\n\t\t\tblock = {\n\t\t\t\t...this.state.block,\n\t\t\t};\n\t\t} else {\n\t\t\tblock = {\n\t\t\t\t...this.props.block,\n\t\t\t};\n\t\t}\n\n\t\tlet subnets6 = [\n\t\t\t...(block.subnets6 || []),\n\t\t];\n\n\t\tlet i = subnets6.indexOf(subnet);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tsubnets6.splice(i, 1);\n\t\tblock.subnets6 = subnets6;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonAddExclude = (): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tif (!this.state.addExclude) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tblock = {\n\t\t\t\t...this.state.block,\n\t\t\t};\n\t\t} else {\n\t\t\tblock = {\n\t\t\t\t...this.props.block,\n\t\t\t};\n\t\t}\n\n\t\tlet excludes = [\n\t\t\t...(block.excludes || []),\n\t\t];\n\n\t\tlet addExclude = this.state.addExclude.trim();\n\t\tif (excludes.indexOf(addExclude) === -1) {\n\t\t\texcludes.push(addExclude);\n\t\t}\n\n\t\texcludes.sort();\n\t\tblock.excludes = excludes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddExclude: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonRemoveExclude = (exclude: string): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tif (this.state.changed) {\n\t\t\tblock = {\n\t\t\t\t...this.state.block,\n\t\t\t};\n\t\t} else {\n\t\t\tblock = {\n\t\t\t\t...this.props.block,\n\t\t\t};\n\t\t}\n\n\t\tlet excludes = [\n\t\t\t...(block.excludes || []),\n\t\t];\n\n\t\tlet i = excludes.indexOf(exclude);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\texcludes.splice(i, 1);\n\t\tblock.excludes = excludes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet block: BlockTypes.Block = this.state.block ||\n\t\t\tthis.props.block;\n\n\t\tlet subnets: JSX.Element[] = [];\n\t\tfor (let subnet of (block.subnets || [])) {\n\t\t\tsubnets.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={subnet}\n\t\t\t\t>\n\t\t\t\t\t{subnet}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveSubnet(subnet);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet subnets6: JSX.Element[] = [];\n\t\tfor (let subnet of (block.subnets6 || [])) {\n\t\t\tsubnets6.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={subnet}\n\t\t\t\t>\n\t\t\t\t\t{subnet}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveSubnet6(subnet);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet excludes: JSX.Element[] = [];\n\t\tfor (let exclude of (block.excludes || [])) {\n\t\t\texcludes.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={exclude}\n\t\t\t\t>\n\t\t\t\t\t{exclude}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveExclude(exclude);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"block\" id={this.props.block.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Block\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this block\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[block.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of IP block\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={block.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Block comment.\"\n\t\t\t\t\t\tplaceholder=\"Block comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={block.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={true}\n\t\t\t\t\t\tlabel=\"Network Mode\"\n\t\t\t\t\t\thelp=\"Network mode IP block.\"\n\t\t\t\t\t\tvalue={block.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"ipv4\">IPv4</option>\n\t\t\t\t\t\t<option value=\"ipv6\">IPv6</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"VLAN\"\n\t\t\t\t\t\thelp=\"VLAN of IP block, used for when provider supplies IP addresses on a VLAN.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter VLAN\"\n\t\t\t\t\t\tvalue={block.vlan || \"\"}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('vlan', parseInt(val, 10));\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t\tlabel=\"Netmask\"\n\t\t\t\t\t\thelp=\"Netmask of IP block\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter netmask\"\n\t\t\t\t\t\tvalue={block.netmask}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('netmask', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t>\n\t\t\t\t\t\tIP Addresses\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"IP Addresses\"\n\t\t\t\t\t\t\tcontent=\"IP addresses that are available for instances.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{subnets}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add addresses\"\n\t\t\t\t\t\tvalue={this.state.addSubnet}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddSubnet: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddSubnet}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={block.type !== 'ipv6'}\n\t\t\t\t\t>\n\t\t\t\t\t\tIPv6 Addresses\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"IPv6 Addresses\"\n\t\t\t\t\t\t\tcontent=\"IPv6 addresses that are available for instances.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{subnets6}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type !== 'ipv6'}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add addresses\"\n\t\t\t\t\t\tvalue={this.state.addSubnet6}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddSubnet6: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddSubnet6}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.block.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Available',\n\t\t\t\t\t\t\t\tvalue: this.props.block.available || '-',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Capacity',\n\t\t\t\t\t\t\t\tvalue: this.props.block.capacity || '-',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t\tlabel=\"Gateway\"\n\t\t\t\t\t\thelp=\"Gateway address of IP block\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter gateway\"\n\t\t\t\t\t\tvalue={block.gateway}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('gateway', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type !== 'ipv6'}\n\t\t\t\t\t\tlabel=\"IPv6 Gateway\"\n\t\t\t\t\t\thelp=\"Gateway address of IPv6 block. Can be left blank to get gateway from router advertisement.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter IPv6 gateway\"\n\t\t\t\t\t\tvalue={block.gateway6}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('gateway6', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t>\n\t\t\t\t\t\tIP Excludes\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"IP Excludes\"\n\t\t\t\t\t\t\tcontent=\"IP addresses that are excluded from block. Add host or other reserved addresses here.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{excludes}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add exclude\"\n\t\t\t\t\t\tvalue={this.state.addExclude}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddExclude: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddExclude}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.block}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tblock: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BlockNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as BlockTypes from '../types/BlockTypes';\nimport * as BlockActions from '../actions/BlockActions';\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageCreate from './PageCreate';\nimport PageInputButton from './PageInputButton';\nimport ConfirmButton from './ConfirmButton';\nimport * as Alert from \"../Alert\";\nimport Help from './Help';\nimport PageSelect from \"./PageSelect\";\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddSubnet: string,\n\taddSubnet6: string,\n\taddExclude: string,\n\tblock: BlockTypes.Block;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tcontrolButton: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class BlockDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddSubnet: '',\n\t\t\taddSubnet6: '',\n\t\t\taddExclude: '',\n\t\t\tblock: {\n\t\t\t\tname: 'new-block',\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet block: any = {\n\t\t\t...this.state.block,\n\t\t};\n\n\t\tblock[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet block: any = {\n\t\t\t...this.state.block,\n\t\t};\n\n\t\tBlockActions.create(block).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Block created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddSubnet = (): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tblock = {\n\t\t\t...this.state.block,\n\t\t};\n\n\t\tif (!this.state.addSubnet) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet subnets = [\n\t\t\t...(block.subnets || []),\n\t\t];\n\n\t\tlet addSubnet = this.state.addSubnet.trim();\n\t\tif (subnets.indexOf(addSubnet) === -1) {\n\t\t\tsubnets.push(addSubnet);\n\t\t}\n\n\t\tsubnets.sort();\n\t\tblock.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddSubnet: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonRemoveSubnet = (subnet: string): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tblock = {\n\t\t\t...this.state.block,\n\t\t};\n\n\t\tlet subnets = [\n\t\t\t...(block.subnets || []),\n\t\t];\n\n\t\tlet i = subnets.indexOf(subnet);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tsubnets.splice(i, 1);\n\t\tblock.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonAddSubnet6 = (): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tblock = {\n\t\t\t...this.state.block,\n\t\t};\n\n\t\tif (!this.state.addSubnet6) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet subnets6 = [\n\t\t\t...(block.subnets6 || []),\n\t\t];\n\n\t\tlet addSubnet6 = this.state.addSubnet6.trim();\n\t\tif (subnets6.indexOf(addSubnet6) === -1) {\n\t\t\tsubnets6.push(addSubnet6);\n\t\t}\n\n\t\tsubnets6.sort();\n\t\tblock.subnets6 = subnets6;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddSubnet6: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonRemoveSubnet6 = (subnet: string): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tblock = {\n\t\t\t...this.state.block,\n\t\t};\n\n\t\tlet subnets6 = [\n\t\t\t...(block.subnets6 || []),\n\t\t];\n\n\t\tlet i = subnets6.indexOf(subnet);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tsubnets6.splice(i, 1);\n\t\tblock.subnets6 = subnets6;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonAddExclude = (): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tif (!this.state.addExclude) {\n\t\t\treturn;\n\t\t}\n\n\t\tblock = {\n\t\t\t...this.state.block,\n\t\t};\n\n\t\tlet excludes = [\n\t\t\t...(block.excludes || []),\n\t\t];\n\n\t\tlet addExclude = this.state.addExclude.trim();\n\t\tif (excludes.indexOf(addExclude) === -1) {\n\t\t\texcludes.push(addExclude);\n\t\t}\n\n\t\texcludes.sort();\n\t\tblock.excludes = excludes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddExclude: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\tonRemoveExclude = (exclude: string): void => {\n\t\tlet block: BlockTypes.Block;\n\n\t\tblock = {\n\t\t\t...this.state.block,\n\t\t};\n\n\t\tlet excludes = [\n\t\t\t...(block.excludes || []),\n\t\t];\n\n\t\tlet i = excludes.indexOf(exclude);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\texcludes.splice(i, 1);\n\t\tblock.excludes = excludes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tblock: block,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet block: BlockTypes.Block = this.state.block;\n\n\t\tlet subnets: JSX.Element[] = [];\n\t\tfor (let subnet of (block.subnets || [])) {\n\t\t\tsubnets.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={subnet}\n\t\t\t\t>\n\t\t\t\t\t{subnet}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveSubnet(subnet);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet subnets6: JSX.Element[] = [];\n\t\tfor (let subnet of (block.subnets6 || [])) {\n\t\t\tsubnets6.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={subnet}\n\t\t\t\t>\n\t\t\t\t\t{subnet}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveSubnet6(subnet);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet excludes: JSX.Element[] = [];\n\t\tfor (let exclude of (block.excludes || [])) {\n\t\t\texcludes.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={exclude}\n\t\t\t\t>\n\t\t\t\t\t{exclude}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveExclude(exclude);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t<td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of IP block\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={block.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Block comment.\"\n\t\t\t\t\t\tplaceholder=\"Block comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={block.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Network Mode\"\n\t\t\t\t\t\thelp=\"Network mode IP block.\"\n\t\t\t\t\t\tvalue={block.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"ipv4\">IPv4</option>\n\t\t\t\t\t\t<option value=\"ipv6\">IPv6</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"VLAN\"\n\t\t\t\t\t\thelp=\"VLAN of IP block, used for when provider supplies IP addresses on a VLAN.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter VLAN\"\n\t\t\t\t\t\tvalue={block.vlan || \"\"}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('vlan', parseInt(val, 10));\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t\tlabel=\"Netmask\"\n\t\t\t\t\t\thelp=\"Netmask of IP block\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter netmask\"\n\t\t\t\t\t\tvalue={block.netmask}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('netmask', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t>\n\t\t\t\t\t\tIP Addresses\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"IP Addresses\"\n\t\t\t\t\t\t\tcontent=\"IP addresses that are available for instances.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{subnets}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add addresses\"\n\t\t\t\t\t\tvalue={this.state.addSubnet}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddSubnet: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddSubnet}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={block.type !== 'ipv6'}\n\t\t\t\t\t>\n\t\t\t\t\t\tIPv6 Addresses\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"IPv6 Addresses\"\n\t\t\t\t\t\t\tcontent=\"IPv6 addresses that are available for instances.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{subnets6}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type !== 'ipv6'}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add addresses\"\n\t\t\t\t\t\tvalue={this.state.addSubnet6}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddSubnet6: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddSubnet6}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t\tlabel=\"Gateway\"\n\t\t\t\t\t\thelp=\"Gateway address of IP block\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter gateway\"\n\t\t\t\t\t\tvalue={block.gateway}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('gateway', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type !== 'ipv6'}\n\t\t\t\t\t\tlabel=\"IPv6 Gateway\"\n\t\t\t\t\t\thelp=\"Gateway address of IPv6 block. Can be left blank to get gateway from router advertisement.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter IPv6 gateway\"\n\t\t\t\t\t\tvalue={block.gateway6}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('gateway6', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t>\n\t\t\t\t\t\tIP Excludes\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"IP Excludes\"\n\t\t\t\t\t\t\tcontent=\"IP addresses that are excluded from block. Add host or other reserved addresses here.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{excludes}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={block.type === 'ipv6'}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add exclude\"\n\t\t\t\t\t\tvalue={this.state.addExclude}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddExclude: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddExclude}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageCreate\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.block}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tclosed={this.state.closed}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\tonCreate={this.onCreate}\n\t\t\t/>\n\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Blocks.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as BlockTypes from '../types/BlockTypes';\nimport BlocksStore from '../stores/BlocksStore';\nimport * as BlockActions from '../actions/BlockActions';\nimport Block from './Block';\nimport BlockNew from './BlockNew';\nimport BlocksFilter from './BlocksFilter';\nimport BlocksPage from './BlocksPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tblocks: BlockTypes.BlocksRo;\n\tfilter: BlockTypes.Filter;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Blocks extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tblocks: BlocksStore.blocks,\n\t\t\tfilter: BlocksStore.filter,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tBlocksStore.addChangeListener(this.onChange);\n\t\tBlockActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tBlocksStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet blocks = BlocksStore.blocks;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tblocks.forEach((block: BlockTypes.Block): void => {\n\t\t\tif (curSelected[block.id]) {\n\t\t\t\tselected[block.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[block.id]) {\n\t\t\t\topened[block.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tblocks: blocks,\n\t\t\tfilter: BlocksStore.filter,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tBlockActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet blocksDom: JSX.Element[] = [];\n\n\t\tthis.state.blocks.forEach((\n\t\t\t\tblock: BlockTypes.BlockRo): void => {\n\t\t\tblocksDom.push(<Block\n\t\t\t\tkey={block.id}\n\t\t\t\tblock={block}\n\t\t\t\tselected={!!this.state.selected[block.id]}\n\t\t\t\topen={!!this.state.opened[block.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet blocks = this.state.blocks;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < blocks.length; i++) {\n\t\t\t\t\t\t\tlet usr = blocks[i];\n\n\t\t\t\t\t\t\tif (usr.id === block.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[blocks[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: block.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[block.id]) {\n\t\t\t\t\t\tdelete selected[block.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[block.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: block.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[block.id]) {\n\t\t\t\t\t\tdelete opened[block.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[block.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = BlocksStore.block(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newBlockDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewBlockDom = <BlockNew\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Blocks</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tBlockActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tBlockActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected blocks\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<BlocksFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tBlockActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newBlockDom}\n\t\t\t\t\t{blocksDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!blocksDom.length}\n\t\t\t\ticonClass=\"bp5-icon-ip-address\"\n\t\t\t\ttitle=\"No blocks\"\n\t\t\t\tdescription=\"Add a new block to get started.\"\n\t\t\t/>\n\t\t\t<BlocksPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BlocksFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as BlockTypes from '../types/BlockTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: BlockTypes.Filter;\n\tonFilter: (filter: BlockTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class BlocksFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Block ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/BlocksPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport BlocksStore from '../stores/BlocksStore';\nimport * as BlockActions from '../actions/BlockActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class BlocksPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: BlocksStore.page,\n\t\t\tpageCount: BlocksStore.pageCount,\n\t\t\tpages: BlocksStore.pages,\n\t\t\tcount: BlocksStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tBlocksStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tBlocksStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: BlocksStore.page,\n\t\t\tpageCount: BlocksStore.pageCount,\n\t\t\tpages: BlocksStore.pages,\n\t\t\tcount: BlocksStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBlockActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBlockActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBlockActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBlockActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tBlockActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Certificate.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as SecretTypes from '../types/SecretTypes';\nimport * as CertificateActions from '../actions/CertificateActions';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport CertificateDetailed from './CertificateDetailed';\nimport OrganizationsStore from \"../stores/OrganizationsStore\";\nimport CertificateDomain from './CertificateDomain';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageTextArea from './PageTextArea';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\tcertificate: CertificateTypes.CertificateRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Certificate extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet certificate = this.props.certificate;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<CertificateDetailed\n\t\t\t\t\tcertificate={this.props.certificate}\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tsecrets={this.props.secrets}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{certificate.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-map-marker\"\n\t\t\t\t/>\n\t\t\t\t{certificate.info?.dns_names?.join(\", \") || \"-\"}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/CertificateDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as SecretTypes from '../types/SecretTypes';\nimport * as CertificateActions from '../actions/CertificateActions';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport CertificateDomain from './CertificateDomain';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageTextArea from './PageTextArea';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport Help from './Help';\nimport * as Alert from \"../Alert\";\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\tcertificate: CertificateTypes.CertificateRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tcertificate: CertificateTypes.Certificate;\n\taddDomain: string;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tdomain: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tcontrolButton: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class CertificateDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tcertificate: null,\n\t\t\taddDomain: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet certificate: any;\n\n\t\tif (this.state.changed) {\n\t\t\tcertificate = {\n\t\t\t\t...this.state.certificate,\n\t\t\t};\n\t\t} else {\n\t\t\tcertificate = {\n\t\t\t\t...this.props.certificate,\n\t\t\t};\n\t\t}\n\n\t\tcertificate[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tcertificate: certificate,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tCertificateActions.commit(this.state.certificate).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tcertificate: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tCertificateActions.remove(this.props.certificate.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddDomain = (): void => {\n\t\tlet cert: CertificateTypes.Certificate;\n\n\t\tif (this.state.changed) {\n\t\t\tcert = {\n\t\t\t\t...this.state.certificate,\n\t\t\t};\n\t\t} else {\n\t\t\tcert = {\n\t\t\t\t...this.props.certificate,\n\t\t\t};\n\t\t}\n\n\t\tlet acmeDomains = [\n\t\t\t...cert.acme_domains,\n\t\t\t'',\n\t\t];\n\n\t\tcert.acme_domains = acmeDomains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddDomain: '',\n\t\t\tcertificate: cert,\n\t\t});\n\t}\n\n\tonChangeDomain(i: number, state: string): void {\n\t\tlet cert: CertificateTypes.Certificate;\n\n\t\tif (this.state.changed) {\n\t\t\tcert = {\n\t\t\t\t...this.state.certificate,\n\t\t\t};\n\t\t} else {\n\t\t\tcert = {\n\t\t\t\t...this.props.certificate,\n\t\t\t};\n\t\t}\n\n\t\tlet acmeDomains = [\n\t\t\t...cert.acme_domains,\n\t\t];\n\n\t\tacmeDomains[i] = state;\n\n\t\tcert.acme_domains = acmeDomains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tcertificate: cert,\n\t\t});\n\t}\n\n\tonRemoveDomain(i: number): void {\n\t\tlet cert: CertificateTypes.Certificate;\n\n\t\tif (this.state.changed) {\n\t\t\tcert = {\n\t\t\t\t...this.state.certificate,\n\t\t\t};\n\t\t} else {\n\t\t\tcert = {\n\t\t\t\t...this.props.certificate,\n\t\t\t};\n\t\t}\n\n\t\tlet acmeDomains = [\n\t\t\t...cert.acme_domains,\n\t\t];\n\n\t\tacmeDomains.splice(i, 1);\n\n\t\tcert.acme_domains = acmeDomains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddDomain: '',\n\t\t\tcertificate: cert,\n\t\t});\n\t}\n\n\tonRefresh = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tCertificateActions.commit({\n\t\t\t\t...this.props.certificate,\n\t\t\t\trefresh: true,\n\t\t}).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tAlert.success('Certificate renew started');\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet cert: CertificateTypes.Certificate = this.state.certificate ||\n\t\t\tthis.props.certificate;\n\n\t\tlet info: CertificateTypes.Info = this.props.certificate.info || {};\n\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\torganizationsSelect.push(\n\t\t\t<option key=\"null\" value=\"\">\n\t\t\t\tNode Certificate\n\t\t\t</option>,\n\t\t);\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet hasSecrets = false;\n\t\tlet secretsSelect: JSX.Element[] = [];\n\t\tif (this.props.secrets.length) {\n\t\t\tsecretsSelect.push(<option key=\"null\" value=\"\">Select Secret</option>);\n\n\t\t\tfor (let secret of this.props.secrets) {\n\t\t\t\tif (Constants.user) {\n\t\t\t\t\tif (cert.organization !== CompletionStore.userOrganization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (cert.organization != secret.organization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\thasSecrets = true;\n\t\t\t\tsecretsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={secret.id}\n\t\t\t\t\t\tvalue={secret.id}\n\t\t\t\t\t>{secret.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasSecrets) {\n\t\t\tsecretsSelect = [<option key=\"null\" value=\"\">No Secrets</option>];\n\t\t}\n\n\t\tlet domains: JSX.Element[] = [];\n\t\t(cert.acme_domains || []).forEach((acmeDomain, index) => {\n\t\t\tdomains.push(\n\t\t\t\t<CertificateDomain\n\t\t\t\t\tkey={index}\n\t\t\t\t\tdisabled={this.state.disabled || Constants.user}\n\t\t\t\t\tdomain={acmeDomain}\n\t\t\t\t\tonChange={(state: string): void => {\n\t\t\t\t\t\tthis.onChangeDomain(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveDomain(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t})\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"certificate\" id={this.props.certificate.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Certificate\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this certifcate\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[cert.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of certificate\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Name\"\n\t\t\t\t\t\tvalue={cert.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Certificate comment.\"\n\t\t\t\t\t\tplaceholder=\"Certificate comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={cert.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\treadOnly={cert.type !== 'text'}\n\t\t\t\t\t\tlabel=\"Private Key\"\n\t\t\t\t\t\thelp=\"Certificate private key in PEM format\"\n\t\t\t\t\t\tplaceholder=\"Private key\"\n\t\t\t\t\t\trows={6}\n\t\t\t\t\t\tvalue={cert.key}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('key', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\treadOnly={cert.type !== 'text'}\n\t\t\t\t\t\tlabel=\"Certificate Chain\"\n\t\t\t\t\t\thelp=\"Certificate followed by certificate chain in PEM format\"\n\t\t\t\t\t\tplaceholder=\"Certificate chain\"\n\t\t\t\t\t\trows={6}\n\t\t\t\t\t\tvalue={cert.certificate}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('certificate', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tstyle={css.itemsLabel}\n\t\t\t\t\t\thidden={cert.type !== 'lets_encrypt'}\n\t\t\t\t\t>\n\t\t\t\t\t\tLetsEncrypt Domains\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"LetsEncrypt Domains\"\n\t\t\t\t\t\t\tcontent=\"Enter domain names for the certificate. All domains names must point to a Pritunl Cloud server in the cluster. The servers must also have port 80 publicy open. The port will need to stay open to renew the certificate.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div hidden={cert.type !== 'lets_encrypt'}>\n\t\t\t\t\t\t{domains}\n\t\t\t\t\t</div>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tdisabled={this.state.disabled || Constants.user}\n\t\t\t\t\t\tstyle={css.itemsAdd}\n\t\t\t\t\t\thidden={cert.type !== 'lets_encrypt'}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={this.onAddDomain}\n\t\t\t\t\t>\n\t\t\t\t\t\tAdd Domain\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.certificate.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Signature Algorithm',\n\t\t\t\t\t\t\t\tvalue: info.signature_alg || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Public Key Algorithm',\n\t\t\t\t\t\t\t\tvalue: info.public_key_alg || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Issuer',\n\t\t\t\t\t\t\t\tvalue: info.issuer || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Issued On',\n\t\t\t\t\t\t\t\tvalue: MiscUtils.formatDate(info.issued_on) || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Expires On',\n\t\t\t\t\t\t\t\tvalue: MiscUtils.formatDate(info.expires_on) || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'DNS Names',\n\t\t\t\t\t\t\t\tvalue: info.dns_names || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thelp=\"Certificate type, use text to provide a certificate. LetsEncrypt provides free certificates that automatically renew.\"\n\t\t\t\t\t\tvalue={cert.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"text\">Text</option>\n\t\t\t\t\t\t<option value=\"lets_encrypt\">LetsEncrypt</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\thelp=\"Organization for certificate. Select node to create a certificate for nodes. Load balancers in the same organization will have access to this certificate.\"\n\t\t\t\t\t\tvalue={cert.organization}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tlabel=\"LetsEncrypt Verification Type\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={cert.type != \"lets_encrypt\"}\n\t\t\t\t\t\thelp=\"Verification type for LetsEncrypt certificate. HTTP verification will use a HTTP request on port 80 from the host. DNS will use a DNS API provider to set a DNS TXT record.\"\n\t\t\t\t\t\tvalue={cert.acme_type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('acme_type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{(this.props.certificate.acme_type !== \"acme_http\" &&\n\t\t\t\t\t\t\tConstants.user) ? null : <option value=\"acme_http\">HTTP</option>}\n\t\t\t\t\t\t<option value=\"acme_dns\">DNS TXT</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tlabel=\"LetsEncrypt Verification Provider\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={cert.type != \"lets_encrypt\" ||\n\t\t\t\t\t\t\tcert.acme_type != \"acme_dns\"}\n\t\t\t\t\t\thelp=\"API provider for LetsEncrypt verification.\"\n\t\t\t\t\t\tvalue={cert.acme_auth}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('acme_auth', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"acme_aws\">AWS</option>\n\t\t\t\t\t\t<option value=\"acme_cloudflare\">Cloudflare</option>\n\t\t\t\t\t\t<option value=\"acme_oracle_cloud\">Oracle Cloud</option>\n\t\t\t\t\t\t<option value=\"acme_google_cloud\">Google Cloud</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={cert.type != \"lets_encrypt\" ||\n\t\t\t\t\t\t\tcert.acme_type != \"acme_dns\"}\n\t\t\t\t\t\tlabel=\"LetsEncrypt Verification Secret\"\n\t\t\t\t\t\thelp=\"Secret containing API keys to use for LetsEncrypt verification.\"\n\t\t\t\t\t\tvalue={cert.acme_secret}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('acme_secret', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{secretsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.certificate}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tcertificate: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t>\n\t\t\t\t<ConfirmButton\n\t\t\t\t\tlabel=\"Renew\"\n\t\t\t\t\tclassName=\"bp5-intent-success bp5-icon-refresh\"\n\t\t\t\t\tprogressClassName=\"bp5-intent-success\"\n\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\thidden={cert.type !== \"lets_encrypt\"}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tonConfirm={(): void => {\n\t\t\t\t\t\tthis.onRefresh();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</PageSave>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/CertificateDomain.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tdomain: string;\n\tdisabled: boolean;\n\tonChange: (state: string) => void;\n\tonRemove: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tdomain: {\n\t\twidth: '100%',\n\t\tborderRadius: '0 3px 3px 0',\n\t} as React.CSSProperties,\n\tdomainBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class CertificateDomain extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div className=\"bp5-control-group\" style={css.group}>\n\t\t\t<div style={css.domainBox}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.domain}\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Domain\"\n\t\t\t\t\tvalue={this.props.domain || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tthis.props.onChange(evt.target.value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/CertificateNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as SecretTypes from '../types/SecretTypes';\nimport * as CertificateActions from '../actions/CertificateActions';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport CertificateDomain from './CertificateDomain';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageTextArea from './PageTextArea';\nimport PageCreate from './PageCreate';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tcertificate: CertificateTypes.Certificate;\n\taddDomain: string;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tdomain: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class CertificateDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddDomain: null,\n\t\t\tcertificate: {\n\t\t\t\tname: 'new-certificate',\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet certificate: any = {\n\t\t\t...this.state.certificate,\n\t\t};\n\n\t\tcertificate[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tcertificate: certificate,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet certificate: any = {\n\t\t\t...this.state.certificate,\n\t\t};\n\n\t\tCertificateActions.create(certificate).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Certificate created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddDomain = (): void => {\n\t\tlet cert: CertificateTypes.Certificate;\n\n\t\tcert = {\n\t\t\t...this.state.certificate,\n\t\t};\n\n\t\tlet acmeDomains = [\n\t\t\t...(cert.acme_domains || []),\n\t\t\t'',\n\t\t];\n\n\t\tcert.acme_domains = acmeDomains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddDomain: '',\n\t\t\tcertificate: cert,\n\t\t});\n\t}\n\n\tonChangeDomain(i: number, state: string): void {\n\t\tlet cert: CertificateTypes.Certificate;\n\n\t\tcert = {\n\t\t\t...this.state.certificate,\n\t\t};\n\n\t\tlet acmeDomains = [\n\t\t\t...(cert.acme_domains || []),\n\t\t];\n\n\t\tacmeDomains[i] = state;\n\n\t\tcert.acme_domains = acmeDomains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tcertificate: cert,\n\t\t});\n\t}\n\n\tonRemoveDomain(i: number): void {\n\t\tlet cert: CertificateTypes.Certificate;\n\n\t\tcert = {\n\t\t\t...this.state.certificate,\n\t\t};\n\n\t\tlet acmeDomains = [\n\t\t\t...(cert.acme_domains || []),\n\t\t];\n\n\t\tacmeDomains.splice(i, 1);\n\n\t\tcert.acme_domains = acmeDomains;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddDomain: '',\n\t\t\tcertificate: cert,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet cert: CertificateTypes.Certificate = this.state.certificate;\n\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\torganizationsSelect.push(\n\t\t\t<option key=\"null\" value=\"\">\n\t\t\t\tNode Certificate\n\t\t\t</option>,\n\t\t);\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet hasSecrets = false;\n\t\tlet secretsSelect: JSX.Element[] = [];\n\t\tif (this.props.secrets.length) {\n\t\t\tsecretsSelect.push(<option key=\"null\" value=\"\">Select Secret</option>);\n\n\t\t\tfor (let secret of this.props.secrets) {\n\t\t\t\tif (Constants.user) {\n\t\t\t\t\tif (cert.organization !== CompletionStore.userOrganization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (cert.organization != secret.organization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\thasSecrets = true;\n\t\t\t\tsecretsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={secret.id}\n\t\t\t\t\t\tvalue={secret.id}\n\t\t\t\t\t>{secret.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasSecrets) {\n\t\t\tsecretsSelect = [<option key=\"null\" value=\"\">No Secrets</option>];\n\t\t}\n\n\t\tlet domains: JSX.Element[] = [];\n\t\t(cert.acme_domains || []).forEach((acmeDomain, index) => {\n\t\t\tdomains.push(\n\t\t\t\t<CertificateDomain\n\t\t\t\t\tkey={index}\n\t\t\t\t\tdisabled={this.state.disabled || Constants.user}\n\t\t\t\t\tdomain={acmeDomain}\n\t\t\t\t\tonChange={(state: string): void => {\n\t\t\t\t\t\tthis.onChangeDomain(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveDomain(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t})\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of certificate\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Name\"\n\t\t\t\t\t\t\tvalue={cert.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Certificate comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Certificate comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={cert.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\treadOnly={cert.type && cert.type !== 'text'}\n\t\t\t\t\t\t\tlabel=\"Private Key\"\n\t\t\t\t\t\t\thelp=\"Certificate private key in PEM format\"\n\t\t\t\t\t\t\tplaceholder=\"Private key\"\n\t\t\t\t\t\t\trows={6}\n\t\t\t\t\t\t\tvalue={cert.key}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('key', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\treadOnly={cert.type && cert.type !== 'text'}\n\t\t\t\t\t\t\tlabel=\"Certificate Chain\"\n\t\t\t\t\t\t\thelp=\"Certificate followed by certificate chain in PEM format\"\n\t\t\t\t\t\t\tplaceholder=\"Certificate chain\"\n\t\t\t\t\t\t\trows={6}\n\t\t\t\t\t\t\tvalue={cert.certificate}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('certificate', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tstyle={css.itemsLabel}\n\t\t\t\t\t\t\thidden={cert.type !== 'lets_encrypt'}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tLetsEncrypt Domains\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"LetsEncrypt Domains\"\n\t\t\t\t\t\t\t\tcontent=\"Enter domain names for the certificate. All domains names must point to a Pritunl Cloud server in the cluster. The servers must also have port 80 publicy open. The port will need to stay open to renew the certificate.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<div hidden={cert.type !== 'lets_encrypt'}>\n\t\t\t\t\t\t\t{domains}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled || Constants.user}\n\t\t\t\t\t\t\tstyle={css.itemsAdd}\n\t\t\t\t\t\t\thidden={cert.type !== 'lets_encrypt'}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={this.onAddDomain}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tAdd Domain\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thelp=\"Certificate type, use text to provide a certificate. LetsEncrypt provides free certificates that automatically renew.\"\n\t\t\t\t\t\t\tvalue={cert.type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"text\">Text</option>\n\t\t\t\t\t\t\t<option value=\"lets_encrypt\">LetsEncrypt</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for certificate. Select node to create a certificate for nodes. Load balancers in the same organization will have access to this certificate.\"\n\t\t\t\t\t\t\tvalue={cert.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tlabel=\"LetsEncrypt Verification Type\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={cert.type != \"lets_encrypt\"}\n\t\t\t\t\t\t\thelp=\"Verification type for LetsEncrypt certificate. HTTP verification will use a HTTP request on port 80 from the host. DNS will use a DNS API provider to set a DNS TXT record.\"\n\t\t\t\t\t\t\tvalue={cert.acme_type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('acme_type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{Constants.user ? null : <option value=\"acme_http\">HTTP</option>}\n\t\t\t\t\t\t\t<option value=\"acme_dns\">DNS TXT</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tlabel=\"LetsEncrypt Verification Provider\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={cert.type != \"lets_encrypt\" ||\n\t\t\t\t\t\t\t\tcert.acme_type != \"acme_dns\"}\n\t\t\t\t\t\t\thelp=\"API provider for LetsEncrypt verification.\"\n\t\t\t\t\t\t\tvalue={cert.acme_auth}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('acme_auth', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"acme_aws\">AWS</option>\n\t\t\t\t\t\t\t<option value=\"acme_cloudflare\">Cloudflare</option>\n\t\t\t\t\t\t\t<option value=\"acme_oracle_cloud\">Oracle Cloud</option>\n\t\t\t\t\t\t\t<option value=\"acme_google_cloud\">Google Cloud</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={cert.type != \"lets_encrypt\" ||\n\t\t\t\t\t\t\t\tcert.acme_type != \"acme_dns\"}\n\t\t\t\t\t\t\tlabel=\"LetsEncrypt Verification Secret\"\n\t\t\t\t\t\t\thelp=\"Secret containing API keys to use for LetsEncrypt verification.\"\n\t\t\t\t\t\t\tvalue={cert.acme_secret}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('acme_secret', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{secretsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.certificate}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Certificates.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport CertificatesStore from '../stores/CertificatesStore';\nimport * as CertificateActions from '../actions/CertificateActions';\nimport Certificate from './Certificate';\nimport CertificateNew from './CertificateNew';\nimport CertificatesFilter from './CertificatesFilter';\nimport CertificatesPage from './CertificatesPage';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as SecretTypes from '../types/SecretTypes';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tcertificates: CertificateTypes.CertificatesRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tfilter: CertificateTypes.Filter;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Certificates extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tcertificates: CertificatesStore.certificates,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tsecrets: CompletionStore.secrets,\n\t\t\tfilter: CertificatesStore.filter,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tCertificatesStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tCertificateActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tCertificatesStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet certificates = CertificatesStore.certificates;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tcertificates.forEach((certificate: CertificateTypes.Certificate): void => {\n\t\t\tif (curSelected[certificate.id]) {\n\t\t\t\tselected[certificate.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[certificate.id]) {\n\t\t\t\topened[certificate.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tcertificates: CertificatesStore.certificates,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tsecrets: CompletionStore.secrets,\n\t\t\tfilter: CertificatesStore.filter,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tCertificateActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet certificatesDom: JSX.Element[] = [];\n\n\t\tthis.state.certificates.forEach((\n\t\t\t\tcertificate: CertificateTypes.CertificateRo): void => {\n\t\t\tcertificatesDom.push(<Certificate\n\t\t\t\tkey={certificate.id}\n\t\t\t\tcertificate={certificate}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tsecrets={this.state.secrets}\n\t\t\t\tselected={!!this.state.selected[certificate.id]}\n\t\t\t\topen={!!this.state.opened[certificate.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet certificates = this.state.certificates;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < certificates.length; i++) {\n\t\t\t\t\t\t\tlet usr = certificates[i];\n\n\t\t\t\t\t\t\tif (usr.id === certificate.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[certificates[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: certificate.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[certificate.id]) {\n\t\t\t\t\t\tdelete selected[certificate.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[certificate.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: certificate.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[certificate.id]) {\n\t\t\t\t\t\tdelete opened[certificate.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[certificate.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = CertificatesStore.certificate(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newCertificateDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewCertificateDom = <CertificateNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tsecrets={this.state.secrets}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Certificates</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tCertificateActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tCertificateActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected certificates\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<CertificatesFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tCertificateActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newCertificateDom}\n\t\t\t\t\t{certificatesDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!certificatesDom.length}\n\t\t\t\ticonClass=\"bp5-icon-endorsed\"\n\t\t\t\ttitle=\"No certificates\"\n\t\t\t\tdescription=\"Add a new certificate to get started.\"\n\t\t\t/>\n\t\t\t<CertificatesPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/CertificatesFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: CertificateTypes.Filter;\n\tonFilter: (filter: CertificateTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class CertificatesFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Certificate ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/CertificatesPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport CertificatesStore from '../stores/CertificatesStore';\nimport * as CertificateActions from '../actions/CertificateActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class CertificatesPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: CertificatesStore.page,\n\t\t\tpageCount: CertificatesStore.pageCount,\n\t\t\tpages: CertificatesStore.pages,\n\t\t\tcount: CertificatesStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tCertificatesStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tCertificatesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: CertificatesStore.page,\n\t\t\tpageCount: CertificatesStore.pageCount,\n\t\t\tpages: CertificatesStore.pages,\n\t\t\tcount: CertificatesStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tCertificateActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tCertificateActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tCertificateActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tCertificateActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tCertificateActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ConfirmButton.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as Constants from '../Constants';\nimport * as MiscUtils from '../utils/MiscUtils';\n\ninterface Props {\n\tstyle?: React.CSSProperties;\n\tgrouped?: boolean;\n\tclassName?: string;\n\tdialogClassName?: string;\n\thidden?: boolean;\n\tprogressClassName?: string;\n\tlabel?: string;\n\tdialogLabel?: string;\n\tconfirmMsg?: string;\n\tconfirmInput?: boolean;\n\titems?: string[];\n\tmenuItem?: boolean;\n\tdisabled?: boolean;\n\tsafe?: boolean;\n\tchildren?: React.ReactNode\n\tonConfirm?: () => void;\n}\n\ninterface State {\n\tinput: string;\n\tdialog: boolean;\n\tconfirm: number;\n\tconfirming: string;\n\tforce: boolean;\n}\n\nconst css = {\n\tbox: {\n\t\tdisplay: 'inline-flex',\n\t\tverticalAlign: 'middle',\n\t} as React.CSSProperties,\n\tactionProgress: {\n\t\tposition: 'absolute',\n\t\tbottom: 0,\n\t\tleft: 0,\n\t\tborderRadius: 0,\n\t\tborderBottomLeftRadius: '3px',\n\t\tborderBottomRightRadius: '3px',\n\t\twidth: '100%',\n\t\theight: '4px',\n\t} as React.CSSProperties,\n\tsquareActionProgress: {\n\t\tposition: 'absolute',\n\t\tbottom: 0,\n\t\tleft: 0,\n\t\tborderRadius: 0,\n\t\tborderBottomLeftRadius: '1px',\n\t\tborderBottomRightRadius: '3px',\n\t\twidth: '100%',\n\t\theight: '4px',\n\t} as React.CSSProperties,\n\tdialog: {\n\t\tscrollbarGutter: \"stable\",\n\t\twidth: '340px',\n\t\tposition: 'absolute',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '220px',\n\t\tmargin: '18px 0 0 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n};\n\nexport default class ConfirmButton extends React.Component<Props, State> {\n\tconstructor(props: Props, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tinput: '',\n\t\t\tdialog: false,\n\t\t\tconfirm: 0,\n\t\t\tconfirming: null,\n\t\t\tforce: false,\n\t\t};\n\t}\n\n\topenDialog = (evt?: React.MouseEvent<HTMLElement>): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdialog: true,\n\t\t\tforce: evt?.ctrlKey || false,\n\t\t});\n\t}\n\n\tcloseDialog = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdialog: false,\n\t\t\tinput: '',\n\t\t\tforce: false,\n\t\t});\n\t}\n\n\tcloseDialogConfirm = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdialog: false,\n\t\t\tinput: '',\n\t\t});\n\t\tif (this.props.onConfirm) {\n\t\t\tthis.props.onConfirm();\n\t\t}\n\t}\n\n\tconfirm = (evt: React.MouseEvent<{}>): void => {\n\t\tlet confirmId = MiscUtils.uuid();\n\n\t\tif (evt.shiftKey) {\n\t\t\tif (this.props.onConfirm) {\n\t\t\t\tthis.props.onConfirm();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tconfirming: confirmId,\n\t\t});\n\n\t\tlet i = 10;\n\t\tlet id = setInterval(() => {\n\t\t\tif (i > 100) {\n\t\t\t\tclearInterval(id);\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tif (this.state.confirming === confirmId) {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tconfirm: 0,\n\t\t\t\t\t\t\tconfirming: null,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (this.props.onConfirm) {\n\t\t\t\t\t\t\tthis.props.onConfirm();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}, 250);\n\t\t\t\treturn;\n\t\t\t} else if (!this.state.confirming) {\n\t\t\t\tclearInterval(id);\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tconfirm: 0,\n\t\t\t\t\tconfirming: null,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (i % 10 === 0) {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tconfirm: i / 10,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\ti += 2;\n\t\t}, 8);\n\t}\n\n\tclearConfirm = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tconfirm: 0,\n\t\t\tconfirming: null,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet dialog = Constants.mobile || this.props.safe || this.props.menuItem;\n\n\t\tlet style = {\n\t\t\t...this.props.style,\n\t\t};\n\t\tstyle.position = 'relative';\n\n\t\tlet className = this.props.className || '';\n\t\tif (!this.props.label) {\n\t\t\tclassName += ' bp5-button-empty';\n\t\t}\n\n\t\tlet dialogClassName = this.props.dialogClassName ||\n\t\t\tthis.props.className || '';\n\t\tif (!this.props.label && !this.props.dialogLabel) {\n\t\t\tdialogClassName += ' bp5-button-empty';\n\t\t}\n\n\t\tlet confirmInput: JSX.Element;\n\t\tif (this.props.confirmInput && !this.state.force) {\n\t\t\tconfirmInput = <label\n\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\tstyle={css.label}\n\t\t\t>\n\t\t\t\tEnter \"delete\" to confirm:\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.input}\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder='Enter \"delete\" to confirm'\n\t\t\t\t\tvalue={this.state.input}\n\t\t\t\t\tonKeyDown={(evt): void => {\n\t\t\t\t\t\tif (evt.key === \"Enter\" && this.state.input === 'delete') {\n\t\t\t\t\t\t\tthis.closeDialogConfirm();\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tinput: evt.target.value,\n\t\t\t\t\t\t});\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</label>;\n\t\t}\n\n\t\tif (dialog) {\n\t\t\tlet confirmMsg = this.props.confirmMsg ? this.props.confirmMsg :\n\t\t\t\t'Confirm ' + (this.props.label || '');\n\t\t\tlet itemsList: JSX.Element;\n\t\t\tif (this.props.items) {\n\t\t\t\tlet items: JSX.Element[] = [];\n\t\t\t\tfor (let item of this.props.items) {\n\t\t\t\t\titems.push(<li key={item}>{item}</li>);\n\t\t\t\t}\n\t\t\t\titemsList = <ul>{items}</ul>;\n\t\t\t}\n\n\t\t\tlet dialogElem = <Blueprint.Dialog\n\t\t\t\ttitle=\"Confirm\"\n\t\t\t\tstyle={css.dialog}\n\t\t\t\tisOpen={this.state.dialog}\n\t\t\t\tusePortal={true}\n\t\t\t\tportalContainer={document.body}\n\t\t\t\tonClose={this.closeDialog}\n\t\t\t>\n\t\t\t\t<div className=\"bp5-dialog-body\">\n\t\t\t\t\t{confirmMsg}\n\t\t\t\t\t{itemsList}\n\t\t\t\t\t{confirmInput}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"bp5-dialog-footer\">\n\t\t\t\t\t<div className=\"bp5-dialog-footer-actions\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={this.closeDialog}\n\t\t\t\t\t\t>Cancel</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={'bp5-button ' + dialogClassName}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tdisabled={this.props.confirmInput && !this.state.force &&\n\t\t\t\t\t\t\t\tthis.state.input !== 'delete'}\n\t\t\t\t\t\t\tonClick={this.closeDialogConfirm}\n\t\t\t\t\t\t>{this.props.dialogLabel || this.props.label}</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</Blueprint.Dialog>\n\n\t\t\tif (this.props.menuItem) {\n\t\t\t\treturn <div>\n\t\t\t\t\t<Blueprint.MenuItem\n\t\t\t\t\t\tkey=\"menu-new-unit\"\n\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\thidden={this.props.hidden}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tevt.preventDefault()\n\t\t\t\t\t\t\tevt.stopPropagation()\n\t\t\t\t\t\t\tthis.openDialog(evt as React.MouseEvent<HTMLElement>)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\ttext={this.props.label}\n\t\t\t\t\t>{this.props.children}</Blueprint.MenuItem>\n\t\t\t\t\t{dialogElem}\n\t\t\t\t</div>\n\t\t\t}\n\n\t\t\treturn <div style={css.box}>\n\t\t\t\t<button\n\t\t\t\t\tclassName={'bp5-button ' + className}\n\t\t\t\t\tstyle={style}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\thidden={this.props.hidden}\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tonMouseDown={dialog ? undefined : this.confirm}\n\t\t\t\t\tonMouseUp={dialog ? undefined : this.clearConfirm}\n\t\t\t\t\tonMouseLeave={dialog ? undefined : this.clearConfirm}\n\t\t\t\t\tonClick={dialog ? (evt) => this.openDialog(evt) : undefined}\n\t\t\t\t>\n\t\t\t\t\t{this.props.children || this.props.label}\n\t\t\t\t</button>\n\t\t\t\t{dialogElem}\n\t\t\t</div>\n\t\t} else {\n\t\t\tlet confirmElem: JSX.Element;\n\n\t\t\tif (this.state.confirming) {\n\t\t\t\tlet confirmStyle = {\n\t\t\t\t\twidth: this.state.confirm * 10 + '%',\n\t\t\t\t\tbackgroundColor: style.color,\n\t\t\t\t\tborderRadius: 0,\n\t\t\t\t\tleft: 0,\n\t\t\t\t};\n\n\t\t\t\tlet progressStyle: React.CSSProperties;\n\t\t\t\tif (this.props.grouped) {\n\t\t\t\t\tprogressStyle = css.squareActionProgress;\n\t\t\t\t} else {\n\t\t\t\t\tprogressStyle = css.actionProgress;\n\t\t\t\t}\n\n\t\t\t\tconfirmElem = <div\n\t\t\t\t\tclassName={'bp5-progress-bar bp5-no-stripes ' + (\n\t\t\t\t\t\tthis.props.progressClassName || '')}\n\t\t\t\t\tstyle={progressStyle}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bp5-progress-meter\" style={confirmStyle}/>\n\t\t\t\t</div>;\n\t\t\t}\n\n\t\t\treturn <button\n\t\t\t\tclassName={'bp5-button ' + className}\n\t\t\t\tstyle={style}\n\t\t\t\ttype=\"button\"\n\t\t\t\thidden={this.props.hidden}\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\tonMouseDown={dialog ? undefined : this.confirm}\n\t\t\t\tonMouseUp={dialog ? undefined : this.clearConfirm}\n\t\t\t\tonMouseLeave={dialog ? undefined : this.clearConfirm}\n\t\t\t\tonClick={dialog ? this.openDialog : undefined}\n\t\t\t>\n\t\t\t\t{this.props.children || this.props.label}\n\t\t\t\t{confirmElem}\n\t\t\t</button>;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "www/app/components/CopyButton.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\tvalue: string;\n}\n\ninterface State {\n\thover: boolean;\n\tclick: boolean;\n\tcopied: boolean;\n}\n\nconst css = {\n\tcopy: {\n\t\tverticalAlign: 'middle',\n\t\tcursor: 'pointer',\n\t\tmarginLeft: '3px',\n\t\tposition: 'relative',\n\t\ttop: '-1px',\n\t} as React.CSSProperties,\n\tcopyHover: {\n\t\tverticalAlign: 'middle',\n\t\tcursor: 'pointer',\n\t\tmarginLeft: '3px',\n\t\tposition: 'relative',\n\t\ttop: '-1px',\n\t\topacity: 0.7,\n\t} as React.CSSProperties,\n\tcopyClicked: {\n\t\tverticalAlign: 'middle',\n\t\tcursor: 'pointer',\n\t\tmarginLeft: '3px',\n\t\tposition: 'relative',\n\t\ttop: '-1px',\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class CopyButton extends React.Component<Props, State> {\n\telem: HTMLSpanElement;\n\n\tconstructor(props: Props, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\thover: false,\n\t\t\tclick: false,\n\t\t\tcopied: false,\n\t\t};\n\t}\n\trender(): JSX.Element {\n\t\tlet style: React.CSSProperties;\n\t\tif (this.state.click && !this.state.copied) {\n\t\t\tstyle = css.copyClicked;\n\t\t} else if (this.state.hover && !this.state.copied) {\n\t\t\tstyle = css.copyHover;\n\t\t} else {\n\t\t\tstyle = css.copy;\n\t\t}\n\n\t\tlet className = 'bp5-icon-standard';\n\t\tif (this.state.copied) {\n\t\t\tclassName += ' bp5-icon-tick bp5-intent-success';\n\t\t} else {\n\t\t\tclassName += ' bp5-icon-clipboard';\n\t\t}\n\n\t\treturn <span\n\t\t\tref={(elem) => this.elem = elem}\n\t\t\tclassName={className}\n\t\t\tstyle={style}\n\t\t\tonMouseEnter={() => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\thover: true,\n\t\t\t\t});\n\t\t\t}}\n\t\t\tonMouseLeave={() => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\thover: false,\n\t\t\t\t\tclick: false,\n\t\t\t\t});\n\t\t\t}}\n\t\t\tonMouseDown={() => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tclick: true,\n\t\t\t\t});\n\t\t\t}}\n\t\t\tonMouseUp={() => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tclick: false,\n\t\t\t\t});\n\t\t\t}}\n\t\t\tonClick={() => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tcopied: true,\n\t\t\t\t});\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tcopied: false,\n\t\t\t\t\t});\n\t\t\t\t}, 1200);\n\n\t\t\t\tlet elem = document.createElement('input');\n\t\t\t\telem.contentEditable = 'true';\n\t\t\t\telem.readOnly = true;\n\t\t\t\telem.style.position = 'absolute';\n\t\t\t\telem.style.width = '1px';\n\t\t\t\telem.style.height = '1px';\n\t\t\t\telem.style.padding = '0px';\n\t\t\t\telem.style.border = 'none';\n\t\t\t\telem.style.opacity = '0';\n\t\t\t\telem.value = this.props.value;\n\t\t\t\tthis.elem.parentElement.append(elem);\n\n\t\t\t\tif (Constants.mobileOs === 'iOS') {\n\t\t\t\t\tlet range = document.createRange();\n\t\t\t\t\trange.selectNodeContents(elem);\n\n\t\t\t\t\tlet selection = window.getSelection();\n\t\t\t\t\tselection.removeAllRanges();\n\t\t\t\t\tselection.addRange(range);\n\t\t\t\t\telem.setSelectionRange(0, 999999);\n\t\t\t\t} else {\n\t\t\t\t\telem.select();\n\t\t\t\t}\n\n\t\t\t\tdocument.execCommand('copy');\n\t\t\t\telem.remove();\n\t\t\t}}\n\t\t/>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Datacenter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as StorageTypes from '../types/StorageTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport DatacenterDetailed from './DatacenterDetailed';\n\ninterface Props {\n\tdatacenter: DatacenterTypes.DatacenterRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tstorages: StorageTypes.StoragesRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Datacenter extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet datacenter = this.props.datacenter;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<DatacenterDetailed\n\t\t\t\t\tdatacenter={this.props.datacenter}\n\t\t\t\t\tstorages={this.props.storages}\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{datacenter.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DatacenterDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as StorageTypes from '../types/StorageTypes';\nimport * as DatacenterActions from '../actions/DatacenterActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageSelect from './PageSelect';\nimport PageSelectButton from './PageSelectButton';\nimport PageNumInput from './PageNumInput';\nimport PageSwitch from './PageSwitch';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tdatacenter: DatacenterTypes.DatacenterRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tstorages: StorageTypes.StoragesRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tdatacenter: DatacenterTypes.Datacenter;\n\taddStorage: string;\n\taddOrganization: string;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class DatacenterDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tdatacenter: null,\n\t\t\taddStorage: '',\n\t\t\taddOrganization: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet datacenter: any;\n\n\t\tif (this.state.changed) {\n\t\t\tdatacenter = {\n\t\t\t\t...this.state.datacenter,\n\t\t\t};\n\t\t} else {\n\t\t\tdatacenter = {\n\t\t\t\t...this.props.datacenter,\n\t\t\t};\n\t\t}\n\n\t\tdatacenter[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\ttoggle(name: string): void {\n\t\tlet datacenter: any;\n\n\t\tif (this.state.changed) {\n\t\t\tdatacenter = {\n\t\t\t\t...this.state.datacenter,\n\t\t\t};\n\t\t} else {\n\t\t\tdatacenter = {\n\t\t\t\t...this.props.datacenter,\n\t\t\t};\n\t\t}\n\n\t\tdatacenter[name] = !datacenter[name];\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDatacenterActions.commit(this.state.datacenter).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tdatacenter: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDatacenterActions.remove(this.props.datacenter.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddStorage = (): void => {\n\t\tlet datacenter: DatacenterTypes.Datacenter;\n\n\t\tif (!this.state.addStorage && !this.props.storages.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet storageId = this.state.addStorage;\n\t\tif (!storageId) {\n\t\t\tfor (let store of this.props.storages) {\n\t\t\t\tif (store.type === \"public\" || store.type === \"web\") {\n\t\t\t\t\tstorageId = store.id\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tdatacenter = {\n\t\t\t\t...this.state.datacenter,\n\t\t\t};\n\t\t} else {\n\t\t\tdatacenter = {\n\t\t\t\t...this.props.datacenter,\n\t\t\t};\n\t\t}\n\n\t\tlet publicStorages = [\n\t\t\t...(datacenter.public_storages || []),\n\t\t];\n\n\t\tif (publicStorages.indexOf(storageId) === -1) {\n\t\t\tpublicStorages.push(storageId);\n\t\t}\n\n\t\tpublicStorages.sort();\n\n\t\tdatacenter.public_storages = publicStorages;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\tonRemoveStorage = (storage: string): void => {\n\t\tlet datacenter: DatacenterTypes.Datacenter;\n\n\t\tif (this.state.changed) {\n\t\t\tdatacenter = {\n\t\t\t\t...this.state.datacenter,\n\t\t\t};\n\t\t} else {\n\t\t\tdatacenter = {\n\t\t\t\t...this.props.datacenter,\n\t\t\t};\n\t\t}\n\n\t\tlet publicStorages = [\n\t\t\t...(datacenter.public_storages || []),\n\t\t];\n\n\t\tlet i = publicStorages.indexOf(storage);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tpublicStorages.splice(i, 1);\n\n\t\tdatacenter.public_storages = publicStorages;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\tonAddOrganization = (): void => {\n\t\tlet datacenter: DatacenterTypes.Datacenter;\n\n\t\tif (!this.state.addOrganization && !this.props.organizations.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet organizationId = this.state.addOrganization ||\n\t\t\tthis.props.organizations[0].id;\n\n\t\tif (this.state.changed) {\n\t\t\tdatacenter = {\n\t\t\t\t...this.state.datacenter,\n\t\t\t};\n\t\t} else {\n\t\t\tdatacenter = {\n\t\t\t\t...this.props.datacenter,\n\t\t\t};\n\t\t}\n\n\t\tlet organizations = [\n\t\t\t...(datacenter.organizations || []),\n\t\t];\n\n\t\tif (organizations.indexOf(organizationId) === -1) {\n\t\t\torganizations.push(organizationId);\n\t\t}\n\n\t\torganizations.sort();\n\n\t\tdatacenter.organizations = organizations;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\tonRemoveOrganization = (organization: string): void => {\n\t\tlet datacenter: DatacenterTypes.Datacenter;\n\n\t\tif (this.state.changed) {\n\t\t\tdatacenter = {\n\t\t\t\t...this.state.datacenter,\n\t\t\t};\n\t\t} else {\n\t\t\tdatacenter = {\n\t\t\t\t...this.props.datacenter,\n\t\t\t};\n\t\t}\n\n\t\tlet organizations = [\n\t\t\t...(datacenter.organizations || []),\n\t\t];\n\n\t\tlet i = organizations.indexOf(organization);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\torganizations.splice(i, 1);\n\n\t\tdatacenter.organizations = organizations;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet datacenter: DatacenterTypes.Datacenter = this.state.datacenter ||\n\t\t\tthis.props.datacenter;\n\n\t\tlet organizations: JSX.Element[] = [];\n\t\tfor (let organizationId of (datacenter.organizations || [])) {\n\t\t\tlet organization = CompletionStore.organization(organizationId);\n\t\t\tif (!organization) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\torganizations.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={organization.id}\n\t\t\t\t>\n\t\t\t\t\t{organization.name}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveOrganization(organization.id);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\torganizationsSelect.push(<option key=\"null\" value=\"\">None</option>);\n\t\t}\n\n\t\tlet publicStorages: JSX.Element[] = [];\n\t\tfor (let storageId of (datacenter.public_storages || [])) {\n\t\t\tlet storage = CompletionStore.storage(storageId);\n\t\t\tif (!storage) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tpublicStorages.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={storage.id}\n\t\t\t\t>\n\t\t\t\t\t{storage.name}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveStorage(storage.id);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet hasStorages = false;\n\t\tlet privateStoragesSelect: JSX.Element[] = [\n\t\t\t<option key=\"null\" value=\"\">None</option>,\n\t\t];\n\t\tlet backupStoragesSelect: JSX.Element[] = [\n\t\t\t<option key=\"null\" value=\"\">None</option>,\n\t\t];\n\t\tlet publicStoragesSelect: JSX.Element[] = [];\n\t\tif (this.props.storages.length) {\n\t\t\tfor (let storage of this.props.storages) {\n\t\t\t\tif (storage.type === 'public' || storage.type === 'web') {\n\t\t\t\t\thasStorages = true;\n\t\t\t\t\tpublicStoragesSelect.push(\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tkey={storage.id}\n\t\t\t\t\t\t\tvalue={storage.id}\n\t\t\t\t\t\t>{storage.name}</option>,\n\t\t\t\t\t);\n\t\t\t\t} else if (storage.type === 'private') {\n\t\t\t\t\tprivateStoragesSelect.push(\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tkey={storage.id}\n\t\t\t\t\t\t\tvalue={storage.id}\n\t\t\t\t\t\t>{storage.name}</option>,\n\t\t\t\t\t);\n\t\t\t\t\tbackupStoragesSelect.push(\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tkey={storage.id}\n\t\t\t\t\t\t\tvalue={storage.id}\n\t\t\t\t\t\t>{storage.name}</option>,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!hasStorages) {\n\t\t\tpublicStoragesSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Storages</option>);\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"datacenter\" id={this.props.datacenter.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Datacenter\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this datacenter\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[datacenter.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of datacenter\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={datacenter.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Datacenter comment.\"\n\t\t\t\t\t\tplaceholder=\"Datacenter comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={datacenter.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Private Storage\"\n\t\t\t\t\t\thelp=\"Private storage that will store instance snapshots and pod images.\"\n\t\t\t\t\t\tvalue={datacenter.private_storage}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('private_storage', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{privateStoragesSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Private Storage Class\"\n\t\t\t\t\t\thelp=\"Private storage class to use when upload new objects.\"\n\t\t\t\t\t\tvalue={datacenter.private_storage_class}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('private_storage_class', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"\">Default</option>\n\t\t\t\t\t\t<option value=\"aws_standard\">AWS Standard</option>\n\t\t\t\t\t\t<option value=\"aws_infrequent_access\">AWS Standard-IA</option>\n\t\t\t\t\t\t<option value=\"aws_glacier\">AWS Glacier</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Backup Storage\"\n\t\t\t\t\t\thelp=\"Backup storage that will store instance backups.\"\n\t\t\t\t\t\tvalue={datacenter.backup_storage}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('backup_storage', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{backupStoragesSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Backup Storage Class\"\n\t\t\t\t\t\thelp=\"Backup storage class to use when upload new objects.\"\n\t\t\t\t\t\tvalue={datacenter.backup_storage_class}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('backup_storage_class', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"\">Default</option>\n\t\t\t\t\t\t<option value=\"aws_standard\">AWS Standard</option>\n\t\t\t\t\t\t<option value=\"aws_infrequent_access\">AWS Standard-IA</option>\n\t\t\t\t\t\t<option value=\"aws_glacier\">AWS Glacier</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.datacenter.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t>\n\t\t\t\t\t\tPublic Storages\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Public Storages\"\n\t\t\t\t\t\t\tcontent=\"Public storages that can be used for new instance images.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{publicStorages}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\tlabel=\"Add Storage\"\n\t\t\t\t\t\tvalue={this.state.addStorage}\n\t\t\t\t\t\tdisabled={!hasStorages|| this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddStorage: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddStorage}\n\t\t\t\t\t>\n\t\t\t\t\t\t{publicStoragesSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Match organizations\"\n\t\t\t\t\t\thelp=\"Limit what organizations can access this datacenter, by default all organizations will have access.\"\n\t\t\t\t\t\tchecked={datacenter.match_organizations}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.toggle('match_organizations');\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\thidden={!datacenter.match_organizations}\n\t\t\t\t\t>\n\t\t\t\t\t\tOrganizations\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Organizations\"\n\t\t\t\t\t\t\tcontent=\"Organizations that can access this zone.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{organizations}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\tlabel=\"Add Organization\"\n\t\t\t\t\t\tvalue={this.state.addOrganization}\n\t\t\t\t\t\tdisabled={!this.props.organizations.length}\n\t\t\t\t\t\thidden={!datacenter.match_organizations}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddOrganization: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddOrganization}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Network Mode\"\n\t\t\t\t\t\thelp=\"Network mode for internal VPC networking. If layer 2 networking with VLAN support isn't available VXLan must be used. A network bridge is required for the node internal interfaces when using default.\"\n\t\t\t\t\t\tvalue={datacenter.network_mode}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('network_mode', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"default\">Default</option>\n\t\t\t\t\t\t<option value=\"vxlan_vlan\">VXLAN</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\tlabel=\"Jumbo Frames MTU\"\n\t\t\t\t\t\thelp=\"Custom MTU size for jumbo frames. The node must have the jumbo frame option enabled to use this option. Defaults to 9000.\"\n\t\t\t\t\t\tplaceholder=\"9000\"\n\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\tmajorStepSize={1}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\tthis.set('jumbo_mtu', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tvalue={datacenter.jumbo_mtu || undefined}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.datacenter}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tdatacenter: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DatacenterNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as StorageTypes from '../types/StorageTypes';\nimport * as DatacenterActions from '../actions/DatacenterActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageSelect from './PageSelect';\nimport PageSelectButton from './PageSelectButton';\nimport PageNumInput from './PageNumInput';\nimport PageSwitch from './PageSwitch';\nimport PageCreate from './PageCreate';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tstorages: StorageTypes.StoragesRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tdatacenter: DatacenterTypes.Datacenter;\n\taddStorage: string;\n\taddOrganization: string;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class DatacenterDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddStorage: '',\n\t\t\taddOrganization: null,\n\t\t\tdatacenter: {\n\t\t\t\tname: 'new-datacenter',\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet datacenter: any = {\n\t\t\t...this.state.datacenter,\n\t\t};\n\n\t\tdatacenter[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\ttoggle(name: string): void {\n\t\tlet datacenter: any = {\n\t\t\t...this.state.datacenter,\n\t\t};\n\n\t\tdatacenter[name] = !datacenter[name];\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet datacenter: any = {\n\t\t\t...this.state.datacenter,\n\t\t};\n\n\t\tDatacenterActions.create(datacenter).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Datacenter created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddStorage = (): void => {\n\t\tlet datacenter: DatacenterTypes.Datacenter;\n\n\t\tif (!this.state.addStorage && !this.props.storages.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet storageId = this.state.addStorage;\n\t\tif (!storageId) {\n\t\t\tfor (let store of this.props.storages) {\n\t\t\t\tif (store.type === \"public\" || store.type === \"web\") {\n\t\t\t\t\tstorageId = store.id\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdatacenter = {\n\t\t\t...this.state.datacenter,\n\t\t};\n\n\t\tlet publicStorages = [\n\t\t\t...(datacenter.public_storages || []),\n\t\t];\n\n\t\tif (publicStorages.indexOf(storageId) === -1) {\n\t\t\tpublicStorages.push(storageId);\n\t\t}\n\n\t\tpublicStorages.sort();\n\n\t\tdatacenter.public_storages = publicStorages;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\tonRemoveStorage = (storage: string): void => {\n\t\tlet datacenter: DatacenterTypes.Datacenter;\n\n\t\tdatacenter = {\n\t\t\t...this.state.datacenter,\n\t\t};\n\n\t\tlet publicStorages = [\n\t\t\t...(datacenter.public_storages || []),\n\t\t];\n\n\t\tlet i = publicStorages.indexOf(storage);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tpublicStorages.splice(i, 1);\n\n\t\tdatacenter.public_storages = publicStorages;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\tonAddOrganization = (): void => {\n\t\tlet datacenter: DatacenterTypes.Datacenter;\n\n\t\tif (!this.state.addOrganization && !this.props.organizations.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet organizationId = this.state.addOrganization ||\n\t\t\tthis.props.organizations[0].id;\n\n\t\tdatacenter = {\n\t\t\t...this.state.datacenter,\n\t\t};\n\n\t\tlet organizations = [\n\t\t\t...(datacenter.organizations || []),\n\t\t];\n\n\t\tif (organizations.indexOf(organizationId) === -1) {\n\t\t\torganizations.push(organizationId);\n\t\t}\n\n\t\torganizations.sort();\n\n\t\tdatacenter.organizations = organizations;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\tonRemoveOrganization = (organization: string): void => {\n\t\tlet datacenter: DatacenterTypes.Datacenter;\n\n\t\tdatacenter = {\n\t\t\t...this.state.datacenter,\n\t\t};\n\n\t\tlet organizations = [\n\t\t\t...(datacenter.organizations || []),\n\t\t];\n\n\t\tlet i = organizations.indexOf(organization);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\torganizations.splice(i, 1);\n\n\t\tdatacenter.organizations = organizations;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdatacenter: datacenter,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet datacenter: DatacenterTypes.Datacenter = this.state.datacenter;\n\n\t\tlet organizations: JSX.Element[] = [];\n\t\tfor (let organizationId of (datacenter.organizations || [])) {\n\t\t\tlet organization = CompletionStore.organization(organizationId);\n\t\t\tif (!organization) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\torganizations.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={organization.id}\n\t\t\t\t>\n\t\t\t\t\t{organization.name}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveOrganization(organization.id);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\torganizationsSelect.push(<option key=\"null\" value=\"\">None</option>);\n\t\t}\n\n\t\tlet publicStorages: JSX.Element[] = [];\n\t\tfor (let storageId of (datacenter.public_storages || [])) {\n\t\t\tlet storage = CompletionStore.storage(storageId);\n\t\t\tif (!storage) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tpublicStorages.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={storage.id}\n\t\t\t\t>\n\t\t\t\t\t{storage.name}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveStorage(storage.id);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet hasStorages = false;\n\t\tlet privateStoragesSelect: JSX.Element[] = [\n\t\t\t<option key=\"null\" value=\"\">None</option>,\n\t\t];\n\t\tlet backupStoragesSelect: JSX.Element[] = [\n\t\t\t<option key=\"null\" value=\"\">None</option>,\n\t\t];\n\t\tlet publicStoragesSelect: JSX.Element[] = [];\n\t\tif (this.props.storages.length) {\n\t\t\tfor (let storage of this.props.storages) {\n\t\t\t\tif (storage.type === 'public' || storage.type === 'web') {\n\t\t\t\t\thasStorages = true;\n\t\t\t\t\tpublicStoragesSelect.push(\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tkey={storage.id}\n\t\t\t\t\t\t\tvalue={storage.id}\n\t\t\t\t\t\t>{storage.name}</option>,\n\t\t\t\t\t);\n\t\t\t\t} else if (storage.type === 'private') {\n\t\t\t\t\tprivateStoragesSelect.push(\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tkey={storage.id}\n\t\t\t\t\t\t\tvalue={storage.id}\n\t\t\t\t\t\t>{storage.name}</option>,\n\t\t\t\t\t);\n\t\t\t\t\tbackupStoragesSelect.push(\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tkey={storage.id}\n\t\t\t\t\t\t\tvalue={storage.id}\n\t\t\t\t\t\t>{storage.name}</option>,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!hasStorages) {\n\t\t\tpublicStoragesSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Storages</option>);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of datacenter\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={datacenter.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Datacenter comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Datacenter comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={datacenter.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Private Storage\"\n\t\t\t\t\t\t\thelp=\"Private storage that will store instance snapshots and pod images.\"\n\t\t\t\t\t\t\tvalue={datacenter.private_storage}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('private_storage', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{privateStoragesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Private Storage Class\"\n\t\t\t\t\t\t\thelp=\"Private storage class to use when upload new objects.\"\n\t\t\t\t\t\t\tvalue={datacenter.private_storage_class}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('private_storage_class', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"\">Default</option>\n\t\t\t\t\t\t\t<option value=\"aws_standard\">AWS Standard</option>\n\t\t\t\t\t\t\t<option value=\"aws_infrequent_access\">AWS Standard-IA</option>\n\t\t\t\t\t\t\t<option value=\"aws_glacier\">AWS Glacier</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Backup Storage\"\n\t\t\t\t\t\t\thelp=\"Backup storage that will store instance backups.\"\n\t\t\t\t\t\t\tvalue={datacenter.backup_storage}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('backup_storage', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{backupStoragesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Backup Storage Class\"\n\t\t\t\t\t\t\thelp=\"Backup storage class to use when upload new objects.\"\n\t\t\t\t\t\t\tvalue={datacenter.backup_storage_class}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('backup_storage_class', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"\">Default</option>\n\t\t\t\t\t\t\t<option value=\"aws_standard\">AWS Standard</option>\n\t\t\t\t\t\t\t<option value=\"aws_infrequent_access\">AWS Standard-IA</option>\n\t\t\t\t\t\t\t<option value=\"aws_glacier\">AWS Glacier</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tPublic Storages\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Public Storages\"\n\t\t\t\t\t\t\t\tcontent=\"Public storages that can be used for new instance images.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{publicStorages}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\t\tlabel=\"Add Storage\"\n\t\t\t\t\t\t\tvalue={this.state.addStorage}\n\t\t\t\t\t\t\tdisabled={!hasStorages|| this.state.disabled}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddStorage: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddStorage}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{publicStoragesSelect}\n\t\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"Match organizations\"\n\t\t\t\t\t\t\thelp=\"Limit what organizations can access this datacenter, by default all organizations will have access.\"\n\t\t\t\t\t\t\tchecked={datacenter.match_organizations}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.toggle('match_organizations');\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\t\thidden={!datacenter.match_organizations}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tOrganizations\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Organizations\"\n\t\t\t\t\t\t\t\tcontent=\"Organizations that can access this zone.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{organizations}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\t\tlabel=\"Add Organization\"\n\t\t\t\t\t\t\tvalue={this.state.addOrganization}\n\t\t\t\t\t\t\tdisabled={!this.props.organizations.length}\n\t\t\t\t\t\t\thidden={!datacenter.match_organizations}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddOrganization: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddOrganization}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Network Mode\"\n\t\t\t\t\t\t\thelp=\"Network mode for internal VPC networking. If layer 2 networking with VLAN support isn't available VXLan must be used. A network bridge is required for the node internal interfaces when using default.\"\n\t\t\t\t\t\t\tvalue={datacenter.network_mode}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('network_mode', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"default\">Default</option>\n\t\t\t\t\t\t\t<option value=\"vxlan_vlan\">VXLAN</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"Jumbo Frames MTU\"\n\t\t\t\t\t\t\thelp=\"Custom MTU size for jumbo frames. The node must have the jumbo frame option enabled to use this option. Defaults to 9000.\"\n\t\t\t\t\t\t\tplaceholder=\"9000\"\n\t\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\t\tmajorStepSize={1}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('jumbo_mtu', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tvalue={datacenter.jumbo_mtu || undefined}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.datacenter}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Datacenters.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as StorageTypes from '../types/StorageTypes';\nimport DatacentersStore from '../stores/DatacentersStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as DatacenterActions from '../actions/DatacenterActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Datacenter from './Datacenter';\nimport DatacenterNew from './DatacenterNew';\nimport DatacentersFilter from './DatacentersFilter';\nimport DatacentersPage from './DatacentersPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tstorages: StorageTypes.StoragesRo;\n\tfilter: DatacenterTypes.Filter;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Datacenters extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdatacenters: DatacentersStore.datacenters,\n\t\t\tstorages: CompletionStore.storages,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tfilter: CompletionStore.filter,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tDatacentersStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tDatacenterActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tDatacentersStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet datacenters = DatacentersStore.datacenters;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tdatacenters.forEach((datacenter: DatacenterTypes.Datacenter): void => {\n\t\t\tif (curSelected[datacenter.id]) {\n\t\t\t\tselected[datacenter.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[datacenter.id]) {\n\t\t\t\topened[datacenter.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdatacenters: DatacentersStore.datacenters,\n\t\t\tstorages: CompletionStore.storages,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tfilter: DatacentersStore.filter,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDatacenterActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet datacentersDom: JSX.Element[] = [];\n\n\t\tthis.state.datacenters.forEach((\n\t\t\t\tdatacenter: DatacenterTypes.DatacenterRo): void => {\n\t\t\tdatacentersDom.push(<Datacenter\n\t\t\t\tkey={datacenter.id}\n\t\t\t\tdatacenter={datacenter}\n\t\t\t\tstorages={this.state.storages}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tselected={!!this.state.selected[datacenter.id]}\n\t\t\t\topen={!!this.state.opened[datacenter.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet datacenters = this.state.datacenters;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < datacenters.length; i++) {\n\t\t\t\t\t\t\tlet usr = datacenters[i];\n\n\t\t\t\t\t\t\tif (usr.id === datacenter.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[datacenters[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: datacenter.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[datacenter.id]) {\n\t\t\t\t\t\tdelete selected[datacenter.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[datacenter.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: datacenter.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[datacenter.id]) {\n\t\t\t\t\t\tdelete opened[datacenter.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[datacenter.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = DatacentersStore.datacenter(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newDatacenterDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewDatacenterDom = <DatacenterNew\n\t\t\t\tstorages={this.state.storages}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Datacenters</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tDatacenterActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tDatacenterActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected datacenters\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<DatacentersFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tDatacenterActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newDatacenterDom}\n\t\t\t\t\t{datacentersDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!datacentersDom.length}\n\t\t\t\ticonClass=\"bp5-icon-cloud\"\n\t\t\t\ttitle=\"No datacenters\"\n\t\t\t\tdescription=\"Add a new datacenter to get started.\"\n\t\t\t/>\n\t\t\t<DatacentersPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DatacentersFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: DatacenterTypes.Filter;\n\tonFilter: (filter: DatacenterTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class DatacentersFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Datacenter ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DatacentersPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport DatacentersStore from '../stores/DatacentersStore';\nimport * as DatacenterActions from '../actions/DatacenterActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class DatacentersPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: DatacentersStore.page,\n\t\t\tpageCount: DatacentersStore.pageCount,\n\t\t\tpages: DatacentersStore.pages,\n\t\t\tcount: DatacentersStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tDatacentersStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tDatacentersStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: DatacentersStore.page,\n\t\t\tpageCount: DatacentersStore.pageCount,\n\t\t\tpages: DatacentersStore.pages,\n\t\t\tcount: DatacentersStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDatacenterActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDatacenterActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDatacenterActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDatacenterActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDatacenterActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Device.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DeviceTypes from '../types/DeviceTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as DeviceActions from '../actions/DeviceActions';\nimport * as PageInfos from './PageInfo';\nimport PageInfo from './PageInfo';\nimport ConfirmButton from './ConfirmButton';\nimport * as Alert from '../Alert';\nimport PageSwitch from \"./PageSwitch\";\nimport PageSave from \"./PageSave\";\nimport PageInput from \"./PageInput\";\n\ninterface Props {\n\tdevice: DeviceTypes.DeviceRo;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tdevice: DeviceTypes.Device;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px',\n\t\tmarginBottom: '5px',\n\t} as React.CSSProperties,\n\tinfo: {\n\t\tmarginBottom: '-5px',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '250px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\tmarginBottom: '11px',\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tcontrolButton: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingTop: '10px',\n\t} as React.CSSProperties,\n};\n\nexport default class Device extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tdevice: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet device: any;\n\n\t\tif (this.state.changed) {\n\t\t\tdevice = {\n\t\t\t\t...this.state.device,\n\t\t\t};\n\t\t} else {\n\t\t\tdevice = {\n\t\t\t\t...this.props.device,\n\t\t\t};\n\t\t}\n\n\t\tdevice[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdevice: device,\n\t\t});\n\t}\n\n\ttoggleLevel(level: number) {\n\t\tlet device: any;\n\n\t\tif (this.state.changed) {\n\t\t\tdevice = {\n\t\t\t\t...this.state.device,\n\t\t\t};\n\t\t} else {\n\t\t\tdevice = {\n\t\t\t\t...this.props.device,\n\t\t\t};\n\t\t}\n\n\t\tlet levels: number[] = Object.assign([], (device.alert_levels || []));\n\t\tlet index = levels.indexOf(level);\n\n\t\tif (index !== -1) {\n\t\t\tlevels.splice(index, 1);\n\t\t} else {\n\t\t\tlevels.push(level);\n\t\t}\n\n\t\tdevice.alert_levels = levels;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdevice: device,\n\t\t});\n\t}\n\n\tonTestAlert = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDeviceActions.testAlert(this.props.device.id).then((): void => {\n\t\t\tAlert.success('Test alert sent');\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDeviceActions.commit(this.state.device).then((): void => {\n\t\t\tAlert.success('Device name updated');\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tdevice: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDeviceActions.remove(this.props.device.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet device: DeviceTypes.Device = this.state.device ||\n\t\t\tthis.props.device;\n\n\t\tlet isPhone: boolean = this.props.device.mode === 'phone';\n\n\t\tlet deviceType = 'Unknown';\n\t\tswitch (device.type) {\n\t\t\tcase 'webauthn':\n\t\t\t\tdeviceType = 'WebAuthn';\n\t\t\t\tbreak;\n\t\t\tcase 'u2f':\n\t\t\t\tdeviceType = 'U2F';\n\t\t\t\tbreak;\n\t\t\tcase 'call':\n\t\t\t\tdeviceType = 'Call';\n\t\t\t\tbreak;\n\t\t\tcase 'message':\n\t\t\t\tdeviceType = 'SMS';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet deviceMode = 'Unknown';\n\t\tswitch (device.mode) {\n\t\t\tcase 'secondary':\n\t\t\t\tdeviceMode = 'Secondary';\n\t\t\t\tbreak;\n\t\t\tcase 'phone':\n\t\t\t\tdeviceMode = 'Phone';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet deviceOther: PageInfos.Field;\n\t\tif (device.wan_rp_id) {\n\t\t\tdeviceOther = {\n\t\t\t\tlabel: 'WebAuthn Domain',\n\t\t\t\tvalue: device.wan_rp_id,\n\t\t\t};\n\t\t} else if (device.type === 'call' || device.type === 'message') {\n\t\t\tdeviceOther = {\n\t\t\t\tlabel: 'Phone Number',\n\t\t\t\tvalue: device.number,\n\t\t\t};\n\t\t}\n\n\t\tlet alertIcon = 'bp5-icon-phone';\n\t\tif (device.type === 'message') {\n\t\t\talertIcon = 'bp5-icon-mobile-phone';\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\t\tif (device.disabled) {\n\t\t\tcardStyle.opacity = 0.6;\n\t\t}\n\n\t\tlet fields1: PageInfos.Field[];\n\t\tlet fields2: PageInfos.Field[];\n\n\t\tif (isPhone) {\n\t\t\tfields1 = [\n\t\t\t\t{\n\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\tvalue: device.id || 'None',\n\t\t\t\t},\n\t\t\t];\n\t\t\tfields2 = [\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Type',\n\t\t\t\t\tvalue: deviceType,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Mode',\n\t\t\t\t\tvalue: deviceMode,\n\t\t\t\t},\n\t\t\t\tdeviceOther,\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Registered',\n\t\t\t\t\tvalue: MiscUtils.formatDate(device.timestamp) || 'Unknown',\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Last Active',\n\t\t\t\t\tvalue: MiscUtils.formatDate(device.last_active) || 'Unknown',\n\t\t\t\t},\n\t\t\t];\n\t\t} else {\n\t\t\tfields1 = [\n\t\t\t\t{\n\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\tvalue: device.id || 'None',\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Type',\n\t\t\t\t\tvalue: deviceType,\n\t\t\t\t},\n\t\t\t\tdeviceOther,\n\t\t\t];\n\t\t\tfields2 = [\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Mode',\n\t\t\t\t\tvalue: deviceMode,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Registered',\n\t\t\t\t\tvalue: MiscUtils.formatDate(device.timestamp) || 'Unknown',\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Last Active',\n\t\t\t\t\tvalue: MiscUtils.formatDate(device.last_active) || 'Unknown',\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\tlet testButton: JSX.Element;\n\t\tif (isPhone) {\n\t\t\ttestButton = <ConfirmButton\n\t\t\t\tlabel=\"Send Test Alert\"\n\t\t\t\tclassName={'bp5-intent-success ' + alertIcon}\n\t\t\t\tprogressClassName=\"bp5-intent-success\"\n\t\t\t\tstyle={css.controlButton}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tonConfirm={(): void => {\n\t\t\t\t\tthis.onTestAlert();\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card\"\n\t\t\tstyle={cardStyle}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div style={css.remove}>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tconfirmMsg=\"Confirm device remove\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Device Name\"\n\t\t\t\t\t\thelp=\"Name of device.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tvalue={device.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Low alerts\"\n\t\t\t\t\t\thelp=\"Recieve low level alerts on this device.\"\n\t\t\t\t\t\thidden={!isPhone}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tchecked={(device.alert_levels || []).indexOf(1) !== -1}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.toggleLevel(1);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Medium alerts\"\n\t\t\t\t\t\thelp=\"Recieve medium level alerts on this device.\"\n\t\t\t\t\t\thidden={!isPhone}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tchecked={(device.alert_levels || []).indexOf(5) !== -1}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.toggleLevel(5);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"High alerts\"\n\t\t\t\t\t\thelp=\"Recieve high level alerts on this device.\"\n\t\t\t\t\t\thidden={!isPhone}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tchecked={(device.alert_levels || []).indexOf(10) !== -1}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.toggleLevel(10);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\tfields={fields1}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\tfields={fields2}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.device && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tdevice: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t>\n\t\t\t\t{testButton}\n\t\t\t</PageSave>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Devices.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as SuperAgent from 'superagent';\nimport * as WebAuthn from '@github/webauthn-json';\nimport * as DeviceTypes from '../types/DeviceTypes';\nimport DevicesStore from '../stores/DevicesStore';\nimport * as DeviceActions from '../actions/DeviceActions';\nimport * as Constants from \"../Constants\";\nimport NonState from './NonState';\nimport Device from './Device';\nimport PageHeader from './PageHeader';\nimport Loader from \"../Loader\";\nimport * as Csrf from \"../Csrf\";\nimport * as Alert from \"../Alert\";\n\ninterface Props {\n\tuserId: string;\n}\n\ninterface State {\n\tdevices: DeviceTypes.DevicesRo;\n\tdeviceType: string;\n\tdeviceName: string;\n\tdevicePubKey: string;\n\tdevicePhoneNumber: string;\n\tshowEnded: boolean;\n\tdisabled: boolean;\n}\n\nconst css = {\n\theader: {\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '15px 0 -5px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tmarginTop: '18px',\n\t} as React.CSSProperties,\n\tgroupBox: {\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class Devices extends React.Component<Props, State> {\n\talertKey: string;\n\tu2fToken: string;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdevices: DevicesStore.devices,\n\t\t\tdeviceName: '',\n\t\t\tdeviceType: '',\n\t\t\tdevicePubKey: '',\n\t\t\tdevicePhoneNumber: '',\n\t\t\tshowEnded: false,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tDevicesStore.addChangeListener(this.onChange);\n\t\tif (this.props.userId) {\n\t\t\tDeviceActions.load(this.props.userId);\n\t\t}\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tDevicesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdevices: DevicesStore.devices,\n\t\t});\n\t}\n\n\twanRegister = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet loader = new Loader().loading();\n\n\t\tSuperAgent\n\t\t\t.get('/device/' + DevicesStore.userId + '/webauthn/register')\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to request device registration');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.wanCreate(res.body.token, res.body.options);\n\t\t\t});\n\t}\n\n\twanCreate = (token: string, options: any): void => {\n\t\tWebAuthn.create(options).then((cred: any): void => {\n\t\t\tcred.name = this.state.deviceName;\n\t\t\tcred.token = token;\n\t\t\tthis.wanRespond(cred);\n\t\t}).catch((err: any): void => {\n\t\t\tAlert.errorRes(err, 'Failed to register device');\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\twanRespond = (cred: any): void => {\n\t\tlet loader = new Loader().loading();\n\n\t\tSuperAgent\n\t\t\t.post('/device/' + DevicesStore.userId + '/webauthn/register')\n\t\t\t.send(cred)\n\t\t\t.set('Accept', 'application/json')\n\t\t\t.set('Csrf-Token', Csrf.token)\n\t\t\t.end((err: any, res: SuperAgent.Response): void => {\n\t\t\t\tloader.done();\n\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tdeviceName: '',\n\t\t\t\t});\n\n\t\t\t\tif (err) {\n\t\t\t\t\tAlert.errorRes(res, 'Failed to register device');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tAlert.success('Successfully registered device');\n\t\t\t});\n\t}\n\n\taddDevice = (): void => {\n\t\tif (this.state.deviceType === 'phone_call' ||\n\t\t\tthis.state.deviceType === 'phone_message') {\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: true,\n\t\t\t});\n\n\t\t\tlet deviceTypes = this.state.deviceType.split('_');\n\t\t\tlet deviceMode = deviceTypes[0];\n\t\t\tlet deviceType = deviceTypes[1];\n\n\t\t\tDeviceActions.create({\n\t\t\t\tid: null,\n\t\t\t\tuser: this.props.userId,\n\t\t\t\tname: this.state.deviceName,\n\t\t\t\ttype: deviceType,\n\t\t\t\tmode: deviceMode,\n\t\t\t\tnumber: this.state.devicePhoneNumber,\n\t\t\t}).then((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tdeviceName: '',\n\t\t\t\t\tdevicePubKey: '',\n\t\t\t\t\tdevicePhoneNumber: '',\n\t\t\t\t});\n\n\t\t\t\tAlert.success('Successfully registered device');\n\t\t\t}).catch((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t});\n\t\t\t});\n\t\t} else {\n\t\t\tthis.wanRegister();\n\t\t}\n\t}\n\n\trender(): JSX.Element {\n\t\tif (!this.props.userId) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet devices: JSX.Element[] = [];\n\n\t\tthis.state.devices.forEach((device: DeviceTypes.DeviceRo): void => {\n\t\t\tdevices.push(<Device\n\t\t\t\tkey={device.id}\n\t\t\t\tdevice={device}\n\t\t\t/>);\n\t\t});\n\n\t\treturn <div>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>User Devices</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.groupBox} className=\"layout horizontal\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"bp5-control-group\"\n\t\t\t\t\t\t\tstyle={css.group}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className=\"bp5-select\">\n\t\t\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t\t\tvalue={this.state.deviceType}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\t\t\tdeviceType: evt.target.value,\n\t\t\t\t\t\t\t\t\t\t\tdevicePubKey: '',\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<option value=\"webauthn\">WebAuthn</option>\n\t\t\t\t\t\t\t\t\t<option value=\"phone_call\">Phone (Call)</option>\n\t\t\t\t\t\t\t\t\t<option value=\"phone_message\">Phone (SMS)</option>\n\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"layout horizontal\" style={css.inputBox}>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\t\tplaceholder=\"Device name\"\n\t\t\t\t\t\t\t\t\tvalue={this.state.deviceName}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\t\t\tdeviceName: evt.target.value,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonKeyPress={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tif (this.state.deviceType !== 'smart_card' &&\n\t\t\t\t\t\t\t\t\t\t\t\tevt.key === 'Enter') {\n\t\t\t\t\t\t\t\t\t\t\tthis.addDevice();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\t\t\t\thidden={this.state.deviceType !== 'phone_call' &&\n\t\t\t\t\t\t\t\t\tthis.state.deviceType !== 'phone_message'}\n\t\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\t\tplaceholder=\"Device phone number\"\n\t\t\t\t\t\t\t\t\tvalue={this.state.devicePhoneNumber}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\t\t\tdevicePhoneNumber: evt.target.value,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonKeyPress={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tif (evt.key === 'Enter') {\n\t\t\t\t\t\t\t\t\t\t\tthis.addDevice();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\t\t\tonClick={this.addDevice}\n\t\t\t\t\t\t\t\t>Add Device</button>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<div>\n\t\t\t\t{devices}\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!devices.length}\n\t\t\t\ticonClass=\"bp5-icon-id-number\"\n\t\t\t\ttitle=\"No devices\"\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Disk.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as DiskTypes from '../types/DiskTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\nimport DiskDetailed from './DiskDetailed';\nimport * as PoolTypes from \"../types/PoolTypes\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tpools: PoolTypes.PoolsRo;\n\tdisk: DiskTypes.DiskRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Disk extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet disk = this.props.disk;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<DiskDetailed\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tpools={this.props.pools}\n\t\t\t\t\tdisk={this.props.disk}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet orgName = '';\n\t\tif (disk.organization) {\n\t\t\tlet org = CompletionStore.organization(disk.organization);\n\t\t\torgName = org ? org.name : disk.organization;\n\t\t} else {\n\t\t\torgName = 'Unknown Organization';\n\t\t}\n\n\t\tlet statusText = 'Unknown';\n\t\tlet statusClass = 'bp5-cell';\n\t\tswitch (disk.state) {\n\t\t\tcase 'provision':\n\t\t\t\tstatusText = 'Provisioning';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t\tcase 'available':\n\t\t\t\tif (disk.instance) {\n\t\t\t\t\tstatusText = 'Connected';\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = 'Available';\n\t\t\t\t}\n\t\t\t\tstatusClass += ' bp5-text-intent-success';\n\t\t\t\tbreak;\n\t\t\tcase 'attached':\n\t\t\t\tstatusText = 'Connected';\n\t\t\t\tstatusClass += ' bp5-text-intent-success';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tswitch (disk.action) {\n\t\t\tcase 'destroy':\n\t\t\t\tstatusText = 'Destroying';\n\t\t\t\tstatusClass += ' bp5-text-intent-danger';\n\t\t\t\tbreak;\n\t\t\tcase 'snapshot':\n\t\t\t\tstatusText = 'Snapshotting';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t\tcase 'backup':\n\t\t\t\tstatusText = 'Backing Up';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t\tcase 'restore':\n\t\t\t\tstatusText = 'Restoring';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t\tcase 'expand':\n\t\t\t\tstatusText = 'Expanding';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet resourceIcon = \"\";\n\t\tlet resourceValue = \"\";\n\t\tif (this.props.disk.type === \"lvm\") {\n\t\t\tresourceIcon = \"bp5-icon-control\";\n\t\t\tresourceValue = \"Pool Unavailable\"\n\n\t\t\tif (this.props.pools.length) {\n\t\t\t\tfor (let pool of this.props.pools) {\n\t\t\t\t\tif (pool.id === disk.pool) {\n\t\t\t\t\t\tresourceValue = pool.name;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlet node = CompletionStore.node(disk.node);\n\t\t\tresourceIcon = \"bp5-icon-layers\";\n\t\t\tresourceValue = node ? node.name : disk.node;\n\t\t}\n\n\t\tif (disk.deployment) {\n\t\t\tresourceIcon = \"bp5-icon-server\";\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.card}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{disk.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className={statusClass} style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-icon-pulse\"\n\t\t\t\t/>\n\t\t\t\t{statusText}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName={'bp5-icon-standard bp5-text-muted ' + (disk.organization ?\n\t\t\t\t\t\t'bp5-icon-people' : 'bp5-icon-layers')}\n\t\t\t\t/>\n\t\t\t\t{orgName}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName={\"bp5-icon-standard bp5-text-muted \" + resourceIcon}\n\t\t\t\t/>\n\t\t\t\t{resourceValue}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-database\"\n\t\t\t\t/>\n\t\t\t\t{disk.size}GB\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DiskDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DiskTypes from '../types/DiskTypes';\nimport * as DiskActions from '../actions/DiskActions';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageSwitch from './PageSwitch';\nimport PageNumInput from './PageNumInput';\nimport PageInfo from './PageInfo';\nimport PageSelectButtonConfirm from './PageSelectButtonConfirm';\nimport Help from './Help';\nimport * as PageInfos from './PageInfo';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as InstanceActions from '../actions/InstanceActions';\nimport InstancesNodeStore from '../stores/InstancesNodeStore';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as Alert from '../Alert';\nimport PageTextArea from \"./PageTextArea\";\nimport * as PoolTypes from \"../types/PoolTypes\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tpools: PoolTypes.PoolsRo;\n\tdisk: DiskTypes.DiskRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tdisk: DiskTypes.Disk;\n\tinstances: InstanceTypes.InstancesRo;\n\trestoreImage: string;\n\tresizeDisk: boolean;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class DiskDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tdisk: null,\n\t\t\tinstances: null,\n\t\t\trestoreImage: null,\n\t\t\tresizeDisk: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tInstancesNodeStore.addChangeListener(this.onChange);\n\t\tInstanceActions.syncNode(this.props.disk.node, this.props.disk.pool);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tInstancesNodeStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tinstances: InstancesNodeStore.instances(\n\t\t\t\tthis.props.disk.node || this.props.disk.pool),\n\t\t});\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet disk: any;\n\n\t\tif (this.state.changed) {\n\t\t\tdisk = {\n\t\t\t\t...this.state.disk,\n\t\t\t};\n\t\t} else {\n\t\t\tdisk = {\n\t\t\t\t...this.props.disk,\n\t\t\t};\n\t\t}\n\n\t\tdisk[name] = val;\n\n\t\tif (name === 'instance' && !Number(disk.index)) {\n\t\t\tdisk['index'] = '0';\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdisk: disk,\n\t\t});\n\t}\n\n\tsetResizeDisk(val: boolean): void {\n\t\tlet disk: any;\n\n\t\tif (this.state.changed) {\n\t\t\tdisk = {\n\t\t\t\t...this.state.disk,\n\t\t\t};\n\t\t} else {\n\t\t\tdisk = {\n\t\t\t\t...this.props.disk,\n\t\t\t};\n\t\t}\n\n\t\tif (val) {\n\t\t\tdisk.new_size = disk.size;\n\t\t} else {\n\t\t\tdisk.new_size = 0;\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tresizeDisk: val,\n\t\t\tdisk: disk,\n\t\t});\n\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet disk = {\n\t\t\t...this.state.disk,\n\t\t};\n\n\t\tif (this.state.resizeDisk && disk.new_size > disk.size) {\n\t\t\tdisk.action = 'expand';\n\t\t}\n\n\t\tDiskActions.commit(disk).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tdisk: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tresizeDisk: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDiskActions.remove(this.props.disk.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonRestoreBackup = (): void => {\n\t\tlet restoreImage: string;\n\n\t\tif (this.state.restoreImage) {\n\t\t\trestoreImage = this.state.restoreImage;\n\t\t} else if (this.props.disk.backups && this.props.disk.backups.length) {\n\t\t\trestoreImage = this.props.disk.backups[0].image;\n\t\t} else {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet disk: DiskTypes.Disk;\n\n\t\tif (this.state.changed) {\n\t\t\tdisk = {\n\t\t\t\t...this.state.disk,\n\t\t\t};\n\t\t} else {\n\t\t\tdisk = {\n\t\t\t\t...this.props.disk,\n\t\t\t};\n\t\t}\n\n\t\tdisk.action = 'restore';\n\t\tdisk.restore_image = restoreImage;\n\n\t\tDiskActions.commit(disk).then((): void => {\n\t\t\tAlert.success('Disk restore started');\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet disk: DiskTypes.Disk = this.state.disk ||\n\t\t\tthis.props.disk;\n\n\t\tlet node = CompletionStore.node(this.props.disk.node);\n\t\tlet org = CompletionStore.organization(this.props.disk.organization);\n\n\t\tlet hasInstances = false;\n\t\tlet instancesSelect: JSX.Element[] = [];\n\t\tif (this.state.instances === null) {\n\t\t\tinstancesSelect = [<option key=\"null\" value=\"\">Loading</option>];\n\t\t} else {\n\t\t\tif (this.state.instances.length) {\n\t\t\t\tinstancesSelect.push(\n\t\t\t\t\t<option key=\"null\" value=\"\">Detached Disk</option>);\n\n\t\t\t\thasInstances = true;\n\t\t\t\tfor (let instance of this.state.instances) {\n\t\t\t\t\tinstancesSelect.push(\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tkey={instance.id}\n\t\t\t\t\t\t\tvalue={instance.id}\n\t\t\t\t\t\t>{instance.name}</option>,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!hasInstances) {\n\t\t\t\tinstancesSelect = [<option key=\"null\" value=\"\">No Instances</option>];\n\t\t\t}\n\t\t}\n\n\t\tlet resourceLabel = \"\";\n\t\tlet resourceValue = \"\";\n\t\tif (disk.type === \"lvm\") {\n\t\t\tresourceLabel = \"Pool\"\n\t\t\tresourceValue = \"Pool Unavailable\"\n\n\t\t\tif (this.props.pools.length) {\n\t\t\t\tfor (let pool of this.props.pools) {\n\t\t\t\t\tif (pool.id === disk.pool) {\n\t\t\t\t\t\tresourceValue = pool.name;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tresourceLabel = \"Node\";\n\t\t\tresourceValue = (node ? node.name : this.props.disk.node) || '-';\n\t\t}\n\n\t\tlet backupsSelect: JSX.Element[] = [];\n\t\tfor (let backup of (disk.backups || [])) {\n\t\t\tbackupsSelect.push(\n\t\t\t\t<option key={backup.image} value={backup.image}>\n\t\t\t\t\t{backup.name}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet hasBackups = false;\n\t\tif (!backupsSelect.length) {\n\t\t\tbackupsSelect = [<option key=\"null\" value=\"\">No Backups</option>];\n\t\t} else {\n\t\t\thasBackups = true\n\t\t}\n\n\t\tlet statusText = 'Unknown';\n\t\tlet statusClass = 'tab-close ';\n\t\tswitch (this.props.disk.state) {\n\t\t\tcase 'provision':\n\t\t\t\tstatusText = 'Provisioning';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t\tcase 'available':\n\t\t\t\tif (this.props.disk.instance) {\n\t\t\t\t\tstatusText = 'Connected';\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = 'Available';\n\t\t\t\t}\n\t\t\t\tstatusClass += ' bp5-text-intent-success';\n\t\t\t\tbreak;\n\t\t\tcase 'attached':\n\t\t\t\tstatusText = 'Connected';\n\t\t\t\tstatusClass += ' bp5-text-intent-success';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tswitch (this.props.disk.action) {\n\t\t\tcase 'destroy':\n\t\t\t\tstatusText = 'Destroying';\n\t\t\t\tstatusClass += ' bp5-text-intent-danger';\n\t\t\t\tbreak;\n\t\t\tcase 'snapshot':\n\t\t\t\tstatusText = 'Snapshotting';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t\tcase 'backup':\n\t\t\t\tstatusText = 'Backing Up';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t\tcase 'restore':\n\t\t\t\tstatusText = 'Restoring';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t\tcase 'expand':\n\t\t\t\tstatusText = 'Expanding';\n\t\t\t\tstatusClass += ' bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet fields: PageInfos.Field[] = [\n\t\t\t{\n\t\t\t\tlabel: 'ID',\n\t\t\t\tvalue: this.props.disk.id || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Organization',\n\t\t\t\tvalue: org ? org.name : this.props.disk.organization,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: resourceLabel,\n\t\t\t\tvalue: resourceValue,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Size',\n\t\t\t\tvalue: this.props.disk.size + 'GB',\n\t\t\t},\n\t\t];\n\n\t\tif (this.props.disk.uuid) {\n\t\t\tfields.splice(2, 0, {\n\t\t\t\tlabel: 'UUID',\n\t\t\t\tvalue: this.props.disk.uuid,\n\t\t\t});\n\t\t}\n\n\t\tif (this.props.disk.file_system) {\n\t\t\tfields.splice(3, 0, {\n\t\t\t\tlabel: 'File System',\n\t\t\t\tvalue: this.props.disk.file_system,\n\t\t\t});\n\t\t}\n\n\t\tif (this.props.disk.image) {\n\t\t\tfields.splice(2, 0, {\n\t\t\t\tlabel: 'Image',\n\t\t\t\tvalue: this.props.disk.image || 'Blank Disk',\n\t\t\t});\n\t\t}\n\n\t\tif (this.props.disk.deployment) {\n\t\t\tfields.push({\n\t\t\t\tlabel: 'Deployment',\n\t\t\t\tvalue: this.props.disk.deployment,\n\t\t\t})\n\t\t}\n\n\t\tlet backingImage = this.props.disk.backing_image;\n\t\tif (backingImage) {\n\t\t\tbackingImage = backingImage.replace('-', '\\n');\n\n\t\t\tfields.splice(2, 0, {\n\t\t\t\tlabel: 'Backing Image',\n\t\t\t\tvalue: backingImage,\n\t\t\t});\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={5}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n            <div>\n              <label\n                className=\"bp5-control bp5-checkbox tab-close\"\n                style={css.select}\n              >\n                <input\n                  type=\"checkbox\"\n                  checked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n                  onClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n                />\n                <span className=\"bp5-control-indicator\"/>\n              </label>\n            </div>\n\t\t\t\t\t\t<div className={statusClass} style={css.status}>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\t\t\t\tclassName=\"bp5-icon-standard bp5-icon-pulse\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{statusText}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Disk\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this disk\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[disk.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of disk.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={disk.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Disk comment.\"\n\t\t\t\t\t\tplaceholder=\"Disk comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={disk.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={true}\n\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\thelp=\"Type of disk. QCOW disk files are stored locally on the node filesystem. LVM disks are partitioned as a logical volume.\"\n\t\t\t\t\t\tvalue={disk.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option key=\"qcow2\" value=\"qcow2\">QCOW</option>\n\t\t\t\t\t\t<option key=\"lvm\" value=\"lvm\">LVM</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasInstances}\n\t\t\t\t\t\tlabel=\"Instance\"\n\t\t\t\t\t\thelp=\"Instance to attach disk to.\"\n\t\t\t\t\t\tvalue={disk.instance}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('instance', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{instancesSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\tlabel=\"Index\"\n\t\t\t\t\t\thelp=\"Index to attach disk.\"\n\t\t\t\t\t\thidden={!disk.instance}\n\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\tmax={8}\n\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\tmajorStepSize={1}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\tvalue={Number(disk.index) || 0}\n\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\tthis.set('index', String(val));\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\thelp=\"Block disk from being deleted.\"\n\t\t\t\t\t\tchecked={disk.delete_protection}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('delete_protection', !disk.delete_protection);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={fields}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled || disk.state != 'available'}\n\t\t\t\t\t\tlabel=\"Resize disk\"\n\t\t\t\t\t\thelp=\"Change size of disk. Instance will be stopped.\"\n\t\t\t\t\t\tchecked={this.state.resizeDisk}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.setResizeDisk(!this.state.resizeDisk);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\tlabel=\"New Size\"\n\t\t\t\t\t\thelp=\"New disk size in gigabytes.\"\n\t\t\t\t\t\thidden={!this.state.resizeDisk}\n\t\t\t\t\t\tmin={disk.size}\n\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\tmajorStepSize={1}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\tvalue={disk.new_size}\n\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\tthis.set('new_size', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Automatic backup\"\n\t\t\t\t\t\thelp=\"Automatically backup disk daily.\"\n\t\t\t\t\t\tchecked={disk.backup}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('backup', !disk.backup);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t>\n\t\t\t\t\t\tRestore Backup\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Restore Backup\"\n\t\t\t\t\t\t\tcontent=\"Select a backup to restore and replace the existing disk with the backup image. Instance will be stopped.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButtonConfirm\n\t\t\t\t\t\tlabel=\"Restore\"\n\t\t\t\t\t\tvalue={this.state.restoreImage}\n\t\t\t\t\t\tdisabled={!hasBackups || this.state.disabled}\n\t\t\t\t\t\tconfirmMsg=\"Confirm disk restore\"\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-box\"\n\t\t\t\t\t\tprogressClassName=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\trestoreImage: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onRestoreBackup}\n\t\t\t\t\t>\n\t\t\t\t\t\t{backupsSelect}\n\t\t\t\t\t</PageSelectButtonConfirm>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.disk && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tdisk: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DiskNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DiskTypes from '../types/DiskTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as PoolTypes from '../types/PoolTypes';\nimport * as DiskActions from '../actions/DiskActions';\nimport * as ImageActions from '../actions/ImageActions';\nimport * as InstanceActions from '../actions/InstanceActions';\nimport * as NodeActions from '../actions/NodeActions';\nimport ImagesDatacenterStore from '../stores/ImagesDatacenterStore';\nimport InstancesNodeStore from '../stores/InstancesNodeStore';\nimport NodesZoneStore from '../stores/NodesZoneStore';\nimport PageInput from './PageInput';\nimport PageInputButton from './PageInputButton';\nimport PageCreate from './PageCreate';\nimport PageSelect from './PageSelect';\nimport PageSwitch from \"./PageSwitch\";\nimport PageNumInput from './PageNumInput';\nimport Help from './Help';\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tpools: PoolTypes.PoolsRo;\n\tzones: ZoneTypes.ZonesRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tdisk: DiskTypes.Disk;\n\tdatacenter: string;\n\tzone: string;\n\timages: ImageTypes.ImagesRo;\n\tnodes: NodeTypes.NodesRo;\n\tinstances: InstanceTypes.InstancesRo;\n\thiddenImages: boolean;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class DiskNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tdisk: {\n\t\t\t\tname: 'new-disk',\n\t\t\t\tindex: \"1\",\n\t\t\t\tsize: 10,\n\t\t\t},\n\t\t\tdatacenter: '',\n\t\t\tzone: '',\n\t\t\timages: [],\n\t\t\tnodes: [],\n\t\t\tinstances: [],\n\t\t\thiddenImages: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tImagesDatacenterStore.addChangeListener(this.onChange);\n\t\tNodesZoneStore.addChangeListener(this.onChange);\n\t\tInstancesNodeStore.addChangeListener(this.onChange);\n\t\tImageActions.syncDatacenter('');\n\t\tNodeActions.syncZone('');\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tImagesDatacenterStore.removeChangeListener(this.onChange);\n\t\tNodesZoneStore.removeChangeListener(this.onChange);\n\t\tInstancesNodeStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\timages: ImagesDatacenterStore.images,\n\t\t\tnodes: NodesZoneStore.nodes,\n\t\t\tinstances: InstancesNodeStore.instances(this.state.disk.node),\n\t\t});\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet disk: any = {\n\t\t\t...this.state.disk,\n\t\t};\n\n\t\tdisk[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdisk: disk,\n\t\t});\n\t}\n\n\tsetUnset(name: string, val: any, unset: string): void {\n\t\tlet disk: any = {\n\t\t\t...this.state.disk,\n\t\t};\n\n\t\tdisk[name] = val;\n\t\tdisk[unset] = null;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdisk: disk,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet disk: any = {\n\t\t\t...this.state.disk,\n\t\t};\n\n\t\tif (this.props.organizations.length && !disk.organization) {\n\t\t\tdisk.organization = this.props.organizations[0].id;\n\t\t}\n\n\t\tDiskActions.create(disk).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Disk created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet disk = this.state.disk;\n\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters.length) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Datacenter</option>);\n\n\t\t\thasDatacenters = true;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\tlet datacenter = this.state.datacenter;\n\t\tlet hasZones = false;\n\t\tlet zonesSelect: JSX.Element[] = [];\n\t\tif (this.props.zones.length) {\n\t\t\tzonesSelect.push(<option key=\"null\" value=\"\">Select Zone</option>);\n\n\t\t\tfor (let zone of this.props.zones) {\n\t\t\t\tif (zone.datacenter !== datacenter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasZones = true;\n\n\t\t\t\tzonesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={zone.id}\n\t\t\t\t\t\tvalue={zone.id}\n\t\t\t\t\t>{zone.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasZones) {\n\t\t\tzonesSelect = [<option key=\"null\" value=\"\">No Zones</option>];\n\t\t}\n\n\t\tlet hasNodes = false;\n\t\tlet nodesSelect: JSX.Element[] = [];\n\t\tif (this.state.nodes.length) {\n\t\t\tnodesSelect.push(<option key=\"null\" value=\"\">Select Node</option>);\n\n\t\t\thasNodes = true;\n\t\t\tfor (let node of this.state.nodes) {\n\t\t\t\tnodesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={node.id}\n\t\t\t\t\t\tvalue={node.id}\n\t\t\t\t\t>{node.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasNodes) {\n\t\t\tnodesSelect = [<option key=\"null\" value=\"\">No Nodes</option>];\n\t\t}\n\n\t\tlet hasPools = false;\n\t\tlet poolsSelect: JSX.Element[] = [];\n\t\tif (this.props.pools.length) {\n\t\t\tpoolsSelect.push(<option key=\"null\" value=\"\">Select Pool</option>);\n\n\t\t\tfor (let pool of this.props.pools) {\n\t\t\t\tif (pool.zone !== this.state.zone) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\thasPools = true;\n\t\t\t\tpoolsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={pool.id}\n\t\t\t\t\t\tvalue={pool.id}\n\t\t\t\t\t>{pool.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasPools) {\n\t\t\tpoolsSelect = [<option key=\"null\" value=\"\">No Pools</option>];\n\t\t}\n\n\t\tlet hasInstances = false;\n\t\tlet instancesSelect: JSX.Element[] = [];\n\t\tif (this.state.instances.length) {\n\t\t\tinstancesSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Detached Disk</option>);\n\n\t\t\thasInstances = true;\n\t\t\tfor (let instance of this.state.instances) {\n\t\t\t\tinstancesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={instance.id}\n\t\t\t\t\t\tvalue={instance.id}\n\t\t\t\t\t>{instance.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasInstances) {\n\t\t\tinstancesSelect = [<option key=\"null\" value=\"\">No Instances</option>];\n\t\t}\n\n\t\tlet imagesSelect: JSX.Element[] = [\n\t\t\t<option key=\"null\" value=\"\">Blank Disk</option>,\n\t\t];\n\t\tlet imagesMap = new Map();\n\t\tlet imagesVer = new Map();\n\t\tif (this.state.images.length) {\n\t\t\tfor (let image of this.state.images) {\n\t\t\t\tif (!this.state.hiddenImages && image.signed) {\n\t\t\t\t\tlet imgSpl = image.key.split('_');\n\n\t\t\t\t\tif (imgSpl.length >= 2 && imgSpl[imgSpl.length - 1].length >= 4) {\n\t\t\t\t\t\tlet imgKey = imgSpl[0] + '_' + image.firmware\n\n\t\t\t\t\t\tlet imgVer = parseInt(\n\t\t\t\t\t\t\timgSpl[imgSpl.length - 1].substring(0, 4), 10);\n\t\t\t\t\t\tif (imgVer) {\n\t\t\t\t\t\t\tlet curImg = imagesVer.get(imgKey);\n\t\t\t\t\t\t\tif (!curImg || imgVer > curImg[0]) {\n\t\t\t\t\t\t\t\timagesVer.set(imgKey, [imgVer, image.id, image.name]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\timagesMap.set(image.id, image.name);\n\t\t\t}\n\n\t\t\tfor (let item of imagesMap.entries()) {\n\t\t\t\timagesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={item[0]}\n\t\t\t\t\t\tvalue={item[0]}\n\t\t\t\t\t>{item[1]}</option>,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tfor (let item of imagesVer.entries()) {\n\t\t\t\timagesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={item[1][1]}\n\t\t\t\t\t\tvalue={item[1][1]}\n\t\t\t\t\t>{item[1][2]}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={5}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of disk\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tvalue={disk.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\t\thelp=\"Type of disk. QCOW disk files are stored locally on the node filesystem. LVM disks are partitioned as a logical volume.\"\n\t\t\t\t\t\t\tvalue={disk.type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option key=\"qcow2\" value=\"qcow2\">QCOW</option>\n\t\t\t\t\t\t\t<option key=\"lvm\" value=\"lvm\">LVM</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for disk.\"\n\t\t\t\t\t\t\tvalue={disk.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\t\thelp=\"Datacenter for disk.\"\n\t\t\t\t\t\t\tvalue={this.state.datacenter}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdatacenter: val,\n\t\t\t\t\t\t\t\t\tdisk: {\n\t\t\t\t\t\t\t\t\t\t...this.state.disk,\n\t\t\t\t\t\t\t\t\t\tnode: '',\n\t\t\t\t\t\t\t\t\t\timage: '',\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tImageActions.syncDatacenter(val);\n\t\t\t\t\t\t\t\tNodeActions.syncZone('');\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasZones}\n\t\t\t\t\t\t\tlabel=\"Zone\"\n\t\t\t\t\t\t\thelp=\"Zone for disk.\"\n\t\t\t\t\t\t\tvalue={this.state.zone}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tzone: val,\n\t\t\t\t\t\t\t\t\tdisk: {\n\t\t\t\t\t\t\t\t\t\t...this.state.disk,\n\t\t\t\t\t\t\t\t\t\tnode: '',\n\t\t\t\t\t\t\t\t\t\tinstance: '',\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tNodeActions.syncZone(val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{zonesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasNodes}\n\t\t\t\t\t\t\tlabel=\"Node\"\n\t\t\t\t\t\t\thelp=\"Node to run disk on.\"\n\t\t\t\t\t\t\thidden={disk.type === \"lvm\"}\n\t\t\t\t\t\t\tvalue={disk.node}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdisk: {\n\t\t\t\t\t\t\t\t\t\t...this.state.disk,\n\t\t\t\t\t\t\t\t\t\tnode: val,\n\t\t\t\t\t\t\t\t\t\tinstance: '',\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tInstanceActions.syncNode(val, null);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{nodesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasPools}\n\t\t\t\t\t\t\tlabel=\"Pool\"\n\t\t\t\t\t\t\thelp=\"LVM volume group to add disk to.\"\n\t\t\t\t\t\t\thidden={disk.type !== \"lvm\"}\n\t\t\t\t\t\t\tvalue={disk.pool}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('pool', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{poolsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\t\thelp=\"Block disk from being deleted.\"\n\t\t\t\t\t\t\tchecked={disk.delete_protection}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('delete_protection', !disk.delete_protection);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasInstances}\n\t\t\t\t\t\t\tlabel=\"Instance\"\n\t\t\t\t\t\t\thelp=\"Instance to attach disk to.\"\n\t\t\t\t\t\t\tvalue={disk.instance}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('instance', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{instancesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"Index\"\n\t\t\t\t\t\t\thelp=\"Index to attach disk.\"\n\t\t\t\t\t\t\thidden={!disk.instance}\n\t\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\t\tmax={8}\n\t\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\t\tmajorStepSize={1}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tvalue={Number(disk.index)}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('index', String(val));\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Image\"\n\t\t\t\t\t\t\thelp=\"Starting image for disk.\"\n\t\t\t\t\t\t\tvalue={disk.image}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setUnset('image', val, 'file_system');\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{imagesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"File System\"\n\t\t\t\t\t\t\thelp=\"Starting file system for disk.\"\n\t\t\t\t\t\t\tvalue={disk.file_system}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setUnset('file_system', val, 'image');\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"\">None</option>\n\t\t\t\t\t\t\t<option value=\"xfs\">XFS</option>\n\t\t\t\t\t\t\t<option value=\"ext4\">ext4</option>\n\t\t\t\t\t\t\t<option value=\"lvm_xfs\">LVM + XFS</option>\n\t\t\t\t\t\t\t<option value=\"lvm_ext4\">LVM + ext4</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"Show hidden images\"\n\t\t\t\t\t\t\thelp=\"Show previous versions of images.\"\n\t\t\t\t\t\t\tchecked={this.state.hiddenImages}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\thiddenImages: !this.state.hiddenImages,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"Linked disk image\"\n\t\t\t\t\t\t\thelp=\"Link to source disk image instead of creating full copy. This will reduce disk size and provide faster creation.\"\n\t\t\t\t\t\t\tchecked={disk.backing}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('backing', !disk.backing);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"LV Size\"\n\t\t\t\t\t\t\thelp=\"Disk size in gigabytes of LVM logical volume. Must be less then disk size.\"\n\t\t\t\t\t\t\thidden={!disk.file_system?.includes(\"lvm\")}\n\t\t\t\t\t\t\tmin={10}\n\t\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\t\tmajorStepSize={2}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tvalue={disk.lv_size}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('lv_size', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"Size\"\n\t\t\t\t\t\t\thelp=\"Disk size in gigabytes.\"\n\t\t\t\t\t\t\tmin={10}\n\t\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\t\tmajorStepSize={2}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tvalue={disk.size}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('size', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.disk}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Disks.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from \"../Constants\";\nimport * as DiskTypes from '../types/DiskTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as NodeTypes from '../types/NodeTypes';\nimport DisksStore from '../stores/DisksStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as DiskActions from '../actions/DiskActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Disk from './Disk';\nimport DisksFilter from './DisksFilter';\nimport DisksPage from './DisksPage';\nimport DiskNew from './DiskNew';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\nimport * as ZoneTypes from \"../types/ZoneTypes\";\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\nimport * as PoolTypes from \"../types/PoolTypes\";\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tdisks: DiskTypes.DisksRo;\n\tfilter: DiskTypes.Filter;\n\tdebug: boolean;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tnodes: NodeTypes.NodesRo;\n\tpools: PoolTypes.PoolsRo;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n\tdebug: {\n\t\tmargin: '0 0 4px 0',\n\t\tjustifyContent: 'flex-end',\n\t} as React.CSSProperties,\n\tdebugButton: {\n\t\tmargin: '0 0 0 8px',\n\t} as React.CSSProperties,\n\tdebugToggle: {\n\t\topacity: 0.3,\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Disks extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisks: DisksStore.disks,\n\t\t\tfilter: DisksStore.filter,\n\t\t\tdebug: false,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tnodes: CompletionStore.nodes,\n\t\t\tpools: CompletionStore.pools,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tDisksStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tDiskActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tDisksStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet disks = DisksStore.disks;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tdisks.forEach((disk: DiskTypes.Disk): void => {\n\t\t\tif (curSelected[disk.id]) {\n\t\t\t\tselected[disk.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[disk.id]) {\n\t\t\t\topened[disk.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisks: disks,\n\t\t\tfilter: DisksStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tnodes: CompletionStore.nodes,\n\t\t\tpools: CompletionStore.pools,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDiskActions.removeMulti(\n\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonForceDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDiskActions.forceRemoveMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonSnapshot = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDiskActions.updateMulti(\n\t\t\tObject.keys(this.state.selected), 'snapshot').then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonBackup = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDiskActions.updateMulti(\n\t\t\tObject.keys(this.state.selected), 'backup').then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet disksDom: JSX.Element[] = [];\n\n\t\tthis.state.disks.forEach((\n\t\t\tdisk: DiskTypes.DiskRo): void => {\n\t\t\tdisksDom.push(<Disk\n\t\t\t\tkey={disk.id}\n\t\t\t\tdisk={disk}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tpools={this.state.pools}\n\t\t\t\tselected={!!this.state.selected[disk.id]}\n\t\t\t\topen={!!this.state.opened[disk.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet disks = this.state.disks;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < disks.length; i++) {\n\t\t\t\t\t\t\tlet usr = disks[i];\n\n\t\t\t\t\t\t\tif (usr.id === disk.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[disks[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: disk.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[disk.id]) {\n\t\t\t\t\t\tdelete selected[disk.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[disk.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: disk.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[disk.id]) {\n\t\t\t\t\t\tdelete opened[disk.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[disk.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet newDiskDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewDiskDom = <DiskNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tzones={this.state.zones}\n\t\t\t\tpools={this.state.pools}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\tlet debugClass = 'bp5-button bp5-icon-console ';\n\t\tif (this.state.debug) {\n\t\t\tdebugClass += 'bp5-active';\n\t\t}\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = DisksStore.disk(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet sizeRow = <div style={{\"display\": \"table-row\"}}>\n\t\t\t<div style={{display: \"table-cell\", width: \"auto\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"auto\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"auto\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"auto\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"120px\"}}></div>\n\t\t</div>\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Disks</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={debugClass}\n\t\t\t\t\t\t\tstyle={css.debugToggle}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdebug: !this.state.debug,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tDebug\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tDiskActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tDiskActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Snapshot Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-primary bp5-icon-floppy-disk\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-primary\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Snapshot the selected disks\"\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onSnapshot}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Backup Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-primary bp5-icon-compressed\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-primary\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Backup the selected disks\"\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onBackup}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected disks\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"layout horizontal wrap\"\n\t\t\t\t\tstyle={css.debug}\n\t\t\t\t\thidden={!this.state.debug}\n\t\t\t\t>\n\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\tlabel=\"Force Delete Selected\"\n\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-warning-sign\"\n\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\tstyle={css.debugButton}\n\t\t\t\t\t\tconfirmMsg=\"Permanently force delete the selected disks\"\n\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\tonConfirm={this.onForceDelete}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<DisksFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tDiskActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tnodes={this.state.nodes}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{sizeRow}\n\t\t\t\t\t{newDiskDom}\n\t\t\t\t\t{disksDom}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!disksDom.length}\n\t\t\t\ticonClass=\"bp5-icon-floppy-disk\"\n\t\t\t\ttitle=\"No disks\"\n\t\t\t\tdescription=\"Add a new disk to get started.\"\n\t\t\t/>\n\t\t\t<DisksPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DisksFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as DiskTypes from '../types/DiskTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as NodeTypes from '../types/NodeTypes';\n\ninterface Props {\n\tfilter: DiskTypes.Filter;\n\tonFilter: (filter: DiskTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tnodes: NodeTypes.NodesRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class DisksFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet nodesSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any Node</option>,\n\t\t];\n\t\tif (this.props.nodes && this.props.nodes.length) {\n\t\t\tfor (let node of this.props.nodes) {\n\t\t\t\tnodesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={node.id}\n\t\t\t\t\t\tvalue={node.id}\n\t\t\t\t\t>{node.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Disk ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Instance ID\"\n\t\t\t\tvalue={this.props.filter.instance}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.instance = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.instance;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.node || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.node;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.node = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{nodesSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DisksPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport DisksStore from '../stores/DisksStore';\nimport * as DiskActions from '../actions/DiskActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class DisksPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: DisksStore.page,\n\t\t\tpageCount: DisksStore.pageCount,\n\t\t\tpages: DisksStore.pages,\n\t\t\tcount: DisksStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tDisksStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tDisksStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: DisksStore.page,\n\t\t\tpageCount: DisksStore.pageCount,\n\t\t\tpages: DisksStore.pages,\n\t\t\tcount: DisksStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDiskActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDiskActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDiskActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDiskActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDiskActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Domain.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DomainTypes from '../types/DomainTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\nimport DomainDetailed from './DomainDetailed';\nimport * as SecretTypes from \"../types/SecretTypes\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tdomain: DomainTypes.DomainRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Domain extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet domain = this.props.domain;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<DomainDetailed\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tsecrets={this.props.secrets}\n\t\t\t\t\tdomain={this.props.domain}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet orgName = '';\n\t\tif (domain.organization) {\n\t\t\tlet org = CompletionStore.organization(domain.organization);\n\t\t\torgName = org ? org.name : domain.organization;\n\t\t} else {\n\t\t\torgName = 'Node Domain';\n\t\t}\n\n\t\tlet providerName: string\n\t\tswitch (domain.type) {\n\t\t\tcase \"aws\":\n\t\t\t\tproviderName = \"AWS\"\n\t\t\t\tbreak\n\t\t\tcase \"cloudflare\":\n\t\t\t\tproviderName = \"Cloudflare\"\n\t\t\t\t\tbreak\n\t\t\tcase \"oracle_cloud\":\n\t\t\t\tproviderName = \"Oracle Cloud\"\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\tproviderName = \"-\"\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{domain.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName={'bp5-icon-standard bp5-text-muted ' + (\n\t\t\t\t\t\tdomain.organization ? 'bp5-icon-people' : 'bp5-icon-layers')}\n\t\t\t\t/>\n\t\t\t\t{orgName}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName={'bp5-icon-standard bp5-text-muted bp5-icon-database'}\n\t\t\t\t/>\n\t\t\t\t{providerName}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DomainDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DomainTypes from '../types/DomainTypes';\nimport * as DomainActions from '../actions/DomainActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from \"../stores/CompletionStore\";\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport PageSelect from \"./PageSelect\";\nimport PageTextArea from \"./PageTextArea\";\nimport DomainRecord from \"./DomainRecord\";\nimport * as Constants from \"../Constants\";\nimport * as SecretTypes from \"../types/SecretTypes\";\nimport Help from \"./Help\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tdomain: DomainTypes.DomainRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tdomain: DomainTypes.Domain;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class DomainDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tdomain: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet domain: any;\n\n\t\tif (this.state.changed) {\n\t\t\tdomain = {\n\t\t\t\t...this.state.domain,\n\t\t\t};\n\t\t} else {\n\t\t\tdomain = {\n\t\t\t\t...this.props.domain,\n\t\t\t};\n\t\t}\n\n\t\tdomain[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdomain: domain,\n\t\t});\n\t}\n\n\tonAddRecord = (): void => {\n\t\tlet domain: DomainTypes.Domain;\n\n\t\tif (this.state.changed) {\n\t\t\tdomain = {\n\t\t\t\t...this.state.domain,\n\t\t\t};\n\t\t} else {\n\t\t\tdomain = {\n\t\t\t\t...this.props.domain,\n\t\t\t};\n\t\t}\n\n\t\tlet records = [\n\t\t\t...(domain.records || []),\n\t\t\t{\n\t\t\t\toperation: \"insert\",\n\t\t\t},\n\t\t];\n\n\t\tdomain.records = records;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tdomain: domain,\n\t\t});\n\t}\n\n\tonChangeRecord(i: number, state: DomainTypes.Record): void {\n\t\tlet domain: DomainTypes.Domain;\n\n\t\tif (this.state.changed) {\n\t\t\tdomain = {\n\t\t\t\t...this.state.domain,\n\t\t\t};\n\t\t} else {\n\t\t\tdomain = {\n\t\t\t\t...this.props.domain,\n\t\t\t};\n\t\t}\n\n\t\tlet records = [\n\t\t\t...(domain.records || []),\n\t\t];\n\n\t\tstate.type = (state.type || \"A\")\n\n\t\trecords[i] = state;\n\n\t\tdomain.records = records;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tdomain: domain,\n\t\t});\n\t}\n\n\tonRemoveRecord(i: number): void {\n\t\tlet domain: DomainTypes.Domain;\n\n\t\tif (this.state.changed) {\n\t\t\tdomain = {\n\t\t\t\t...this.state.domain,\n\t\t\t};\n\t\t} else {\n\t\t\tdomain = {\n\t\t\t\t...this.props.domain,\n\t\t\t};\n\t\t}\n\n\t\tlet records = [\n\t\t\t...(domain.records || []),\n\t\t];\n\n\t\trecords[i] = {\n\t\t\t...records[i],\n\t\t\toperation: \"delete\",\n\t\t};\n\n\t\tdomain.records = records;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tdomain: domain,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDomainActions.commit(this.state.domain).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tdomain: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDomainActions.remove(this.props.domain.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet domain: DomainTypes.Domain = this.state.domain ||\n\t\t\tthis.props.domain;\n\n\t\tlet hasOrganizations = false\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\thasOrganizations = true\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet hasSecrets = false;\n\t\tlet secretsSelect: JSX.Element[] = [];\n\t\tif (this.props.secrets.length) {\n\t\t\tsecretsSelect.push(<option key=\"null\" value=\"\">Select Secret</option>);\n\n\t\t\tfor (let secret of this.props.secrets) {\n\t\t\t\tif (Constants.user) {\n\t\t\t\t\tif (domain.organization !== CompletionStore.userOrganization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (domain.organization != secret.organization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\thasSecrets = true;\n\t\t\t\tsecretsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={secret.id}\n\t\t\t\t\t\tvalue={secret.id}\n\t\t\t\t\t>{secret.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasSecrets) {\n\t\t\tsecretsSelect = [<option key=\"null\" value=\"\">No Secrets</option>];\n\t\t}\n\n\t\tlet records: JSX.Element[] = [];\n\t\tfor (let i = 0; i < domain.records.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tif (domain.records[index].operation === \"delete\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\trecords.push(\n\t\t\t\t<DomainRecord\n\t\t\t\t\tkey={index}\n\t\t\t\t\trecord={domain.records[index]}\n\t\t\t\t\tonChange={(state: DomainTypes.Record): void => {\n\t\t\t\t\t\tthis.onChangeRecord(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveRecord(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={3}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Domain\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this domain\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[domain.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Domain name.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={domain.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Domain comment.\"\n\t\t\t\t\t\tplaceholder=\"Domain comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={domain.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Domain\"\n\t\t\t\t\t\thelp=\"Root domain name.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter domain\"\n\t\t\t\t\t\tvalue={domain.root_domain}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('root_domain', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\tDomain Records\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Domain Records\"\n\t\t\t\t\t\t\tcontent=\"Domain DNS records.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t{records}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tstyle={css.itemsAdd}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={this.onAddRecord}\n\t\t\t\t\t>\n\t\t\t\t\t\tAdd Record\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.domain.id || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\thelp=\"Organization for domain.\"\n\t\t\t\t\t\tvalue={domain.organization}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tlabel=\"Provider\"\n\t\t\t\t\t\thelp=\"Domain provider.\"\n\t\t\t\t\t\tvalue={domain.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"local\">Local</option>\n\t\t\t\t\t\t<option value=\"aws\">AWS</option>\n\t\t\t\t\t\t<option value=\"cloudflare\">Cloudflare</option>\n\t\t\t\t\t\t<option value=\"oracle_cloud\">Oracle Cloud</option>\n\t\t\t\t\t\t<option value=\"google_cloud\">Google Cloud</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Provider API Secret\"\n\t\t\t\t\t\thelp=\"Secret containing API keys to use for provider.\"\n\t\t\t\t\t\thidden={!domain.type || domain.type === \"local\"}\n\t\t\t\t\t\tvalue={domain.secret}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('secret', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{secretsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.domain && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tdomain: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DomainNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as DomainActions from '../actions/DomainActions';\nimport PageInput from './PageInput';\nimport PageCreate from './PageCreate';\nimport PageSelect from './PageSelect';\nimport * as SecretTypes from \"../types/SecretTypes\";\nimport * as DomainTypes from \"../types/DomainTypes\";\nimport * as Constants from \"../Constants\";\nimport CompletionStore from \"../stores/CompletionStore\";\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tdomain: DomainTypes.Domain;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class DomainNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tdomain: {},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet domain: any = {\n\t\t\t...this.state.domain,\n\t\t};\n\n\t\tdomain[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tdomain: domain,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet domain: any = {\n\t\t\t...this.state.domain,\n\t\t};\n\n\t\tif (this.props.organizations.length && !domain.organization) {\n\t\t\tdomain.organization = this.props.organizations[0].id;\n\t\t}\n\n\t\tDomainActions.create(domain).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Domain created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet domain = this.state.domain;\n\t\tif (this.props.organizations.length && !domain.organization) {\n\t\t\tdomain.organization = this.props.organizations[0].id;\n\t\t}\n\n\t\tlet hasOrganizations = false\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\thasOrganizations = true\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet hasSecrets = false;\n\t\tlet secretsSelect: JSX.Element[] = [];\n\t\tif (this.props.secrets.length) {\n\t\t\tsecretsSelect.push(<option key=\"null\" value=\"\">Select Secret</option>);\n\n\t\t\tfor (let secret of this.props.secrets) {\n\t\t\t\tif (Constants.user) {\n\t\t\t\t\tif (domain.organization !== CompletionStore.userOrganization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (domain.organization != secret.organization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\thasSecrets = true;\n\t\t\t\tsecretsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={secret.id}\n\t\t\t\t\t\tvalue={secret.id}\n\t\t\t\t\t>{secret.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasSecrets) {\n\t\t\tsecretsSelect = [<option key=\"null\" value=\"\">No Secrets</option>];\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={3}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Domain name.\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={domain.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Domain comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Domain comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={domain.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Domain\"\n\t\t\t\t\t\t\thelp=\"Root domain name.\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter domain\"\n\t\t\t\t\t\t\tvalue={domain.root_domain}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('root_domain', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for domain.\"\n\t\t\t\t\t\t\tvalue={domain.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tlabel=\"Provider\"\n\t\t\t\t\t\t\thelp=\"Domain provider.\"\n\t\t\t\t\t\t\tvalue={domain.type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"local\">Local</option>\n\t\t\t\t\t\t\t<option value=\"aws\">AWS</option>\n\t\t\t\t\t\t\t<option value=\"cloudflare\">Cloudflare</option>\n\t\t\t\t\t\t\t<option value=\"oracle_cloud\">Oracle Cloud</option>\n\t\t\t\t\t\t\t<option value=\"google_cloud\">Google Cloud</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Provider API Secret\"\n\t\t\t\t\t\t\thelp=\"Secret containing API keys to use for provider.\"\n\t\t\t\t\t\t\thidden={!domain.type || domain.type === \"local\"}\n\t\t\t\t\t\t\tvalue={domain.secret}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('secret', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{secretsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.domain}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DomainRecord.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as DomainTypes from '../types/DomainTypes';\n\ninterface Props {\n\trecord: DomainTypes.Record;\n\tonChange: (record: DomainTypes.Record) => void;\n\tonRemove: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tdomain: {\n\t\twidth: '100%',\n\t\tborderRadius: '0 3px 3px 0',\n\t} as React.CSSProperties,\n\tdomainBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class DomainRecord extends React.Component<Props, {}> {\n\tclone(): DomainTypes.Record {\n\t\treturn {\n\t\t\t...this.props.record,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet record = this.props.record;\n\n\t\treturn <div className=\"bp5-control-group\" style={css.group}>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={record.type}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.type = evt.target.value;\n\t\t\t\t\t\tif (!state.operation) {\n\t\t\t\t\t\t\tstate.operation = \"update\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<option value=\"A\">A</option>\n\t\t\t\t\t<option value=\"AAAA\">AAAA</option>\n\t\t\t\t\t<option value=\"CNAME\">CNAME</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div style={css.domainBox}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.domain}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Sub Domain\"\n\t\t\t\t\tvalue={record.sub_domain || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.sub_domain = evt.target.value;\n\t\t\t\t\t\tif (!state.operation) {\n\t\t\t\t\t\t\tstate.operation = \"update\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div style={css.domainBox}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.domain}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"IP Address\"\n\t\t\t\t\tvalue={record.value || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.value = evt.target.value;\n\t\t\t\t\t\tif (!state.operation) {\n\t\t\t\t\t\t\tstate.operation = \"update\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Domains.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from \"../Constants\";\nimport * as DomainTypes from '../types/DomainTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport DomainsStore from '../stores/DomainsStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as DomainActions from '../actions/DomainActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Domain from './Domain';\nimport DomainsFilter from './DomainsFilter';\nimport DomainsPage from './DomainsPage';\nimport DomainNew from './DomainNew';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\nimport * as SecretTypes from \"../types/SecretTypes\";\nimport DiskNew from \"./DiskNew\";\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tdomains: DomainTypes.DomainsRo;\n\tfilter: DomainTypes.Filter;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\torganization: string;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tgroupBox: {\n\t\tmargin: '16px 0 0 8px',\n\t\twidth: '100%',\n\t\tmaxWidth: '200px',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tselectFirst: {\n\t\twidth: '100%',\n\t\tborderTopLeftRadius: '3px',\n\t\tborderBottomLeftRadius: '3px',\n\t} as React.CSSProperties,\n\tselectInner: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tselectBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Domains extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdomains: DomainsStore.domains,\n\t\t\tfilter: DomainsStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tsecrets: CompletionStore.secrets,\n\t\t\torganization: '',\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tDomainsStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tDomainActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tDomainsStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet domains = DomainsStore.domains;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tdomains.forEach((domain: DomainTypes.Domain): void => {\n\t\t\tif (curSelected[domain.id]) {\n\t\t\t\tselected[domain.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[domain.id]) {\n\t\t\t\topened[domain.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdomains: domains,\n\t\t\tfilter: DomainsStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tsecrets: CompletionStore.secrets,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tDomainActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet domainsDom: JSX.Element[] = [];\n\n\t\tlet hasOrganizations = false;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.state.organizations.length) {\n\t\t\thasOrganizations = true;\n\t\t\tfor (let organization of this.state.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey=\"null\"\n\t\t\t\t\tvalue=\"\"\n\t\t\t\t>No Organizations</option>,\n\t\t\t);\n\t\t}\n\n\t\tthis.state.domains.forEach((\n\t\t\t\tdomain: DomainTypes.DomainRo): void => {\n\t\t\tdomainsDom.push(<Domain\n\t\t\t\tkey={domain.id}\n\t\t\t\tdomain={domain}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tsecrets={this.state.secrets}\n\t\t\t\tselected={!!this.state.selected[domain.id]}\n\t\t\t\topen={!!this.state.opened[domain.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet domains = this.state.domains;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < domains.length; i++) {\n\t\t\t\t\t\t\tlet usr = domains[i];\n\n\t\t\t\t\t\t\tif (usr.id === domain.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[domains[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: domain.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[domain.id]) {\n\t\t\t\t\t\tdelete selected[domain.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[domain.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: domain.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[domain.id]) {\n\t\t\t\t\t\tdelete opened[domain.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[domain.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let domainId of Object.keys(this.state.selected)) {\n\t\t\tlet domain = DomainsStore.domain(domainId);\n\t\t\tif (domain) {\n\t\t\t\tselectedNames.push(domain.name || domainId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(domainId);\n\t\t\t}\n\t\t}\n\n\t\tlet newDiskDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewDiskDom = <DomainNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tsecrets={this.state.secrets}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Domains</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tDomainActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tDomainActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected domains\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<DomainsFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tDomainActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newDiskDom}\n\t\t\t\t\t{domainsDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={3} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!domainsDom.length}\n\t\t\t\ticonClass=\"bp5-icon-map-marker\"\n\t\t\t\ttitle=\"No domains\"\n\t\t\t\tdescription=\"Add a new domain to get started.\"\n\t\t\t/>\n\t\t\t<DomainsPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DomainsFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as DomainTypes from '../types/DomainTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: DomainTypes.Filter;\n\tonFilter: (filter: DomainTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class DomainsFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Domain ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/DomainsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport DomainsStore from '../stores/DomainsStore';\nimport * as DomainActions from '../actions/DomainActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class DomainsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: DomainsStore.page,\n\t\t\tpageCount: DomainsStore.pageCount,\n\t\t\tpages: DomainsStore.pages,\n\t\t\tcount: DomainsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tDomainsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tDomainsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: DomainsStore.page,\n\t\t\tpageCount: DomainsStore.pageCount,\n\t\t\tpages: DomainsStore.pages,\n\t\t\tcount: DomainsStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDomainActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDomainActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDomainActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDomainActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tDomainActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Editor.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from \"react\"\nimport * as Blueprint from \"@blueprintjs/core\"\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Theme from \"../Theme\"\nimport * as MonacoEditor from \"@monaco-editor/react\"\nimport * as Monaco from \"monaco-editor\"\n\ninterface Props {\n\tdisabled?: boolean\n\tvalue?: string\n\treadOnly?: boolean\n\tmode?: string\n\tfontSize?: number\n\theight?: string\n\twidth?: string\n\tinterval?: number\n\tautoScroll?: boolean\n\tstyle?: React.CSSProperties\n\trefresh?: (first: boolean) => Promise<string>\n\tonChange?: (value: string) => void\n}\n\ninterface State {\n\tpaused: boolean\n}\n\nconst css = {\n\teditor: {\n\t\tmargin: \"0\",\n\t\tborderRadius: \"3px\",\n\t\twidth: \"100%\",\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: \"absolute\",\n\t\tbottom: \"10px\",\n\t\tright: \"24px\",\n\t\tzIndex: 100,\n\t\topacity: 0.7,\n\t} as React.CSSProperties,\n\tcardBox: {\n\t\tposition: \"relative\",\n\t} as React.CSSProperties,\n}\n\nexport default class Editor extends React.Component<Props, State> {\n\teditor: Monaco.editor.IStandaloneCodeEditor\n\tmonaco: MonacoEditor.Monaco\n\tvalue: string\n\tsync: MiscUtils.SyncInterval;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context)\n\t\tthis.state = {\n\t\t\tpaused: false,\n\t\t}\n\t}\n\n\tcomponentDidMount(): void {\n\t\tif (this.props.interval) {\n\t\t\tthis.sync = new MiscUtils.SyncInterval(\n\t\t\t\t() => {\n\t\t\t\t\tif (!this.isScrolledToBottom()) {\n\t\t\t\t\t\tif (!this.state.paused) {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tpaused: true,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Promise.resolve()\n\t\t\t\t\t}\n\t\t\t\t\tif (this.state.paused) {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tpaused: false,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\treturn this.props.refresh(false).then((val) => {\n\t\t\t\t\t\tthis.update(val)\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tthis.props.interval,\n\t\t\t)\n\t\t}\n\t\tif (!this.props.value && this.props.refresh) {\n\t\t\tthis.props.refresh(true).then((val) => {\n\t\t\t\tthis.update(val)\n\t\t\t})\n\t\t}\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tthis.sync?.stop()\n\t}\n\n\trefresh(): void {\n\t\tthis.props.refresh(true).then((val) => {\n\t\t\tthis.update(val)\n\t\t})\n\t}\n\n\tisScrolledToBottom(): boolean {\n\t\tif (!this.editor) {\n\t\t\treturn false\n\t\t}\n\n\t\tconst scrollTop = this.editor.getScrollTop()\n\t\tconst scrollHeight = this.editor.getScrollHeight()\n\t\tconst layoutInfo = this.editor.getLayoutInfo()\n\t\tconst visibleHeight = layoutInfo.height\n\n\t\tconst threshold = 10\n\t\treturn scrollTop + visibleHeight >= scrollHeight - threshold\n\t}\n\n\tupdate(val: string): void {\n\t\tlet curValue = this.value || this.props.value\n\t\tif (curValue === val) {\n\t\t\treturn\n\t\t}\n\t\tthis.value = val;\n\n\t\tconst model = this.editor.getModel()\n\t\tif (model) {\n\t\t\tmodel.setValue(val)\n\n\t\t\tif (this.props.autoScroll) {\n\t\t\t\tconst lineCount = model.getLineCount()\n\t\t\t\tthis.editor.revealLine(lineCount)\n\t\t\t\tthis.editor.setPosition({\n\t\t\t\t\tlineNumber: lineCount,\n\t\t\t\t\tcolumn: model.getLineMaxColumn(lineCount),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\trender(): JSX.Element {\n\t\tlet style: React.CSSProperties\n\t\tif (this.props.style) {\n\t\t\tstyle = {\n\t\t\t\t...css.editor,\n\t\t\t\t...this.props.style,\n\t\t\t}\n\t\t} else {\n\t\t\tstyle = css.editor\n\t\t}\n\n\t\treturn <div style={style}>\n\t\t\t<MonacoEditor.Editor\n\t\t\t\theight={this.props.height}\n\t\t\t\twidth={this.props.width}\n\t\t\t\tdefaultLanguage=\"markdown\"\n\t\t\t\ttheme={Theme.getEditorTheme()}\n\t\t\t\tvalue={this.props.value}\n\t\t\t\tonMount={(editor: Monaco.editor.IStandaloneCodeEditor,\n\t\t\t\t\t\tmonaco: MonacoEditor.Monaco): void => {\n\t\t\t\t\tthis.monaco = monaco\n\t\t\t\t\tthis.editor = editor\n\t\t\t\t}}\n\t\t\t\toptions={{\n\t\t\t\t\tfolding: false,\n\t\t\t\t\tfontSize: this.props.fontSize || 12,\n\t\t\t\t\tfontFamily: Theme.monospaceFont,\n\t\t\t\t\tfontWeight: Theme.monospaceWeight,\n\t\t\t\t\ttabSize: 4,\n\t\t\t\t\tdetectIndentation: false,\n\t\t\t\t\treadOnly: this.props.readOnly,\n\t\t\t\t\t//rulers: [80],\n\t\t\t\t\tscrollBeyondLastLine: false,\n\t\t\t\t\tminimap: {\n\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t},\n\t\t\t\t\twordWrap: \"on\",\n\t\t\t\t}}\n\t\t\t\tonChange={(val): void => {\n\t\t\t\t\tif (this.props.onChange) {\n\t\t\t\t\t\tthis.props.onChange(val)\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{this.state.paused && <div style={css.cardBox}>\n\t\t\t\t<Blueprint.Tag style={css.card}>\n\t\t\t\t\tRefresh Paused While Scrolling\n\t\t\t\t</Blueprint.Tag>\n\t\t\t</div>}\n\t\t</div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Firewall.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as FirewallTypes from '../types/FirewallTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\nimport FirewallDetailed from './FirewallDetailed';\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tfirewall: FirewallTypes.FirewallRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Firewall extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet firewall = this.props.firewall;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<FirewallDetailed\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tfirewall={this.props.firewall}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet networkRoles: JSX.Element[] = [];\n\t\tfor (let networkRole of (firewall.roles || [])) {\n\t\t\tnetworkRoles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-intent-primary\"\n\t\t\t\t\tstyle={css.tag}\n\t\t\t\t\tkey={networkRole}\n\t\t\t\t>\n\t\t\t\t\t{networkRole}\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet orgName = '';\n\t\tif (!MiscUtils.objectIdNil(firewall.organization)) {\n\t\t\tlet org = CompletionStore.organization(firewall.organization);\n\t\t\torgName = org ? org.name : firewall.organization;\n\t\t} else {\n\t\t\torgName = 'Node Firewall';\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{firewall.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName={'bp5-icon-standard bp5-text-muted ' + (\n\t\t\t\t\t\tfirewall.organization ? 'bp5-icon-people' : 'bp5-icon-layers')}\n\t\t\t\t/>\n\t\t\t\t{orgName}\n\t\t\t</div>\n\t\t\t<div className=\"flex bp5-cell\" style={css.roles}>\n\t\t\t\t{networkRoles}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/FirewallDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as FirewallTypes from '../types/FirewallTypes';\nimport * as FirewallActions from '../actions/FirewallActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport FirewallRule from './FirewallRule';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageInputButton from './PageInputButton';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tfirewall: FirewallTypes.FirewallRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddNetworkRole: string;\n\tfirewall: FirewallTypes.Firewall;\n\tingress: FirewallTypes.Rule;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class FirewallDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: null,\n\t\t\tfirewall: null,\n\t\t\tingress: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet firewall: any;\n\n\t\tif (this.state.changed) {\n\t\t\tfirewall = {\n\t\t\t\t...this.state.firewall,\n\t\t\t};\n\t\t} else {\n\t\t\tfirewall = {\n\t\t\t\t...this.props.firewall,\n\t\t\t};\n\t\t}\n\n\t\tfirewall[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonAddNetworkRole = (): void => {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tif (!this.state.addNetworkRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tfirewall = {\n\t\t\t\t...this.state.firewall,\n\t\t\t};\n\t\t} else {\n\t\t\tfirewall = {\n\t\t\t\t...this.props.firewall,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(firewall.roles || []),\n\t\t];\n\n\n\t\tif (roles.indexOf(this.state.addNetworkRole) === -1) {\n\t\t\troles.push(this.state.addNetworkRole);\n\t\t}\n\n\t\troles.sort();\n\t\tfirewall.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonRemoveNetworkRole = (networkRole: string): void => {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tif (this.state.changed) {\n\t\t\tfirewall = {\n\t\t\t\t...this.state.firewall,\n\t\t\t};\n\t\t} else {\n\t\t\tfirewall = {\n\t\t\t\t...this.props.firewall,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(firewall.roles || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(networkRole);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\t\tfirewall.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonAddIngress = (i: number, prepend: boolean): void => {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tif (this.state.changed) {\n\t\t\tfirewall = {\n\t\t\t\t...this.state.firewall,\n\t\t\t};\n\t\t} else {\n\t\t\tfirewall = {\n\t\t\t\t...this.props.firewall,\n\t\t\t};\n\t\t}\n\n\t\tlet ingress = [\n\t\t\t...firewall.ingress,\n\t\t];\n\n\t\tingress.splice(prepend ? i : i + 1, 0, {\n\t\t\tprotocol: 'all',\n\t\t\tsource_ips: [\n\t\t\t\t'0.0.0.0/0',\n\t\t\t\t'::/0',\n\t\t\t],\n\t\t} as FirewallTypes.Rule);\n\t\tfirewall.ingress = ingress;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonChangeIngress(i: number, rule: FirewallTypes.Rule): void {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tif (this.state.changed) {\n\t\t\tfirewall = {\n\t\t\t\t...this.state.firewall,\n\t\t\t};\n\t\t} else {\n\t\t\tfirewall = {\n\t\t\t\t...this.props.firewall,\n\t\t\t};\n\t\t}\n\n\t\tlet ingress = [\n\t\t\t...firewall.ingress,\n\t\t];\n\n\t\tingress[i] = rule;\n\n\t\tfirewall.ingress = ingress;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonRemoveIngress(i: number): void {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tif (this.state.changed) {\n\t\t\tfirewall = {\n\t\t\t\t...this.state.firewall,\n\t\t\t};\n\t\t} else {\n\t\t\tfirewall = {\n\t\t\t\t...this.props.firewall,\n\t\t\t};\n\t\t}\n\n\t\tlet ingress = [\n\t\t\t...firewall.ingress,\n\t\t];\n\n\t\tingress.splice(i, 1);\n\n\t\tif (!ingress.length) {\n\t\t\tingress = [\n\t\t\t\t{\n\t\t\t\t\tprotocol: 'all',\n\t\t\t\t\tsource_ips: [\n\t\t\t\t\t\t'0.0.0.0/0',\n\t\t\t\t\t\t'::/0',\n\t\t\t\t\t],\n\t\t\t\t} as FirewallTypes.Rule,\n\t\t\t];\n\t\t}\n\n\t\tfirewall.ingress = ingress;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tFirewallActions.commit(this.state.firewall).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tfirewall: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tFirewallActions.remove(this.props.firewall.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet firewall: FirewallTypes.Firewall = this.state.firewall ||\n\t\t\tthis.props.firewall;\n\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\torganizationsSelect.push(\n\t\t\t<option key=\"null\" value=\"\">Node Firewall</option>);\n\t\t(this.props.organizations || []).forEach((org, index) => {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey={org.id}\n\t\t\t\t\tvalue={org.id}\n\t\t\t\t>{org.name}</option>,\n\t\t\t);\n\t\t});\n\n\t\tlet roles: JSX.Element[] = [];\n\t\t(firewall.roles || []).forEach((role, index) => {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveNetworkRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t})\n\n\t\tlet rules: JSX.Element[] = [];\n\t\t(firewall.ingress || []).forEach((rule, index) => {\n\t\t\trules.push(\n\t\t\t\t<FirewallRule\n\t\t\t\t\tkey={index}\n\t\t\t\t\trule={firewall.ingress[index]}\n\t\t\t\t\tonChange={(state: FirewallTypes.Rule): void => {\n\t\t\t\t\t\tthis.onChangeIngress(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddIngress(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveIngress(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t})\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={3}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\t\t\t\tif (target.className && target.className.indexOf &&\n\t\t\t\t\t\t\t\ttarget.className.indexOf('tab-close') !== -1) {\n\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n            <div>\n              <label\n                className=\"bp5-control bp5-checkbox\"\n                style={css.select}\n              >\n                <input\n                  type=\"checkbox\"\n                  checked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n                  onClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n                />\n                <span className=\"bp5-control-indicator\"/>\n              </label>\n            </div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"firewall\" id={this.props.firewall.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Firewall\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this firewall\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[firewall.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of firewall\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={firewall.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Firewall comment.\"\n\t\t\t\t\t\tplaceholder=\"Firewall comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={firewall.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\tIngress Rules\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Ingress Rules\"\n\t\t\t\t\t\t\tcontent=\"Firewall rules.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.rules}>\n\t\t\t\t\t\t{rules}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.firewall.id || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\thelp=\"Organization for firewall, both the organaization and role must match. Select node firewall to match node network roles.\"\n\t\t\t\t\t\tvalue={firewall.organization}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\tRoles\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\tcontent=\"Roles that will be matched with firewall rules. Roles are case-sensitive.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\tvalue={this.state.addNetworkRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddNetworkRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddNetworkRole}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.firewall && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tfirewall: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/FirewallNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as FirewallTypes from '../types/FirewallTypes';\nimport * as FirewallActions from '../actions/FirewallActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport FirewallRule from './FirewallRule';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageInputButton from './PageInputButton';\nimport PageCreate from './PageCreate';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddNetworkRole: string;\n\tfirewall: FirewallTypes.Firewall;\n\tingress: FirewallTypes.Rule;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class FirewallNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: null,\n\t\t\tingress: null,\n\t\t\tfirewall: {\n\t\t\t\tname: \"new-firewall\",\n\t\t\t\tingress: [\n\t\t\t\t\t{\n\t\t\t\t\t\tsource_ips: [\n\t\t\t\t\t\t\t'0.0.0.0/0',\n\t\t\t\t\t\t\t'::/0',\n\t\t\t\t\t\t],\n\t\t\t\t\t\tprotocol: 'icmp',\n\t\t\t\t\t} as FirewallTypes.Rule,\n\t\t\t\t\t{\n\t\t\t\t\t\tsource_ips: [\n\t\t\t\t\t\t\t'0.0.0.0/0',\n\t\t\t\t\t\t\t'::/0',\n\t\t\t\t\t\t],\n\t\t\t\t\t\tprotocol: 'tcp',\n\t\t\t\t\t\tport: '22',\n\t\t\t\t\t} as FirewallTypes.Rule,\n\t\t\t\t],\n\t\t\t\tcomment: '22/tcp - SSH connections',\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet firewall: any = {\n\t\t\t...this.state.firewall,\n\t\t};\n\n\t\tfirewall[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonAddNetworkRole = (): void => {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tfirewall = {\n\t\t\t...this.state.firewall,\n\t\t};\n\n\t\tif (!this.state.addNetworkRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet networkRoles = [\n\t\t\t...(firewall.roles || []),\n\t\t];\n\n\n\t\tif (networkRoles.indexOf(this.state.addNetworkRole) === -1) {\n\t\t\tnetworkRoles.push(this.state.addNetworkRole);\n\t\t}\n\n\t\tnetworkRoles.sort();\n\t\tfirewall.roles = networkRoles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonRemoveNetworkRole = (networkRole: string): void => {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tfirewall = {\n\t\t\t...this.state.firewall,\n\t\t};\n\n\t\tlet networkRoles = [\n\t\t\t...(firewall.roles || []),\n\t\t];\n\n\t\tlet i = networkRoles.indexOf(networkRole);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tnetworkRoles.splice(i, 1);\n\t\tfirewall.roles = networkRoles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonAddIngress = (i: number, prepend: boolean): void => {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tfirewall = {\n\t\t\t...this.state.firewall,\n\t\t};\n\n\t\tlet ingress = [\n\t\t\t...firewall.ingress,\n\t\t];\n\n\t\tingress.splice(prepend ? i : i + 1, 0, {\n\t\t\tprotocol: 'all',\n\t\t\tsource_ips: [\n\t\t\t\t'0.0.0.0/0',\n\t\t\t\t'::/0',\n\t\t\t],\n\t\t} as FirewallTypes.Rule);\n\t\tfirewall.ingress = ingress;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonChangeIngress(i: number, rule: FirewallTypes.Rule): void {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tfirewall = {\n\t\t\t...this.state.firewall,\n\t\t};\n\n\t\tlet ingress = [\n\t\t\t...firewall.ingress,\n\t\t];\n\n\t\tingress[i] = rule;\n\n\t\tfirewall.ingress = ingress;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonRemoveIngress(i: number): void {\n\t\tlet firewall: FirewallTypes.Firewall;\n\n\t\tfirewall = {\n\t\t\t...this.state.firewall,\n\t\t};\n\n\t\tlet ingress = [\n\t\t\t...firewall.ingress,\n\t\t];\n\n\t\tingress.splice(i, 1);\n\n\t\tif (!ingress.length) {\n\t\t\tingress = [\n\t\t\t\t{\n\t\t\t\t\tprotocol: 'all',\n\t\t\t\t\tsource_ips: [\n\t\t\t\t\t\t'0.0.0.0/0',\n\t\t\t\t\t\t'::/0',\n\t\t\t\t\t],\n\t\t\t\t} as FirewallTypes.Rule,\n\t\t\t];\n\t\t}\n\n\t\tfirewall.ingress = ingress;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tfirewall: firewall,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet firewall: any = {\n\t\t\t...this.state.firewall,\n\t\t};\n\n\t\tFirewallActions.create(firewall).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Firewall created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet firewall: FirewallTypes.Firewall = this.state.firewall;\n\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\torganizationsSelect.push(\n\t\t\t<option key=\"null\" value=\"\">Node Firewall</option>);\n\t\t(this.props.organizations || []).forEach((org, index) => {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey={org.id}\n\t\t\t\t\tvalue={org.id}\n\t\t\t\t>{org.name}</option>,\n\t\t\t);\n\t\t});\n\n\t\tlet networkRoles: JSX.Element[] = [];\n\t\t(firewall.roles || []).forEach((role, index) => {\n\t\t\tnetworkRoles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveNetworkRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t})\n\n\t\tlet rules: JSX.Element[] = [];\n\t\t(firewall.ingress || []).forEach((rule, index) => {\n\t\t\trules.push(\n\t\t\t\t<FirewallRule\n\t\t\t\t\tkey={index}\n\t\t\t\t\trule={firewall.ingress[index]}\n\t\t\t\t\tonChange={(state: FirewallTypes.Rule): void => {\n\t\t\t\t\t\tthis.onChangeIngress(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddIngress(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveIngress(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t})\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={3}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of firewall\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={firewall.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Firewall comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Firewall comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={firewall.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\t\tIngress Rules\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Ingress Rules\"\n\t\t\t\t\t\t\t\tcontent=\"Firewall rules.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<div style={css.rules}>\n\t\t\t\t\t\t\t{rules}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for firewall, both the organaization and role must match. Select node firewall to match node network roles.\"\n\t\t\t\t\t\t\tvalue={firewall.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\t\tRoles\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\t\tcontent=\"Roles that will be matched with firewall rules. Roles are case-sensitive.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{networkRoles}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\t\tvalue={this.state.addNetworkRole}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddNetworkRole: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddNetworkRole}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.firewall}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/FirewallRule.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as FirewallTypes from '../types/FirewallTypes';\n\ninterface Props {\n\trule: FirewallTypes.Rule;\n\tonChange: (state: FirewallTypes.Rule) => void;\n\tonAdd: (prepend: boolean) => void;\n\tonRemove: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tsourceGroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '219px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tportBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tother: {\n\t\tflex: '0 1 auto',\n\t\twidth: '52px',\n\t\tborderRadius: '0 3px 3px 0',\n\t} as React.CSSProperties,\n};\n\nexport default class FirewallRule extends React.Component<Props, {}> {\n\tclone(): FirewallTypes.Rule {\n\t\treturn {\n\t\t\t...this.props.rule,\n\t\t};\n\t}\n\n\tonAddSourceIp = (i: number, prepend: boolean): void => {\n\t\tlet state = this.clone();\n\n\t\tlet sourceIps = [\n\t\t\t...(state.source_ips || []),\n\t\t];\n\n\t\tsourceIps.splice(prepend ? i : i + 1, 0, '');\n\t\tstate.source_ips = sourceIps;\n\n\t\tthis.props.onChange(state);\n\t}\n\n\tonChangeSourceIp = (i: number, sourceIp: string): void => {\n\t\tlet state = this.clone();\n\n\t\tlet sourceIps = [\n\t\t\t...(state.source_ips || []),\n\t\t];\n\n\t\tsourceIps[i] = sourceIp;\n\t\tstate.source_ips = sourceIps;\n\n\t\tthis.props.onChange(state);\n\t}\n\n\tonRemoveSourceIp = (i: number): void => {\n\t\tlet state = this.clone();\n\n\t\tlet sourceIps = [\n\t\t\t...(state.source_ips || []),\n\t\t];\n\n\t\tsourceIps.splice(i, 1);\n\t\tstate.source_ips = sourceIps;\n\n\t\tthis.props.onChange(state);\n\t}\n\n\trender(): JSX.Element {\n\t\tlet rule = this.props.rule;\n\n\t\tlet port = rule.port;\n\t\tlet placeholder = '';\n\t\tswitch (rule.protocol) {\n\t\t\tcase 'all':\n\t\t\t\tport = null;\n\t\t\t\tplaceholder = 'Allow all traffic';\n\t\t\t\tbreak;\n\t\t\tcase 'icmp':\n\t\t\t\tport = null;\n\t\t\t\tplaceholder = 'Allow all ICMP traffic';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet sourceIps = (rule.source_ips || []);\n\t\tif (sourceIps.length === 0) {\n\t\t\tsourceIps.push('');\n\t\t}\n\n\t\tlet sourceIpsDoms: JSX.Element[] = [];\n\t\tsourceIps.forEach((sourceIp: string, i: number): void => {\n\t\t\tsourceIpsDoms.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-control-group\"\n\t\t\t\t\tstyle={css.sourceGroup}\n\t\t\t\t\thidden={rule.protocol === \"multicast\" ||\n\t\t\t\t\t\trule.protocol === \"broadcast\"}\n\t\t\t\t\tkey={i}\n\t\t\t\t>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.port}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"Source IP range\"\n\t\t\t\t\t\tvalue={sourceIp}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tthis.onChangeSourceIp(i, evt.target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveSourceIp(i);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tthis.onAddSourceIp(i, evt.shiftKey);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t);\n\t\t});\n\n\t\treturn <div>\n\t\t\t<div className=\"bp5-control-group\" style={css.group}>\n\t\t\t\t<div className=\"bp5-select\" style={css.protocol}>\n\t\t\t\t\t<select\n\t\t\t\t\t\tvalue={rule.protocol}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.protocol = evt.target.value;\n\n\t\t\t\t\t\t\tif (state.protocol === 'all' || state.protocol === 'icmp') {\n\t\t\t\t\t\t\t\tstate.port = null;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"all\">All Traffic</option>\n\t\t\t\t\t\t<option value=\"icmp\">ICMP</option>\n\t\t\t\t\t\t<option value=\"tcp\">TCP</option>\n\t\t\t\t\t\t<option value=\"udp\">UDP</option>\n\t\t\t\t\t\t<option value=\"multicast\">Multicast</option>\n\t\t\t\t\t\t<option value=\"broadcast\">Broadcast</option>\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.portBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.port}\n\t\t\t\t\t\tdisabled={!!placeholder}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder={placeholder || 'Enter port range'}\n\t\t\t\t\t\tvalue={rule.port || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.port = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\tthis.props.onAdd(evt.shiftKey);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t{sourceIpsDoms}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Firewalls.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as FirewallTypes from '../types/FirewallTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport FirewallsStore from '../stores/FirewallsStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as FirewallActions from '../actions/FirewallActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Firewall from './Firewall';\nimport FirewallNew from './FirewallNew';\nimport FirewallsFilter from './FirewallsFilter';\nimport FirewallsPage from './FirewallsPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tfirewalls: FirewallTypes.FirewallsRo;\n\tfilter: FirewallTypes.Filter;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Firewalls extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tfirewalls: FirewallsStore.firewalls,\n\t\t\tfilter: FirewallsStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tFirewallsStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tFirewallActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tFirewallsStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet firewalls = FirewallsStore.firewalls;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tfirewalls.forEach((firewall: FirewallTypes.Firewall): void => {\n\t\t\tif (curSelected[firewall.id]) {\n\t\t\t\tselected[firewall.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[firewall.id]) {\n\t\t\t\topened[firewall.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tfirewalls: firewalls,\n\t\t\tfilter: FirewallsStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tFirewallActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet firewallsDom: JSX.Element[] = [];\n\n\t\tthis.state.firewalls.forEach((\n\t\t\t\tfirewall: FirewallTypes.FirewallRo): void => {\n\t\t\tfirewallsDom.push(<Firewall\n\t\t\t\tkey={firewall.id}\n\t\t\t\tfirewall={firewall}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tselected={!!this.state.selected[firewall.id]}\n\t\t\t\topen={!!this.state.opened[firewall.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet firewalls = this.state.firewalls;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < firewalls.length; i++) {\n\t\t\t\t\t\t\tlet usr = firewalls[i];\n\n\t\t\t\t\t\t\tif (usr.id === firewall.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[firewalls[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: firewall.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[firewall.id]) {\n\t\t\t\t\t\tdelete selected[firewall.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[firewall.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: firewall.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[firewall.id]) {\n\t\t\t\t\t\tdelete opened[firewall.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[firewall.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = FirewallsStore.firewall(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newFireDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewFireDom = <FirewallNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Firewalls</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tFirewallActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tFirewallActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected firewalls\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<FirewallsFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tFirewallActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newFireDom}\n\t\t\t\t\t{firewallsDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={3} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!firewallsDom.length}\n\t\t\t\ticonClass=\"bp5-icon-shield\"\n\t\t\t\ttitle=\"No firewalls\"\n\t\t\t\tdescription=\"Add a new firewall to get started.\"\n\t\t\t/>\n\t\t\t<FirewallsPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/FirewallsFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as FirewallTypes from '../types/FirewallTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: FirewallTypes.Filter;\n\tonFilter: (filter: FirewallTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class FirewallsFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Firewall ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.role}\n\t\t\t\tplaceholder=\"Network Role\"\n\t\t\t\tvalue={this.props.filter.role}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.role = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.role;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/FirewallsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport FirewallsStore from '../stores/FirewallsStore';\nimport * as FirewallActions from '../actions/FirewallActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class FirewallsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: FirewallsStore.page,\n\t\t\tpageCount: FirewallsStore.pageCount,\n\t\t\tpages: FirewallsStore.pages,\n\t\t\tcount: FirewallsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tFirewallsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tFirewallsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: FirewallsStore.page,\n\t\t\tpageCount: FirewallsStore.pageCount,\n\t\t\tpages: FirewallsStore.pages,\n\t\t\tcount: FirewallsStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tFirewallActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tFirewallActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tFirewallActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tFirewallActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tFirewallActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Help.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\n\ninterface Props {\n\ttitle: string;\n\tcontent?: string | JSX.Element;\n\tcontents?: string[];\n\texamples?: string[];\n}\n\ninterface State {\n\tpopover: boolean;\n}\n\nconst css = {\n\tbox: {\n\t\tdisplay: 'inline',\n\t} as React.CSSProperties,\n\tcontent: {\n\t\tpadding: '20px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tdisplay: 'inline-block',\n\t\tbackground: 'none',\n\t\topacity: 0.3,\n\t\tmarginLeft: '5px',\n\t\theight: '16px',\n\t\tminHeight: '16px',\n\t\twidth: '16px',\n\t\tminWidth: '16px',\n\t\tposition: 'relative',\n\t\ttop: '-1px',\n\t} as React.CSSProperties,\n\tpopover: {\n\t\twidth: '230px',\n\t} as React.CSSProperties,\n\tpopoverTarget: {\n\t\ttop: '9px',\n\t\tleft: '18px',\n\t} as React.CSSProperties,\n\tdialog: {\n\t\tmaxWidth: '400px',\n\t\tmargin: '30px 20px',\n\t} as React.CSSProperties,\n\tinputFirst: {\n\t\twidth: '100%',\n\t\tmarginTop: '15px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n};\n\nlet dialog = true;\n\nexport default class Help extends React.Component<Props, State> {\n\tconstructor(props: Props, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpopover: false,\n\t\t};\n\t}\n\n\tautoSelect = (evt: React.MouseEvent<HTMLInputElement>): void => {\n\t\tevt.currentTarget.select();\n\t}\n\n\trender(): JSX.Element {\n\t\tlet helpContent: string | JSX.Element | JSX.Element[];\n\t\tif (this.props.contents) {\n\t\t\thelpContent = [];\n\t\t\tfor (let content of this.props.contents) {\n\t\t\t\thelpContent.push(<div>{content}</div>);\n\t\t\t}\n\t\t} else {\n\t\t\thelpContent = this.props.content;\n\t\t}\n\n\t\tlet examplesElem: JSX.Element[] = [];\n\t\tfor (let i = 0; i < (this.props.examples || []).length; i++) {\n\t\t\texamplesElem.push(\n\t\t\t\t<input\n\t\t\t\t\tkey={'example-' + i}\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={i === 0 ? css.inputFirst : css.input}\n\t\t\t\t\tdisabled={false}\n\t\t\t\t\treadOnly={true}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tvalue={this.props.examples[i]}\n\t\t\t\t\tonClick={this.autoSelect}\n\t\t\t\t/>\n\t\t\t);\n\t\t}\n\n\t\tlet helpElm: JSX.Element;\n\t\tif (this.state.popover) {\n\t\t\tif (dialog) {\n\t\t\t\thelpElm = <Blueprint.Dialog\n\t\t\t\t\ttitle={this.props.title}\n\t\t\t\t\tstyle={css.dialog}\n\t\t\t\t\tisOpen={this.state.popover}\n\t\t\t\t\tusePortal={true}\n\t\t\t\t\tportalContainer={document.body}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tpopover: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bp5-dialog-body\">\n\t\t\t\t\t\t{this.props.content}\n\t\t\t\t\t\t{examplesElem}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"bp5-dialog-footer\">\n\t\t\t\t\t\t<div className=\"bp5-dialog-footer-actions\">\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"bp5-button\"\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\t\tpopover: !this.state.popover,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>Close</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</Blueprint.Dialog>;\n\t\t\t} else {\n\t\t\t\thelpElm = <span\n\t\t\t\t\tclassName=\"bp5-popover-target\"\n\t\t\t\t\tstyle={css.popoverTarget}\n\t\t\t\t>\n\t\t\t\t<span className=\"bp5-overlay bp5-overlay-inline\">\n\t\t\t\t\t<span>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName={'bp5-transition-container ' +\n\t\t\t\t\t\t\t'bp5-tether-element-attached-middle ' +\n\t\t\t\t\t\t\t'bp5-tether-element-attached-left ' +\n\t\t\t\t\t\t\t'bp5-tether-target-attached-middle ' +\n\t\t\t\t\t\t\t'bp5-tether-target-attached-right bp5-overlay-content'}\n\t\t\t\t\t\t\tstyle={css.popover}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className=\"bp5-popover\">\n\t\t\t\t\t\t\t\t<div className=\"bp5-popover-arrow\">\n\t\t\t\t\t\t\t\t\t<svg viewBox=\"0 0 30 30\">\n\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"bp5-popover-arrow-border\"\n\t\t\t\t\t\t\t\t\t\t\td={'M8.11 6.302c1.015-.936 1.887-2.922 ' +\n\t\t\t\t\t\t\t\t\t\t\t'1.887-4.297v26c0-1.378-' +\n\t\t\t\t\t\t\t\t\t\t\t'.868-3.357-1.888-4.297L.925 ' +\n\t\t\t\t\t\t\t\t\t\t\t'17.09c-1.237-1.14-1.233-3.034 0-4.17L8.11 6.302z'}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"bp5-popover-arrow-fill\"\n\t\t\t\t\t\t\t\t\t\t\td={'M8.787 7.036c1.22-1.125 2.21-3.376 ' +\n\t\t\t\t\t\t\t\t\t\t\t'2.21-5.03V0v30-2.005c0-1.654-' +\n\t\t\t\t\t\t\t\t\t\t\t'.983-3.9-2.21-5.03l-7.183-6.616c-' +\n\t\t\t\t\t\t\t\t\t\t\t'.81-.746-.802-1.96 0-2.7l7.183-6.614z'}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclassName=\"bp5-popover-content\"\n\t\t\t\t\t\t\t\t\tstyle={css.content}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<h5>{this.props.title}</h5>\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t{helpContent}\n\t\t\t\t\t\t\t\t\t\t{examplesElem}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</span>\n\t\t\t\t</span>\n\t\t\t</span>;\n\t\t\t}\n\t\t}\n\n\t\treturn <div style={css.box}>\n\t\t\t<div\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-help\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tpopover: !this.state.popover,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{helpElm}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Image.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as Icons from '@blueprintjs/icons';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as ImageTypes from '../types/ImageTypes';\nimport ImageDetailed from './ImageDetailed';\nimport CompletionStore from \"../stores/CompletionStore\";\n\ninterface Props {\n\timage: ImageTypes.ImageRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\tlogo: {\n\t\tdisplay: \"inline-block\",\n\t\tbackgroundRepeat: \"no-repeat\",\n\t\tbackgroundSize: \"contain\",\n\t\tbackgroundPosition: \"center\",\n\t\theight: '19px',\n\t\twidth: '19px',\n\t\tmarginRight: '5px',\n\t} as React.CSSProperties,\n};\n\nexport default class Image extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet image = this.props.image;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<ImageDetailed\n\t\t\t\t\timage={this.props.image}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet name = image.name\n\t\tlet icon: Blueprint.MaybeElement\n\t\tlet orgClass = '';\n\t\tlet orgIcon = '';\n\t\tlet orgName = '';\n\t\tif (image.organization) {\n\t\t\tlet org = CompletionStore.organization(image.organization);\n\t\t\torgIcon = 'bp5-text-muted bp5-icon-people';\n\t\t\torgName = org ? org.name : image.organization;\n\t\t} else {\n\t\t\torgIcon = 'bp5-text-muted bp5-icon-globe';\n\t\t\torgName = 'Public Image';\n\t\t}\n\n\t\tif (image.signed) {\n\t\t\tlet imgSpl = image.key.split('_');\n\t\t\tlet imgVer = imgSpl?.[1]?.split(\".\")[0] || \"\"\n\t\t\tlet imgNameSpl = imgSpl[0].match(/^(.+?)(\\d+)$/);\n\t\t\tlet distro = imgNameSpl?.[1] || imgSpl[0]\n\t\t\tlet version = imgNameSpl?.[2] || \"\"\n\t\t\tlet matched = true\n\n\t\t\tswitch (distro) {\n\t\t\t\tcase \"almalinux\":\n\t\t\t\t\tname = `AlmaLinux ${version}`\n\t\t\t\t\ticon = <span style={css.logo} className=\"almalinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"alpinelinux\":\n\t\t\t\t\tname = `Alpine Linux`\n\t\t\t\t\ticon = <span style={css.logo} className=\"alpinelinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"archlinux\":\n\t\t\t\t\tname = `Arch Linux`\n\t\t\t\t\ticon = <span style={css.logo} className=\"archlinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"fedora\":\n\t\t\t\t\tname = `Fedora ${version}`\n\t\t\t\t\ticon = <span style={css.logo} className=\"fedora-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"freebsd\":\n\t\t\t\t\tname = `FreeBSD`\n\t\t\t\t\ticon = <span style={css.logo} className=\"freebsd-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"oraclelinux\":\n\t\t\t\t\tname = `Oracle Linux ${version}`\n\t\t\t\t\ticon = <span style={css.logo} className=\"oraclelinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"rockylinux\":\n\t\t\t\t\tname = `Rocky Linux ${version}`\n\t\t\t\t\ticon = <span style={css.logo} className=\"rockylinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"ubuntu\":\n\t\t\t\t\tname = `Ubuntu ${version.slice(0, 2) + \".\" + version.slice(2)}`\n\t\t\t\t\ticon = <span style={css.logo} className=\"ubuntu-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\tmatched = false\n\t\t\t}\n\n\t\t\tif (matched && imgVer) {\n\t\t\t\tname += ` (${MiscUtils.parseImageDate(imgVer)})`\n\t\t\t}\n\n\t\t\torgClass = 'bp5-text-intent-success';\n\t\t\torgIcon = 'bp5-icon-endorsed';\n\t\t\torgName = 'Signed Public Image';\n\t\t}\n\n\t\tlet diskIcon = 'bp5-icon-box';\n\t\tswitch (this.props.image.storage_class) {\n\t\t\tcase 'aws_standard':\n\t\t\t\tdiskIcon = 'bp5-icon-box';\n\t\t\t\tbreak;\n\t\t\tcase 'aws_infrequent_access':\n\t\t\t\tdiskIcon = 'bp5-icon-compressed';\n\t\t\t\tbreak;\n\t\t\tcase 'aws_glacier':\n\t\t\t\tdiskIcon = 'bp5-icon-snowflake';\n\t\t\t\tbreak;\n\t\t\tcase 'oracle_standard':\n\t\t\t\tdiskIcon = 'bp5-icon-box';\n\t\t\t\tbreak;\n\t\t\tcase 'oracle_archive':\n\t\t\t\tdiskIcon = 'bp5-icon-snowflake';\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{icon}{name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className={'bp5-cell ' + orgClass} style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName={'bp5-icon-standard ' + orgIcon}\n\t\t\t\t/>\n\t\t\t\t{orgName}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\thidden={!image.key}\n\t\t\t\t\tclassName={'bp5-icon-standard bp5-text-muted ' + diskIcon}\n\t\t\t\t/>\n\t\t\t\t{image.key}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ImageDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as ImageActions from '../actions/ImageActions';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport * as PageInfos from './PageInfo';\nimport ConfirmButton from './ConfirmButton';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\timage: ImageTypes.ImageRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\timage: ImageTypes.Image;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class ImageDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\timage: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet image: any;\n\n\t\tif (this.state.changed) {\n\t\t\timage = {\n\t\t\t\t...this.state.image,\n\t\t\t};\n\t\t} else {\n\t\t\timage = {\n\t\t\t\t...this.props.image,\n\t\t\t};\n\t\t}\n\n\t\timage[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\timage: image,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tImageActions.commit(this.state.image).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\timage: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tImageActions.remove(this.props.image.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet image: ImageTypes.Image = this.state.image ||\n\t\t\tthis.props.image;\n\n\t\tlet org = CompletionStore.organization(this.props.image.organization);\n\t\tlet store = CompletionStore.storage(this.props.image.storage);\n\n\t\tlet imgType = image.type;\n\t\tif (imgType) {\n\t\t\timgType = imgType.charAt(0).toUpperCase() + imgType.slice(1);\n\t\t}\n\n\t\tlet orgName = '';\n\t\tif (image.organization) {\n\t\t\torgName = org ? org.name : image.organization;\n\t\t} else {\n\t\t\torgName = 'Public Image';\n\t\t}\n\n\t\tif (image.signed) {\n\t\t\torgName = 'Signed Public Image';\n\t\t}\n\n\t\tlet storageClass = 'Default';\n\t\tswitch (this.props.image.storage_class) {\n\t\t\tcase 'aws_standard':\n\t\t\t\tstorageClass = 'AWS Standard';\n\t\t\t\tbreak;\n\t\t\tcase 'aws_infrequent_access':\n\t\t\t\tstorageClass = 'AWS Standard-IA';\n\t\t\t\tbreak;\n\t\t\tcase 'aws_glacier':\n\t\t\t\tstorageClass = 'AWS Glacier';\n\t\t\t\tbreak;\n\t\t\tcase 'oracle_standard':\n\t\t\t\tstorageClass = 'Oracle Standard';\n\t\t\t\tbreak;\n\t\t\tcase 'oracle_archive':\n\t\t\t\tstorageClass = 'Oracle Archive';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet fields: PageInfos.Field[] = [\n\t\t\t{\n\t\t\t\tlabel: 'ID',\n\t\t\t\tvalue: this.props.image.id || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Storage',\n\t\t\t\tvalue: store ? store.name :\n\t\t\t\t\tthis.props.image.storage || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Organization',\n\t\t\t\tvalue: orgName,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Type',\n\t\t\t\tvalue: imgType || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Key',\n\t\t\t\tvalue: this.props.image.key || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Storage Class',\n\t\t\t\tvalue: storageClass,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Last Modified',\n\t\t\t\tvalue: MiscUtils.formatDate(\n\t\t\t\t\tthis.props.image.last_modified) || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'ETag',\n\t\t\t\tvalue: this.props.image.etag || 'Unknown',\n\t\t\t},\n\t\t];\n\n\t\tif (this.props.image.system_type || this.props.image.system_kind) {\n\t\t\tfields.push({\n\t\t\t\tlabel: 'System Type',\n\t\t\t\tvalue: this.props.image.system_type &&\n\t\t\t\t\tthis.props.image.system_kind ?\n\t\t\t\t\t`${this.props.image.system_type}/${this.props.image.system_kind}` :\n\t\t\t\t\tthis.props.image.system_type || this.props.image.system_kind,\n\t\t\t});\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={3}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n            <div>\n              <label\n                className=\"bp5-control bp5-checkbox\"\n                style={css.select}\n              >\n                <input\n                  type=\"checkbox\"\n                  checked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n                  onClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n                />\n                <span className=\"bp5-control-indicator\"/>\n              </label>\n            </div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Image\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this image\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[image.name]}\n\t\t\t\t\t\t\tdisabled={this.props.image.type === 'public' ||\n\t\t\t\t\t\t\t\tthis.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of image\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={image.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Image comment.\"\n\t\t\t\t\t\tplaceholder=\"Image comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={image.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={fields}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.image && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\timage: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Images.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport ImagesStore from '../stores/ImagesStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as ImageActions from '../actions/ImageActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Image from './Image';\nimport ImagesFilter from './ImagesFilter';\nimport ImagesPage from './ImagesPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\timages: ImageTypes.ImagesRo;\n\tfilter: ImageTypes.Filter;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Images extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\timages: ImagesStore.images,\n\t\t\tfilter: ImagesStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tImagesStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tImageActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tImagesStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet images = ImagesStore.images;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\timages.forEach((image: ImageTypes.Image): void => {\n\t\t\tif (curSelected[image.id]) {\n\t\t\t\tselected[image.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[image.id]) {\n\t\t\t\topened[image.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\timages: images,\n\t\t\tfilter: ImagesStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tImageActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet imagesDom: JSX.Element[] = [];\n\n\t\tthis.state.images.forEach((\n\t\t\t\timage: ImageTypes.ImageRo): void => {\n\t\t\timagesDom.push(<Image\n\t\t\t\tkey={image.id}\n\t\t\t\timage={image}\n\t\t\t\tselected={!!this.state.selected[image.id]}\n\t\t\t\topen={!!this.state.opened[image.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet images = this.state.images;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < images.length; i++) {\n\t\t\t\t\t\t\tlet usr = images[i];\n\n\t\t\t\t\t\t\tif (usr.id === image.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[images[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: image.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[image.id]) {\n\t\t\t\t\t\tdelete selected[image.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[image.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: image.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[image.id]) {\n\t\t\t\t\t\tdelete opened[image.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[image.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = ImagesStore.image(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Images</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tImageActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tImageActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently force delete the selected images\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<ImagesFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tImageActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{imagesDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={3} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!imagesDom.length}\n\t\t\t\ticonClass=\"bp5-icon-compressed\"\n\t\t\t\ttitle=\"No images\"\n\t\t\t\tdescription=\"Add a new image to get started.\"\n\t\t\t/>\n\t\t\t<ImagesPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ImagesFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as ImageTypes from '../types/ImageTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: ImageTypes.Filter;\n\tonFilter: (filter: ImageTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class ImagesFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Image ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.type || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.type;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.type = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<option value=\"any\">Any</option>\n\t\t\t\t\t<option value=\"private\">Private</option>\n\t\t\t\t\t<option value=\"public\">Public</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ImagesPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport ImagesStore from '../stores/ImagesStore';\nimport * as ImageActions from '../actions/ImageActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class ImagesPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: ImagesStore.page,\n\t\t\tpageCount: ImagesStore.pageCount,\n\t\t\tpages: ImagesStore.pages,\n\t\t\tcount: ImagesStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tImagesStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tImagesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: ImagesStore.page,\n\t\t\tpageCount: ImagesStore.pageCount,\n\t\t\tpages: ImagesStore.pages,\n\t\t\tcount: ImagesStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tImageActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tImageActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tImageActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tImageActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tImageActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Instance.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as DomainTypes from '../types/DomainTypes'\nimport InstanceDetailed from './InstanceDetailed';\nimport CompletionStore from \"../stores/CompletionStore\";\n\ninterface Props {\n\tvpcs: VpcTypes.VpcsRo;\n\tdomains: DomainTypes.DomainsRo;\n\tinstance: InstanceTypes.InstanceRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tstatusIcon: {\n\t\tmargin: '1px 8px 0 -2px',\n\t} as React.CSSProperties,\n\tupdateIcon: {\n\t\tmarginTop: '1px',\n\t\tmarginRight: '5px',\n\t} as React.CSSProperties,\n\tip: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '150px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '85px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n};\n\nexport default class Instance extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet instance = this.props.instance;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<InstanceDetailed\n\t\t\t\t\tinstance={this.props.instance}\n\t\t\t\t\tvpcs={this.props.vpcs}\n\t\t\t\t\tdomains={this.props.domains}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet node = CompletionStore.node(this.props.instance.node);\n\t\tlet nodeName = node ? node.name : null;\n\t\tlet zone = CompletionStore.zone(this.props.instance.zone);\n\t\tlet zoneName = zone ? zone.name : null;\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet publicIp = '';\n\t\tlet privateIp = '';\n\t\tif (instance.public_ips && instance.public_ips.length > 0) {\n\t\t\tpublicIp = instance.public_ips[0];\n\t\t} else if (instance.host_ips && instance.host_ips.length > 0) {\n\t\t\tpublicIp = instance.host_ips[0];\n\t\t}\n\t\tif (instance.private_ips && instance.private_ips.length > 0) {\n\t\t\tprivateIp = instance.private_ips[0];\n\t\t}\n\n\t\tlet memoryStyle: React.CSSProperties = {\n\t\t\twidth: (instance.guest?.memory || 0) + '%',\n\t\t};\n\t\tlet load1Style: React.CSSProperties = {\n\t\t\twidth: (instance.guest?.load1 || 0) + '%',\n\t\t};\n\t\tlet load5Style: React.CSSProperties = {\n\t\t\twidth: (instance.guest?.load5 || 0) + '%',\n\t\t};\n\n\t\tlet statusClass = '';\n\t\tswitch (instance.status) {\n\t\t\tcase 'Running':\n\t\t\t\tstatusClass += ' bp5-text-intent-success';\n\t\t\t\tbreak;\n\t\t\tcase 'Stopped':\n\t\t\tcase 'Failed':\n\t\t\tcase 'Destroying':\n\t\t\t\tstatusClass += ' bp5-text-intent-danger';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (instance.status?.includes(\"Restart Required\")) {\n\t\t\tstatusClass += ' bp5-text-intent-warning';\n\t\t}\n\n\t\tlet updateElm: JSX.Element\n\t\tlet updateSev = 0\n\t\tlet updateClass = \"\"\n\t\tinstance.guest?.updates?.forEach((update) => {\n\t\t\tswitch (update.severity) {\n\t\t\t\tcase \"moderate\":\n\t\t\t\t\tif (updateSev < 1) {\n\t\t\t\t\t\tupdateSev = 1\n\t\t\t\t\t\tupdateClass = \"bp5-icon-issue bp5-intent-primary\"\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase \"important\":\n\t\t\t\t\tif (updateSev < 2) {\n\t\t\t\t\t\tupdateSev = 2\n\t\t\t\t\t\tupdateClass = \"bp5-icon-issue bp5-intent-warning\"\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase \"critical\":\n\t\t\t\t\tupdateSev = 3\n\t\t\t\t\tupdateClass = \"bp5-icon-issue bp5-intent-danger\"\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t})\n\n\t\tif (updateClass) {\n\t\t\tupdateElm = <Blueprint.Tooltip\n\t\t\t\tcontent=\"Updates available\"\n\t\t\t\topenOnTargetFocus={false}\n\t\t\t\tcompact={true}\n\t\t\t\trenderTarget={({isOpen, ...tooltipProps}) => (\n\t\t\t\t\t<span\n\t\t\t\t\t\t{...tooltipProps}\n\t\t\t\t\t\tstyle={css.updateIcon}\n\t\t\t\t\t\thidden={!instance.status}\n\t\t\t\t\t\tclassName={\"bp5-icon-standard \" + updateClass}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t/>\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tstyle={css.statusIcon}\n\t\t\t\t\t\t\thidden={!instance.status}\n\t\t\t\t\t\t\tclassName={\"bp5-icon-standard bp5-icon-power\" + statusClass}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{updateElm}\n\t\t\t\t\t\t{instance.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\thidden={!nodeName}\n\t\t\t\t\tclassName={\n\t\t\t\t\t\t\"bp5-icon-standard bp5-text-muted \" +\n\t\t\t\t\t\t(!!instance.deployment ?\n\t\t\t\t\t\t\t\"bp5-icon-server\" :\n\t\t\t\t\t\t\t\"bp5-icon-layers\"\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t/>\n\t\t\t\t{nodeName}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\thidden={!zoneName}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-layout-circle\"\n\t\t\t\t/>\n\t\t\t\t{zoneName}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.ip}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\thidden={!publicIp}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-ip-address\"\n\t\t\t\t/>\n\t\t\t\t{publicIp}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.ip}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\thidden={!privateIp}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-ip-address\"\n\t\t\t\t/>\n\t\t\t\t{privateIp}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.bars}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-progress-bar bp5-no-stripes bp5-intent-success\"\n\t\t\t\t\tstyle={css.bar}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bp5-progress-meter\" style={load1Style}/>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-progress-bar bp5-no-stripes bp5-intent-warning\"\n\t\t\t\t\tstyle={css.bar}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bp5-progress-meter\" style={load5Style}/>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-progress-bar bp5-no-stripes bp5-intent-primary\"\n\t\t\t\t\tstyle={css.barLast}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bp5-progress-meter\" style={memoryStyle}/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/InstanceDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport RFB from '@novnc/novnc';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as InstanceActions from '../actions/InstanceActions';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as DomainTypes from '../types/DomainTypes';\nimport * as PageInfos from './PageInfo';\nimport * as Csrf from '../Csrf';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Constants from \"../Constants\";\nimport CompletionStore from '../stores/CompletionStore';\nimport InstanceIscsiDevice from './InstanceIscsiDevice';\nimport InstanceNodePort from './InstanceNodePort';\nimport InstanceMount from './InstanceMount';\nimport PageInput from './PageInput';\nimport PageInputButton from './PageInputButton';\nimport PageInfo from './PageInfo';\nimport PageSwitch from './PageSwitch';\nimport PageSelect from './PageSelect';\nimport PageSave from './PageSave';\nimport PageNumInput from './PageNumInput';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageSelectButton from \"./PageSelectButton\";\nimport PageTextArea from \"./PageTextArea\";\nimport Relations from './Relations';\nimport InstanceAdvDialog from './InstanceAdvDialog';\n\ninterface Props {\n\tvpcs: VpcTypes.VpcsRo;\n\tdomains: DomainTypes.DomainsRo;\n\tinstance: InstanceTypes.InstanceRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tinstance: InstanceTypes.Instance;\n\taddCert: string;\n\taddNetworkRole: string;\n\taddVpc: string;\n\taddDriveDevice: string;\n\taddIso: string;\n\taddUsbDevice: string;\n\taddPciDevice: string;\n\tstartupScript: boolean;\n\tforwardedChecked: boolean;\n\tshowSettings: boolean;\n\tvnc: boolean;\n\tvncCtrl: boolean;\n\tvncAlt: boolean;\n\tvncSuper: boolean;\n\tvncScale: boolean;\n\tvncHeight: number;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t\tmaxWidth: '1060px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tcontrolButton: {\n\t\tmarginRight: '10px',\n\t\tmarginBottom: '10px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\twordBreak: 'break-all',\n\t} as React.CSSProperties,\n\titems: {\n\t\tmarginBottom: \"10px\",\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tlist: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tvncBox: {\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n};\n\nexport default class InstanceDetailed extends React.Component<Props, State> {\n\tvncState: boolean;\n\tvncRef: React.RefObject<HTMLDivElement>;\n\tvncRfb: RFB;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tinstance: null,\n\t\t\taddCert: null,\n\t\t\taddNetworkRole: '',\n\t\t\taddVpc: '',\n\t\t\taddDriveDevice: '',\n\t\t\taddIso: '',\n\t\t\taddUsbDevice: '',\n\t\t\taddPciDevice: '',\n\t\t\tstartupScript: null,\n\t\t\tforwardedChecked: false,\n\t\t\tshowSettings: false,\n\t\t\tvnc: false,\n\t\t\tvncCtrl: false,\n\t\t\tvncAlt: false,\n\t\t\tvncSuper: false,\n\t\t\tvncScale: true,\n\t\t\tvncHeight: null,\n\t\t};\n\n\t\tthis.vncRef = React.createRef();\n\t}\n\n\tcomponentDidMount(): void {\n\t\tthis.vncState = true;\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tthis.vncState = false;\n\t\tif (this.vncRfb) {\n\t\t\tthis.vncRfb.disconnect();\n\t\t}\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet instance: any;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tinstance[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonTogleVnc = (): void => {\n\t\tif (this.state.vnc) {\n\t\t\tif (this.vncRfb) {\n\t\t\t\tthis.vncRfb.disconnect();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.connectVnc();\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tvnc: !this.state.vnc,\n\t\t});\n\t}\n\n\tconnectVnc = (): void => {\n\t\tlet path = '/instance/' + this.props.instance.id +\n\t\t\t'/vnc?csrf_token=' + Csrf.token\n\n\t\tif (Constants.user) {\n\t\t\tpath += '&organization=' + CompletionStore.userOrganization\n\t\t}\n\n\t\tthis.vncRfb = new RFB(\n\t\t\tthis.vncRef.current,\n\t\t\t'wss://' + location.hostname + (\n\t\t\t\tlocation.port ? ':' + location.port : '') + path,\n\t\t\t{\n\t\t\t\tshared: true,\n\t\t\t\twsProtocols: ['binary'],\n\t\t\t\tcredentials: {\n\t\t\t\t\tpassword: this.props.instance.vnc_password,\n\t\t\t\t},\n\t\t\t\tscaleViewport: this.state.vncScale,\n\t\t\t},\n\t\t);\n\t\tthis.vncRfb.addEventListener('connect', function() {\n\t\t\tif (this.state.vncScale) {\n\t\t\t\tthis.vncRfb.scaleViewport = true;\n\t\t\t} else {\n\t\t\t\tthis.vncRfb.scaleViewport = false;\n\t\t\t}\n\t\t}.bind(this));\n\t\tthis.vncRfb.addEventListener('disconnect', function() {\n\t\t\tthis.vncRfb.scaleViewport = false;\n\t\t\tsetTimeout(function() {\n\t\t\t\tif (this.state.vnc && this.vncState) {\n\t\t\t\t\tthis.connectVnc();\n\t\t\t\t}\n\t\t\t}.bind(this), 250);\n\t\t}.bind(this));\n\t}\n\n\tonToggleVncCtrl = (): void => {\n\t\tif (this.vncRfb) {\n\t\t\tif (this.state.vncCtrl) {\n\t\t\t\tthis.vncRfb.sendKey(0xffe3, 'ControlLeft', false);\n\t\t\t} else {\n\t\t\t\tthis.vncRfb.sendKey(0xffe3, 'ControlLeft', true);\n\t\t\t}\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tvncCtrl: !this.state.vncCtrl,\n\t\t});\n\t}\n\n\tonToggleVncAlt = (): void => {\n\t\tif (this.vncRfb) {\n\t\t\tif (this.state.vncAlt) {\n\t\t\t\tthis.vncRfb.sendKey(0xffe9, 'AltLeft', false);\n\t\t\t} else {\n\t\t\t\tthis.vncRfb.sendKey(0xffe9, 'AltLeft', true);\n\t\t\t}\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tvncAlt: !this.state.vncAlt,\n\t\t});\n\t}\n\n\tonToggleVncSuper = (): void => {\n\t\tif (this.vncRfb) {\n\t\t\tif (this.state.vncSuper) {\n\t\t\t\tthis.vncRfb.sendKey(0xffeb, 'MetaLeft', false);\n\t\t\t} else {\n\t\t\t\tthis.vncRfb.sendKey(0xffeb, 'MetaLeft', true);\n\t\t\t}\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tvncSuper: !this.state.vncSuper,\n\t\t});\n\t}\n\n\tonVncCtrlAltDel = (): void => {\n\t\tif (this.vncRfb) {\n\t\t\tthis.vncRfb.sendCtrlAltDel();\n\t\t}\n\t}\n\n\tonVncTab = (): void => {\n\t\tif (this.vncRfb) {\n\t\t\tthis.vncRfb.sendKey(0xff09, 'Tab');\n\t\t}\n\t}\n\n\tonVncEsc = (): void => {\n\t\tif (this.vncRfb) {\n\t\t\tthis.vncRfb.sendKey(0xff1b, 'Escape');\n\t\t}\n\t}\n\n\tonToggleVncFullscreen = (): void => {\n\t\tif (document.fullscreenElement) {\n\t\t\tif (document.exitFullscreen) {\n\t\t\t\tdocument.exitFullscreen();\n\t\t\t}\n\t\t} else {\n\t\t\tif (this.vncRef) {\n\t\t\t\tif (this.vncRef.current.requestFullscreen) {\n\t\t\t\t\tthis.vncRef.current.requestFullscreen();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tonToggleVncScale = (): void => {\n\t\tlet vncHeight: number;\n\t\tlet vncScale = this.state.vncScale;\n\n\t\tif (vncScale) {\n\t\t\tthis.vncRfb.scaleViewport = false;\n\t\t} else {\n\t\t\tlet ratio = this.vncRfb._canvas.height / this.vncRfb._canvas.width;\n\t\t\tvncHeight = Math.floor(this.vncRef.current.offsetWidth * ratio);\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tvncScale: !this.state.vncScale,\n\t\t\tvncHeight: vncHeight,\n\t\t});\n\n\t\tif (!vncScale) {\n\t\t\tthis.vncRfb.scaleViewport = true;\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (this.state.vncScale) {\n\t\t\t\t\tthis.vncRfb.scaleViewport = true;\n\t\t\t\t}\n\t\t\t\tsetTimeout((): void => {\n\t\t\t\t\tif (this.state.vncScale) {\n\t\t\t\t\t\tthis.vncRfb.scaleViewport = true;\n\t\t\t\t\t}\n\t\t\t\t\tsetTimeout((): void => {\n\t\t\t\t\t\tif (this.state.vncScale) {\n\t\t\t\t\t\t\tthis.vncRfb.scaleViewport = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsetTimeout((): void => {\n\t\t\t\t\t\t\tif (this.state.vncScale) {\n\t\t\t\t\t\t\t\tthis.vncRfb.scaleViewport = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsetTimeout((): void => {\n\t\t\t\t\t\t\t\tif (this.state.vncScale) {\n\t\t\t\t\t\t\t\t\tthis.vncRfb.scaleViewport = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsetTimeout((): void => {\n\t\t\t\t\t\t\t\t\tif (this.state.vncScale) {\n\t\t\t\t\t\t\t\t\t\tthis.vncRfb.scaleViewport = true;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}, 50);\n\t\t\t\t\t\t\t}, 50);\n\t\t\t\t\t\t}, 50);\n\t\t\t\t\t}, 50);\n\t\t\t\t}, 50);\n\t\t\t}, 50);\n\t\t}\n\t}\n\n\tonAddNetworkRole = (): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (!this.state.addNetworkRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(instance.roles || []),\n\t\t];\n\n\t\tif (roles.indexOf(this.state.addNetworkRole) === -1) {\n\t\t\troles.push(this.state.addNetworkRole);\n\t\t}\n\n\t\troles.sort();\n\t\tinstance.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveNetworkRole = (networkRole: string): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(instance.roles || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(networkRole);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\t\tinstance.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonAddDriveDevice = (): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\t\tlet infoDriveDevices = this.props.instance.info?.drive_devices || [];\n\n\t\tif (!this.state.addDriveDevice && !infoDriveDevices.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet addDevice = this.state.addDriveDevice;\n\t\tif (!addDevice) {\n\t\t\taddDevice = infoDriveDevices[0].id;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet driveDevices = [\n\t\t\t...(instance.drive_devices || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < driveDevices.length; i++) {\n\t\t\tlet dev = driveDevices[i];\n\t\t\tif (dev.id === addDevice) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (index === -1) {\n\t\t\tdriveDevices.push({\n\t\t\t\tid: addDevice,\n\t\t\t});\n\t\t}\n\n\t\tinstance.drive_devices = driveDevices;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddDriveDevice: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveDriveDevice = (device: string): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet driveDevices = [\n\t\t\t...(instance.drive_devices || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < driveDevices.length; i++) {\n\t\t\tlet dev = driveDevices[i];\n\t\t\tif (dev.id === device) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (index === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tdriveDevices.splice(index, 1);\n\t\tinstance.drive_devices = driveDevices;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddDriveDevice: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonAddIso = (): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\t\tlet infoIsos = this.props.instance.info?.isos || [];\n\n\t\tif (!this.state.addIso && !infoIsos.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet addIso = this.state.addIso;\n\t\tif (!addIso) {\n\t\t\taddIso = infoIsos[0].name;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet isos = [\n\t\t\t...(instance.isos || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < isos.length; i++) {\n\t\t\tlet iso = isos[i];\n\t\t\tif (iso.name === addIso) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (index === -1) {\n\t\t\tisos.push({\n\t\t\t\tname: addIso,\n\t\t\t});\n\t\t}\n\n\t\tinstance.isos = isos;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddIso: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveIso = (isoName: string): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet isos = [\n\t\t\t...(instance.isos || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < isos.length; i++) {\n\t\t\tlet iso = isos[i];\n\t\t\tif (iso.name == isoName) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (index === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tisos.splice(index, 1);\n\t\tinstance.isos = isos;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddIso: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonAddUsbDevice = (): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\t\tlet infoUsbDevices = this.props.instance.info?.usb_devices || [];\n\n\t\tif (!this.state.addUsbDevice && !infoUsbDevices.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet addDevice = this.state.addUsbDevice;\n\t\tif (!addDevice) {\n\t\t\taddDevice = infoUsbDevices[0].vendor + ':' + infoUsbDevices[0].product;\n\t\t}\n\n\t\tlet bus = addDevice.indexOf('-') !== -1;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet usbDevices = [\n\t\t\t...(instance.usb_devices || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < usbDevices.length; i++) {\n\t\t\tlet dev = usbDevices[i];\n\t\t\tif (!bus && dev.vendor + ':' + dev.product === addDevice) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t} else if (bus && dev.bus + '-' + dev.address === addDevice) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (!bus) {\n\t\t\tlet device = addDevice.split(':');\n\n\t\t\tif (index === -1) {\n\t\t\t\tusbDevices.push({\n\t\t\t\t\tvendor: device[0],\n\t\t\t\t\tproduct: device[1],\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tlet port = addDevice.split('-');\n\n\t\t\tif (index === -1) {\n\t\t\t\tusbDevices.push({\n\t\t\t\t\tbus: port[0],\n\t\t\t\t\taddress: port[1],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tinstance.usb_devices = usbDevices;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddUsbDevice: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveUsbDevice = (device: string): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet usbDevices = [\n\t\t\t...(instance.usb_devices || []),\n\t\t];\n\n\t\tlet bus = device.indexOf('-') !== -1;\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < usbDevices.length; i++) {\n\t\t\tlet dev = usbDevices[i];\n\t\t\tif (!bus && dev.vendor + ':' + dev.product == device) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t} else if (bus && dev.bus + '-' + dev.address == device) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (index === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tusbDevices.splice(index, 1);\n\t\tinstance.usb_devices = usbDevices;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddUsbDevice: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonAddPciDevice = (): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\t\tlet infoPciDevices = this.props.instance.info?.pci_devices || [];\n\n\t\tif (!this.state.addPciDevice && !infoPciDevices.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet addDevice = this.state.addPciDevice;\n\t\tif (!addDevice) {\n\t\t\taddDevice = infoPciDevices[0].slot;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet pciDevices = [\n\t\t\t...(instance.pci_devices || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < pciDevices.length; i++) {\n\t\t\tlet dev = pciDevices[i];\n\t\t\tif (dev.slot === addDevice) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (index === -1) {\n\t\t\tpciDevices.push({\n\t\t\t\tslot: addDevice,\n\t\t\t});\n\t\t}\n\n\t\tinstance.pci_devices = pciDevices;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddPciDevice: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemovePciDevice = (device: string): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet pciDevices = [\n\t\t\t...(instance.pci_devices || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < pciDevices.length; i++) {\n\t\t\tlet dev = pciDevices[i];\n\t\t\tif (dev.slot === device) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (index === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tpciDevices.splice(index, 1);\n\t\tinstance.pci_devices = pciDevices;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddPciDevice: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonAddIscsiDevice = (i: number): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet iscsiDevices = [\n\t\t\t...(instance.iscsi_devices || []),\n\t\t];\n\n\t\tif (iscsiDevices.length === 0) {\n\t\t\tiscsiDevices = [{}];\n\t\t}\n\n\t\tiscsiDevices.splice(i + 1, 0, {} as InstanceTypes.IscsiDevice);\n\t\tinstance.iscsi_devices = iscsiDevices;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonChangeIscsiDevice(i: number, subnet: InstanceTypes.IscsiDevice): void {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet iscsiDevices = [\n\t\t\t...(instance.iscsi_devices || []),\n\t\t];\n\n\t\tif (iscsiDevices.length === 0) {\n\t\t\tiscsiDevices = [{}];\n\t\t}\n\n\t\tiscsiDevices[i] = subnet;\n\n\t\tinstance.iscsi_devices = iscsiDevices;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveIscsiDevice(i: number): void {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet iscsiDevices = [\n\t\t\t...(instance.iscsi_devices || []),\n\t\t];\n\n\t\tif (iscsiDevices.length !== 0) {\n\t\t\tiscsiDevices.splice(i, 1);\n\t\t}\n\n\t\tif (iscsiDevices.length === 0) {\n\t\t\tiscsiDevices = [{}];\n\t\t}\n\n\t\tinstance.iscsi_devices = iscsiDevices;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonAddNodePort = (i: number, prepend?: boolean): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet nodePorts = [\n\t\t\t...(instance.node_ports || []),\n\t\t];\n\n\t\tlet nodePort = {\n\t\t\tprotocol: \"tcp\",\n\t\t\texternal_port: 0,\n\t\t\tinternal_port: 0,\n\t\t} as InstanceTypes.NodePort;\n\n\t\tif (i === -1) {\n\t\t\tnodePorts.push(nodePort);\n\t\t} else {\n\t\t\tnodePorts.splice(prepend ? i : i + 1, 0, nodePort);\n\t\t}\n\t\tinstance.node_ports = nodePorts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonChangeNodePort(i: number, state: InstanceTypes.NodePort): void {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet nodePorts = [\n\t\t\t...instance.node_ports,\n\t\t];\n\n\t\tnodePorts[i] = state;\n\n\t\tinstance.node_ports = nodePorts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveNodePort(i: number): void {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet nodePorts = [\n\t\t\t...instance.node_ports,\n\t\t];\n\n\t\tnodePorts[i] = {\n\t\t\t...nodePorts[i],\n\t\t\tdelete: true,\n\t\t};\n\n\t\tinstance.node_ports = nodePorts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tlet cloudScript = this.state.instance.cloud_script\n\t\tif (this.state.startupScript === false) {\n\t\t\tcloudScript = \"\"\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tInstanceActions.commit({\n\t\t\t...this.state.instance,\n\t\t\tnode_ports: InstanceTypes.FilterNodePorts(\n\t\t\t\tthis.state.instance.node_ports),\n\t\t\tcloud_script: cloudScript,\n\t\t\taction: null,\n\t\t}).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tinstance: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddMount = (i: number, prepend?: boolean): void => {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet mounts = [\n\t\t\t...(instance.mounts || []),\n\t\t];\n\n\t\tif (i === -1) {\n\t\t\tmounts.push({})\n\t\t} else {\n\t\t\tmounts.splice(prepend ? i : i + 1, 0, {});\n\t\t}\n\t\tinstance.mounts = mounts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonChangeMount(i: number, block: InstanceTypes.Mount): void {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet mounts = [\n\t\t\t...(instance.mounts || []),\n\t\t];\n\t\tif (!mounts.length) {\n\t\t\tmounts.push({})\n\t\t}\n\n\t\tmounts[i] = block;\n\n\t\tinstance.mounts = mounts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveMount(i: number): void {\n\t\tlet instance: InstanceTypes.Instance;\n\n\t\tif (this.state.changed) {\n\t\t\tinstance = {\n\t\t\t\t...this.state.instance,\n\t\t\t};\n\t\t} else {\n\t\t\tinstance = {\n\t\t\t\t...this.props.instance,\n\t\t\t};\n\t\t}\n\n\t\tlet mounts = [\n\t\t\t...(instance.mounts || []),\n\t\t];\n\n\t\tmounts.splice(i, 1);\n\n\t\tif (!mounts.length) {\n\t\t\tmounts = [];\n\t\t}\n\n\t\tinstance.mounts = mounts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tInstanceActions.remove(this.props.instance.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tupdate(action: string): void {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tInstanceActions.updateMulti([this.props.instance.id],\n\t\t\t\taction).then((): void => {\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t});\n\t\t\t}, 250);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet instance: InstanceTypes.Instance = this.state.instance ||\n\t\t\tthis.props.instance;\n\t\tlet info: InstanceTypes.Info = this.props.instance.info || {};\n\n\t\tlet org = CompletionStore.organization(\n\t\t\tthis.props.instance.organization);\n\t\tlet zone = CompletionStore.zone(this.props.instance.zone);\n\n\t\tlet privateIps: any = this.props.instance.private_ips;\n\t\tif (!privateIps || !privateIps.length) {\n\t\t\tprivateIps = 'None';\n\t\t}\n\n\t\tlet privateIps6: any = this.props.instance.private_ips6;\n\t\tif (!privateIps6 || !privateIps6.length) {\n\t\t\tprivateIps6 = 'None';\n\t\t}\n\n\t\tlet gatewayIps: any = this.props.instance.gateway_ips;\n\t\tif (!gatewayIps || !gatewayIps.length) {\n\t\t\tgatewayIps = 'None';\n\t\t}\n\n\t\tlet gatewayIps6: any = this.props.instance.gateway_ips6;\n\t\tif (!gatewayIps6 || !gatewayIps6.length) {\n\t\t\tgatewayIps6 = 'None';\n\t\t}\n\n\t\tlet publicIps: any = this.props.instance.public_ips;\n\t\tif (!publicIps || !publicIps.length) {\n\t\t\tpublicIps = null;\n\t\t}\n\n\t\tlet publicIps6: any = this.props.instance.public_ips6;\n\t\tif (!publicIps6 || !publicIps6.length) {\n\t\t\tpublicIps6 = null;\n\t\t}\n\n\t\tlet hostIps: any = this.props.instance.host_ips;\n\t\tif (!hostIps || !hostIps.length) {\n\t\t\thostIps = 'None';\n\t\t}\n\n\t\tlet cloudPrivateIps: any = this.props.instance.cloud_private_ips;\n\t\tif (!cloudPrivateIps || !cloudPrivateIps.length) {\n\t\t\tcloudPrivateIps = null;\n\t\t}\n\t\tlet cloudPublicIps: any = this.props.instance.cloud_public_ips;\n\t\tif (!cloudPublicIps || !cloudPublicIps.length) {\n\t\t\tcloudPublicIps = null;\n\t\t}\n\t\tlet cloudPublicIps6: any = this.props.instance.cloud_public_ips6;\n\t\tif (!cloudPublicIps6 || !cloudPublicIps6.length) {\n\t\t\tcloudPublicIps6 = null;\n\t\t}\n\n\t\tlet statusClass = 'no-select tab-close';\n\t\tswitch (instance.status) {\n\t\t\tcase 'Running':\n\t\t\t\tstatusClass += ' bp5-text-intent-success';\n\t\t\t\tbreak;\n\t\t\tcase 'Stopped':\n\t\t\tcase 'Failed':\n\t\t\tcase 'Destroying':\n\t\t\t\tstatusClass += ' bp5-text-intent-danger';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (instance.status?.includes(\"Restart Required\")) {\n\t\t\tstatusClass += ' bp5-text-intent-warning';\n\t\t}\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let networkRole of (instance.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={networkRole}\n\t\t\t\t>\n\t\t\t\t\t{networkRole}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveNetworkRole(networkRole);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet hasVpcs = false;\n\t\tlet vpcsSelect: JSX.Element[] = [];\n\t\tif (this.props.vpcs && this.props.vpcs.length) {\n\t\t\tvpcsSelect.push(<option key=\"null\" value=\"\">Select VPC</option>);\n\n\t\t\tfor (let vpc of this.props.vpcs) {\n\t\t\t\tif (vpc.organization != instance.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\thasVpcs = true;\n\t\t\t\tvpcsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={vpc.id}\n\t\t\t\t\t\tvalue={vpc.id}\n\t\t\t\t\t>{vpc.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasVpcs) {\n\t\t\tvpcsSelect = [<option key=\"null\" value=\"\">No VPCs</option>];\n\t\t}\n\n\t\tlet hasSubnets = false;\n\t\tlet subnetSelect: JSX.Element[] = [];\n\t\tif (this.props.vpcs && this.props.vpcs.length) {\n\t\t\tsubnetSelect.push(<option key=\"null\" value=\"\">Select Subnet</option>);\n\n\t\t\tfor (let vpc of this.props.vpcs) {\n\t\t\t\tif (vpc.organization != instance.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (vpc.id === instance.vpc) {\n\t\t\t\t\tfor (let sub of (vpc.subnets || [])) {\n\t\t\t\t\t\thasSubnets = true;\n\t\t\t\t\t\tsubnetSelect.push(\n\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\tkey={sub.id}\n\t\t\t\t\t\t\t\tvalue={sub.id}\n\t\t\t\t\t\t\t>{sub.name + ' - ' + sub.network}</option>,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!hasSubnets) {\n\t\t\tsubnetSelect = [<option key=\"null\" value=\"\">No Subnets</option>];\n\t\t}\n\n\t\tlet cloudSubnetsSelect: JSX.Element[] = [\n\t\t\t<option key=\"null\" value=\"\">Disabled</option>,\n\t\t];\n\t\tfor (let subnet of (info.cloud_subnets || [])) {\n\t\t\tcloudSubnetsSelect.push(\n\t\t\t\t<option key={subnet.id} value={subnet.id}>\n\t\t\t\t\t{subnet.name}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet domainsSelect: JSX.Element[] = [\n\t\t\t<option key=\"null\" value=\"\">No Domain</option>,\n\t\t];\n\t\tif (this.props.domains && this.props.domains.length) {\n\t\t\tfor (let domain of this.props.domains) {\n\t\t\t\tif (domain.organization != instance.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tdomainsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={domain.id}\n\t\t\t\t\t\tvalue={domain.id}\n\t\t\t\t\t>{domain.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet driveDevices: JSX.Element[] = [];\n\t\tfor (let device of (instance.drive_devices || [])) {\n\t\t\tlet key = device.id;\n\t\t\tdriveDevices.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={key}\n\t\t\t\t>\n\t\t\t\t\t{key}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveDriveDevice(key);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet infoDriveDevices = info.drive_devices;\n\t\tlet driveDevicesSelect: JSX.Element[] = [];\n\t\tfor (let i = 0; i < (infoDriveDevices || []).length; i++) {\n\t\t\tlet device = infoDriveDevices[i];\n\t\t\tdriveDevicesSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey={device.id}\n\t\t\t\t\tvalue={device.id}\n\t\t\t\t>\n\t\t\t\t\t{device.id}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet isos: JSX.Element[] = [];\n\t\tfor (let iso of (instance.isos || [])) {\n\t\t\tlet key = iso.name;\n\t\t\tisos.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={key}\n\t\t\t\t>\n\t\t\t\t\t{key}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveIso(key);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet infoIsos = info.isos;\n\t\tlet isosSelect: JSX.Element[] = [];\n\t\tfor (let i = 0; i < (infoIsos || []).length; i++) {\n\t\t\tlet iso = infoIsos[i];\n\t\t\tisosSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey={iso.name}\n\t\t\t\t\tvalue={iso.name}\n\t\t\t\t>\n\t\t\t\t\t{iso.name}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet usbDevices: JSX.Element[] = [];\n\t\tfor (let device of (instance.usb_devices || [])) {\n\t\t\tlet key = '';\n\t\t\tif (device.bus && device.address) {\n\t\t\t\tkey = device.bus + '-' + device.address;\n\t\t\t} else {\n\t\t\t\tkey = device.vendor + ':' + device.product;\n\t\t\t}\n\t\t\tusbDevices.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={key}\n\t\t\t\t>\n\t\t\t\t\t{key}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveUsbDevice(key);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet infoUsbDevices = info.usb_devices;\n\t\tlet usbDevicesSelect: JSX.Element[] = [];\n\t\tfor (let i = 0; i < (infoUsbDevices || []).length; i++) {\n\t\t\tlet device = infoUsbDevices[i];\n\t\t\tusbDevicesSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey={i + '_' + device.vendor + ':' + device.product}\n\t\t\t\t\tvalue={device.vendor + ':' + device.product}\n\t\t\t\t>\n\t\t\t\t\t{'Device=' + device.vendor + ':' + device.product +\n\t\t\t\t\t' (' + device.name + ')'}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\t\tfor (let i = 0; i < (infoUsbDevices || []).length; i++) {\n\t\t\tlet device = infoUsbDevices[i];\n\t\t\tusbDevicesSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey={i + '_' + device.bus + '-' + device.address}\n\t\t\t\t\tvalue={device.bus + '-' + device.address}\n\t\t\t\t>\n\t\t\t\t\t{'Bus=' + device.bus + ' Port=' + device.address +\n\t\t\t\t\t' (' + device.name + ')'}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet pciDevices: JSX.Element[] = [];\n\t\tfor (let device of (instance.pci_devices || [])) {\n\t\t\tlet key = device.slot;\n\t\t\tpciDevices.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={key}\n\t\t\t\t>\n\t\t\t\t\t{key}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemovePciDevice(key);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet infoPciDevices = info.pci_devices;\n\t\tlet pciDevicesSelect: JSX.Element[] = [];\n\t\tfor (let i = 0; i < (infoPciDevices || []).length; i++) {\n\t\t\tlet device = infoPciDevices[i];\n\t\t\tpciDevicesSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey={device.slot}\n\t\t\t\t\tvalue={device.slot}\n\t\t\t\t>\n\t\t\t\t\t{device.slot + ' ' + device.class + ':' + device.name}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet iscsiDevices = [...(instance.iscsi_devices || [])];\n\t\tif (iscsiDevices.length === 0) {\n\t\t\tiscsiDevices.push({});\n\t\t}\n\n\t\tlet iscsiDevicesElem: JSX.Element[] = [];\n\t\tfor (let i = 0; i < iscsiDevices.length; i++) {\n\t\t\tlet index = i;\n\t\t\tlet device = iscsiDevices[i];\n\n\t\t\tiscsiDevicesElem.push(\n\t\t\t\t<InstanceIscsiDevice\n\t\t\t\t\tkey={'iscsi-' + index}\n\t\t\t\t\tiscsi={device}\n\t\t\t\t\tonChange={(state: InstanceTypes.IscsiDevice): void => {\n\t\t\t\t\t\tthis.onChangeIscsiDevice(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(): void => {\n\t\t\t\t\t\tthis.onAddIscsiDevice(index);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveIscsiDevice(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet instanceMounts = instance.mounts || [];\n\t\tlet mounts: JSX.Element[] = [];\n\t\tfor (let i = 0; i < instanceMounts.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tmounts.push(\n\t\t\t\t<InstanceMount\n\t\t\t\t\tkey={index}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tmount={instanceMounts[index]}\n\t\t\t\t\tonChange={(state: InstanceTypes.Mount): void => {\n\t\t\t\t\t\tthis.onChangeMount(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddMount(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveMount(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet nodePorts: JSX.Element[] = [];\n\t\t(instance.node_ports || []).forEach((nodePort, index) => {\n\t\t\tif (nodePort.delete) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnodePorts.push(\n\t\t\t\t<InstanceNodePort\n\t\t\t\t\tkey={index}\n\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\tnodePort={nodePort}\n\t\t\t\t\tonChange={(state: InstanceTypes.NodePort): void => {\n\t\t\t\t\t\tthis.onChangeNodePort(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddNodePort(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveNodePort(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t})\n\n\t\tlet fields: PageInfos.Field[] = [\n\t\t\t{\n\t\t\t\tlabel: 'ID',\n\t\t\t\tvalue: this.props.instance.id || 'None',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Organization',\n\t\t\t\tvalue: org ? org.name :\n\t\t\t\t\tthis.props.instance.organization || 'None',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Zone',\n\t\t\t\tvalue: zone ? zone.name : this.props.instance.zone || 'None',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Node',\n\t\t\t\tvalue: info.node || 'None',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'State',\n\t\t\t\tvalue: (this.props.instance.action || 'None') + ':' + (\n\t\t\t\t\tthis.props.instance.state || 'None'),\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Uptime',\n\t\t\t\tvalue: this.props.instance.uptime || '-',\n\t\t\t},\n\t\t]\n\n\t\tfields.push(\n\t\t\t{\n\t\t\t\tlabel: 'Private IPv4',\n\t\t\t\tvalue: privateIps,\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Private IPv6',\n\t\t\t\tvalue: privateIps6,\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t);\n\n\t\tif (!cloudPublicIps) {\n\t\t\tfields.push(\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Public IPv4',\n\t\t\t\t\tvalue: publicIps || 'None',\n\t\t\t\t\tcopy: true,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (!cloudPublicIps6) {\n\t\t\tfields.push(\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Public IPv6',\n\t\t\t\t\tvalue: publicIps6 || 'None',\n\t\t\t\t\tcopy: true,\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\tif (cloudPrivateIps || cloudPublicIps || cloudPublicIps6) {\n\t\t\tfields.push(\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Cloud Private IPv4',\n\t\t\t\t\tvalue: cloudPrivateIps || 'None',\n\t\t\t\t\tcopy: true,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (cloudPrivateIps || cloudPublicIps || cloudPublicIps6) {\n\t\t\tfields.push(\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Cloud Public IPv4',\n\t\t\t\t\tvalue: cloudPublicIps || 'None',\n\t\t\t\t\tcopy: true,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (cloudPrivateIps || cloudPublicIps || cloudPublicIps6) {\n\t\t\tfields.push(\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Cloud Public IPv6',\n\t\t\t\t\tvalue: cloudPublicIps6 || 'None',\n\t\t\t\t\tcopy: true,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tlet networkFields: PageInfos.Field[] = []\n\n\t\tif (cloudPublicIps && publicIps) {\n\t\t\tnetworkFields.push(\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Public IPv4',\n\t\t\t\t\tvalue: publicIps || 'None',\n\t\t\t\t\tcopy: true,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (cloudPublicIps6 && publicIps6) {\n\t\t\tnetworkFields.push(\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Public IPv6',\n\t\t\t\t\tvalue: publicIps6 || 'None',\n\t\t\t\t\tcopy: true,\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\tnetworkFields.push(\n\t\t\t{\n\t\t\t\tlabel: 'Host IPv4',\n\t\t\t\tvalue: hostIps,\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Gateway IPv4',\n\t\t\t\tvalue: gatewayIps,\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Gateway IPv6',\n\t\t\t\tvalue: gatewayIps6,\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Public MAC Address',\n\t\t\t\tvalue: this.props.instance.public_mac || '-',\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Network MTU',\n\t\t\t\tvalue: info.mtu || '-',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Network Namespace',\n\t\t\t\tvalue: this.props.instance.network_namespace || '-',\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t)\n\n\t\tif (instance.node_ports) {\n\t\t\tlet fields: string[] = []\n\t\t\tinstance.node_ports.forEach((mapping) => {\n\t\t\t\tfields.push(`${mapping.protocol.toUpperCase()} ` +\n\t\t\t\t\t`${mapping.external_port} -> ${mapping.internal_port}`)\n\t\t\t})\n\n\t\t\tnetworkFields.push({\n\t\t\t\tlabel: 'Node Ports',\n\t\t\t\tvalue: fields.length ? fields : \"-\",\n\t\t\t})\n\t\t}\n\n\t\tlet instanceFields: PageInfos.Field[] = [\n\t\t\t{\n\t\t\t\tlabel: 'QEMU Version',\n\t\t\t\tvalue: this.props.instance.qemu_version || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Platform',\n\t\t\t\tvalue: (this.props.instance.uefi ? 'UEFI' : 'BIOS'),\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'SecureBoot',\n\t\t\t\tvalue: (this.props.instance.secure_boot ? 'Enabled' : 'Disabled'),\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Disks',\n\t\t\t\tvalue: info.disks || '-',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Authorities',\n\t\t\t\tvalue: info.authorities?.join(', ') || '-',\n\t\t\t},\n\t\t]\n\n\t\tif (this.props.instance.guest) {\n\t\t\tinstanceFields.push({\n\t\t\t\tlabel: 'Agent Heartbeat',\n\t\t\t\tvalue: MiscUtils.formatDateLocal(this.props.instance.guest.heartbeat),\n\t\t\t})\n\t\t}\n\n\t\tif (this.props.instance.deployment) {\n\t\t\tinstanceFields.push({\n\t\t\t\tlabel: 'Deployment',\n\t\t\t\tvalue: this.props.instance.deployment,\n\t\t\t})\n\t\t}\n\n\t\tfields.push(\n\t\t\t{\n\t\t\t\tlabel: 'Instance Details',\n\t\t\t\tvalue: 'Hover to Expand',\n\t\t\t\tvalueClass: 'bp5-text-intent-primary',\n\t\t\t\tembedded: {\n\t\t\t\t\tfields: instanceFields,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Networking',\n\t\t\t\tvalue: 'Hover to Expand',\n\t\t\t\tvalueClass: 'bp5-text-intent-primary',\n\t\t\t\tembedded: {\n\t\t\t\t\tfields: networkFields,\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\n\t\tif (this.props.instance.root_enabled ||\n\t\t\t\tthis.props.instance.vnc || this.props.instance.spice) {\n\n\t\t\tlet accessFields: PageInfos.Field[] = []\n\n\t\t\tif (this.props.instance.root_enabled) {\n\t\t\t\taccessFields.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: 'Root Password',\n\t\t\t\t\t\tvalue: this.props.instance.root_passwd,\n\t\t\t\t\t\tcopy: true,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (this.props.instance.vnc) {\n\t\t\t\tif (info.node_public_ip) {\n\t\t\t\t\taccessFields.push(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'VNC IP',\n\t\t\t\t\t\t\tvalue: info.node_public_ip,\n\t\t\t\t\t\t\tcopy: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tlet vncPort;\n\t\t\t\tif (this.props.instance.vnc_display) {\n\t\t\t\t\tvncPort = this.props.instance.vnc_display + 5900;\n\t\t\t\t} else {\n\t\t\t\t\tvncPort = '-';\n\t\t\t\t}\n\n\t\t\t\taccessFields.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: 'VNC Port',\n\t\t\t\t\t\tvalue: vncPort,\n\t\t\t\t\t\tcopy: true,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: 'VNC Password',\n\t\t\t\t\t\tvalue: this.props.instance.vnc_password,\n\t\t\t\t\t\tcopy: true,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (this.props.instance.spice) {\n\t\t\t\tif (info.node_public_ip) {\n\t\t\t\t\tfields.push(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Spice IP',\n\t\t\t\t\t\t\tvalue: info.node_public_ip,\n\t\t\t\t\t\t\tcopy: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tfields.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: 'Spice Port',\n\t\t\t\t\t\tvalue: this.props.instance.spice_port || '-',\n\t\t\t\t\t\tcopy: true,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: 'Spice Password',\n\t\t\t\t\t\tvalue: this.props.instance.spice_password,\n\t\t\t\t\t\tcopy: true,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tfields.push(\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Remote Access',\n\t\t\t\t\tvalue: 'Hover to Expand',\n\t\t\t\t\tvalueClass: 'bp5-text-intent-primary',\n\t\t\t\t\tembedded: {\n\t\t\t\t\t\tfields: accessFields,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tfields.push(\n\t\t\t{\n\t\t\t\tlabel: 'Firewall Rules',\n\t\t\t\tvalue: 'Hover to Expand',\n\t\t\t\tvalueClass: 'bp5-text-intent-primary',\n\t\t\t\tembedded: {\n\t\t\t\t\tfields: InstanceTypes.FirewallFields(info),\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\n\t\tif (instance.guest?.updates?.length > 0) {\n\t\t\tfields.push({\n\t\t\t\tlabel: 'System Updates',\n\t\t\t\tvalue: 'Hover to Expand',\n\t\t\t\tvalueClass: 'bp5-text-intent-warning',\n\t\t\t\tembedded: {\n\t\t\t\t\tfields: InstanceTypes.UpdateFields(instance),\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tlet resourceBars: PageInfos.Bar[] = []\n\t\tif (this.props.instance.status === \"Provisioning\" &&\n\t\t\tthis.props.instance.status_info?.download_progress) {\n\n\t\t\tlet speedLabel = \"\"\n\t\t\tif (this.props.instance.status_info?.download_speed) {\n\t\t\t\tspeedLabel = ` (${MiscUtils.humanReadableSpeedMb(\n\t\t\t\t\tthis.props.instance.status_info?.download_speed\n\t\t\t\t)})`\n\t\t\t}\n\n\t\t\tresourceBars.push({\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-primary',\n\t\t\t\tlabel: 'Image Download' + speedLabel,\n\t\t\t\tvalue: this.props.instance.status_info.download_progress || 0,\n\t\t\t})\n\t\t}\n\n\t\tif (this.props.instance.guest) {\n\t\t\tresourceBars.push({\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-success',\n\t\t\t\tlabel: 'Load1',\n\t\t\t\tvalue: this.props.instance.guest.load1 || 0,\n\t\t\t})\n\t\t\tresourceBars.push({\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-warning',\n\t\t\t\tlabel: 'Load5',\n\t\t\t\tvalue: this.props.instance.guest.load5 || 0,\n\t\t\t})\n\t\t\tresourceBars.push({\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-danger',\n\t\t\t\tlabel: 'Load15',\n\t\t\t\tvalue: this.props.instance.guest.load15 || 0,\n\t\t\t})\n\t\t\tresourceBars.push({\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-primary',\n\t\t\t\tlabel: 'Memory',\n\t\t\t\tvalue: this.props.instance.guest.memory || 0,\n\t\t\t})\n\n\t\t\tif (this.props.instance.guest.hugepages) {\n\t\t\t\tresourceBars.push({\n\t\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-primary',\n\t\t\t\t\tlabel: 'HugePages',\n\t\t\t\t\tvalue: this.props.instance.guest.hugepages || 0,\n\t\t\t\t\tcolor: '#7207d4',\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tlet vncStyle = {\n\t\t\theight: this.state.vncHeight ? this.state.vncHeight + 'px' : '100%',\n\t\t\tmarginBottom: '10px',\n\t\t} as React.CSSProperties;\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={6}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n            <div>\n              <label\n                className=\"bp5-control bp5-checkbox\"\n                style={css.select}\n              >\n                <input\n                  type=\"checkbox\"\n                  checked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n                  onClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n                />\n                <span className=\"bp5-control-indicator\"/>\n              </label>\n            </div>\n\t\t\t\t\t\t<div className={statusClass} style={css.status}>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\t\t\t\thidden={!instance.status}\n\t\t\t\t\t\t\t\tclassName=\"bp5-icon-standard bp5-icon-power\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{instance.status}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<InstanceAdvDialog updates={this.props.instance.guest?.updates}/>\n\t\t\t\t\t\t<Relations kind=\"instance\" id={this.props.instance.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Instance\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this instance\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[instance.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of instance\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={instance.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Instance comment.\"\n\t\t\t\t\t\tplaceholder=\"Instance comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={instance.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\tlabel=\"Memory Size\"\n\t\t\t\t\t\thelp=\"Instance memory size in megabytes.\"\n\t\t\t\t\t\tmin={256}\n\t\t\t\t\t\tminorStepSize={512}\n\t\t\t\t\t\tstepSize={1024}\n\t\t\t\t\t\tmajorStepSize={2048}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\tthis.set('memory', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tvalue={instance.memory}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\tlabel=\"Processors\"\n\t\t\t\t\t\thelp=\"Number of instance processors.\"\n\t\t\t\t\t\tmin={1}\n\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\tmajorStepSize={2}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\tthis.set('processors', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tvalue={instance.processors}\n\t\t\t\t\t/>\n\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\tRoles\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\tcontent=\"Roles that will be matched with firewall rules. Roles are case-sensitive.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\tvalue={this.state.addNetworkRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddNetworkRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddNetworkRole}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasVpcs}\n\t\t\t\t\t\tlabel=\"VPC\"\n\t\t\t\t\t\thelp=\"VPC for instance.\"\n\t\t\t\t\t\tvalue={instance.vpc}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('vpc', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{vpcsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasVpcs}\n\t\t\t\t\t\tlabel=\"Subnet\"\n\t\t\t\t\t\thelp=\"Subnet for instance.\"\n\t\t\t\t\t\tvalue={instance.subnet}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('subnet', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{subnetSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={cloudSubnetsSelect.length <= 1}\n\t\t\t\t\t\tlabel=\"Oracle Cloud Subnet\"\n\t\t\t\t\t\thelp=\"Oracle Cloud subnet for instance.\"\n\t\t\t\t\t\tvalue={instance.cloud_subnet}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('cloud_subnet', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{cloudSubnetsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\thidden={!this.state.showSettings ||\n\t\t\t\t\t\t\t(!isos.length && !isosSelect.length)}\n\t\t\t\t\t>\n\t\t\t\t\t\tISO Images\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"ISO Images\"\n\t\t\t\t\t\t\tcontent=\"ISO images to attach to instance.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{isos}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\thidden={!this.state.showSettings ||\n\t\t\t\t\t\t\t(!isos.length && !isosSelect.length)}\n\t\t\t\t\t\tlabel=\"Add ISO\"\n\t\t\t\t\t\tvalue={this.state.addIso}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddIso: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddIso}\n\t\t\t\t\t>\n\t\t\t\t\t\t{isosSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\thidden={!this.state.showSettings ||\n\t\t\t\t\t\t\t(!driveDevices.length && !driveDevicesSelect.length)}\n\t\t\t\t\t>\n\t\t\t\t\t\tDisk Passthrough Devices\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Disk Passthrough Devices\"\n\t\t\t\t\t\t\tcontent=\"Passthrough node disk to instance.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{driveDevices}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\thidden={!this.state.showSettings ||\n\t\t\t\t\t\t\t(!driveDevices.length && !driveDevicesSelect.length)}\n\t\t\t\t\t\tlabel=\"Add Device\"\n\t\t\t\t\t\tvalue={this.state.addDriveDevice}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddDriveDevice: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddDriveDevice}\n\t\t\t\t\t>\n\t\t\t\t\t\t{driveDevicesSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<label\n\t\t\t\t\t\thidden={!this.state.showSettings || (!info.iscsi &&\n\t\t\t\t\t\t\t(!this.props.instance.iscsi_devices ||\n\t\t\t\t\t\t\tthis.props.instance.iscsi_devices.length === 0))}\n\t\t\t\t\t\tstyle={css.itemsLabel}\n\t\t\t\t\t>\n\t\t\t\t\t\tiSCSI Devices\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"iSCSI Devices\"\n\t\t\t\t\t\t\tcontent=\"Mount iSCSI disks with URI, below are examples without and with authentication.\"\n\t\t\t\t\t\t\texamples={[\n\t\t\t\t\t\t\t\t'iscsi://10.0.0.1/iqn.2001-04.com.example/lun',\n\t\t\t\t\t\t\t\t'iscsi://username:password@10.0.0.1/iqn.2001-04.com.example/lun',\n\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div\n\t\t\t\t\t\thidden={!this.state.showSettings || (!info.iscsi &&\n\t\t\t\t\t\t\t(!this.props.instance.iscsi_devices ||\n\t\t\t\t\t\t\tthis.props.instance.iscsi_devices.length === 0))}\n\t\t\t\t\t\tstyle={css.list}\n\t\t\t\t\t>\n\t\t\t\t\t\t{iscsiDevicesElem}\n\t\t\t\t\t</div>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\thidden={!this.state.showSettings ||\n\t\t\t\t\t\t\t(!pciDevices.length && !pciDevicesSelect.length)}\n\t\t\t\t\t>\n\t\t\t\t\t\tPCI Devices\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"PCI Devices\"\n\t\t\t\t\t\t\tcontent=\"PCI devices to for host passthrough to instance.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{pciDevices}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\thidden={!this.state.showSettings ||\n\t\t\t\t\t\t\t(!pciDevices.length && !pciDevicesSelect.length)}\n\t\t\t\t\t\tlabel=\"Add Device\"\n\t\t\t\t\t\tvalue={this.state.addPciDevice}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddPciDevice: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddPciDevice}\n\t\t\t\t\t>\n\t\t\t\t\t\t{pciDevicesSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\thidden={\n\t\t\t\t\t\t\t!this.state.showSettings || infoUsbDevices === null ||\n\t\t\t\t\t\t\t(!instance.usb_devices && !infoUsbDevices)\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\tUSB Devices\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"USB Devices\"\n\t\t\t\t\t\t\tcontent=\"USB devices to for host passthrough to instance.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{usbDevices}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\thidden={\n\t\t\t\t\t\t\t!this.state.showSettings || infoUsbDevices === null ||\n\t\t\t\t\t\t\t(!instance.usb_devices && !infoUsbDevices)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlabel=\"Add Device\"\n\t\t\t\t\t\tvalue={this.state.addUsbDevice}\n\t\t\t\t\t\tdisabled={!usbDevicesSelect.length || this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddUsbDevice: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddUsbDevice}\n\t\t\t\t\t>\n\t\t\t\t\t\t{usbDevicesSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\t\tlabel=\"CloudInit Type\"\n\t\t\t\t\t\thelp=\"Target operating system for cloud init\"\n\t\t\t\t\t\tvalue={instance.cloud_type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('cloud_type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option key=\"linux\" value=\"linux\">Linux</option>,\n\t\t\t\t\t\t<option key=\"bsd\" value=\"bsd\">BSD</option>,\n\t\t\t\t\t\t<option key=\"linux_legacy\" value=\"linux_legacy\">Linux Legacy</option>,\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Startup script\"\n\t\t\t\t\t\thelp=\"Script to run on instance startup. These commands will run on every startup. File must start with #! such as `#!/bin/bash` to specify code interpreter.\"\n\t\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\t\tchecked={this.state.startupScript === true || (\n\t\t\t\t\t\t\tthis.state.startupScript === null && !!instance.cloud_script)}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\t\t\tinstance = {\n\t\t\t\t\t\t\t\t\t...this.state.instance,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tinstance = {\n\t\t\t\t\t\t\t\t\t...this.props.instance,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\t\t\tinstance: instance,\n\t\t\t\t\t\t\t\tstartupScript: !(this.state.startupScript === true || (\n\t\t\t\t\t\t\t\t\tthis.state.startupScript === null &&\n\t\t\t\t\t\t\t\t\t!!instance.cloud_script)),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Startup Script\"\n\t\t\t\t\t\thelp=\"Script to run on instance startup. These commands will run on every startup. File must start with #! such as `#!/bin/bash` to specify code interpreter.\"\n\t\t\t\t\t\tplaceholder=\"Startup script\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\thidden={!this.state.showSettings ||\n\t\t\t\t\t\t\t!(this.state.startupScript === true || (\n\t\t\t\t\t\t\tthis.state.startupScript === null && !!instance.cloud_script))}\n\t\t\t\t\t\tvalue={instance.cloud_script}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('cloud_script', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\t\tlabel=\"UEFI\"\n\t\t\t\t\t\thelp=\"Enable UEFI boot, requires OVMF package for UEFI image.\"\n\t\t\t\t\t\tchecked={instance.uefi}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('uefi', !instance.uefi);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!this.state.showSettings || !instance.uefi}\n\t\t\t\t\t\tlabel=\"SecureBoot\"\n\t\t\t\t\t\thelp=\"Enable secure boot, requires OVMF package for UEFI image.\"\n\t\t\t\t\t\tchecked={instance.secure_boot}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('secure_boot', !instance.secure_boot);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!this.state.showSettings || !instance.uefi}\n\t\t\t\t\t\tlabel=\"TPM\"\n\t\t\t\t\t\thelp=\"Enable TPM, requires swtpm and OVMF package.\"\n\t\t\t\t\t\tchecked={instance.tpm}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('tpm', !instance.tpm);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Public IPv4 address\"\n\t\t\t\t\t\thelp=\"Enable or disable public IPv4 address for instance. Node must have network mode configured to assign public address.\"\n\t\t\t\t\t\tchecked={!instance.no_public_address}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('no_public_address', !instance.no_public_address);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Public IPv6 address\"\n\t\t\t\t\t\thelp=\"Enable or disable public IPv6 address for instance. Node must have network mode configured to assign public address.\"\n\t\t\t\t\t\tchecked={!instance.no_public_address6}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('no_public_address6', !instance.no_public_address6);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Host address\"\n\t\t\t\t\t\thelp=\"Enable or disable host address for instance. Node must have host networking configured to assign host address.\"\n\t\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\t\tchecked={!instance.no_host_address}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('no_host_address', !instance.no_host_address);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Skip source/destination check\"\n\t\t\t\t\t\thelp=\"Allow network traffic from non-instance addresses.\"\n\t\t\t\t\t\tchecked={instance.skip_source_dest_check}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('skip_source_dest_check',\n\t\t\t\t\t\t\t\t!instance.skip_source_dest_check);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"DHCP server\"\n\t\t\t\t\t\thelp=\"Enable instance DHCP server, use for instances without cloud init network configuration support.\"\n\t\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\t\tchecked={instance.dhcp_server}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('dhcp_server', !instance.dhcp_server);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\thelp=\"Block instance and any attached disks from being deleted.\"\n\t\t\t\t\t\tchecked={instance.delete_protection}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('delete_protection', !instance.delete_protection);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={fields}\n\t\t\t\t\t\tbars={resourceBars}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tstyle={css.itemsLabel}\n\t\t\t\t\t\thidden={!this.state.showSettings || !nodePorts.length}\n\t\t\t\t\t>\n\t\t\t\t\t\tNode Ports\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Node Ports\"\n\t\t\t\t\t\t\tcontent=\"Node port mappings from node public IP to internal instance. Acceptable external port range is 30000-32767, leave external port empty to automatically assign a port.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div\n\t\t\t\t\t\tstyle={css.items}\n\t\t\t\t\t\thidden={!this.state.showSettings || !nodePorts.length}\n\t\t\t\t\t>\n\t\t\t\t\t\t{nodePorts}\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Node ports\"\n\t\t\t\t\t\thelp=\"Node port mappings from node public IP to internal instance. Acceptable external port range is 30000-32767, leave external port empty to automatically assign a port.\"\n\t\t\t\t\t\tchecked={false}\n\t\t\t\t\t\thidden={!this.state.showSettings || !!nodePorts.length}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.onAddNodePort(-1)\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tstyle={css.itemsLabel}\n\t\t\t\t\t\thidden={!this.state.showSettings || !instance.mounts?.length}\n\t\t\t\t\t>\n\t\t\t\t\t\tHost Paths\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Host Paths\"\n\t\t\t\t\t\t\tcontent=\"Local paths on the host that are available for instances to access through VirtIO-FS sharing. The path must be match or be a subdirectory of a configured host share path in the node settings. The instance's organization must also have a matching role to access the host share.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div\n\t\t\t\t\t\tstyle={css.items}\n\t\t\t\t\t\thidden={!this.state.showSettings || !instance.mounts?.length}\n\t\t\t\t\t>\n\t\t\t\t\t\t{mounts}\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Host paths\"\n\t\t\t\t\t\thelp=\"Local paths on the host that are available for instances to access through VirtIO-FS sharing. The path must be match or be a subdirectory of a configured host share path in the node settings. The instance's organization must also have a matching role to access the host share.\"\n\t\t\t\t\t\tchecked={false}\n\t\t\t\t\t\thidden={!this.state.showSettings || !!instance.mounts?.length}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.onAddMount(-1)\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\t\tlabel=\"VNC server\"\n\t\t\t\t\t\thelp=\"Enable VNC server for remote control of instance.\"\n\t\t\t\t\t\tchecked={instance.vnc}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('vnc', !instance.vnc);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\t\tlabel=\"Spice server\"\n\t\t\t\t\t\thelp=\"Enable Spice server for remote control of instance.\"\n\t\t\t\t\t\tchecked={instance.spice}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('spice', !instance.spice);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\t\tlabel=\"Desktop GUI\"\n\t\t\t\t\t\thelp=\"Enable desktop GUI window for instance display.\"\n\t\t\t\t\t\tchecked={instance.gui}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('gui', !instance.gui);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!this.state.showSettings}\n\t\t\t\t\t\tlabel=\"Root enabled\"\n\t\t\t\t\t\thelp=\"Enable root unix account for VNC/Spice access. Random password will be generated.\"\n\t\t\t\t\t\tchecked={instance.root_enabled}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('root_enabled', !instance.root_enabled);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\thidden={!this.state.instance && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\twrap={true}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tforwardedChecked: false,\n\t\t\t\t\t\tinstance: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t>\n\t\t\t\t<button\n\t\t\t\t\tclassName={\"bp5-button bp5-icon-cog \" + (this.state.showSettings ?\n\t\t\t\t\t\t\"bp5-intent-danger\" : \"bp5-intent-primary\")}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tshowSettings: !this.state.showSettings,\n\t\t\t\t\t\t})\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{this.state.showSettings ? \"Collapse\" : \"Expand\"} Settings\n\t\t\t\t</button>\n\t\t\t\t<ConfirmButton\n\t\t\t\t\tlabel=\"Start\"\n\t\t\t\t\tclassName=\"bp5-intent-success bp5-icon-power\"\n\t\t\t\t\tprogressClassName=\"bp5-intent-success\"\n\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\thidden={this.props.instance.action !== 'stop'}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tonConfirm={(): void => {\n\t\t\t\t\t\tthis.update('start');\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<ConfirmButton\n\t\t\t\t\tlabel=\"Stop\"\n\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-power\"\n\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\thidden={this.props.instance.action !== 'start'}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tonConfirm={(): void => {\n\t\t\t\t\t\tthis.update('stop');\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-console\"\n\t\t\t\t\thidden={this.state.vnc || !this.props.instance.vnc}\n\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.onTogleVnc();\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tVNC Console\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-intent-danger bp5-icon-console\"\n\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.onTogleVnc();\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tVNC Console\n\t\t\t\t</button>\n\t\t\t</PageSave>\n\t\t\t<div style={css.vncBox}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={'bp5-button bp5-icon-key-control' +\n\t\t\t\t\t\t\t(this.state.vncCtrl ? ' bp5-active' : '')}\n\t\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onToggleVncCtrl();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tCtrl\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={'bp5-button bp5-icon-key-option' +\n\t\t\t\t\t\t\t(this.state.vncAlt ? ' bp5-active' : '')}\n\t\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onToggleVncAlt();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tAlt\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={'bp5-button bp5-icon-key-command' +\n\t\t\t\t\t\t\t(this.state.vncSuper ? ' bp5-active' : '')}\n\t\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onToggleVncSuper();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tSuper\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-icon-key-tab\"\n\t\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onVncTab();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tTab\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-icon-key-escape\"\n\t\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onVncEsc();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tEsc\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-icon-fullscreen\"\n\t\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onToggleVncFullscreen();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tFullscreen\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={'bp5-button bp5-icon-zoom-to-fit' +\n\t\t\t\t\t\t\t(this.state.vncScale ? ' bp5-active' : '')}\n\t\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onToggleVncScale();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tScale\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-icon-control\"\n\t\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onVncCtrlAltDel();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tCtrl+Alt+Del\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tref={this.vncRef}\n\t\t\t\t\tstyle={vncStyle}\n\t\t\t\t\thidden={!this.state.vnc}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/InstanceImages.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as Icons from '@blueprintjs/icons';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport PageCustom from './PageCustom';\n\ninterface Props {\n\timages: ImageTypes.ImagesRo\n\timage: ImageTypes.Image;\n\tuefi: boolean\n\tshowHidden: boolean\n\tdisabled: boolean\n\tonChange: (img: ImageTypes.Image) => void\n}\n\nconst css = {\n\tlogo: {\n\t\tbackgroundRepeat: \"no-repeat\",\n    backgroundSize: \"contain\",\n    backgroundPosition: \"center\",\n\t\theight: '19px',\n\t\twidth: '19px',\n\t} as React.CSSProperties,\n\timagesOpen: {\n\t} as React.CSSProperties,\n\timagesMenu: {\n\t\tmaxHeight: '520px',\n\t\toverflowY: \"auto\",\n\t} as React.CSSProperties,\n};\n\nexport default class InstanceImages extends React.Component<Props, {}> {\n\tparseImage(img: ImageTypes.Image, button?: boolean): JSX.Element {\n\t\tlet name = img.name\n\t\tlet icon: Blueprint.IconName | Blueprint.MaybeElement = <Icons.Compressed/>\n\n\t\tif (img.signed) {\n\t\t\tlet imgSpl = img.key.split('_');\n\t\t\tlet imgVer = imgSpl?.[1]?.split(\".\")[0] || \"\"\n\t\t\tlet imgNameSpl = imgSpl[0].match(/^(.+?)(\\d+)$/);\n\t\t\tlet distro = imgNameSpl?.[1] || imgSpl[0]\n\t\t\tlet version = imgNameSpl?.[2] || \"\"\n\t\t\tlet matched = true\n\n\t\t\tswitch (distro) {\n\t\t\t\tcase \"almalinux\":\n\t\t\t\t\tname = `AlmaLinux ${version}`\n\t\t\t\t\ticon = <div style={css.logo} className=\"almalinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"alpinelinux\":\n\t\t\t\t\tname = `Alpine Linux`\n\t\t\t\t\ticon = <div style={css.logo} className=\"alpinelinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"archlinux\":\n\t\t\t\t\tname = `Arch Linux`\n\t\t\t\t\ticon = <div style={css.logo} className=\"archlinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"fedora\":\n\t\t\t\t\tname = `Fedora ${version}`\n\t\t\t\t\ticon = <div style={css.logo} className=\"fedora-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"freebsd\":\n\t\t\t\t\tname = `FreeBSD`\n\t\t\t\t\ticon = <div style={css.logo} className=\"freebsd-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"oraclelinux\":\n\t\t\t\t\tname = `Oracle Linux ${version}`\n\t\t\t\t\ticon = <div style={css.logo} className=\"oraclelinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"rockylinux\":\n\t\t\t\t\tname = `Rocky Linux ${version}`\n\t\t\t\t\ticon = <div style={css.logo} className=\"rockylinux-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tcase \"ubuntu\":\n\t\t\t\t\tname = `Ubuntu ${version.slice(0, 2) + \".\" + version.slice(2)}`\n\t\t\t\t\ticon = <div style={css.logo} className=\"ubuntu-logo\"/>\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\tmatched = false\n\t\t\t}\n\n\t\t\tif (matched && imgVer) {\n\t\t\t\tname += ` (${MiscUtils.parseImageDate(imgVer)})`\n\t\t\t}\n\t\t}\n\n\t\tif (button) {\n\t\t\treturn <Blueprint.Button\n\t\t\t\talignText=\"left\"\n\t\t\t\ticon={icon}\n\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\tstyle={css.imagesOpen}\n\t\t\t>\n\t\t\t\t{name}\n\t\t\t</Blueprint.Button>\n\t\t}\n\n\t\treturn <Blueprint.MenuItem\n\t\t\tkey={img.id}\n\t\t\troleStructure=\"listoption\"\n\t\t\ticon={icon}\n\t\t\tonClick={(): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\timage: img,\n\t\t\t\t})\n\t\t\t\tthis.props.onChange(img)\n\t\t\t}}\n\t\t\ttext={name}\n\t\t/>\n\t}\n\n\trender(): JSX.Element {\n\t\tlet hasImages = false\n\t\tlet imagesSelect: JSX.Element[] = []\n\t\tlet signedImages: ImageTypes.Image[] = []\n\t\tlet otherImages: ImageTypes.Image[] = []\n\t\tlet imagesVer = new Map<string, [number, ImageTypes.Image]>()\n\t\tlet selectButton: JSX.Element\n\n\t\tif (this.props.images.length) {\n\t\t\thasImages = true;\n\t\t\tfor (let image of this.props.images) {\n\t\t\t\tif (this.props.uefi && image.firmware === 'bios') {\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if (!this.props.uefi && image.firmware === 'uefi') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (image.signed) {\n\t\t\t\t\tif (!this.props.showHidden) {\n\t\t\t\t\t\tlet imgSpl = image.key.split('_');\n\n\t\t\t\t\t\tif (imgSpl.length >= 2 && imgSpl[imgSpl.length - 1].length >= 4) {\n\t\t\t\t\t\t\tlet imgKey = imgSpl[0]\n\t\t\t\t\t\t\tlet imgVer = parseInt(\n\t\t\t\t\t\t\t\timgSpl[imgSpl.length - 1].substring(0, 4), 10);\n\t\t\t\t\t\t\tif (imgVer) {\n\t\t\t\t\t\t\t\tlet curImg = imagesVer.get(imgKey);\n\t\t\t\t\t\t\t\tif (!curImg || imgVer > curImg[0]) {\n\t\t\t\t\t\t\t\t\timagesVer.set(imgKey, [imgVer, image]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsignedImages.push(image)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\totherImages.push(image)\n\t\t\t}\n\n\t\t\tif (!this.props.showHidden) {\n\t\t\t\tlet fedoraReleases: [string, [number, ImageTypes.Image]][] = []\n\t\t\t\tlet nonFedoraImages: [string, [number, ImageTypes.Image]][] = []\n\n\t\t\t\tfor (let entry of imagesVer.entries()) {\n\t\t\t\t\tif (entry[0].startsWith('fedora')) {\n\t\t\t\t\t\tfedoraReleases.push(entry)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnonFedoraImages.push(entry)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (fedoraReleases.length > 2) {\n\t\t\t\t\tfedoraReleases.sort((a, b) => {\n\t\t\t\t\t\tlet aNum = parseInt(a[0].replace('fedora', ''), 10) || 0\n\t\t\t\t\t\tlet bNum = parseInt(b[0].replace('fedora', ''), 10) || 0\n\t\t\t\t\t\treturn bNum - aNum\n\t\t\t\t\t})\n\t\t\t\t\tfedoraReleases = fedoraReleases.slice(0, 2)\n\t\t\t\t}\n\n\t\t\t\timagesVer = new Map([...nonFedoraImages, ...fedoraReleases])\n\t\t\t}\n\n\t\t\tconst sortedVersionedImages = Array.from(imagesVer.entries())\n\t\t\t\t.sort((a, b) => MiscUtils.naturalSort(a[1][1].name, b[1][1].name));\n\t\t\tfor (let [key, [ver, img]] of sortedVersionedImages) {\n\t\t\t\timagesSelect.push(this.parseImage(img));\n\t\t\t}\n\n\t\t\tsignedImages.sort((a, b) => MiscUtils.naturalSort(a.name, b.name));\n\t\t\tfor (let img of signedImages) {\n\t\t\t\timagesSelect.push(this.parseImage(img));\n\t\t\t}\n\n\t\t\tif (imagesSelect.length && otherImages.length) {\n\t\t\t\timagesSelect.push(<Blueprint.MenuDivider\n\t\t\t\t\tkey=\"menu-spec-divider\"\n\t\t\t\t/>)\n\t\t\t}\n\n\t\t\tfor (let img of otherImages) {\n\t\t\t\timagesSelect.push(this.parseImage(img));\n\t\t\t}\n\t\t}\n\n\t\tif (this.props.image) {\n\t\t\tselectButton = this.parseImage(this.props.image, true)\n\t\t} else {\n\t\t\tselectButton = <Blueprint.Button\n\t\t\t\talignText=\"left\"\n\t\t\t\ticon={<Icons.Compressed/>}\n\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\ttext=\"Select Image\"\n\t\t\t\tstyle={css.imagesOpen}\n\t\t\t\tdisabled={this.props.disabled || !hasImages}\n\t\t\t/>\n\t\t}\n\n\t\treturn <PageCustom\n\t\t\tlabel=\"Image\"\n\t\t\thelp=\"Starting image for instance.\"\n\t\t>\n\t\t\t<Blueprint.Popover\n\t\t\t\tcontent={<Blueprint.Menu style={css.imagesMenu}>\n\t\t\t\t\t{imagesSelect}\n\t\t\t\t</Blueprint.Menu>}\n\t\t\t\tplacement=\"bottom\"\n\t\t\t>\n\t\t\t\t{selectButton}\n\t\t\t</Blueprint.Popover>\n\t\t</PageCustom>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/InstanceIscsiDevice.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as InstanceTypes from '../types/InstanceTypes';\n\ninterface Props {\n\tdisabled?: boolean;\n\tiscsi: InstanceTypes.IscsiDevice;\n\tonChange?: (state: InstanceTypes.IscsiDevice) => void;\n\tonAdd: () => void;\n\tonRemove?: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class InstanceIscsiDevice extends React.Component<Props, {}> {\n\tclone(): InstanceTypes.IscsiDevice {\n\t\treturn {\n\t\t\t...this.props.iscsi,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet iscsi = this.props.iscsi;\n\n\t\treturn <div>\n\t\t\t<div className=\"bp5-control-group\" style={css.group}>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"iSCSI URI\"\n\t\t\t\t\t\tvalue={iscsi.uri || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.uri = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onAdd();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/InstanceLicense.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as License from '../License';\nimport * as Theme from '../Theme';\n\ninterface Props {\n\topen?: boolean;\n\tonClose?: () => void;\n}\n\nconst css = {\n\tdialog: {\n\t\theight: '500px',\n\t\twidth: '90%',\n\t\tmaxWidth: '700px',\n\t} as React.CSSProperties,\n\ttextarea: {\n\t\tresize: 'none',\n\t\tfontSize: Theme.monospaceSize,\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n};\n\nconst license = `ORACLE LINUX LICENSE AGREEMENT\n\n“We,” “us,” “our” and “Oracle” refers to Oracle America, Inc.  “You” and “your” refers to the\nindividual or entity that has acquired the Oracle Linux programs.  “Oracle Linux programs”\nrefers to the Linux software product which you have acquired.  “License” refers to your right to\nuse the Oracle Linux programs under the terms of this Agreement and the licenses referenced\nherein.  This Agreement is governed by the substantive and procedural laws of the United States\nand the State of California and you and Oracle agree to submit to the exclusive jurisdiction of,\nand venue in, the courts of San Francisco or Santa Clara counties in California in any dispute\narising out of or relating to this Agreement.\n\nWe are willing to provide a copy of the Oracle Linux programs to you only upon the condition\nthat you accept all of the terms contained in this Agreement.  Read the terms carefully and\nindicate your acceptance by either selecting the “Accept” button at the bottom of the page to\nconfirm your acceptance, if you are downloading the Oracle Linux programs, or continuing to\ninstall the Oracle Linux programs, if you have received this Agreement during the installation\nprocess.  If you are not willing to be bound by these terms, select the “Do Not Accept” button or\ndiscontinue the installation process.\n\n1. Grant of Licenses to the Oracle Linux programs. Subject to the terms of this Agreement,\nOracle grants to you a license to the Oracle Linux programs under the GNU General Public\nLicense version 2.0. The Oracle Linux programs contain many components developed by Oracle\nand various third parties. The license for each component is located in the licensing\ndocumentation and/or in the component's source code.  In addition, a list of components may be\ndelivered with the Oracle Linux programs and the Additional Oracle Linux programs (as defined\nbelow) or accessed online at http://oss.oracle.com/linux/legal/oracle-list.html.  The source code\nfor the Oracle Linux Programs and the Additional Oracle Linux programs can be found and\naccessed online at https://oss.oracle.com/sources/.  This agreement does not limit, supersede or\nmodify your rights under the license associated with any separately licensed individual\ncomponent.\n\n2. Licenses to Additional Oracle Linux programs.  Certain third-party technology (collectively\nthe “Additional Oracle Linux programs”) may be included on the same medium or as part of the\ndownload of Oracle Linux programs you receive, but is not part of the Oracle Linux programs.\nEach Additional Oracle Linux program is licensed solely under the terms of the Mozilla Public\nLicense, Apache License, Common Public License, GNU Lesser General Public License,\nNetscape Public License or similar license that is included with the relevant Additional Oracle\nLinux program.\n\n3. Ownership. The Oracle Linux programs and their components and the Additional Oracle\nLinux programs are owned by Oracle or its licensors.  Subject to the licenses granted and/or\nreferenced herein, title to the Oracle Linux programs and their components and the Additional\nOracle Linux programs remains with Oracle and/or its licensors.\n\n4. Trademark License. You are permitted to distribute unmodified Oracle Linux programs or\nunmodified Additional Oracle Linux programs without removing the trademark(s) owned by\nOracle or its affiliates that are included in the unmodified Oracle Linux programs or unmodified\nAdditional Oracle Linux programs (the “Oracle Linux trademarks”). You may only distribute\nmodified Oracle Linux programs or modified Additional Oracle Linux programs if you remove\nrelevant images containing the Oracle Linux trademarks. Certain files, identified in\nhttp://oss.oracle.com/linux/legal/oracle-list.html, include such trademarks. Do not delete these\nfiles, as deletion may corrupt the Oracle Linux programs or Additional Oracle Linux programs.\nYou are not granted any other rights to Oracle Linux trademarks, and you acknowledge that you\nshall not gain any proprietary interest in the Oracle Linux trademarks. All goodwill arising out of\nuse of the Oracle Linux trademarks shall inure to the benefit of Oracle or its affiliates. You may\nnot use any trademarks owned by Oracle or its affiliates (including “ORACLE”) or potentially\nconfusing variations (such as, “ORA”) as a part of your logo(s), product name(s), service\nname(s), company name, or domain name(s) even if such products, services or domains include,\nor are related to, the Oracle Linux programs or Additional Oracle Linux programs.\n\n5. Limited Warranty. THE ORACLE LINUX PROGRAMS AND ADDITIONAL ORACLE\nLINUX PROGRAMS ARE PROVIDED \"AS IS\" WITHOUT WARRANTY OF ANY KIND.\nWE FURTHER DISCLAIM ALL WARRANTIES, EXPRESS AND IMPLIED, INCLUDING\nWITHOUT LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR\nFITNESS FOR A PARTICULAR PURPOSE.\n\n6. Limitation of Liability. IN NO EVENT SHALL WE BE LIABLE FOR ANY INDIRECT,\nINCIDENTAL, SPECIAL, PUNITIVE OR CONSEQUENTIAL DAMAGES, OR DAMAGES\nFOR LOSS OF PROFITS, REVENUE, DATA OR DATA USE, INCURRED BY YOU OR\nANY THIRD PARTY, WHETHER IN AN ACTION IN CONTRACT OR TORT, EVEN IF WE\nHAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  OUR ENTIRE\nLIABILITY FOR DAMAGES HEREUNDER SHALL IN NO EVENT EXCEED ONE\nHUNDRED DOLLARS (U.S.).\n\n7. No Technical Support.  Our technical support organization will not provide technical support,\nphone support, or updates to you for the materials licensed under this Agreement.  Technical\nsupport, if available, may be acquired from Oracle or its affiliates under a separate agreement.\n\n8. Relationship Between the Parties. The relationship between you and us is that of\nlicensee/licensor.  Neither party will represent that it has any authority to assume or create any\nobligation, express or implied, on behalf of the other party, nor to represent the other party as\nagent, employee, franchisee, or in any other capacity.  Nothing in this Agreement shall be\nconstrued to limit either party's right to independently develop or distribute programs that are\nfunctionally similar to the other party’s products, so long as proprietary information of the other\nparty is not included in such programs.\n\n9. Entire Agreement.  You agree that this Agreement is the complete Agreement for the Oracle\nLinux programs and the Additional Oracle Linux programs, and this Agreement supersedes all\nprior or contemporaneous Agreements or representations.  If any term of this Agreement is found\nto be invalid or unenforceable, the remaining provisions will remain effective. Neither the\nUniform Computer Information Transactions Act nor the United Nations Convention on the\nInternational Sale of Goods applies to this agreement.\n\nYou can find a copy of the GNU General Public License version 2.0 in the “copying” or\n“license” file included with the Oracle Linux programs or here:\nhttp://oss.oracle.com/licenses/GPL-2.\n\nOFFER TO PROVIDE SOURCE CODE\n\nFor software that you receive from Oracle in binary form that is licensed under an open source\nlicense that gives you the right to receive the source code for that binary, you can obtain a copy\nof the applicable source code from https://oss.oracle.com/sources/ or\nhttp://www.oracle.com/goto/opensourcecode.  Alternatively, if the source code for the\ntechnology was not provided to you with the binary, you can also receive a copy of the source\ncode on physical media by submitting a written request to:\n\nOracle America, Inc.\nAttn: Associate General Counsel\nDevelopment and Engineering Legal\n500 Oracle Parkway, 10th Floor\nRedwood Shores, CA 94065\n\nOr, you may send an email to Oracle using the form linked from\nhttp://www.oracle.com/goto/opensourcecode.  Your written or emailed request should include:\n•\tThe name of the component or binary file(s) for which you are requesting the source code\n•\tThe name and version number of the Oracle product\n•\tThe date you received the Oracle product\n•\tYour name\n•\tYour company name (if applicable)\n•\tYour return mailing address and email\n•\tA telephone number in the event we need to reach you.\nWe may charge you a fee to cover the cost of physical media and processing. Your request must\nbe sent (i) within three (3) years of the date you received the Oracle product that included the\ncomponent or binary file(s) that are the subject of your request, or (ii) in the case of code\nlicensed under the GPL v3, for as long as Oracle offers spare parts or customer support for that\nproduct model or version.\n\nLast updated 24 March 2017`;\n\nexport default class InstanceLicense extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tif (!this.props.open) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div>\n\t\t\t<Blueprint.Dialog\n\t\t\t\ttitle=\"Oracle Linux End-User License Agreement\"\n\t\t\t\tstyle={css.dialog}\n\t\t\t\tisOpen={this.props.open}\n\t\t\t\tusePortal={true}\n\t\t\t\tportalContainer={document.body}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.props.onClose();\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<textarea\n\t\t\t\t\tclassName=\"bp5-dialog-body bp5-input\"\n\t\t\t\t\tstyle={css.textarea}\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\treadOnly={true}\n\t\t\t\t\tvalue={license}\n\t\t\t\t/>\n\t\t\t\t<div className=\"bp5-dialog-footer\">\n\t\t\t\t\t<div className=\"bp5-dialog-footer-actions\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-danger\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>Decline</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tLicense.setOracle(true);\n\t\t\t\t\t\t\t\tLicense.save();\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>Accept</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</Blueprint.Dialog>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/InstanceMount.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport PageInputButton from './PageInputButton';\n\ninterface Props {\n\tdisabled: boolean;\n\tmount: InstanceTypes.Mount;\n\tonChange: (state: InstanceTypes.Mount) => void;\n\tonAdd: (prepend: boolean) => void;\n\tonRemove: () => void;\n}\n\ninterface State {\n\taddRole: string;\n}\n\nconst css = {\n\tgroupTop: {\n\t\twidth: '100%',\n\t\tmaxWidth: '278px',\n\t\tmarginTop: '5px',\n\t\tmarginBottom: '0',\n\t} as React.CSSProperties,\n\tgroupBottom: {\n\t\twidth: '100%',\n\t\tmaxWidth: '279px',\n\t\tmarginTop: '2px',\n\t\tmarginBottom: '5px',\n\t} as React.CSSProperties,\n\tsourceGroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '219px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tcontrol: {\n\t\tmarginTop: '0px',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tmarginTop: '0px',\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tnameInput: {\n\t\twidth: '35%',\n\t} as React.CSSProperties,\n\tpathInput: {\n\t\twidth: '64%',\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\troles: {\n\t\twidth: '100%',\n\t\tmaxWidth: '400px',\n\t\tmarginTop: '5px',\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n\tother: {\n\t\tflex: '0 1 auto',\n\t\twidth: '52px',\n\t\tborderRadius: '0 3px 3px 0',\n\t} as React.CSSProperties,\n};\n\nexport default class InstanceMount extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\taddRole: null,\n\t\t};\n\t}\n\n\tclone(): InstanceTypes.Mount {\n\t\treturn {\n\t\t\t...this.props.mount,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet mount = this.props.mount;\n\n\t\treturn <div>\n\t\t\t<div className=\"bp5-control-group\" style={css.groupTop}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.input}\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Enter host path\"\n\t\t\t\t\tvalue={mount.path || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.path = evt.target.value;\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-control-group\" style={css.groupBottom}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.nameInput}\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\tvalue={mount.name || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.name = evt.target.value;\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.pathInput}\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Enter instance path\"\n\t\t\t\t\tvalue={mount.host_path || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.host_path = evt.target.value;\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tstyle={css.control}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tstyle={css.control}\n\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\tthis.props.onAdd(evt.shiftKey);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/InstanceNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as License from '../License';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as DomainTypes from '../types/DomainTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as PoolTypes from '../types/PoolTypes';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as ShapeTypes from '../types/ShapeTypes';\nimport * as InstanceActions from '../actions/InstanceActions';\nimport * as ImageActions from '../actions/ImageActions';\nimport * as NodeActions from '../actions/NodeActions';\nimport ImagesDatacenterStore from '../stores/ImagesDatacenterStore';\nimport NodesZoneStore from '../stores/NodesZoneStore';\nimport InstanceImages from './InstanceImages';\nimport InstanceLicense from './InstanceLicense';\nimport InstanceNodePort from './InstanceNodePort';\nimport InstanceMount from './InstanceMount';\nimport PageInput from './PageInput';\nimport PageInputButton from './PageInputButton';\nimport PageCreate from './PageCreate';\nimport PageSelect from './PageSelect';\nimport PageSwitch from \"./PageSwitch\";\nimport PageNumInput from './PageNumInput';\nimport Help from './Help';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tvpcs: VpcTypes.VpcsRo;\n\tdomains: DomainTypes.DomainsRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tpools: PoolTypes.PoolsRo;\n\tzones: ZoneTypes.ZonesRo;\n\tshapes: ShapeTypes.ShapesRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tlicenseOpen: boolean;\n\tinstance: InstanceTypes.Instance;\n\tdatacenter: string;\n\timage: ImageTypes.Image;\n\timages: ImageTypes.ImagesRo;\n\tnodes: NodeTypes.NodesRo;\n\taddNetworkRole: string;\n\taddVpc: string;\n\tdhcpChanged: boolean;\n\tsecureBootChanged: boolean;\n\thiddenImages: boolean;\n\tstartupScript: boolean;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titems: {\n\t\tmarginBottom: \"10px\",\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class InstanceNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tlicenseOpen: false,\n\t\t\tinstance: this.default,\n\t\t\tdatacenter: '',\n\t\t\timage: null,\n\t\t\timages: [],\n\t\t\tnodes: [],\n\t\t\taddNetworkRole: '',\n\t\t\taddVpc: '',\n\t\t\tdhcpChanged: false,\n\t\t\tsecureBootChanged: false,\n\t\t\thiddenImages: false,\n\t\t\tstartupScript: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tImagesDatacenterStore.addChangeListener(this.onChange);\n\t\tNodesZoneStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tImagesDatacenterStore.removeChangeListener(this.onChange);\n\t\tNodesZoneStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\timages: ImagesDatacenterStore.images,\n\t\t\tnodes: NodesZoneStore.nodes,\n\t\t});\n\t}\n\n\tget default(): InstanceTypes.Instance {\n\t\treturn {\n\t\t\tid: null,\n\t\t\tname: 'new-instance',\n\t\t\tuefi: true,\n\t\t\tsecure_boot: true,\n\t\t\tinit_disk_size: 10,\n\t\t\tmemory: 1024,\n\t\t\tprocessors: 1,\n\t\t\tcount: 1,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet instance: any = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tinstance[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\t// if (this.state.instance.image &&\n\t\t// \t\tthis.state.instance.image.indexOf('oracle') &&\n\t\t// \t\t!License.oracle) {\n\t\t// \tthis.setState({\n\t\t// \t\t...this.state,\n\t\t// \t\tlicenseOpen: true,\n\t\t// \t});\n\t\t// \treturn\n\t\t// }\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet instance: any = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tif (this.props.organizations.length && !instance.organization) {\n\t\t\tinstance.organization = this.props.organizations[0].id;\n\t\t}\n\n\t\tif (this.state.startupScript === false) {\n\t\t\tinstance.cloud_script = \"\"\n\t\t}\n\n\t\tinstance.node_ports = InstanceTypes.FilterNodePorts(instance.node_ports)\n\n\t\tInstanceActions.create(instance).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Instance created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonUefi(uefi: boolean): void {\n\t\tlet instance: any = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tlet img = this.state.image\n\t\tif (instance.uefi !== uefi) {\n\t\t\timg = null\n\t\t}\n\t\tinstance.uefi = uefi;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tinstance: instance,\n\t\t\timage: img,\n\t\t});\n\t}\n\n\tonSecureBoot(secureBoot: boolean): void {\n\t\tlet instance: InstanceTypes.Instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tlet img = this.state.image\n\t\tif (instance.secure_boot !== secureBoot) {\n\t\t\timg = null\n\t\t}\n\t\tinstance.secure_boot = secureBoot;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tinstance: instance,\n\t\t\timage: img,\n\t\t\tsecureBootChanged: true,\n\t\t});\n\t}\n\n\tonDhcpServer(dhcpServer: boolean): void {\n\t\tlet instance: InstanceTypes.Instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tinstance.dhcp_server = dhcpServer;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tinstance: instance,\n\t\t\tdhcpChanged: true,\n\t\t});\n\t}\n\n\tonNode(nodeId: string): void {\n\t\tlet instance: InstanceTypes.Instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tinstance.shape = \"\";\n\t\tinstance.node = nodeId;\n\n\t\tlet node = NodesZoneStore.node(instance.node);\n\t\tif (node) {\n\t\t\tinstance.no_public_address = node.default_no_public_address;\n\t\t\tinstance.no_public_address6 = node.default_no_public_address6;\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonShape(shapeId: string): void {\n\t\tlet instance: InstanceTypes.Instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tinstance.node = \"\";\n\t\tinstance.shape = shapeId;\n\n\t\tlet shape = CompletionStore.shape(shapeId);\n\t\tif (shape) {\n\t\t\tinstance.disk_type = shape.disk_type;\n\t\t\tinstance.disk_pool = shape.disk_pool;\n\t\t\tinstance.processors = shape.processors;\n\t\t\tinstance.memory = shape.memory;\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonAddNetworkRole = (): void => {\n\t\tif (!this.state.addNetworkRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tlet networkRoles = [\n\t\t\t...(instance.roles || []),\n\t\t];\n\n\t\tif (networkRoles.indexOf(this.state.addNetworkRole) === -1) {\n\t\t\tnetworkRoles.push(this.state.addNetworkRole);\n\t\t}\n\n\t\tnetworkRoles.sort();\n\t\tinstance.roles = networkRoles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveNetworkRole = (networkRole: string): void => {\n\t\tlet instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tlet networkRoles = [\n\t\t\t...(instance.roles || []),\n\t\t];\n\n\t\tlet i = networkRoles.indexOf(networkRole);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tnetworkRoles.splice(i, 1);\n\t\tinstance.roles = networkRoles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonAddNodePort = (i: number, prepend?: boolean): void => {\n\t\tlet instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tlet nodePorts = [\n\t\t\t...(instance.node_ports || []),\n\t\t];\n\n\t\tlet nodePort = {\n\t\t\tprotocol: \"tcp\",\n\t\t\texternal_port: 0,\n\t\t\tinternal_port: 0,\n\t\t} as InstanceTypes.NodePort;\n\n\t\tif (i === -1) {\n\t\t\tnodePorts.push(nodePort);\n\t\t} else {\n\t\t\tnodePorts.splice(prepend ? i : i + 1, 0, nodePort);\n\t\t}\n\t\tinstance.node_ports = nodePorts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonChangeNodePort(i: number, state: InstanceTypes.NodePort): void {\n\t\tlet instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tlet nodePorts = [\n\t\t\t...instance.node_ports,\n\t\t];\n\n\t\tnodePorts[i] = state;\n\n\t\tinstance.node_ports = nodePorts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveNodePort(i: number): void {\n\t\tlet instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tlet nodePorts = [\n\t\t\t...instance.node_ports,\n\t\t];\n\n\t\tnodePorts[i] = {\n\t\t\t...nodePorts[i],\n\t\t\tdelete: true,\n\t\t};\n\n\t\tinstance.node_ports = nodePorts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonAddMount = (i: number, prepend?: boolean): void => {\n\t\tlet instance = {\n\t\t\t...this.state.instance,\n\t\t}\n\n\t\tlet mounts = [\n\t\t\t...(instance.mounts || []),\n\t\t]\n\n\t\tif (i === -1) {\n\t\t\tmounts.push({})\n\t\t} else {\n\t\t\tmounts.splice(prepend ? i : i + 1, 0, {});\n\t\t}\n\t\tinstance.mounts = mounts\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t})\n\t}\n\n\tonChangeMount(i: number, block: InstanceTypes.Mount): void {\n\t\tlet instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tlet mounts = [\n\t\t\t...(instance.mounts || []),\n\t\t];\n\t\tif (!mounts.length) {\n\t\t\tmounts.push({})\n\t\t}\n\n\t\tmounts[i] = block;\n\n\t\tinstance.mounts = mounts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonRemoveMount(i: number): void {\n\t\tlet instance = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tlet mounts = [\n\t\t\t...(instance.mounts || []),\n\t\t];\n\n\t\tmounts.splice(i, 1);\n\n\t\tif (!mounts.length) {\n\t\t\tmounts = [];\n\t\t}\n\n\t\tinstance.mounts = mounts;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\tonImageselect = (img: ImageTypes.Image): void => {\n\t\tlet instance: any = {\n\t\t\t...this.state.instance,\n\t\t};\n\n\t\tinstance.image = img.id;\n\t\tif (img.name.toLowerCase().indexOf('bsd') !== -1) {\n\t\t\tinstance.secure_boot = false;\n\t\t\tinstance.cloud_type = 'bsd';\n\t\t} else if (img.name.toLowerCase().indexOf('alpinelinux') !== -1) {\n\t\t\tinstance.secure_boot = false;\n\t\t\tinstance.cloud_type = 'linux';\n\t\t} else if (img.name.toLowerCase().indexOf('archlinux') !== -1) {\n\t\t\tinstance.secure_boot = false;\n\t\t\tinstance.cloud_type = 'linux';\n\t\t} else {\n\t\t\tif (!this.state.secureBootChanged) {\n\t\t\t\tinstance.secure_boot = true;\n\t\t\t}\n\t\t\tinstance.cloud_type = 'linux';\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\timage: img,\n\t\t\tinstance: instance,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet instance = this.state.instance;\n\t\tlet lockResources = false;\n\t\tif (instance.shape) {\n\t\t\tlet shape = CompletionStore.shape(instance.shape);\n\t\t\tlockResources = !shape?.flexible;\n\t\t}\n\n\t\tlet node: NodeTypes.Node;\n\t\tif (instance.node) {\n\t\t\tnode = NodesZoneStore.node(instance.node);\n\t\t}\n\n\t\tlet hasOrganizations = !!this.props.organizations.length;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Organization</option>);\n\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters && this.props.datacenters.length) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Datacenter</option>);\n\n\t\t\thasDatacenters = true;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\tlet datacenter = this.state.datacenter;\n\t\tlet hasZones = false;\n\t\tlet zonesSelect: JSX.Element[] = [];\n\t\tif (this.props.zones && this.props.zones.length) {\n\t\t\tzonesSelect.push(<option key=\"null\" value=\"\">Select Zone</option>);\n\n\t\t\tfor (let zone of this.props.zones) {\n\t\t\t\tif (zone.datacenter !== datacenter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasZones = true;\n\n\t\t\t\tzonesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={zone.id}\n\t\t\t\t\t\tvalue={zone.id}\n\t\t\t\t\t>{zone.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasZones) {\n\t\t\tzonesSelect = [<option key=\"null\" value=\"\">No Zones</option>];\n\t\t}\n\n\t\tlet hasShapes = false;\n\t\tlet shapesSelect: JSX.Element[] = [];\n\t\tif (this.props.shapes && this.props.shapes.length) {\n\t\t\tshapesSelect.push(<option key=\"null\" value=\"\">Select Shape</option>);\n\n\t\t\tfor (let shape of this.props.shapes) {\n\t\t\t\tif (shape.datacenter !== datacenter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasShapes = true;\n\n\t\t\t\tshapesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={shape.id}\n\t\t\t\t\t\tvalue={shape.id}\n\t\t\t\t\t>{shape.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasShapes) {\n\t\t\tshapesSelect = [<option key=\"null\" value=\"\">No Shapes</option>];\n\t\t}\n\n\t\tlet instanceMounts = instance.mounts || [];\n\t\tlet mounts: JSX.Element[] = [];\n\t\tfor (let i = 0; i < instanceMounts.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tmounts.push(\n\t\t\t\t<InstanceMount\n\t\t\t\t\tkey={index}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tmount={instanceMounts[index]}\n\t\t\t\t\tonChange={(state: InstanceTypes.Mount): void => {\n\t\t\t\t\t\tthis.onChangeMount(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddMount(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveMount(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet hasNodes = false;\n\t\tlet nodesSelect: JSX.Element[] = [];\n\t\tif (this.state.nodes && this.state.nodes.length) {\n\t\t\tnodesSelect.push(<option key=\"null\" value=\"\">Select Node</option>);\n\n\t\t\thasNodes = true;\n\t\t\tfor (let node of this.state.nodes) {\n\t\t\t\tnodesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={node.id}\n\t\t\t\t\t\tvalue={node.id}\n\t\t\t\t\t>{node.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasNodes) {\n\t\t\tnodesSelect = [<option key=\"null\" value=\"\">No Nodes</option>];\n\t\t}\n\n\t\tlet hasVpcs = false;\n\t\tlet vpcsSelect: JSX.Element[] = [];\n\t\tif (this.props.vpcs && this.props.vpcs.length) {\n\t\t\tvpcsSelect.push(<option key=\"null\" value=\"\">Select VPC</option>);\n\n\t\t\tfor (let vpc of this.props.vpcs) {\n\t\t\t\tif (Constants.user) {\n\t\t\t\t\tif (vpc.organization !== CompletionStore.userOrganization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (vpc.organization != instance.organization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (vpc.datacenter !== this.state.datacenter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\thasVpcs = true;\n\t\t\t\tvpcsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={vpc.id}\n\t\t\t\t\t\tvalue={vpc.id}\n\t\t\t\t\t>{vpc.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasVpcs) {\n\t\t\tvpcsSelect = [<option key=\"null\" value=\"\">No VPCs</option>];\n\t\t}\n\n\t\tlet hasSubnets = false;\n\t\tlet subnetSelect: JSX.Element[] = [];\n\t\tif (this.props.vpcs && this.props.vpcs.length) {\n\t\t\tsubnetSelect.push(<option key=\"null\" value=\"\">Select Subnet</option>);\n\n\t\t\tfor (let vpc of this.props.vpcs) {\n\t\t\t\tif (Constants.user) {\n\t\t\t\t\tif (vpc.organization !== CompletionStore.userOrganization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (vpc.organization != instance.organization) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (vpc.datacenter !== this.state.datacenter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (vpc.id === instance.vpc) {\n\t\t\t\t\tfor (let sub of (vpc.subnets || [])) {\n\t\t\t\t\t\thasSubnets = true;\n\t\t\t\t\t\tsubnetSelect.push(\n\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\tkey={sub.id}\n\t\t\t\t\t\t\t\tvalue={sub.id}\n\t\t\t\t\t\t\t>{sub.name + ' - ' + sub.network}</option>,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!hasSubnets) {\n\t\t\tsubnetSelect = [<option key=\"null\" value=\"\">No Subnets</option>];\n\t\t}\n\n\t\tlet cloudSubnetsSelect: JSX.Element[] = [\n\t\t\t<option key=\"null\" value=\"\">Disabled</option>,\n\t\t];\n\t\tif (node && node.cloud_subnets && node.cloud_subnets.length) {\n\t\t\tlet subnets: Map<string, string> = new Map();\n\n\t\t\tfor (let vpc of (node.available_vpcs || [])) {\n\t\t\t\tfor (let subnet of (vpc.subnets || [])) {\n\t\t\t\t\tsubnets.set(subnet.id, vpc.name + ' - ' + subnet.name);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (let subnetId of (node.cloud_subnets || [])) {\n\t\t\t\tcloudSubnetsSelect.push(\n\t\t\t\t\t<option key={subnetId} value={subnetId}>\n\t\t\t\t\t\t{subnets.get(subnetId) || subnetId}\n\t\t\t\t\t</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet domainsSelect: JSX.Element[] = [\n\t\t\t<option key=\"null\" value=\"\">No Domain</option>,\n\t\t];\n\t\tif (this.props.domains && this.props.domains.length) {\n\t\t\tfor (let domain of this.props.domains) {\n\t\t\t\tif (!Constants.user &&\n\t\t\t\t\t\tdomain.organization != instance.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tdomainsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={domain.id}\n\t\t\t\t\t\tvalue={domain.id}\n\t\t\t\t\t>{domain.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet hasPools = false;\n\t\tlet poolsSelect: JSX.Element[] = [];\n\t\tif (this.props.pools.length) {\n\t\t\tpoolsSelect.push(<option key=\"null\" value=\"\">Select Pool</option>);\n\n\t\t\thasPools = true;\n\t\t\tfor (let pool of this.props.pools) {\n\t\t\t\tif (!node || !node.pools || node.pools.indexOf(pool.id) === -1) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tpoolsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={pool.id}\n\t\t\t\t\t\tvalue={pool.id}\n\t\t\t\t\t>{pool.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasPools) {\n\t\t\tpoolsSelect = [<option key=\"null\" value=\"\">No Pools</option>];\n\t\t}\n\n\t\tlet networkRoles: JSX.Element[] = [];\n\t\tfor (let networkRole of (instance.roles || [])) {\n\t\t\tnetworkRoles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={networkRole}\n\t\t\t\t>\n\t\t\t\t\t{networkRole}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveNetworkRole(networkRole);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet nodePorts: JSX.Element[] = [];\n\t\t(instance.node_ports || []).forEach((nodePort, index) => {\n\t\t\tif (nodePort.delete) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnodePorts.push(\n\t\t\t\t<InstanceNodePort\n\t\t\t\t\tkey={index}\n\t\t\t\t\tnodePort={nodePort}\n\t\t\t\t\tonChange={(state: InstanceTypes.NodePort): void => {\n\t\t\t\t\t\tthis.onChangeNodePort(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddNodePort(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveNodePort(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t})\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={6}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<InstanceLicense\n\t\t\t\t\t\topen={this.state.licenseOpen}\n\t\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlicenseOpen: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of instance. String formatting such as %d or %02d can be used to add the instance number or zero padded number.\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tvalue={instance.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Instance comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Instance comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={instance.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasOrganizations}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for instance.\"\n\t\t\t\t\t\t\tvalue={instance.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\t\thelp=\"Datacenter for instance.\"\n\t\t\t\t\t\t\tvalue={this.state.datacenter}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdatacenter: val,\n\t\t\t\t\t\t\t\t\tinstance: {\n\t\t\t\t\t\t\t\t\t\t...this.state.instance,\n\t\t\t\t\t\t\t\t\t\tnode: '',\n\t\t\t\t\t\t\t\t\t\tzone: '',\n\t\t\t\t\t\t\t\t\t\timage: '',\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\timage: null,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tImageActions.syncDatacenter(val);\n\t\t\t\t\t\t\t\tNodeActions.syncZone('');\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasZones}\n\t\t\t\t\t\t\tlabel=\"Zone\"\n\t\t\t\t\t\t\thelp=\"Zone for instance.\"\n\t\t\t\t\t\t\tvalue={instance.zone}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tinstance: {\n\t\t\t\t\t\t\t\t\t\t...this.state.instance,\n\t\t\t\t\t\t\t\t\t\tnode: '',\n\t\t\t\t\t\t\t\t\t\tzone: val,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tNodeActions.syncZone(val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{zonesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasShapes || !!instance.node}\n\t\t\t\t\t\t\tlabel=\"Shape\"\n\t\t\t\t\t\t\thelp=\"Instance shape to run instance on, node will be automatically selected.\"\n\t\t\t\t\t\t\tvalue={instance.shape}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.onShape(val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{shapesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasNodes || !!instance.shape}\n\t\t\t\t\t\t\tlabel=\"Node\"\n\t\t\t\t\t\t\thelp=\"Node to run instance on.\"\n\t\t\t\t\t\t\tvalue={instance.node}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.onNode(val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{nodesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasVpcs}\n\t\t\t\t\t\t\tlabel=\"VPC\"\n\t\t\t\t\t\t\thelp=\"VPC for instance.\"\n\t\t\t\t\t\t\tvalue={instance.vpc}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tinstance: {\n\t\t\t\t\t\t\t\t\t\t...this.state.instance,\n\t\t\t\t\t\t\t\t\t\tvpc: val,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{vpcsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasVpcs}\n\t\t\t\t\t\t\tlabel=\"Subnet\"\n\t\t\t\t\t\t\thelp=\"Subnet for instance.\"\n\t\t\t\t\t\t\tvalue={instance.subnet}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tinstance: {\n\t\t\t\t\t\t\t\t\t\t...this.state.instance,\n\t\t\t\t\t\t\t\t\t\tsubnet: val,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{subnetSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={cloudSubnetsSelect.length <= 1}\n\t\t\t\t\t\t\tlabel=\"Oracle Cloud Subnet\"\n\t\t\t\t\t\t\thelp=\"Oracle Cloud subnet for instance.\"\n\t\t\t\t\t\t\tvalue={instance.cloud_subnet}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tinstance: {\n\t\t\t\t\t\t\t\t\t\t...this.state.instance,\n\t\t\t\t\t\t\t\t\t\tcloud_subnet: val,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{cloudSubnetsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"CloudInit Type\"\n\t\t\t\t\t\t\thelp=\"Target operating system for cloud init\"\n\t\t\t\t\t\t\tvalue={instance.cloud_type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('cloud_type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option key=\"linux\" value=\"linux\">Linux</option>,\n\t\t\t\t\t\t\t<option key=\"bsd\" value=\"bsd\">BSD</option>,\n\t\t\t\t\t\t\t<option key=\"linux_legacy\" value=\"linux_legacy\">Linux Legacy</option>,\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Startup script\"\n\t\t\t\t\t\t\thelp=\"Script to run on instance startup. These commands will run on every startup. File must start with #! such as `#!/bin/bash` to specify code interpreter.\"\n\t\t\t\t\t\t\tchecked={this.state.startupScript === true || (\n\t\t\t\t\t\t\t\tthis.state.startupScript === null && !!instance.cloud_script)}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\t\t\t\tinstance = {\n\t\t\t\t\t\t\t\t\t\t...this.state.instance,\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\t\t\t\tinstance: instance,\n\t\t\t\t\t\t\t\t\tstartupScript: !(this.state.startupScript === true || (\n\t\t\t\t\t\t\t\t\t\tthis.state.startupScript === null &&\n\t\t\t\t\t\t\t\t\t\t!!instance.cloud_script)),\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Startup Script\"\n\t\t\t\t\t\t\thelp=\"Script to run on instance startup. These commands will run on every startup. File must start with #! such as `#!/bin/bash` to specify code interpreter.\"\n\t\t\t\t\t\t\tplaceholder=\"Startup script\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\thidden={!(this.state.startupScript === true || (\n\t\t\t\t\t\t\t\tthis.state.startupScript === null && !!instance.cloud_script))}\n\t\t\t\t\t\t\tvalue={instance.cloud_script}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('cloud_script', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Start instance\"\n\t\t\t\t\t\t\thelp=\"Automatically start instance. Disable to get the public MAC address before instance is started for first time.\"\n\t\t\t\t\t\t\tchecked={!instance.action}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tif (instance.action === 'stop') {\n\t\t\t\t\t\t\t\t\tthis.set('action', '');\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tthis.set('action', 'stop');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"UEFI\"\n\t\t\t\t\t\t\thelp=\"Enable UEFI boot, requires OVMF package for UEFI image.\"\n\t\t\t\t\t\t\tchecked={instance.uefi}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.onUefi(!instance.uefi);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={!instance.uefi}\n\t\t\t\t\t\t\tlabel=\"SecureBoot\"\n\t\t\t\t\t\t\thelp=\"Enable secure boot, requires OVMF package for UEFI image.\"\n\t\t\t\t\t\t\tchecked={instance.secure_boot}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.onSecureBoot(!instance.secure_boot);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={!instance.uefi}\n\t\t\t\t\t\t\tlabel=\"TPM\"\n\t\t\t\t\t\t\thelp=\"Enable TPM, requires swtpm and OVMF package.\"\n\t\t\t\t\t\t\tchecked={instance.tpm}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('tpm', !instance.tpm);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Public IPv4 address\"\n\t\t\t\t\t\t\thelp=\"Enable or disable public IPv4 address for instance. Node must have network mode configured to assign public address.\"\n\t\t\t\t\t\t\tchecked={!instance.no_public_address}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('no_public_address', !instance.no_public_address);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Public IPv6 address\"\n\t\t\t\t\t\t\thelp=\"Enable or disable public IPv6 address for instance. Node must have network mode configured to assign public address.\"\n\t\t\t\t\t\t\tchecked={!instance.no_public_address6}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('no_public_address6', !instance.no_public_address6);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Host address\"\n\t\t\t\t\t\t\thelp=\"Enable or disable host address for instance. Node must have host networking configured to assign host address.\"\n\t\t\t\t\t\t\tchecked={!instance.no_host_address}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('no_host_address', !instance.no_host_address);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !!instance.shape}\n\t\t\t\t\t\t\tlabel=\"Disk Type\"\n\t\t\t\t\t\t\thelp=\"Type of disk. QCOW disk files are stored locally on the node filesystem. LVM disks are partitioned as a logical volume.\"\n\t\t\t\t\t\t\tvalue={instance.disk_type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('disk_type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option key=\"qcow2\" value=\"qcow2\">QCOW</option>\n\t\t\t\t\t\t\t<option key=\"lvm\" value=\"lvm\">LVM</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasPools}\n\t\t\t\t\t\t\tlabel=\"Pool\"\n\t\t\t\t\t\t\thelp=\"LVM volume group to add disk to.\"\n\t\t\t\t\t\t\thidden={instance.disk_type !== \"lvm\"}\n\t\t\t\t\t\t\tvalue={instance.disk_pool}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('disk_pool', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{poolsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<InstanceImages\n\t\t\t\t\t\t\timages={this.state.images || []}\n\t\t\t\t\t\t\timage={this.state.image}\n\t\t\t\t\t\t\tuefi={instance.uefi}\n\t\t\t\t\t\t\tshowHidden={this.state.hiddenImages}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonChange={this.onImageselect}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"Show hidden images\"\n\t\t\t\t\t\t\thelp=\"Show previous versions of images.\"\n\t\t\t\t\t\t\tchecked={this.state.hiddenImages}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\thiddenImages: !this.state.hiddenImages,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"Linked disk image\"\n\t\t\t\t\t\t\thelp=\"Link to source disk image instead of creating full copy. This will reduce disk size and provide faster startup.\"\n\t\t\t\t\t\t\tchecked={instance.image_backing}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('image_backing', !instance.image_backing);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\t\tRoles\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\t\tcontent=\"Roles that will be matched with firewall rules. Roles are case-sensitive.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{networkRoles}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\t\tvalue={this.state.addNetworkRole}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddNetworkRole: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddNetworkRole}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"Disk Size\"\n\t\t\t\t\t\t\thelp=\"Instance memory size in megabytes.\"\n\t\t\t\t\t\t\tmin={10}\n\t\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\t\tstepSize={2}\n\t\t\t\t\t\t\tmajorStepSize={5}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('init_disk_size', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tvalue={instance.init_disk_size}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"Memory Size\"\n\t\t\t\t\t\t\thelp=\"Instance memory size in megabytes.\"\n\t\t\t\t\t\t\tmin={256}\n\t\t\t\t\t\t\tminorStepSize={512}\n\t\t\t\t\t\t\tstepSize={1024}\n\t\t\t\t\t\t\tmajorStepSize={2048}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || lockResources}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('memory', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tvalue={instance.memory}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"Processors\"\n\t\t\t\t\t\t\thelp=\"Number of instance processors.\"\n\t\t\t\t\t\t\tmin={1}\n\t\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\t\tmajorStepSize={2}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || lockResources}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('processors', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tvalue={instance.processors}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"Count\"\n\t\t\t\t\t\t\thelp=\"Number of instances to create.\"\n\t\t\t\t\t\t\tmin={1}\n\t\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\t\tmajorStepSize={1}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('count', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tvalue={instance.count}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tstyle={css.itemsLabel}\n\t\t\t\t\t\t\thidden={!nodePorts.length}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tNode Ports\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Node Ports\"\n\t\t\t\t\t\t\t\tcontent=\"Node port mappings from node public IP to internal instance. Acceptable external port range is 30000-32767, leave external port empty to automatically assign a port.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tstyle={css.items}\n\t\t\t\t\t\t\thidden={!nodePorts.length}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{nodePorts}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Node ports\"\n\t\t\t\t\t\t\thelp=\"Node port mappings from node public IP to internal instance. Acceptable external port range is 30000-32767, leave external port empty to automatically assign a port.\"\n\t\t\t\t\t\t\tchecked={false}\n\t\t\t\t\t\t\thidden={!!nodePorts.length}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.onAddNodePort(-1)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tstyle={css.itemsLabel}\n\t\t\t\t\t\t\thidden={!instance.mounts?.length}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tHost Paths\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Host Paths\"\n\t\t\t\t\t\t\t\tcontent=\"Local paths on the host that are available for instances to access through VirtIO-FS sharing. The path must be match or be a subdirectory of a configured host share path in the node settings. The instance's organization must also have a matching role to access the host share.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tstyle={css.items}\n\t\t\t\t\t\t\thidden={!instance.mounts?.length}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{mounts}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Host paths\"\n\t\t\t\t\t\t\thelp=\"Local paths on the host that are available for instances to access through VirtIO-FS sharing. The path must be match or be a subdirectory of a configured host share path in the node settings. The instance's organization must also have a matching role to access the host share.\"\n\t\t\t\t\t\t\tchecked={false}\n\t\t\t\t\t\t\thidden={!!instance.mounts?.length}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.onAddMount(-1)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Skip source/destination check\"\n\t\t\t\t\t\t\thelp=\"Allow network traffic from non-instance addresses.\"\n\t\t\t\t\t\t\tchecked={instance.skip_source_dest_check}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('skip_source_dest_check',\n\t\t\t\t\t\t\t\t\t!instance.skip_source_dest_check);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"DHCP server\"\n\t\t\t\t\t\t\thelp=\"Enable instance DHCP server, use for instances without cloud init network configuration support.\"\n\t\t\t\t\t\t\tchecked={instance.dhcp_server}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.onDhcpServer(!instance.dhcp_server);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\t\thelp=\"Block instance and any attached disks from being deleted.\"\n\t\t\t\t\t\t\tchecked={instance.delete_protection}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('delete_protection', !instance.delete_protection);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"VNC server\"\n\t\t\t\t\t\t\thelp=\"Enable VNC server for remote control of instance.\"\n\t\t\t\t\t\t\tchecked={instance.vnc}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('vnc', !instance.vnc);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Spice server\"\n\t\t\t\t\t\t\thelp=\"Enable Spice server for remote control of instance.\"\n\t\t\t\t\t\t\tchecked={instance.spice}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('spice', !instance.spice);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Desktop GUI\"\n\t\t\t\t\t\t\thelp=\"Enable desktop GUI window for instance display.\"\n\t\t\t\t\t\t\tchecked={instance.gui}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('gui', !instance.gui);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Root enabled\"\n\t\t\t\t\t\t\thelp=\"Enable root unix account for VNC/Spice access. Random password will be generated.\"\n\t\t\t\t\t\t\tchecked={instance.root_enabled}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('root_enabled', !instance.root_enabled);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.instance}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/InstanceNodePort.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as InstanceTypes from '../types/InstanceTypes';\n\ninterface Props {\n\tnodePort: InstanceTypes.NodePort;\n\thidden?: boolean;\n\tonChange: (state: InstanceTypes.NodePort) => void;\n\tonAdd: (prepend: boolean) => void;\n\tonRemove: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '342px',\n\t\tmarginTop: '5px',\n\t\tmarginBottom: '5px',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tnodePort: {\n\t\twidth: '100%',\n\t\tborderRadius: '0 3px 3px 0',\n\t} as React.CSSProperties,\n\tnodePortBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tcontrol: {\n\t\tmarginTop: '0px',\n\t} as React.CSSProperties,\n};\n\nexport default class InstanceNodePort extends React.Component<Props, {}> {\n\tclone(): InstanceTypes.NodePort {\n\t\treturn {\n\t\t\t...this.props.nodePort,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet nodePort = this.props.nodePort;\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-control-group\"\n\t\t\tstyle={css.group}\n\t\t\thidden={this.props.hidden}\n\t\t>\n\t\t\t<div className=\"bp5-select\" style={css.protocol}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={nodePort.protocol}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.protocol = evt.target.value;\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<option value=\"tcp\">TCP</option>\n\t\t\t\t\t<option value=\"udp\">UDP</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div style={css.nodePortBox}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.nodePort}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"External Port\"\n\t\t\t\t\tvalue={nodePort.external_port || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.node_port = \"\"\n\t\t\t\t\t\tstate.external_port = parseInt(evt.target.value, 10);\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div style={css.nodePortBox}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.nodePort}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Internal Port\"\n\t\t\t\t\tvalue={nodePort.internal_port || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.internal_port = parseInt(evt.target.value, 10);\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\tstyle={css.control}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\tstyle={css.control}\n\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\tthis.props.onAdd(evt.shiftKey);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Instances.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as DomainTypes from '../types/DomainTypes';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as PoolTypes from '../types/PoolTypes';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as ShapeTypes from '../types/ShapeTypes';\nimport InstancesStore from '../stores/InstancesStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as InstanceActions from '../actions/InstanceActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Instance from './Instance';\nimport InstanceNew from './InstanceNew';\nimport InstancesFilter from './InstancesFilter';\nimport InstancesPage from './InstancesPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tinstances: InstanceTypes.InstancesRo;\n\tdomains: DomainTypes.DomainsRo;\n\tfilter: InstanceTypes.Filter;\n\tdebug: boolean;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tvpcs: VpcTypes.VpcsRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tnodes: NodeTypes.NodesRo;\n\tpools: PoolTypes.PoolsRo;\n\tzones: ZoneTypes.ZonesRo;\n\tshapes: ShapeTypes.ShapesRo;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n\tdebug: {\n\t\tmargin: '0 0 4px 0',\n\t\tjustifyContent: 'flex-end',\n\t} as React.CSSProperties,\n\tdebugButton: {\n\t\tmargin: '0 0 0 8px',\n\t} as React.CSSProperties,\n\tdebugToggle: {\n\t\topacity: 0.3,\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Instances extends React.Component<{}, State> {\n\tsync: MiscUtils.SyncInterval;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tinstances: InstancesStore.instances,\n\t\t\tfilter: InstancesStore.filter,\n\t\t\tdebug: false,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tdomains: CompletionStore.domains,\n\t\t\tvpcs: CompletionStore.vpcs,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tnodes: CompletionStore.nodes,\n\t\t\tpools: CompletionStore.pools,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tshapes: CompletionStore.shapes,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tInstancesStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tInstanceActions.sync();\n\t\tCompletionActions.sync();\n\n\t\tthis.sync = new MiscUtils.SyncInterval(\n\t\t\t() => InstanceActions.sync(true),\n\t\t\t3000,\n\t\t)\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tInstancesStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\n\t\tthis.sync?.stop()\n\t}\n\n\tonChange = (): void => {\n\t\tlet instances = InstancesStore.instances;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tinstances.forEach((instance: InstanceTypes.Instance): void => {\n\t\t\tif (curSelected[instance.id]) {\n\t\t\t\tselected[instance.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[instance.id]) {\n\t\t\t\topened[instance.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tinstances: instances,\n\t\t\tfilter: InstancesStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tdomains: CompletionStore.domains,\n\t\t\tvpcs: CompletionStore.vpcs,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tnodes: CompletionStore.nodes,\n\t\t\tpools: CompletionStore.pools,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tshapes: CompletionStore.shapes,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tInstanceActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonForceDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tInstanceActions.forceRemoveMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tupdateSelected(state: string): void {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tInstanceActions.updateMulti(\n\t\t\tObject.keys(this.state.selected), state).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet instancesDom: JSX.Element[] = [];\n\n\t\tlet zoneSize = 100\n\t\tlet nodeSize = 100\n\t\tlet zoneParsed: Set<string> = new Set();\n\t\tlet nodeParsed: Set<string> = new Set();\n\n\t\tthis.state.instances.forEach((\n\t\t\t\tinstance: InstanceTypes.InstanceRo): void => {\n\n\t\t\tif (!zoneParsed.has(instance.zone)) {\n\t\t\t\tlet zone = CompletionStore.zone(instance.zone);\n\t\t\t\tlet zoneName = zone ? zone.name : null;\n\n\t\t\t\tzoneSize = Math.max(\n\t\t\t\t\tzoneSize,\n\t\t\t\t\tMiscUtils.getTextWidth(zoneName) + 40,\n\t\t\t\t)\n\n\t\t\t\tzoneParsed.add(instance.zone)\n\t\t\t}\n\n\t\t\tif (!nodeParsed.has(instance.node)) {\n\t\t\t\tlet node = CompletionStore.node(instance.node);\n\t\t\t\tlet nodeName = node ? node.name : null;\n\n\t\t\t\tnodeSize = Math.max(\n\t\t\t\t\tnodeSize,\n\t\t\t\t\tMiscUtils.getTextWidth(nodeName) + 40,\n\t\t\t\t)\n\n\t\t\t\tnodeParsed.add(instance.node)\n\t\t\t}\n\n\t\t\tinstancesDom.push(<Instance\n\t\t\t\tkey={instance.id}\n\t\t\t\tinstance={instance}\n\t\t\t\tdomains={this.state.domains}\n\t\t\t\tvpcs={this.state.vpcs}\n\t\t\t\tselected={!!this.state.selected[instance.id]}\n\t\t\t\topen={!!this.state.opened[instance.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet instances = this.state.instances;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < instances.length; i++) {\n\t\t\t\t\t\t\tlet usr = instances[i];\n\n\t\t\t\t\t\t\tif (usr.id === instance.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[instances[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: instance.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[instance.id]) {\n\t\t\t\t\t\tdelete selected[instance.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[instance.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: instance.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[instance.id]) {\n\t\t\t\t\t\tdelete opened[instance.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[instance.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet newInstanceDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewInstanceDom = <InstanceNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tvpcs={this.state.vpcs}\n\t\t\t\tdomains={this.state.domains}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tpools={this.state.pools}\n\t\t\t\tzones={this.state.zones}\n\t\t\t\tshapes={this.state.shapes}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\tlet debugClass = 'bp5-button bp5-icon-console ';\n\t\tif (this.state.debug) {\n\t\t\tdebugClass += 'bp5-active';\n\t\t}\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = InstancesStore.instance(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet sizeRow = <div style={{\"display\": \"table-row\"}}>\n\t\t\t<div style={{display: \"table-cell\", width: \"auto\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: nodeSize + \"px\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: zoneSize + \"px\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"150px\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"150px\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"85px\"}}></div>\n\t\t</div>\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Instances</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={debugClass}\n\t\t\t\t\t\t\tstyle={css.debugToggle}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdebug: !this.state.debug,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tDebug\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tInstanceActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tInstanceActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Start Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-success bp5-icon-power\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-success\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Start the selected instances\"\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={(): void => {\n\t\t\t\t\t\t\t\tthis.updateSelected('start');\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Stop Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-warning bp5-icon-power\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-warning\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Stop the selected instances\"\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={(): void => {\n\t\t\t\t\t\t\t\tthis.updateSelected('stop');\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected instances\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"layout horizontal wrap\"\n\t\t\t\t\tstyle={css.debug}\n\t\t\t\t\thidden={!this.state.debug}\n\t\t\t\t>\n\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\tlabel=\"Force Delete Selected\"\n\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-warning-sign\"\n\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\tstyle={css.debugButton}\n\t\t\t\t\t\tconfirmMsg=\"Permanently force delete the selected instances\"\n\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\tonConfirm={this.onForceDelete}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<InstancesFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tInstanceActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\tnodes={this.state.nodes}\n\t\t\t\tzones={this.state.zones}\n\t\t\t\tvpcs={this.state.vpcs}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{sizeRow}\n\t\t\t\t\t{newInstanceDom}\n\t\t\t\t\t{instancesDom}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!instancesDom.length}\n\t\t\t\ticonClass=\"bp5-icon-dashboard\"\n\t\t\t\ttitle=\"No instances\"\n\t\t\t\tdescription=\"Add a new instance to get started.\"\n\t\t\t/>\n\t\t\t<InstancesPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/InstancesFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as ZoneTypes from '../types/ZoneTypes';\n\ninterface Props {\n\tfilter: InstanceTypes.Filter;\n\tonFilter: (filter: InstanceTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tnodes: NodeTypes.NodesRo;\n\tzones: ZoneTypes.ZonesRo;\n\tvpcs: VpcTypes.VpcsRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class InstancesFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any Organization</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet nodesSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any Node</option>,\n\t\t];\n\t\tif (this.props.nodes && this.props.nodes.length) {\n\t\t\tfor (let node of this.props.nodes) {\n\t\t\t\tnodesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={node.id}\n\t\t\t\t\t\tvalue={node.id}\n\t\t\t\t\t>{node.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet zonesSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any Zone</option>,\n\t\t];\n\t\tif (this.props.zones && this.props.zones.length) {\n\t\t\tfor (let zone of this.props.zones) {\n\t\t\t\tzonesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={zone.id}\n\t\t\t\t\t\tvalue={zone.id}\n\t\t\t\t\t>{zone.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet vpcsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any VPC</option>,\n\t\t];\n\t\tif (this.props.vpcs && this.props.vpcs.length) {\n\t\t\tfor (let vpc of this.props.vpcs) {\n\t\t\t\tvpcsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={vpc.id}\n\t\t\t\t\t\tvalue={vpc.id}\n\t\t\t\t\t>{vpc.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet subnetsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any Subnet</option>,\n\t\t];\n\t\tif (this.props.vpcs && this.props.vpcs.length) {\n\t\t\tfor (let vpc of this.props.vpcs) {\n\t\t\t\tif (vpc.id === this.props.filter.vpc) {\n\t\t\t\t\tfor (let sub of (vpc.subnets || [])) {\n\t\t\t\t\t\tsubnetsSelect.push(\n\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\tkey={sub.id}\n\t\t\t\t\t\t\t\tvalue={sub.id}\n\t\t\t\t\t\t\t>{sub.name + ' - ' + sub.network}</option>,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Instance ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Network Role\"\n\t\t\t\tvalue={this.props.filter.role}\n\t\t\t\tdynamic={true}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.role = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.role;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Network Namespace\"\n\t\t\t\tvalue={this.props.filter.network_namespace}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.network_namespace = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.network_namespace;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.node || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.node;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.node = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{nodesSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.zone || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.zone;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.zone = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{zonesSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.vpc || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.vpc;\n\t\t\t\t\t\t\tdelete filter.subnet;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (filter.vpc !== val) {\n\t\t\t\t\t\t\t\tfilter.vpc = val;\n\t\t\t\t\t\t\t\tdelete filter.subnet;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{vpcsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.subnet || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.subnet;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.subnet = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{subnetsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/InstancesPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport InstancesStore from '../stores/InstancesStore';\nimport * as InstanceActions from '../actions/InstanceActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class InstancesPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: InstancesStore.page,\n\t\t\tpageCount: InstancesStore.pageCount,\n\t\t\tpages: InstancesStore.pages,\n\t\t\tcount: InstancesStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tInstancesStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tInstancesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: InstancesStore.page,\n\t\t\tpageCount: InstancesStore.pageCount,\n\t\t\tpages: InstancesStore.pages,\n\t\t\tcount: InstancesStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tInstanceActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tInstanceActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tInstanceActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tInstanceActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tInstanceActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/LoadingBar.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport LoadingStore from '../stores/LoadingStore';\n\ninterface Props {\n\tsize?: string;\n\tintent?: string;\n}\n\ninterface State {\n\tloading: boolean;\n}\n\nconst css = {\n\tloadingBar: {\n\t\tposition: 'fixed',\n\t\ttop: '1px',\n\t\tleft: '1px',\n\t\tright: '1px',\n\t\tzIndex: 1000,\n\t\tpointerEvents: 'none',\n\t} as React.CSSProperties,\n\tprogress: {\n\t\twidth: '100%',\n\t\theight: '4px',\n\t\tborderRadius: 0,\n\t} as React.CSSProperties,\n\tprogressBar: {\n\t\twidth: '50%',\n\t\tborderRadius: 0,\n\t} as React.CSSProperties,\n};\n\nexport default class LoadingBar extends React.Component<Props, State> {\n\tconstructor(props: Props, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tloading: LoadingStore.loading,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tLoadingStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tLoadingStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\tloading: LoadingStore.loading,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet progress: JSX.Element;\n\n\t\tif (!this.state.loading) {\n\t\t\tprogress = <div style={css.progress}/>;\n\t\t} else {\n\t\t\tlet className = 'bp5-progress-bar bp5-no-stripes bp5-no-animation ';\n\t\t\tif (this.props.intent) {\n\t\t\t\tclassName += ' bp5-intent-' + this.props.intent;\n\t\t\t}\n\n\t\t\tprogress = <div className={className} style={css.progress}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-progress-meter bp5-loading-bar\"\n\t\t\t\t\tstyle={css.progressBar}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\treturn <div style={css.loadingBar}>\n\t\t\t{progress}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/LoadingCircle.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport LoadingStore from '../stores/LoadingStore';\n\ninterface Props {\n\tstyle?: React.CSSProperties;\n\tsize?: string;\n\tintent?: Blueprint.Intent;\n}\n\ninterface State {\n\tloading: boolean;\n}\n\nexport default class LoadingCircle extends React.Component<Props, State> {\n\tconstructor(props: Props, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tloading: LoadingStore.loading,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tLoadingStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tLoadingStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\tloading: LoadingStore.loading,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet spinner: JSX.Element;\n\n\t\tif (!this.state.loading) {\n\t\t\tlet size;\n\t\t\tswitch (this.props.size) {\n\t\t\t\tcase 'small':\n\t\t\t\t\tsize = '24px';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'large':\n\t\t\t\t\tsize = '100px';\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tsize = '50px';\n\t\t\t}\n\n\t\t\tlet style: React.CSSProperties = {\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t};\n\n\t\t\tspinner = <div style={style}/>;\n\t\t} else {\n\t\t\tlet className = '';\n\t\t\tif (this.props.size) {\n\t\t\t\tclassName = 'bp5-' + this.props.size;\n\t\t\t}\n\n\t\t\tspinner = <Blueprint.Spinner\n\t\t\t\tclassName={className}\n\t\t\t\tintent={this.props.intent}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <div style={this.props.style}>\n\t\t\t{spinner}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Log.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as LogTypes from '../types/LogTypes';\nimport * as Theme from '../Theme';\n\ninterface State {\n\tstack: boolean;\n}\n\ninterface Props {\n\tlog: LogTypes.LogRo;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t} as React.CSSProperties,\n\ttimestamp: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '6px',\n\t} as React.CSSProperties,\n\tlevel: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '6px',\n\t} as React.CSSProperties,\n\tmessage: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '6px',\n\t} as React.CSSProperties,\n\tfields: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '6px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tkey: {\n\t\tfontWeight: 'bold',\n\t} as React.CSSProperties,\n\tvalue: {\n\t} as React.CSSProperties,\n\tdialog: {\n\t\theight: '500px',\n\t\twidth: '90%',\n\t\tmaxWidth: '700px',\n\t} as React.CSSProperties,\n\tdialogBody: {\n\t\theight: '100%',\n\t} as React.CSSProperties,\n\ttextarea: {\n\t\tresize: 'none',\n\t\tfontSize: Theme.monospaceSize,\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n};\n\nexport default class Log extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tstack: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet log = this.props.log;\n\n\t\tlet className = 'bp5-cell ';\n\t\tswitch (log.level) {\n\t\t\tcase 'debug':\n\t\t\t\tclassName += 'bp5-text-intent-success';\n\t\t\t\tbreak;\n\t\t\tcase 'info':\n\t\t\t\tclassName += 'bp5-text-intent-primary';\n\t\t\t\tbreak;\n\t\t\tcase 'warning':\n\t\t\t\tclassName += 'bp5-text-intent-warning';\n\t\t\t\tbreak;\n\t\t\tcase 'error':\n\t\t\t\tclassName += 'bp5-text-intent-danger';\n\t\t\t\tbreak;\n\t\t\tcase 'fatal':\n\t\t\t\tclassName += 'bp5-text-intent-danger';\n\t\t\t\tbreak;\n\t\t\tcase 'panic':\n\t\t\t\tclassName += 'bp5-text-intent-danger';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet fields: JSX.Element[] = [];\n\t\tfor (let key in log.fields) {\n\t\t\tif (!log.fields.hasOwnProperty(key)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet val = log.fields[key];\n\n\t\t\tfields.push(\n\t\t\t\t<div key={key}>\n\t\t\t\t\t<span style={css.key}>{key}: </span>\n\t\t\t\t\t<span style={css.value}>\n\t\t\t\t\t\t{JSON.stringify(val)}\n\t\t\t\t\t</span>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className={className} style={css.timestamp}>\n\t\t\t\t{MiscUtils.formatDateShortTime(log.timestamp) || 'Unknown'}\n\t\t\t</div>\n\t\t\t<div className={className} style={css.level}>\n\t\t\t\t{log.level}\n\t\t\t</div>\n\t\t\t<div className={className} style={css.message}>\n\t\t\t\t{log.message}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.fields}>\n\t\t\t\t{fields}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.buttons}>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-document-open\"\n\t\t\t\t\thidden={!log.stack}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tstack: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<Blueprint.Dialog\n\t\t\t\ttitle=\"Stack Trace\"\n\t\t\t\tstyle={css.dialog}\n\t\t\t\tisOpen={this.state.stack}\n\t\t\t\tusePortal={true}\n\t\t\t\tportalContainer={document.body}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tstack: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<textarea\n\t\t\t\t\tclassName=\"bp5-dialog-body bp5-input\"\n\t\t\t\t\tstyle={css.textarea}\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\treadOnly={true}\n\t\t\t\t\tvalue={log.stack || ''}\n\t\t\t\t/>\n\t\t\t</Blueprint.Dialog>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/LogViewer.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Theme from '../Theme';\nimport * as MonacoEditor from \"@monaco-editor/react\"\nimport * as Monaco from \"monaco-editor\";\n\ninterface Props {\n\titem: any;\n\tresource: string;\n\ttitle: string;\n\taction: (item: any, resource: string) => Promise<any>;\n\tcancel: () => void;\n\tdisabled: boolean;\n}\n\ninterface State {\n\tdata: string;\n\tloading: boolean;\n\tcancelable: boolean;\n}\n\nconst css = {\n\theader: {\n\t\tfontSize: '20px',\n\t\tmarginTop: '-10px',\n\t\tpaddingBottom: '2px',\n\t\tmarginBottom: '10px',\n\t\tborderBottomStyle: 'solid',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '250px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\teditorGroup: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class LogViewer extends React.Component<Props, State> {\n\teditor: Monaco.editor.IStandaloneCodeEditor\n\tmonaco: MonacoEditor.Monaco\n\tloaded: boolean;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdata: '',\n\t\t\tloading: false,\n\t\t\tcancelable: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tTheme.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tTheme.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t});\n\t}\n\n\tsetLoading(): void {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tloading: true,\n\t\t\tcancelable: true,\n\t\t});\n\t}\n\n\tsetLoaded(): void {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tloading: false,\n\t\t\tcancelable: false,\n\t\t});\n\t}\n\n\tupdate(): void {\n\t\tlet loading = true;\n\t\tthis.setLoading();\n\n\t\tthis.props.action(\n\t\t\tthis.props.item,\n\t\t\tthis.props.resource,\n\t\t).then((data: string[]): void => {\n\t\t\tif (loading) {\n\t\t\t\tloading = false;\n\t\t\t\tthis.setLoaded();\n\t\t\t}\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdata: data.join(''),\n\t\t\t});\n\n\t\t\tconst model = this.editor.getModel()\n\t\t\tif (model) {\n\t\t\t\tmodel.setValue(data.join(''))\n\t\t\t\tconst lineCount = model.getLineCount()\n\t\t\t\tthis.editor.revealLine(lineCount)\n\t\t\t\tthis.editor.setPosition({\n\t\t\t\t\tlineNumber: lineCount,\n\t\t\t\t\tcolumn: model.getLineMaxColumn(lineCount),\n\t\t\t\t})\n\t\t\t}\n\t\t}).catch((): void => {\n\t\t\tif (loading) {\n\t\t\t\tloading = false;\n\t\t\t\tthis.setLoaded();\n\t\t\t}\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.disabled) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tif (!this.loaded) {\n\t\t\tthis.loaded = true;\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.update();\n\t\t\t});\n\t\t}\n\n\t\tlet refreshDisabled = false;\n\t\tlet refreshLabel = '';\n\t\tlet refreshClass = 'bp5-button';\n\t\tif (Object.entries(this.state.cancelable).length) {\n\t\t\trefreshLabel = 'Cancel';\n\t\t\trefreshClass += ' bp5-intent-warning bp5-icon-delete'\n\t\t} else {\n\t\t\tif (Object.entries(this.state.loading).length) {\n\t\t\t\trefreshDisabled = true;\n\t\t\t}\n\t\t\trefreshLabel = 'Refresh';\n\t\t\trefreshClass += ' bp5-intent-success bp5-icon-refresh'\n\t\t}\n\n\t\treturn <div>\n\t\t\t<div className=\"layout horizontal wrap bp5-border\" style={css.header}>\n\t\t\t\t<h3 style={css.heading}>{this.props.title}</h3>\n\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={refreshClass}\n\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\tdisabled={refreshDisabled}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tif (Object.entries(this.state.cancelable).length) {\n\t\t\t\t\t\t\t\tthis.props.cancel();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.update();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{refreshLabel}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"layout horizontal wrap\" style={css.editorGroup}>\n\t\t\t\t<MonacoEditor.Editor\n\t\t\t\t\theight=\"400px\"\n\t\t\t\t\twidth=\"100%\"\n\t\t\t\t\tdefaultLanguage=\"markdown\"\n\t\t\t\t\ttheme={Theme.getEditorTheme()}\n\t\t\t\t\tdefaultValue={this.state.data}\n\t\t\t\t\tonMount={(editor: Monaco.editor.IStandaloneCodeEditor,\n\t\t\t\t\t\t\tmonaco: MonacoEditor.Monaco): void => {\n\t\t\t\t\t\tthis.monaco = monaco\n\t\t\t\t\t\tthis.editor = editor\n\t\t\t\t\t}}\n\t\t\t\t\toptions={{\n\t\t\t\t\t\tfolding: false,\n\t\t\t\t\t\tfontSize: 12,\n\t\t\t\t\t\tfontFamily: Theme.monospaceFont,\n\t\t\t\t\t\tfontWeight: Theme.monospaceWeight,\n\t\t\t\t\t\ttabSize: 4,\n\t\t\t\t\t\tdetectIndentation: false,\n\t\t\t\t\t\tscrollBeyondLastLine: false,\n\t\t\t\t\t\treadOnly: true,\n\t\t\t\t\t\tminimap: {\n\t\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\twordWrap: \"on\",\n\t\t\t\t\t\tautomaticLayout: true,\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Logs.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as LogTypes from '../types/LogTypes';\nimport LogsStore from '../stores/LogsStore';\nimport * as LogActions from '../actions/LogActions';\nimport Log from './Log';\nimport LogsFilter from './LogsFilter';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport LogsPage from './LogsPage';\n\ninterface State {\n\tlogs: LogTypes.LogsRo;\n\tfilter: LogTypes.Filter;\n}\n\nconst css = {\n\tlogs: {\n\t\twidth: '100%',\n\t\tmarginTop: '-3px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 3px',\n\t} as React.CSSProperties,\n\tlogsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Logs extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tlogs: LogsStore.logs,\n\t\t\tfilter: LogsStore.filter,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tLogsStore.addChangeListener(this.onChange);\n\t\tLogActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tLogsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tlogs: LogsStore.logs,\n\t\t\tfilter: LogsStore.filter,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet logsDom: JSX.Element[] = [];\n\n\t\tthis.state.logs.forEach((log: LogTypes.LogRo): void => {\n\t\t\tlogsDom.push(<Log\n\t\t\t\tkey={log.id}\n\t\t\t\tlog={log}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Logs</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tLogActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tLogActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<LogsFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tLogActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.logsBox}>\n\t\t\t\t<div style={css.logs}>\n\t\t\t\t\t{logsDom}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<LogsPage/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/LogsFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as LogTypes from '../types/LogTypes';\nimport SearchInput from './SearchInput';\n\ninterface Props {\n\tfilter: LogTypes.Filter;\n\tonFilter: (filter: LogTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class LogsFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Message\"\n\t\t\t\tvalue={this.props.filter.message}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.message = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.message;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.level || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.level;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.level = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<option value=\"any\">Any</option>\n\t\t\t\t\t<option value=\"debug\">Debug</option>\n\t\t\t\t\t<option value=\"info\">Info</option>\n\t\t\t\t\t<option value=\"warning\">Warning</option>\n\t\t\t\t\t<option value=\"error\">Error</option>\n\t\t\t\t\t<option value=\"fatal\">Fatal</option>\n\t\t\t\t\t<option value=\"panic\">Panic</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/LogsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport LogsStore from '../stores/LogsStore';\nimport * as LogActions from '../actions/LogActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class LogsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: LogsStore.page,\n\t\t\tpageCount: LogsStore.pageCount,\n\t\t\tpages: LogsStore.pages,\n\t\t\tcount: LogsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tLogsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tLogsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: LogsStore.page,\n\t\t\tpageCount: LogsStore.pageCount,\n\t\t\tpages: LogsStore.pages,\n\t\t\tcount: LogsStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tLogActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tLogActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tLogActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tLogActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tLogActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Main.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from \"@blueprintjs/core\"\nimport * as Icons from '@blueprintjs/icons';\nimport * as Theme from '../Theme';\nimport * as Constants from '../Constants';\nimport * as SubscriptionTypes from '../types/SubscriptionTypes';\nimport SubscriptionStore from '../stores/SubscriptionStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport LoadingBar from './LoadingBar';\nimport Subscription from './Subscription';\nimport Users from './Users';\nimport UserDetailed from './UserDetailed';\nimport Nodes from './Nodes';\nimport Policies from './Policies';\nimport Certificates from './Certificates';\nimport Secrets from './Secrets';\nimport Organizations from './Organizations';\nimport Datacenters from './Datacenters';\nimport Alerts from './Alerts';\nimport Zones from './Zones';\nimport Shapes from './Shapes';\nimport Blocks from './Blocks';\nimport Vpcs from './Vpcs';\nimport Domains from './Domains';\nimport Plans from './Plans';\nimport Balancers from './Balancers';\nimport Storages from './Storages';\nimport Images from './Images';\nimport Pools from './Pools';\nimport Disks from './Disks';\nimport Instances from './Instances';\nimport Pods from './Pods';\nimport Firewalls from './Firewalls';\nimport Authorities from './Authorities';\nimport Logs from './Logs';\nimport Settings from './Settings';\nimport OrganizationSelect from './OrganizationSelect';\nimport * as Router from '../Router';\nimport RouterRoute from './RouterRoute';\nimport RouterRoutes from './RouterRoutes';\nimport RouterLink from './RouterLink';\nimport RouterRedirect from './RouterRedirect';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport * as SubscriptionActions from '../actions/SubscriptionActions';\n\ninterface State {\n\tsubscription: SubscriptionTypes.SubscriptionRo;\n\torganization: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\tapp: {\n\t\tdisplay: 'grid',\n\t\tgridTemplateRows: 'auto 1fr',\n\t\theight: '100vh',\n\t\toverflow: 'hidden',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tminWidth: '310px',\n\t\tmaxWidth: '380px',\n\t\twidth: 'calc(100% - 20px)',\n\t\tmargin: '60px auto',\n\t} as React.CSSProperties,\n\tnav: {\n\t\theight: 'auto',\n\t\tuserSelect: 'none',\n\t\tgap: '5px',\n\t} as React.CSSProperties,\n\tnavTitle: {\n\t\theight: 'auto',\n\t\tflexWrap: 'wrap',\n\t\tgap: '5px',\n\t\tpadding: '8px 0',\n\t\talignContent: 'center',\n\t} as React.CSSProperties,\n\tnavGroup: {\n\t\tflexWrap: 'wrap',\n\t\theight: 'auto',\n\t\tpadding: '10px 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tpadding: '0 7px',\n\t\tcolor: 'inherit',\n\t} as React.CSSProperties,\n\tsub: {\n\t\tcolor: 'inherit',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmarginRight: '11px',\n\t\tfontSize: '18px',\n\t\tfontWeight: 'bold',\n\t\twidth: '100px',\n\t} as React.CSSProperties,\n\tcontent: {\n\t\toverflowY: 'auto',\n\t\tminHeight: 0,\n\t} as React.CSSProperties,\n};\n\nexport default class Main extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tsubscription: SubscriptionStore.subscription,\n\t\t\torganization: CompletionStore.userOrganization,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tif (!Constants.user) {\n\t\t\tSubscriptionStore.addChangeListener(this.onChange);\n\t\t\tSubscriptionActions.sync(false);\n\t\t} else {\n\t\t\tCompletionStore.addChangeListener(this.onChange);\n\t\t\tCompletionActions.sync();\n\t\t}\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tif (!Constants.user) {\n\t\t\tSubscriptionStore.removeChangeListener(this.onChange);\n\t\t} else {\n\t\t\tCompletionStore.removeChangeListener(this.onChange);\n\t\t}\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tsubscription: SubscriptionStore.subscription,\n\t\t\torganization: CompletionStore.userOrganization,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tif ((!Constants.user && !this.state.subscription) ||\n\t\t\t\t(Constants.user && this.state.organization === undefined)) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tif (Constants.user && !this.state.organization) {\n\t\t\treturn <div style={css.card}>\n\t\t\t\t<Blueprint.Callout\n\t\t\t\t\tintent=\"danger\"\n\t\t\t\t\ttitle=\"No Organization\"\n\t\t\t\t\ticon={<Icons.Error/>}\n\t\t\t\t>\n\t\t\t\t\tAccount does not have access to any organizations\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-log-out\"\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\twindow.location.href = '/logout';\n\t\t\t\t\t\t}}\n\t\t\t\t\t>Logout</button>\n\t\t\t\t</Blueprint.Callout>\n\t\t\t</div>;\n\t\t}\n\n\t\treturn <div style={css.app}>\n\t\t\t<nav className=\"bp5-navbar layout horizontal\" style={css.nav}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-navbar-group bp5-align-left flex\"\n\t\t\t\t\tstyle={css.navTitle}\n\t\t\t\t>\n\t\t\t\t\t<img className=\"logo-light bp5-navbar-heading\" style={css.heading} src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAg0AAACkCAYAAAAUlB2bAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4AIWFwYKesQRbAAAIABJREFUeNrtnXl828WZ/z/PfCUfceychKtQjgABR3KCKZACQXJCINCkiWXRAs1ylqW7bem9v8K2a7pdWrqlLce2LEspxwKl8hGupoTYFg5XoCG2bEMS6ALhCJA4wU58St95fn/ICQ4NsWTrO5Ks5/16CQiRvjPzzDPPfOb4zhAEQRAEQchYjvJdVlCS11Wi+tUk21IlxLEBrXVXUdHErnWrHug2mReS6hCyHPL4gycr0p9jZg9IzQDzZGZMIkIJgDwAAwC6GOhSQBcTbWGt21npte2NK/8mJhQEIVOYMz9wnG3hCwScCYIHjM8OxbFPQwN4F6AOJv2sitETrc21G0Q0CMIwShcGj7Ri+hsgWgHwwWNoAa+CcUdRv/qf558P9YllBUFIB94FVWex5p8Q4EvB4/7KwL+1NdX+WUSDkPN4fIF/JsJNAIpS2BBeZ+IvRRrrXhYLC4JgVDBUBH4KxnUp75MZd5xwkPp6KBSyU/VIJdUlZFXj8ge+T4TbUykY4m0LM8HUULZg+fFiZUEQjMU0X+BaMK53ZBBPuGbTNv3zVD5SRIOQNcyZHzgOwE8dTGIybOt2sbQgCCY44YylxSD8u6OJEL4zd0HwJBENQs6hLXwdB94QNGaY+JyyiuCpYm1BEJymIN99CYBih5NRttbfEtEg5BRH+S4rAHCpkcTY/qZYXBAEx0ON5n8wlNSK0xZfUiKiQcgZiqnnNACTjDRk0BeCwaAlVhcEwSnmzQsWgqjcUHIFfX29nxfRIOSQJOfTDaY2aVOnXSZGFwTBKXry7TI4vNy6TwglEtEg5A4K/BmT6RGro8TqgiA4FmMUDjWaHqUmPZdUnZANMGGayfQ0eHomlPso32UFU+ye4li+LoatpjBxPjENsNLR/EHetn5t3fsAWDxEELILDZpu8qAk1iolMVREg5AturzIZN9IRCWmS+hdtKKIY33ng/UCMJ1MhFnArmLbBZA9FGYYABikgaiL4PUHBgC8DuAFBjeSu+iRyOr7e8RfBCHDI5rGBJPHKxLxBBENQg5NNbAbZmW521RSpy2+pKSvr/9HHO29moASgJI55iUfQCmAUgJdiWjvbq+v6q5By/7pxob6TnEcQchQ0QB2sdGgRimJabKnQciWFuY2mxwZSc87P3h0f3//ehC+FxcMY2YiiL+Vp9WrZb7gmeI4gpChKHKbTVCnZNOliAYhe2SDyeYFOP7Kpc/nc8HSNQzMdODxBzHpp+YsqPycuI4gZB7MZLb/5dTENBENQrYQMytROOp0Ep1q+goAJzuYRIHW9HBpMJgn7iMIgogGIXcgRI2mp5Wj6fl8Phcx/6uBkhxtbdMXiQMJgiCiQcgl1dBvNDWlB5x8/kc09TQAxxgSXEvEfwRBENEg5A6atxtNj9UORx8PVWGwNLKvQRAEEQ1C7sCED42mB2dFioY+2mBxPjNz8eJ88SJBEEQ0CDkCvWxUNNixNkdLw2TyxElV0JM3Q3xIEAQRDUJOEFX2MwBsI/IEeL29+ZG3HRUlhGKjkku5isSLBEEQ0SDkBEOnGz5iqIu9z4AwMXtYlUrNEbKCIIhoEITscFam2wwkM2CrwTudT4bNngZHSmYaBEEQ0SDkDi3hmjBAf3G4M7+5veHRD0z04iZtx4yJ4kGCIIhoEHIKrQYvY+AVhzryR+3p1g3j0W5k8AIuQRBENAhCRtDe8OgHFttnAPgfpO5o6S4AP5jK2wMdodCgWFkQBGH/yNXYQtbREl75EYCr556ztDoWc3+JGH4QTgZweIKPsAl4QxNeII01yJtQE1l9f49YVhAEQUSDME7Z8NSj7wH49dAHpy2+pKQ32j+dNE9mtooVc7FWKCRt71JEu2KKdhFhd5+7993XV60aEAsKgiCIaBBylHWrHugG0C2WEARBcAbZ0yAIgiAIgogGQRAEQRBENAiCIAiCIKJBEARBEAQRDYIgCIIgiGgQBEEQBEFEgyAIgiAIgogGQRAEQRBENAiCIAiCkEKSPhGyNBjMwzZMJR2b4lKufNulu12a7dhgQXfbwuO7UF2txawOUV2tPGs2TyIVnawUJrEV61XK3p3nKt49dBqiI5T6goe4lF7GjPkAygEcCqAYwCCAHoBtgD4A8A6Ad4i4lTVe6kZJy5vhe/ql4oRk8C5aPoNsTCbQJNumSQSezEqRgo5pTbug1Qd5ediyfk2oKxvLV15+tdsu/vDwmGWVWLYq0YqLAUxU0DEGdzG7upiiHxXlF21zsl0nmtf+6TuLla0nWzFVErO0JsJu9OfvzOubuHv9+jujpvIxMPn9qYhaU5WCxS7Vi/78nX3FO3vlSHiz0IH+MhgMWps7eQEzLwNwGoDjAUw8wE8GCdjCwJsEtGmip8hV2DzWy4BKFwaPdMX0fBBOZuAYEI4B43AAecPy08vx2wp3KqBDE0cI1OpWqjkbg8vsimXHWlDng+ksDZQScOQItgcDW4mxkRRetEk3TLd3NoXD4VHfBFnmD5zLhK+DcR5Gd+R4PwPNCniSbDzW0lz7mhMdDEdpoQKdooFSIhwExiQAkwCUAHADsBE/Xno3wFsBaiPgFSYORxrrXk5lfk5bfElJf39/FQPzAZwAYEo8XbwFpi0Av8nAW0S4aag9mWrq1zPQocDTNNNBpHAQgacxMB2MAjAXgSgPhBIwrI8FIaIgbAdjK0DPEFFDa2Po3VTmzOMLzCKiSgafQ0DZkM1GhIGtRPQsMz9lsf2noYvMMg6fz+faQVPPZ+B8gM4i4Lghv0yEt5npryD+i85TNR1PhnY4lc+5FZWftUHng3E2gFIAMwEUjPCz3QA2MdCqiMN6sODxtmce3Dlmn/AHywm6ggifZ6Z5AB98gK93ErBJAxsB3gRlrZ1mb3tpLLHPiDj2V10H8H8YS5D5+Ui47vOOiYYyf1UlE/8cjOPGmEYvgP9yc94v1ocf2p6wAy8InhSz9QoifAnA0WNIf5CYntaKH3VF1cMb1oa2ZbIjeSoq55OmahB8I4m6BHgfRDf35vfclowaL/MHzmXghiGhmDqXBZ4G8b+1NdY1j1lULVh+jtLquwDOwdiW2TYDfE83l/x6LLMi5eVXu6OTdlwP5u8MzcKMV2wQ1UPrmyPhuhfGFDR9y2cTrF8x8cIU+Ho/CP/Ng/k3pKLTSpVSK/MHLmfQvwN8WAqe1wfCnYX5BT9O5QxE2fzAXLboRoAXYexL1v0AHrBc0R8PXSiXeOw78+Ip5B5YAeBKAN4x5qMLQJgJT0zIL3g43TM24140eP2BXwD4fkrzC2zV0Is7mupbD+jAvuCZDP0TEPwOmK2fgQc19K0j5SMdAcbjC9xIhH9JQQD9pPVftGLWF0YSTLMrlh2rtPVrEJY4PL11ZxcXXzuaTnrm4sX5hQMT7iLGV1KcqdcU8+UtTXXPJj0T5gtOtGCvBtE85BAM3J3XPe2a0UxRl/kqv8RE9yE+W5jKinxPMV3QEg61pNM2wWDQ2rzdvpdBlzjw+L/BVudEmkNvjH2QEvg6MX4DwEpxHj9Uir/Q0lD3UmIzMdP+CUB1orNMSdLD4D9BWf/T1hB6XkTD2FB/35ir/jHVgmGoozjUgmqaW1H52U8JvId4/VWPMOm1DgkGACgg4AoLqsXrr3qkbMHy4zPFgTz+yu8T4f+lXjAAAJ1qW/rhT3t2+ZIlEzz+yp8ottqdFgxDnc3VxbRrVfmSJROS/W1h/4T7Ui4Y4pk6ToOenOOvPCPZn7pI35xrgmGoTV8xWNJ5x6hGtkT3p14wAAAfpkk3eecHj06nbTZ18rcdEgwAcCxc+pHSYHBM9pvjr7yAGLc6IBgAYIbWamX5wuCkA3acC6rO2qGmRQDc4pBgAIAiAl1OWj/n9QfWlFUET4WQGtFQvmTJBCa+0cH0ptia/vD3HWbVFy2y2wBeanCctJS1avdWBH7jOfPiKemshHLfRdMJVO1whPd7/VUr9jezE92d106gH2Hk9ctUdji+2G73nUkp84rlFQRc6GC2ijToYe+iFUWJ/mDuOUsPY+CKXA0gBFxRtqAyKZGvLfp3JL6mPxomQ+nfp8smpb7gRDBf57Dy9ri269GL5+pqpUG/dGaQ8rGAi8b01/f3NzMXL84v81fdBM1hME40WD0LmPULXn+gNt3CclyIhtiu/BUApjreefkqT/+4Iwh8jcB1AE1PQ/ndYFxL7oFXPBVVi9NVCYOIXgmg0IBQ+uHwIOH1Ba5l0mGMbc/IGOIeXTLHV+VL+Pus/tFAtg5HtOebiX45Fs1bgdFtEh03sKbvJdyhLgweSeDzDKgZv8cfLE+HPSzSVzg4ah7WfvDV0f7W09weADDLQD1c9UlhMmvB8mkT+iesZvAPkJ7X/glAJSzd7vUFrnVWOI1z0cDElxkJMoTLAKDMH/gOGL9F+s+LOISYnyjzB351lO+yAuMeTHy5oaRmzVlQecoewQByZC0zKTTx1QnNxiwMTiJgmZmIQl9Kou4uljCCc2ctWD4toYBj21825nOsq9Jkj0sNpXP67Iplx47Kx51Y4ts/R81eEPDsFSu+wKw8rdYh/nZRupkAwm+8vsATpb7gIdKMkxQNpb7gRMTfwTeQKJ3uqahazMAvMsgWxMC3S9Su57y+ys+YSnT2gqUHI/5qnplO2qbPeysCS0C4OSNGqcAFiSj9mK1PhyNr4PvNU1kiQWRovXa2hBFYbrYqEhNZdJY5MQ6faUMMLXXOMTdkdiXd+QaDQQtgY522sjE3PlBZPpsITwM4NqO8l7DYIr3OMz/okaachGhQFjxwdp1xeFAuJeaH0z3K/ZTMzQXRc3N8QSMNnzjvZMPTGueB8WCm2J6AEs/CwIjLI5r4c0bzpfiYkb5j2/ZsyKmqQxXEJyfYvuaYa8o4CaannvP6PUZ9guMdcjK8+sHgYQAmm2vjNLPMHygFqUYAMzLUg4+EpZ/x+pafJ405QdEAzSYr04XMfpf9CE36GY8/cL7jDcqs3YH4evLEjHJCTTNH/A6T0elDi3nE2SZNdKSEkL3irzTBrx5kUpDO8S2bZNSXWU03a/fk44dyWQeZzCOTPhLASpN1P1p/AanHPP5KWXJMaKYBPE3MsQ9FBNR7/FVfdHbgj5y3O2tdMmIHbTjgMPOIopZ0Sg7sGS+qYcT6OeGMpcUA8k1mK6a52KzfOLyR/JNmZxpFejzZsHN8heOnS2YDLgLdW1ZRuVwa9QiigclsY84S8ghc66TyZFCBmJlGfMWR2MTbJcPqRdHI6REVSd3t8WOMKPwmuovSEGPURLN24HzD6SUdP4iV23QlZJk7u5jpj3P8lRdIyz5AZRLYJebYL5ajypO1O9cNPHRnyEgdtFE7JSJStGEhk9kTDRhxGaAfMeMxxnK5jM40kGK3YcMnnx6TWzx25AGjBoXK5gfmiik+RTRoJhENB1aeDww/XyJ1bV6J3RUncAmPWVFLCZybQUpEwzBGFA0WbOO+nsgyU0r9RhvfYJy0ANCUgRvQM5NCtlCX6OvEOScaFLPsAh/BgUD0eKqPnmbFOX+wCMWst0ce+RMb7WwSmGZmZllaGt4+RsB2WcZjjPHlAiKzZaRRTP2zxJwkOCpPq4fjr6kK+4gGISGmMavH5/iWTRZTpIwdkebQm1kpdkATpPr2MigmEMYpCzZ18o/EDCIaRjt0OU6TdZcYImX2XAeAszTzLBW4l34xgTB+4xRfL/sbRDSMhYDXV3WVmCEFbZHwVPbmnnqkBveYAtvFCMI4xsUW7i4vvzrnN5GKaBh1kORbZs9ffqIYYmyawUVcl739JO2SKtw7EvtAjCCMc+ZESzq/JaJBGC0TlEV3Qm5IGwvrNjTWvZXFPaV0lHssAbwmVhBygOtLzw1OzWUDiGgY21jzTI+/8iKxw6h7mjuyvAQdUol720Kb2EDIASa5ovoHIhqE0YdK0M2nLb6kRCyRNB92o/jhrG48rqh0lB9bo1lsIOTEWIfxzbKK4OEiGoTRckjfwMB3xAxJi63b3wzfk9U77jc89eh7AF6W2sS7bWeXbhAzCDlCIWuds3sbRDSkRnp+o9QXnCiGSJgPXRbdOi7ED/ODOe/+xHegulqLWws5NOq5snzJkpw8p0VEQ2qYqkh/VcyQYHsj/t76NaGu8VAWV3H0dwD+L2cFA7A1T1m3iVcLOcaU6K68oIgGYQzCE9+Wd3gT6mbua22su3+8lGb9Y4/1kuKrkJuHG/UqVheOFwEoCEkG/a+JaBDGwhF2yfZF46QsOwHUgehmYv4eg/8F4OuIcTsYqwBsG+VzH3F3Tx93h2K1NtQ1aaWXAtiRQ/7+MojPag2HnpGmL+Qop3nnB4/OtUJnww2LDGATgHeY8L5idAE8maEOBfgoAMdkUEYvAvBE1s4BAKsJ9IsTplM4FArZB9LYZfMDc6BwBRNWYOQbDvsJ+Fnr2Z6fjte17/aG+qfmnhWcZVt8HQgXAnzYp5juA4DfYeA4Aky+dRMBMEjAZAZPBmgSkr8h8Q0Aa8B8dyRc94L0GULOY+klAG7NpSJnsmiIENNvXXDXrg8/9KlH1JYtWH48s7UUzJcBKE1vp0tf9C5aURRZfX+2HS/8BkNd3tYUenpP7zJSUVubazcA+EapL/hDi7gSpBeD6WQAnwEwAcBOAl5l5tUAft8arnsHTbXjujFtWBvaBuDbqK7+bnlD5OB+F8+wGDa7VK+Oqd6Bwt07X1+1agAAvP7ASwBOMZU3Iq5ubayrH/7/vItWFFncNQk6f3IMmATmyYq5WNPHN4oqoIuht8PF70RW138ovYTgEH3E9IwmvUYpvMQ2b3Pb1DnIBf0oGJiitJoO1kcx4ANQAeCEDMm3iIYMYAcRf6u1se5/kcBFRq0N9ZsB/BLV1b/yPt3+FUDfDND0NOV9IgZ7FgOoySIfqC0sKLhi3aoHukfz445waDeA+4Y+AgBUV+v1wFbEP5mryuPitgfAe1JpQrriPRP/ekJ+4a0HiEE7Ed9s/CKAPwHA7IqqzyvN/wrC4jTnf/5piy8pGW38zEYybU9DxLbU3KGNcsndIFhdrSNNNfdZhFNAeDVtJSCqyKL6f3Aqd345lxxeEISM4f6BwehRbY11P002BrU31jwXCdeer5VeBHA6L0vL6+sf+FwuVVomiYaOQaUrOtaEtozlIRsa694aJH0WgHStuc7PhopnIBw527MiHA7HJHYJgmCQGBjfijTV/sOmZx8d06Vv7Q31T9mWVQ5CGg8X06eKaDDPblK6cmNDfWcqHraxob7TzlMXAHgzDWU5qdx30fQMr/deWLhSDuQRBME4hG9GwrW3pGy0uSa0ZZD0OYhvmDc/AGMlMw3mjU7XDO1NSBkdT4Z2APgygEHTTWKQBjNdeT7UtqY2Zw8kEgQhTXqBcXuksfZ3qX7uxob6Tq1wIYAB42UiPiWX6jATRMOf28I1Dzjx4EhT7ToAt6ShYRyX0ZXO9L8SvgRBMMxbPYW933Pq4e0NtREw/pCGcn1m3rxgoYgGYypN3eDk8weVvokBoxv9mOjYTK70/MJ8uWRJEASzsR74yZ5Xjp3CdqmfIQ2zy31u/RkRDUZ6VzS1NoZedDKJoX0Sd5mdaeCZmVrhDHTL2xKCIBjuV9+bwp2Ov5bdsSa0hZmMH7AXc+ujRDSY4RFDvfgjZtsHjshctU+7JYAJgmB2sKKfNPamluJG4x2pbX1WRIOJxDX+bCKdaXrHc4gfEGIKuSZbEARh77hNrTaVlouUcdHApKeKaHCegZbm2tdNJDSkcDcbdCERDYIgCHtVAxk7R2GSvW0zAG22eGqCiAbn+RDJnvo4Nt436EJFEiUEQRCGhlFRt7F7S4YGiR8ZFQ0MeXvCAF2G0zPpRHkSJgRBEAAAsbZnHjTaiYPQaTQ9ZplpcNzGwDTDSR5ksGw9EARhXyVNA2k4spzcRpNjGTDshx6YnVUGAKMbvllxgYgGx4UgZgSDQctggocabiSCIAwfbvZN7EtDoDG7QU2ZG5wIQk6JBgDWxu2YYyKhUl9wIhizDAqiXeJagrAvfcU7e41rBmaz98AwZkhNCyIaHEtcX2BEnZA+FzC5UYU/EtcShH0ZOg3QNpys6ZP6jpGaFkQ0OCbKEYwPzB3nIsPjm7+JawnCfjE9C2fsqnrPmRdPAXCKVLEgosE5Znt9lQEnEyirCJ4KoNKsGOLXxbUEYX96Gm8bTtE7a8FyI5uuydW/AIAllSyIaHCygyW6obz8amd2OFdXK2b7ZpiZzfg4eLDMNAjCfts70xbTMS6P1YVmGr76mtSwIKLB8YEHTooW7/iFE88uC7ffANCZxo1q8SviWoKwv/bOW4wnyvhhaTDo6KuQnorK+QBXSA0LmdvbU0peC1UZURjia73+QEqXKby+qi8z8fVpKM3uyfaODeKhQmYN8Q2fV/DpvJGGNI+wtutrnHr4vHnBQmK6RZxMSA5t9OwKYhSPH9EQXz54wOurrErJDIM/cAWI74XhZYn4oAbPGbvNTRASDk+ZcWIdM9IlqH8Znw1IfezqKdB3AWZeHxfGkY5nZfR+DAaNK9EAAPkg+qOnIvDV0T6gNBjM8/oC/8nA75Guo5yJ10pzEBJQyYMm01OKDsmEcluw/wrzpwMCgJuYHp4zP3Bcyp5YXa28vsAvAFwsHi0k3/lqw4NLHneiAQAsYtxZVhGo9/qDJyQTgz0VVUHXdt0BwvfS3Bn8WZqDkIDq7zec5AmZUO6W8MqPALyWpuQP0RbWl/mrxvw21WmLLykpa26rTXe8EbI4BhBFDSc5IRV7e1wZaUzGMkAv8foCzSCsJKX/0mVP2vJm+J69gdZz5sVTkNfvIaYvAFgK5hM4/VnfFGmse1mag5CAl/cbblML4poWGdBMsA7A8WlKu5jBNR5/5T2arRs7wqGkXo+euXhx/oSBCVf09fdfB/MHRwnjKwbETK+gu7fhJAAt40407Jl1AMEPwM9aoYR2wesP7ET8IpIZwEA+mDIrx0R/lIYgJIjpmYYjPL7AgrZw7Zr0txOsAmNFWlsq6HKL9D94K6rqGfizC7pxQ2PdW/v78rx5wcJdE2JnWtpayP18EYAjxH2FFAj5XjLchWnS54xn0bA/pgx9MtIHwPSQNAUhsW6Ld5gWvaRwa7nvovnrww9tT2fRlbZXabKiANL9RocF5ioCqmwQvP7K7SDaBo3tIAyAMA2M6T3QByut8jgjJmmE8YIi1Z0Gn/rOnPmBlS3NtaNeInRJ1aVMMvwlEg5tEkMIiY0y6L00vNpzYpQG13n9VTdwNO+xtmce3JmOsreEV37k9QeaASzIMCU3HYzpe2eMRSMITo76mbvJ/GT5IdpCq9cf+BMTnoXGNgxdI25Z3BW1rZ09KHpn+FYAEQ1OxWNSN4kVhIRHGUzvMaWlVzoG4HvJPaC9/sA7AHYAPAhQ91BH2UcKnWD6kFm/pK3Y2vaGRz9IvWhCPVGmiQZBMBgDlOpm1ulIuhDApcS4dPiWCq0JFmmUYFev1x+oVWz/uCW88k0RDY6MGvFSWzj0tFhCSFxk6q1pOEZkn5gF4Mj4Z1g+CGAeGmYTQWm39lYE/gzN/xEJ172QqsTzXOp/o7a+CUCReIOQkzEghm0ZelPJBAArNFlV3orAdyONtb/7ZOAQxgqhWowgJIO2+bUsyaoC4wsget7rD/w2GAymJMytXxPqAiAbh4WcpVsVbc3wLBaC8dsyX+ArIhpSqRaB1W1NtXI2g5AUJx7s2gygL8uy/bVN2/TPU6ZGWN0uniDkKkP7BnZmej6ZcLN30YoiEQ2pYVAp/Q0xg5AsoVDIZkZ71mWc8G2vb/nsVDyqJRxqYSAs3iDkMO9mQR5nINoXENGQmgh6c2tD/WaxgzAq76GxvS+dJiwilTKhrEj9C+Q9BSF3yY7+g7BERMPYae/miT8RMwijhqkxK7MNLEd1dUpiR2tj6EUQnhBnEHIyBHCWiAZwmYiGsdGrbX3hgd5lFYSRsPNpNQA7C7N+0Ny1HbNS9TBNuD5L7SAIY+2Ms+NsH8axe/Y1iGgY3Qjx2vbm+lfFEMJY6HgytIPi9zBkHdq2Pal6VntDbQSMX4tHCDnXlViULXcVKWX3nCSiYXT8PhKuuUvMIKRGf3IoKzNOdHgqH9db2PuvIIgQF3KKE6eqDgydyJjxsYrVFBENyfPkVO68RswgpEy+a30PgN7sEztUksrnvb5q1YAGXQVAi1cIuUIoFLIBZMVsAzMXi2hIjg7F9pfD4XBMTCGkipbwyo8A1GZdxjUmpfqR7Y01z4HwM/EKIZcg0JqsyChjooiGxNlMpM4dCvCCkNq2aKv/RJZtBGTSk5x4bmS+58cA5LA0R3spGv+vuHL2zFgx6Seyw6QiGhJlE5GqaG0MvTtOWlNWzpQwme1UGWTMTm3NoTYG7s2ynseZjqe6WnM0/ysEvC6hJyFHTbpdWNCGYwBH02AXs0t+TKMuY6SxbgOAdzLd1dRQTBTRcGA2Wq7oOBIMAIBoVuaaDQceMpueIvVjAD1ZIxnAjr1u3PbMgzttWy8FsE1CUOrbhUlBPJReNA0OuiuL4ioDdH8WOFuPiIYD0+zmvLM2PPXoe+OsXFkpGkwHOmhl1E6tjaF3Cfhm1ogGIkeDcntz/as29DkAdkgoOmDnOJi8QuVBs1lMS8zZZbhBjKmMpOx7kOEno+qhtzxENOyfh7q5+Nz14Ye2j8Oy7c7GTBNps0FAsfFRf2tT7d0A7s+G+mANx2/o62iqbwXzBcY7gLH4KdNThlNM3k+jZHZvFqFrvMc5Yu4eU9tvqN9MhEeyoe9Im2hgpvqMHIUTfT/SVHuxqdMeyXgnTtkphEgZnapWmtMyNW6z+icCnsuCEa6RNdhIuO4FIrUQwPtZ4KXrXba+1HAkTbo9szLr28zoNN+/mF3a0ikoI2l1Q0bPNpDamV7RoO3rQZxJhyRtUorPiDTW/NKos4H+Ztjyb2elaGDeYjZBKy37WDrCod3FbLslAAAM0ElEQVTsnrAIQHMmV4eyETGVVmtj6EWL+HQgg28FJWyw89Si9Wvr3meg21y6lHR7PuEga6vJkTgBfzNfHdhoOME3xvqIlnCoBcAdmRqBBwcHNqdVNJBSHGmsu5qYfppmdaUB/q174uDJLQ11L5lPnduM2p0zuzP6VI+FetZgcr09hbtfSldZI6vv7yksKFgCwuMZWh07Wio8RjuCDY11bxUWFJwB0KMZaI91g6TP6XgytAMAE2CsTWvSTyf7m6EDhYwdX85MTWkI6+tNJmaxnZLZQZvVDzL0ZNS3Nj376K60ioY9/tQarvkRgaqQhulHBsJk45RIU90/r3/ssbScytcWrt0IwmuGkhu0rehTyEJmTUcLw/l19CFpteb1VasG0toLrXqgO9JYuxSMbyHzNq/+BdXVOi02aar5IgFXGh3NH7hDrHdPHKzY2FDfOWyUu9qUeCvudT0zyt+aEl+7dT6tMi68w/UdMPYaI69N1Rk+HeHQbmheBODNDGvzr+z5j4zYCNnaVFOn2D6RgP8CYCJYr2PQsramWn9rc+2GDIg8fzCU0B/bGx79IBtFQygUsolwtxHJoPRvMmWCJRKuvQXxqfkXMqUuiOje9MaL2ru1pTwA/SWtrZbx8zbf7KpPDjgY6mGYOA6b6Lbnnw/1jeaniu37YOL4cqL/HpqBMV8/MBMvwLg9tYKn7h0rpk4FqDGDQnA4o0QDED9Ot7Wp9uvuGB8N0M8ASvWrjn0AHgRweqSp9vS2ppqM2anqnhi9BYDTew1ibFu/RBZjK3XnUD06yV9bG+qaMqnckca6lyNne85g4B8N+MlIEfLF1saatM9WdawJbYk01SwGaCHM3xTaqcBL2sK1P9zfjEukKbQJgNMXkW0bJPu2scRbAE7vKetyR/XN6fKRqNK3Oj3bwMArU7FjZaqfu2FtaJu7e+p5AF2P9J/fYsNWNRknGvawfm3d1khTzXWRs2cfoYnOIMJNiG8KG43h3mDgbgYtc08cnB5pqr0k0lSbcVcRr3/ssV4QL4ODr5YR8/9raw61IYvpWBPawoRr4dwemB6ycXVGFr66Wrc11d7p7p52LECXgpCOuuxh27oKGbTDO9JU0xBpqp3HoGUAGhwe4UcZdJub82a1NNUd8OhfraLXAnBq824vMy4eviQyutHKhOvg3AxWjIm+un5t3dZ0+cbGhvpOpbhyNG+YJCgZtrPCRU7dR7R+/Z3RSFPNjWCexaDbkKZXjwn8x0hz6I2P/zxEma/yu0xkbCTKjBPbwrUJ73ANBoPWpu2YqVXsSGXTkQw1XSmewhoWEQqZ8BEYXQR0EnhTfkFh27pVD2TEumeieOYHPWTp3wE4I4WP/YiJvtPWWPMHjBM8/sqLCXQ7gCkpfOz/gfmSSLjuhWyxQ9n8wFxWuBiELwE4wuHQ8QGTvrCtsS6jN9J6FgaOUTG6nIkvBnBMqsQSgBpS+sbWhvrNCddPRfBwZn0PgIUpLGKLZny1PVz711Q8rNQXnGhB/ycIV6dwEPmWVvqr7Q31T2WKT5BN/wXweSnswZ622bqqIxwydtx5+cLgpFiMv8zEVQB8AFzOCwa87uK8ecPPLMoa0ZBDkNcfqCTCV5hxHoCCUdb2a9C41428/x6Ph1TNPSt4kHbxNQy+bAydA4PQAua7ivqtP4x2fTgTmD1/+YmWS53N4LPB9DkAR6eoE/g/gH5v59EdaVqbHlNnARsLVbzTnsvAUUkE2h1grAOorrAw/09jGYCULaj0s6arAJwLYFryTopuRWjUTPfMmk6PD739kFK8/uAJYL4GxEtH2Z4GAKxl5gf6CvseSvdG4v0xx195hiZcBqalAGaMZmaBgCeh8Pt0L2F6zrx4inINngHFZ7LGqSCUjq5Mn0o/GPcgT/9bZHX9h58QEiIaMpXTFl9S0jfQewpDnQTm2QQ6BEARgMkgWGAMMNCtgC4GPmJgqwI6iO0XW8Ir38wVO82ZHzjOtvhzisgL5s8w1KEATwJggZAPRtc+diLeoli9EmN6riMcen882qR8yZIJ0R73LNbqRFI4lJgP04SDwJgBII+A4qH2PxmErqFbAWMM2k7x4Bhh4NnI2Z6X0vGmhBOUBoN5aps+hoiPZ1LTAC6CpiKleAqAjzTQqZg6GdQRaQptRqqXYaqr1dy1HbNizKXEfBQxlTBxCYOLFWgig/sYqksxupi4G4R3bNavHMQ7O5yaAt+/AP3iES7L8mrCccTqUIAnY9isHoP7iKkbhG2aeYtloaOw12rPJtFd6gvOVIrnKq2PZKIjARQSMHlvGYm7iGkXE70Hrd8EWW2Rs0tfy+S2MGvB8ml5Ws0E+GBidShDHwLCDCI6bKjdz+B9BxJuxG+u3AXw+wBtBbiDCH+NaeupjnBo96fMPohoEARBEARhZOTuCUEQBEEQRDQIgiAIgiCiQRAEQRAEEQ2CIAiCIIhoEARBEARBRIMgCIIgCCIaBEEQBEEQRDQIgiAIgiCiQRAEQRAEEQ2CIAiCIIhoEARBEARBRIMgCIIgCCIaBEEQBEEQ0SAIgiAIgiCiQRAEQRAEEQ2CIAiCIIhoEARBEARBRIMgCIIgCCIaBEEQBEEQ0SAIgiAIgogGQRAEQRAEEQ2CIAiCICQtGljBNpkwKzsq5hcEQRCEbBQNmox24qzcIhoEQRAEIRtFAwhGO/GCAZlpEARBEITsFA1MPSYT7resXWJ+QRAEQchG0QD+wKRm6AiHdov5BUEQBCELRQORettYqoQ3xPSCIAiCkKWiIdIU2gyQmdkGRlhMLwiZzdyKSngXrRBDCIKwF9c+XTn4cQBXOp2oVrpeTC8ImUmpLwiLNGwGEO1FWUUlWhvrxDBCcn4UDCLvfSCWr5E3wOi3LHSEQ2KYcSQawIxfEuFyOHvoU0d7Q/0aMX3mMce3DFpZo/49AWhtrDWe7/Lyq7F+yWFAdbVUYgpQSgM8PC7QuChXWUUQgP548AJCW2PN6J/nqwIUDxt1MSLDxJWnohIEBdIMDQIRx+1Kw1tM/J8MgIjQ2phYp+rxV0KBwAxoK4r2hkeTzr/XXwUaVtGtTWNvu3MrKqG1AhMD2zVsF0A2EHURLGh4/AEQA0QAsY2W8EppcNkWH4b/oS1cuxGAo7XI4BuxT0gSMoWoyx2vmVF+TFZqWUUQ3opKeCsCiJZ0ovSZDqlAJ4LCuCqXDWbs/RBjTEKTifd9HvYV3MQKYAYT4oIBwwUD9jYc3vNv1vD6AijzBxIQ6EPdPQFKu0e1jPRxyqlpu15fADZTXDAcYGABiqenyYLHXykNLtvjg83qnwG86VB6D7Y11T0oZhfGioYGmER+OiHsYwqf7NrGAxsa6+K9+7CSeZojo3qWZ37w7/5forMEI6gBMABvRRXmzQsm/rtYL4LBYNps6/UHPiGIEu2A5CaDrBcNHeHQ+2A+C6DGFKZjM/Crqdx5qZg8i6D4aCzRD8bJNHauE2kOwWYVHx0rQltT7fhx6U9EPNKj81ly6U/MOoz8HGYCcXx0T3sEL9P+O1tm9BTY8Pl8iU4bYON2nSbBULm/7MSLZSvY0xU4mg97aOblYxlKcHVPlQaXZbj2GzTCde8AWFjmDyzSwBUELAQwmtp9B4zHiXB7pKlW5o+zUDW0hmvEDDnIeN2wpqIW7OEdPnF8T8z6O8f03Dw1kmhgtIU/XXydcMZSFOS5PzGrQ9ihpiej8eH1VyLSVGc8Tnzyj20j7G2auXgxigYmjtnuQoaIhj1e3tpU+ySAJ1FdrbxPdxxH4FLNfChAE5XiKcxcTCAXGINMtIsZXUpxFzG/DdYdLeGVb4qJBUHIFDasDcHrr9pntDtY0pnUM8oqgmDW+3SZ69eMTWRteja+kdHjq/p4/8OeIXt1dRJ7Lwieikq0GXrbpXxhEFFbD58gSWhm6vVVq8QZx6Fo+Jjqah0BNiH+EYRRM3v+F2GpPGjE1zQ+3hhFQIzQ1jy64KsGAc+ZF+/37ybaUTz//IGfO29eELsLNIgYpBU0MRQIxLGkd3iXBoNwbdfxjWZEUGDw0NY1RRYm2dsQDof3+1uvrxJMFmjvNrV4JLYIoJiFDWtHZ58TzlgKt9sNi+LvDxAxoBUAG5ai+Hr/MIbbMhH7eX2VIKJ4ve7tvhTAGu7iKNY/9ljCefUuWg7EFFjH18cUMUBAbKqFjpADMyBJrlBo1vv8RCN1y3Jt4RqU+QMY/k6G9+k2JLXzggmlvqCR2aKBwRiUNewKIwdXKGcvCMCyCRz3YGCoXR1oBudA/grFAFsgBlhpgBU0AUX5+Vi36oG/aweDvHuvuBuJOb5lsGMT4v7hih6wLoLBIF7bbsMm2rvnRjEBSiW/T6a6GrObW6A0gZUFYg0igLWCBqN4QI3Ylkfi/wMTXEuhghJs0QAAAABJRU5ErkJggg==\" alt=\"Pritunl Cloud\"/>\n\t\t\t\t\t<img className=\"logo-dark bp5-navbar-heading\" style={css.heading} src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAArAAAADXCAYAAADiKmJ9AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4AIWFwUd0jrHaAAAIABJREFUeNrt3Xn4HlV9/vH3JwtJCIRASICw77uAIAKyKrJopYq1UmzVFqGiiIBQqfyoiBtQN0CqKFpFtOKGSgWUfd9lRwEVEiABkpCEkH25f3/MCf0mJvBdnnnmzMz9uq7nCkp4njOfOXPmnu1MYGZmZmatJGkoMDZ9hqTPasCM9FdmA5MjYmZO7Q6vOrOsB5Z1gB2B7YCNgXHpsxowbLm//jIwC3gOmAw8ATwEPBoRC1xNM7PW71M2Bg4CdgV2ALZM+5TemAtMSPuV+4FrgbsjYokra2ZI2lPS1yT9UZ0xR9JvJH1Y0uqusJlZq/YpoyR9TNJD6rwXJH1J0qautFl7B5mDJd2rcs2U9AVJI1xxM7PG71fek0Jm2RZKOlfSSFfdrD0DzFBJ31F3PSFpa1ffzKyR+5VBki5U990taW2vAbN2DDK/UjUmpfuhzMysWfuWL6g6t0sa4rVg1uxB5mOq1lVeC2Zmjdqv7Jgu6Vfp1LKX07MQmFU3yAwDJtL7J0DLsn9E3Og1YmbWiH3LN4APV9yMKcDGETG3rB8Y5FVtVpl/yCC8AnzCq8LMrBHhdSRwZAZNGQu8r8wfcIA1q86HM2nH2/1Al5lZIxwOjMqkLR91gDVr3lHy2sDumTRnUBr0zMys3vbPqC07SSrtKqMDrFk13kRe96Af4FViZtaIfUsuAtjHAdasWXbJbdCT5Ic6zcxqKk1dtVVmzXq9A6xZs6yTWXtWBdb0ajEzq621yW92qbEOsGbNMjbDNvntKWZm3q900piyvthvSjCrxuhMA+zjORdN0prA9sC2wMbAumnQXhUYAQxPf3U2sAB4GXiGYr7dp4C7ImKCu5+ZNdCoDNtU2pU9B1izagxzm3odWncC/hE4ENiJAV4ikzQJuBb4AXBtRCxxdzSzBlilTW3yLQRm1RjiNr1m0NxT0m3A/cDJwM505v6u8cA/Ab8DnpT0fkkeC82s7oa2ab/iQdvMA01WbZIUks4EbgX2LPnnNgK+D9wqaby7pZnVWI4nRkrbrzjAmnmgWSqXy09nAKfT3adp9wDukrStu6aZeb/iAGtmK7bEbfprkl4PfKqin18f+KWkUe6eZlZD0aY2OcCaVWOh27RCn6PaswhbAV909zQzy5sDrJkDbBZtkrQncGgGdTjK98OamTnAmtlfW5BhmxZV/PsnZlKHYcB73EXNzBxgzWxZL2XYptlV/bCkocDbM6rFPu6iZmYOsGa2rKkZtumFCn97O4q3aeViZ3dRMzMHWDNb1hSH6mWsn1ktNk5nhc3MzAHWzJI/ZNae5yNidoW/v3Zm9RiSYag2MzMHWLNK3ZxZe26q+PfXznAdjXE3NTNzgDWzJCKeBCZk1KQbKv794RmupjXdU83MHGDNbFkXZdKOucClFbchx1cgruEuambmAGtmy7oQmJdBO34YEdMqbkOOD0yt4i5qZuYAa2Y9RMQU4IcVN2MJcF4G5cgxwHoWAjMzB1gzW4FTqXZKra9FxEM55PkM143PwJqZOcCa2V+ltoipFK8trWIKqyuB07wWPD6amXmANrO+htgbgbcAf+zSTy4GzgXeGRHzvAbMzMwB1sz6E2LvpHh96ccp7yUH84BLgDdExAkRscCVNzOzOhriEphlE2LnA+dJOh/YATgA2AvYBtiavs+V+izFWd0HgeuBmyJipittZmYOsGbW6SAr4KH0eWWGAElrA2Mp3lo1jOIho5EUtwS8lP7aixQPhU2NiLmuppmZOcCaWZXBdiow1ZUwM7O28z2wZmZmZuYAa2ZmZmbmAGtmZmZm5gBrZmZmZg6wZmZmZmYOsGZmZmZmDrBmZmZm5gBrZmZmZuYAa2ZmZmbmAGtmZmZmDrBmZmZmZg6wZmZmZmY9DXEJrGkkbQrsC2wKrA2MBeYAC4CFwMvAS8AzwCTgWeCJiFjk6pmZmTUwwEraAtgG2AAYD6wCrE5xNncmsDiFg0XANGAi8CTwTEQsrCjQrAJsnMLMSGC11O4hqa0z059TImKSu8UytRsObJfW93rAusCItL7XSH9tevpzBvA8MBV4HPhLt9a5pN2ADwB/C2zYj6+YK+l+4G7gduCaiJia0XoYAozr8Vla++HA/BTQ5/Toy09Vtb2toO2rAmsBqwKTIuLlBm4nawKDgVHL/aul62ZWLgdIaTzcBtgB2CL1p/FpHF8ljZGkds8HZgOTgQnpcz/wx4hQC8a/jdL4t20aA8elA+LBwGgg0l+d1WOf93yq12PAg8CTEbEkw2Ubk5Zrw7RMY4GhaWxZenV2HjA3HfQ/D7wA/Ckd8C+ooM3DUr/dKu2PxqX90dA07s1K/XUOMCVlj8cjYo735i0MsJJWA94JvBvYD1izn7+1WNKTwPXANcC1ETGthA6+NvAmijNwuwCbpYFncC//++lp0HkwDdRXR8TTLQqsI4GDgUNSDbfobe1WYKGkh4AbgSuA6zo5kEsamkLrx9OgNhAjgD3T53hgiaR7gN8Cl0XEfV1eD1sDbwX2BrZPA/YqffiKBZIeAx5Z2o+B+7oROtJO5j3A24B90vbX899P6xGGJgBPpR1pbnaVdFTasa+dPmOW+3NUH8aWF9NO9S7gBuC3EfFsl/rTWODItE72TQc+A/GipFuAHwO/jIi5DRn/BqcavQs4CFi/A187TdLvgF+lsWRBRcu2XtqXH5j2kesM4OsWS3ok9eMr035ycQltjjQm/01q9y79OPEmSRPTSZWHUwa5ISJmOQI2N8isIulTkqaoHIsk/ULSLh1o6waSTpX0gKQlHW7nEkn3SjpD0usbvL7XkfQVSdNVnqclfSIFnIH2zWMkPanu+b2kI9OAWtY6WFXSRyU9WNIy/FnSv0lavaT2D5L0cUnPyXpjoaQfSdq1xD61rqTvSJpX4nLMkPTpdKa9ruNfSDpK0lMlr/PJkk5JB9/dWradJF2W+ltZnpV08kDH9h5tHifp9BLH+IWSbkn79V0atB//2wzHuYe6XYRNUmjrhkWSTutnOw+SdI2kxV0OMh/s1IaaSaf/R0kvdrGGj0vacQDr/I8Vbox3StqmhHVwaBcD+fOSjijhAOgmZ9J+u1zSBh1eJ+8s+YB0eRMl7VXD8W+NtB/ppvslbVzycg2SdHbax3bLY5K2HUCbx0k6V9LcLq+PeyUdK2kNB9gaB1hJ60v6SwULeUFvz25J2l/SzRWvlOclnZnuI6pzhz+lhLPWvfGSpNf1oZ0bpzP2OZgl6e86uA6O7fJB2FJndaj9w9OBnQ18TNmqQ+vkvV0OLkvNlfSuGo1/wyTdVdH6npjusS3rjPKPKlquaZI262N7h0g6KY2tVZot6buStnSArWeAvarCBT3hNdo2StKFma2c6ZI+WcczspIOqCi8LvUnSSN6EY5OTwNLThZJOrJD62BxhctxWgeW4Uxnz475swZ4KV7ShpJmVhwCdqjJGPiZitf3NSrhtqR0UFylW/vQ1u3VvSu+vbUgZY31HWDzDbCDlr+MSfEAT1W+uLLLD5L2Bh4Ajsmsz4wGzgIekvTOmmXYc/m/p2irsDlw6qtsjLtTPEx3JsUT7DkZDHxfA79/6utUOx/zZyW9YQAD5prAiVinbAYM9KDiFP56NoRuWhW4WCXeL96hnf3oDPruW4D9O7xcI4BPV7xce0l6ay/OEp8A3APk9nzJ0JQ1npB0Tt1vLWiq5Xecx1fcnuErCjTpktTVwCYZ13JL4DJJV6p42jP3I7X9gB0zaMpJKxocJJ0M3JLqmqshwHdVPLncn3WwD8UUPVUK4NwBhI0PUkxLZ51znPr5oF0KL/+YwTLsQvHUeM6OoZg6rGpHd/j7jmJgMwx0ysdepZ+OAn4DfJWBz4hRphHpgPBBSW/20JRpgFUxv+tBGbTp71VMhbW0XR8FfpZ5J+/pEOA+SW/PvJ0fzaQdq1FMhdXzqPx84D/TUXDudh7AdnN0JsuwZ1qO/ni3h9GOG0Ux5VV/vJf+T3WYezDr5AH84FcLWF12eM993gCXK4BPZLJcb13RgVi67/cW4NAabZMbAdekB8xGeIjKLMBSzHuXw6tlh1PMw4ekD1H9Jdb+WAe4XNI3cuzsKiYyzylgv63HP58NHFez9f3+fqyDIWmby8U7+rEMo1P4tQzWR3JYRstwsLo4XVQf7cJy8xNXaBidu3VvO/K5UjmcYu74nmPG64E7yOPqX18FxVXq+/ryALJ1J8Dum1G7dkv3vH69xrUN4MPAbZI2zKxtu5HXPaVvSoPbCRSXa+rmkH5cgt+JvC69H9TPfjQIK8N+6UCzLwcUsXRbysSo1M9ztH9m7dm7Q9/zlsyWa8flwuvVFG/QqrOtgVslvQPLJsC+Macjd+CydGRadzsDtyuvlyDskVmNVpN0OPClmq7j0RSXmPoit/kyN+/Hf7MrVto2Qd/fTrYJxas1c7JdpvXdPbP2dGpM3inH9Z8eyL2W4pXSTdk+L5N0HFZtgE2XedbOqF1bZdaegVo/HbEdmUl7xmdYo0vo/ytrszrT0IewkZN1+jEV3KYeQku1fR//fo5ntrbJtLbrZNaeTo3JuR3ArCVpc+CqdKDfJIOB89N9sb4SVVWApXjXd7gcpRoOXCLpIxm0ZVyG9an7jfF93SHmdoAW/ViGDbxZl6qvZ2DHZrgMuV4uzm0MHNOhELR2hnX+Kfk8WFiG44Fv5j5tXBMNWXqU5FJ0LSR8XdLQiDi3yqNir4qO6+u8mzleYejrfdHre7WXat0GbNe5XknL7Wzg4NSmFwf4PbnNV7pbS7bVo4HZeE7srlp6xLeKS9HVEPs1SZ+qsA3DvBoqD7AjM1yGvp4FH+3VXqq+nhHPcRzPNcDmWKthDV2utjhB0pkuQ/cD7FCXous+L+l0D96NMb+Pfz/Hba6vAXaUV3upxjSgT+U6f3eOY+CQTL7D+u90Sae4DA6wbXBmRU8xepDrvGkOsFZx+MuxT+V6sJzjGDg0k++wgTmrBi8ycoC1jvhaBZ3dAbbz+nrvWq0DbHoRg/tRuYY1YLvO9XalHGvVibA/2JtNFrnqB+ntptaFAOun56ozGPiJpDdWsN6tcyb38e8vyXAZ+nLGz+G1fEMbsF3nGqhy3Od5XG6ONSnexrm6S+ENpulWpZgUeWOXopYWAw+0bDxwgC3fApfArLa2Ab7lMjjAtsF6wC/7MZm8Ve+RiJjdsmX2pcryzXcJzGrtCEnvcxkcYNtgZ+Bsl6F27m7hMvvsYPnmuQRmtXeupHVcBgfYNjhe0mEuQ61c3bYFjoi5wCKv+lJNcQnMam8McK7L4ADbimwAXCRpvEtRC/OAK1q67LO9+kv1nEtg1gjvlfQul8EBtg3GAt91GWrhioiY1dJln+XV7wBrZr1ynqQRLoMDbBscLOnvXIbs/bTFyz7Bq79Uj7gEZo2xAfARl8EBti2+Immky5CtF4Bftnj5H3YXKNVDLoFZo/y7JL/B0AG2FTYEPukyZOuCiGjzk+IOsOWZBvzJZTBrlDHASS6DA2xb/JukLV2G7MwHLmx5De50NyjN7yJisctg1jgnSVrbZXCAbYNhwGkuQ3Z+EBHPt7wG9wB/dlcoxWUugVkjrQ4c4zI4wLbFkX7NbFbmAp9texEiQsCP3R06bhLtvrfarOn+VZLfZugA2wpDgRNchmycHRETXQaguI1irsvQUZ+LiIUug1ljbQQc6jI4wLbFMb5vJgv3Al90GQoR8TRwpivRMbfge6vN2uBYl8ABti1WdYev3HPA30fEApdiGecA33MZBuxR4F0RscSlMGu8Q3xr4MAMcQlq5QOSPpfuPWy7F4FbgYkU74yfkw7IRlNMVbIpsBXFpZpOmAocEhF/cemXFRFLJB0FvAwc54r02RLgYuCEiJjpcpi1wiDgcOCrLoUD7IosTDvVSMGm7jYH3gDc1dL+Ogv4FvAz4O7eTDMkaSPgzcD7gf1TX+ir+4DDI+IpDxkrD7HAxyT9CjgP2NZVeU3PUZy5vigiPJuDWfsc5gDrAAswA/hf4HrgfuCJ5d9RL2l9YEtgD+CAFGzqVoN/aGGAnU1xmfr8iJjex2A1MYWE76X5dD+SwuxavfjPnwHOBr4ZEYs8XPSq3tcA20naLQ3OmwLrpn+9IK3LeWl7fTZ9JgAfAI7KbHHOSGPKmukAeDSwRi/+eVQ6cF76INY8iisG01JovRO4PR2EuV+Zrdhc4DHgKYq3Hi4EBKwCjKW4urY1sFqNl3FvSWtFxIte3e0MsI9QPFTz89d6K1JELN1h3gCcJWmdtNM8EajLA1LvlXRyiyY5vwP4p4gY8FuJIuIJ4ERJpwBvAvYBdgDWp5ibbwHFrQL3AzcC13gy+X7X+h6KeWJ7RdLfZLgYUyLiXq9Ns665n+IK25XAg691gJemotoWOJjicvxeNcxghwI/9KpvV4B9Gfg34ML+PvSQJqL/gqTzgM9R3L+X+9xs6wF7p4DVdGcBp3f6LFX6vhtbUkMzs9xdDpwVEbf1cSxfTPFK64eBL0t6HXAqcAT9u12sCu9wgO2fus5C8Edgl4j4Riee2I2IlyPiBOBAigeCcndAC/rmpyPi332J1cyssSYAb42Iw/oaXleyL38wIo4E9kw5oQ72djdoT4C9G9izE5eUV9D5bwB2B57MvAb7NrxffjIiPLeomVlz/RrYKd033+l9+Z3ArsClNajD+pLGuzs0P8D+ATg0ImaU9QPpSfP9gaczrsMeklZpaJ/8WUSc403TzKyxvkUxs0tp08ZFxByKh54vqEE93uAu0ewAO5diEvlpZf9QenL9MIonpnM0Atitgf1xFnC8N0szs8b6AXBsNx6QTXOmHw/8jwOsA2yVPhERD3frxyLifuBjGddjrwb2x29GxGRvlmZmjXQ38KFuvm0u/dbRFFdwHWAdYLvu1xHxjW7/aET8N8XTkTnauoH98TveJM3MGmkucEQVr+KOiNnAhyjmkc3Rdu4ezQywC4CTKvz9E4H5GdZli4b1xaci4jFvkmZmjXR2la/iTrMc/DzT2oxv8HMtrQ6w36/yNYvpt7/nAFu6B7w5mpk10kvk8crUszLOYuu7mzQvwJ6bQRu+Qn6XHtaXNKJBffE5b45mZo30nYh4qepGpDfrPZhpjTZxN2lWgL0vIh7JoNM/TvH+8pwExXvmm2KuN0czs0b6UUZt+WWmNdrI3aRZAfbXbsurWrNBfVHeHM3MGmcycG9G7bk20zpt6K7SrAB7s9vyqlZzFzYzs4zdluZjzcXvgcUZ1sn784YF2Ifdllc10l3YzMwyltU9pxHxMvBshnVa1V2lOQF2TkQ8n1GnnwHMyKxGPmIzM7OcPZlhmyY6wDrAluklt8kB1szMam1Khm2anmGbRrirNCfA5niPyqLM2uMHn8zMLGdz3KZe8RnYBgXYHO/vXD2z9sx2FzazZGGGbRqcW4MkBfV5jXoTLMqwTQsybNNwd5XmBNjRkrIJjOmlAWMyq9HL7sJmluQ4l3KOO+URFPNom5kDbGm2yqwtudXLAdbMlsrxsujq6YxnTka7q5g5wJZtr4zasm+G9XGANbOlcj0Dm9uVq03cVcwcYMt2aEZteXuG9ZnuLmxmyZxM27V5Zu3Z1l3FzAG2bAdKGlt1IySNBw7MrDZLyHN+PTNzgO1p18zas6e7ipkDbNmGAh/JoB0nkt/TtM9ExDx3YTNLJmfarr1zaUi6H/et7ipmDrDdcJykNSsc8DYAjs2wLn929zWzHiZm2q5DJQ3NpC27Axu5q5g5wHbD2sAZFf7+ueQ5J60DrJm9IiJmA1MzbNpo4LBM2nKce4qZA2xXBx1Jb+v2j0o6Cjg805o85u5rZsuZkGm7Pl51AyRtAbzXXcSsq0q7/bIuAXYQcLGkrl36kbQHcH7GNbnV24VZqeo42X2uAXYfSVXP5PIViucqzKx7Stvm6vQ6vTHATySV/r5gSdsBl1O8sSVHc4B7vV2YlWpEDdv8RMZtO7eqtytK+mfgHe7S1nDKsE2rOMAW3ghcJqm01xNK2g24keLe21zdERELvK2alWpkDdt8T8Zt2xy4sNtv5kpj+vnuztYCizNsU2l5bVANV9BBwPWS1i1hoHsfcEPm4RXgZm+n1jDzM2zTOAfYjvsH4OwuhtftgStqejBi1leLMmzTGEml3Ac7qKYraQ/g95I6cklI0nqS/ge4pCYD3XXeTs0BtnRb1q2IEfEUMCXzZp4i6YKydmo9xvX90sH+WG9e5gBbmcHAWg6wy1oP+LWkqyTtNYDgehbFE/1H1GS5JwG3eDu1hpmbYZt27Pbl7g65uwZt/AhwbRkP5koaIuk/gGuANb1pmQNs5TZ2gF2xg4FbJT0s6dOS9pE0aiUD2zBJO0s6XtJvgaeBTwKr12h5fxIRS7ydWsPkeAZ2XeD1NazlnTVp537AI5L+oxMPd0kKSYcBDwKfAYZ4szIH2CzsUsaXNmkD3z59zkiD2fPAdGA2xU3EawDjGxDaf+xt1Boo19civ5/6zfhxRQpwdbBaausnJF0M/BS4NSJ6/TCKpM2AdwEfArbxpmQtNifTdh0AfNsBtvfWSZ8m+Qtwl7dRa6BZmbbrXyWdHxF/qlEt7wWeBdavUZtHUbwl6zhghqR7gAcoXo87Oe2Y51PcErAaxSXJbShmptnEm48ZAC9l2q7DJW0aEU86wLbX9yNCLoM10ORM2zUM+J2kt3R68C1LREjS5cCHa9oXRgMHpo+Z1T/ALh1HPxgRHXsJkwNsfcwB/stlsIaalHHbNqW4V/MC4FLgvr5c4q7Ir2scYM2sWQEWYAvgFkkPALdRPIM0nWLu2hnp78yluOoyISJmOsA2x7cjYqrLYA01OfP2jQBOTp+XJE0GXqS4x57054I0GE8FpqU/HwXujIjZXW7vdWlnNspdy6wdImK+pPkUZzxztVP6vJrFkq4GvhoRv3OArbdFwFddBmvwwDtH0nTqMe3RqD4Gw3mSrgD+MyLu6OKO7BKK6arMrD2mUTywXmeDgUOAQyT9HPhYRPzVSY5BXte1cGlETHAZrOGebehyDQcOB26XdEN/563uhwvI893oZlaeyQ1bnncDd0na2gG2fhYAZ7oM1gIPt2AZ9wNuknR82T8UEY8Ct7pbmTnA1twGwM8kDXeArZcvRcTjLoO1wAMtWc7BwNckvacLv/VNdyuzVnmuocu1A3C0A2x9PAt80WWwlniwRcsawNcllX3P78/Ie4YHM+usJm/vJ/Z8vbcDbN5OiYiXXQZriftbtrzjKHmqq4iYD3zeXcusNf7S4GXblOLlJQ6wmbsevzbWWiQiJtHcy18rc0zPMwol+TbwZ/cws1Zo+i2HBznA5m068EG/dcta6OqWLe8mwPYlHxgsBD7jrmXWCo81fPn2coDN24cjYqLLYC10ZQuX+S1d+I0f0p6H5MxaKyJepHiJSlPt4ACbrwsj4icug7XUbyneZtUmO5X9AxGxBDgJzwtr1gYPNXjZxksa6QCbn0fTTsaszWcP7m7ZYu/QpdpeB1zkXmbWeHc1eTdBMS+sA2xGpgHvjIg5LoW13I9atrwbdPG3Tgaedhczc4CtsdUdYPMxDzgsIp5wKcy4GGjTgdw4SYO78UMR8RIlT91lZg6wJVvNATYPAo6KiNtcCjOIiJlAm+4DH0w6o9Cl+l4BfM89zayxY+gzNHs+WJ+BzcSpEfEjl8FsGRe2bHmHdfn3jgXudTcza6wmz+jih7gycE5EnOMymC0rIu4AfucAW1p95wHvBqa4tzWK3KbatqnTrmjwsvkWgoqdHRGfdBlesdjLW7lFmbXnFGCJ+0NpIXYC8L4Wbnu5WlLHflTDcQVgYQvqdAPNfZZgsQNsteH1VJch+wGlbcubVZsi4kHgBy3pD/MrqvHVgMeiPCzwuOI2dXDbngP8oqHbyssOsNU4zeHVAdaDeq/9P2CGA2ypIfZLwBc9BDnAtmhcWdCSOv13Q7eV2Q6w3d9g/jkivuBSOMB6R9PrcPUM8KEW9IXZFdf5U8CXPQzVfvvLMZjleAvB9JaMv9fTzNkIHGC76EXg4Ij4nkvx6h2yRWZ5HfQ6XP0c+K8G94Xn0qteq3YK8N0WbHtLyPNtb3MbGsxyvIIyNcM2zSxh7FRDD0yzv4Xg+YYU+g/A7hFxg4+IX1Xbnoae6nXQJ5+gua+YnZzJgYKAY2juZcelzgQuz6xN89NLJpo2rojiBE5uXsiwTdNK+t7vABMbtg1nfwb27ZkeTfbFJcCeEfHnzNqV46sk2xZgp7hNfQpX84CDgHsa2Bf+mFGdF0fEv1Dce9zEqYa+B3yWEs52ZRI8czvxMzMicrxd6k9tGX8jYj7wuSYe9OccYB9OIbaOl5anA++NiH9KbxXyxlvPNpXp8QzPSMzKuWARMQN4K82bgP+BDGv9eeAIitdcN8X3Kd56uCTD8aZTrxG/33271weNuU3R90iJ330RzZlXe9rSXJX1PbARcTuwN/U6/X0N8LqIyPlVmI9k1p6JGZ6lLtstmbXnhnQJmczHhBnAwRRzHDbFPZnW+ifAAWRyi8MAfQf4lx73Gj+UWfuu69D33JrZcl2fad+eAzyWUZMWAneUuLwC/pVmzOjyysFe9g9xRcT9wBuAmzNv6oR0xuKg9OR0zq4ir/tgr6NlUmCf6HXQr9pNAw4EPkP9X3TwEnB7xrW+A9gRuLSm9RXwaeDong/KRcTT5PV09jUdPDmR0wFHzq8zzemM5O0pVJe5LT8FHEJ6AKrG7q5NgE2FfwHYD/gA5d3o3F+z045024i4tCZnsaaR1xnAi2inizNpx6y6BZR0r+YZwLvI84GM3roi03sElxkvIuII4O/J8+HDlVkAfDAizlzJuHxZJu28nw6dfUshPZeH8G6JiLsy7h85TfL/7S5ty3cC7yS/e8D74s5aBdhUeEXExelswEVUOPF3MgX4ArB5RJwREXNr1glymVrj7oi4lXb6Fnm8+vG76dJ87UTEr4EKeloTAAAH80lEQVStgfOo5ytRz69RrX/K/52Nzf1A/RngzWmfsdJ+Tx5n8D/f4RMfF2WyXF/MvD/fBDyYQVOmAD/t4nJfS3FV++EajpeLgN/WLsD2KP7kiDga2BQ4i+5P0fEAcBSwUUScFhG1nO4rIv6XPO4jPKel4XXpZcxfZTAgnFfzOs6IiI8DuwM31qjpN0XEbTWr9XPpbOxuPXckmbkK2OW1Dowj4lGqPwv3YKfbEBFPAj+veLluJ+/bB5Y6OYOw/59ppoBubsdPAHtSnMiq00uEro6IZa8CSXqz8jOsN0sjaaikgyRdKOm5EtqxRNKdkk6T9LomBShJG0iaWOE6/hktl9bB9ArXwecaWNPXS7pY0iLla56k7RpQ6/0k3ZxJTSdLOkbSoD60fyNJUytq78uSti9pvawjaVKFy7VDjfrwGRX22ZslDa54+beRdHnKOrl7y4oWoLYBdgXLsoWkIyV9TdK1kp5IO4veelbSlZLOSt+zXsMD1E6Snq9g/f5G0moYkt4o6cUK1sH/SBra4Lpum8aBSZmNbQslvadhtd4x1XpaBfWcLenM/o4nkvaSNKXLbZ4iaY+S18mWkp7s8nLNlLRvDfvvCRUc8F4nafWMarCNpG+m7SlHv1hZwxsTYF9l5ayXwtqukg6UdIikw9IZhJ0lbZpTZ+pyx11H0o+7dAQ2S9InJQ1xdF1mHWwu6eouniE5pS9nqmpe28FpjLuopKs0ffGspLc1uNbDJB0h6TJJM0qu5QuSviBpfAfavYmkX3Rh/S9JVwfW7dL6GCXpgnTQVLZfStqwxn13V0k3dKFO0yUdX/WZ11epw0hJ70mZYFYmefAeSWu1NsBarzruzpK+nY6iO22CpNO7NXDXtP6RDq5+JWlBSZdYP9uJHX7N67ydpI9IujQFym54VNKJktZoUZ0HS9oj3X51fYfGleclXZJOPgwroc27SvpGCsed9JSkL0vasqJ1sYGkz0t6rMPLNSmdtXtdg/rtvpK+1eFbuxZJulXSsXW68ihpSLpCeFI6QHmqyznwJUmfWVnNYmmABa7NrHbDu31js73SaVeheIHEG4EdgO2AbYDhvfyKWRRvmnoEuA+4JiIedmX7duaEYq7TfSie/N4BWKcPXzE/rYNHgd+n7fu+nnNh2iu1XgvYPvXz7YHNgHXTZxzQn9ss5lLMV3gLcFVE3OxKF1fCKGaN2BrYChgPjEyfNYDVgSHAHIo5cicBT1E87PR74NFuTFUoKdJ2txewbfqMB8amT6zgP1tM8UT5CxSzIDyaxsDbIuLxjNbBZmm5dkx9fsPUz8cBKzorqB7LNYniLVYPU0xn9FAdpo7s7wFYjz6wC7BRqtX6wKhX2e6fS5+Jqd8+BNxc15leVlCX1VMe2A7YBNggbRsbpT/XGsDXL0z7rd8DlwNXRsRK5611gLW+bMxrAKOB1YBV059QzIW7gOIVus+VPSFzyw8slu5oVl3ugEIUb1mZDUzO9BXGda37uNTX16CYuWUksMpyf21Bqv1M4IWIeMmVa/x42DPEzGhKkJM0ukdAn9HUgNqBOg1JB1wA873fe6UuQ4ExKciOSZ+hPQ5Oh7HsNKhTenyejIgFvf0tB1gzMzMzq5VBLoGZmZmZOcCamZmZmTnAmpmZmZk5wJqZmZmZA6yZmZmZmQOsmZmZmZkDrJmZmZk5wJqZmZmZOcCamZmZmTnAmpmZmZkDrJmZmZmZA6yZmZmZmQOsmZmZmTnAmpmZmZk5wJqZmZmZOcCamZmZmQOsmZmZmZkDrJmZmZmZA6yZmZmZOcCamZmZmTnAmpmZmZk5wJqZmZmZA6yZmZmZmQOsmZmZmZkDrJmZmZk5wJqZmZmZOcCamZmZmTnAmpmZmVnDAqwybJu8eszMzMxsZQF2cYZtW+TVY2ZmZmYrC7ALM2vX4ohY4tVjZmZmZnUJsAu9aszMzMysTgF2gVeNmZmZmb1agJ2RWbte9KoxMzMzs1cLsC9k1q6pXjVmZmZmttIAGxFzgZcyatfzXjVmZmZmttIAm9yfUbvu8KoxMzMzs9cKsDdk1K7rvWrMrM4kLfMxM7NyAuwVmbRpEnCXV42Z1Tm89ub/MzOz/hmy9B8i4k5JtwF7Vdym/4oIzwNrXQsAEeHimlW8HXd6O+zr7/RnfBlIm1f0e52qQbdqXGX9zAYt97+/VHF75gPf9mqxtu/gfenZrLvbWw7bah23907Uz6wTAfZXVPsA1TkR8YJXi3lHata+/p/jd3V7G67LGNDJ8crjnvVHrKAjbQXcBozpclvuAPaLCL+Fy7o6yOVyGauKy37mdZnTdlzFJfSqxpUy61B2/+t0zbxdWH8MWkFHehzYH5jYxXbcBBzi8GpmjTgz4B1y62szkJDnM5Jm/QiwaYB5GNge+DLwcom/PwU4EXhzRMz06rD+7hAH8jHrVr+08oNbp8JfJ8aMJobY12qXx1zr2hjbi846CjgCOITizOyaA/zNqcC1wG+An0bEPK8GG8jA2ZQB0ZedzUG1+5fPB7rd9SbQDSSg9qceZY4lnVx3kjy+WXkBdgUdbmNgG2AzYBywNrAaMHK5vzqb4uztFOAF4C/AHyLiaZfdHPAcYM3bcxMCbH9DXV/OsPYnGHY7wHqcsuwDrJkD3orblPuDF739zbJ+r6xadWv9L1+rbvxuN/pEN/vAQENfN4Nkp0Ljyr6rr7cIdCJQNzHAVjWG1SW0N7k+/x9pqL1OaJJRvwAAAABJRU5ErkJggg==\" alt=\"Pritunl Cloud\"/>\n\t\t\t\t\t<OrganizationSelect hidden={!Constants.user}/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"bp5-navbar-group bp5-align-right\" style={css.navGroup}>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-dashboard\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/instances\"\n\t\t\t\t\t>\n\t\t\t\t\t\tInstances\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-server\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/pods\"\n\t\t\t\t\t>\n\t\t\t\t\t\tPods\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-floppy-disk\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/disks\"\n\t\t\t\t\t>\n\t\t\t\t\t\tDisks\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-compressed\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/images\"\n\t\t\t\t\t>\n\t\t\t\t\t\tImages\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-shield\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/firewalls\"\n\t\t\t\t\t>\n\t\t\t\t\t\tFirewalls\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-layout-auto\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/vpcs\"\n\t\t\t\t\t>\n\t\t\t\t\t\tVPCs\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-map-marker\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/domains\"\n\t\t\t\t\t>\n\t\t\t\t\t\tDomains\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-random\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/balancers\"\n\t\t\t\t\t>\n\t\t\t\t\t\tLoad Balancers\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-control\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/pools\"\n\t\t\t\t\t>\n\t\t\t\t\t\tDisk Pools\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-ip-address\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/blocks\"\n\t\t\t\t\t>\n\t\t\t\t\t\tIP Blocks\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-layers\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/nodes\"\n\t\t\t\t\t>\n\t\t\t\t\t\tNodes\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-zoom-to-fit\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/shapes\"\n\t\t\t\t\t>\n\t\t\t\t\t\tShapes\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-map-marker\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/plans\"\n\t\t\t\t\t>\n\t\t\t\t\t\tPlans\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-people\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/organizations\"\n\t\t\t\t\t>\n\t\t\t\t\t\tOrganizations\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-people\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/users\"\n\t\t\t\t\t>\n\t\t\t\t\t\tUsers\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-filter\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/policies\"\n\t\t\t\t\t>\n\t\t\t\t\t\tPolicies\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-endorsed\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/certificates\"\n\t\t\t\t\t>\n\t\t\t\t\t\tCertificates\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-key\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/secrets\"\n\t\t\t\t\t>\n\t\t\t\t\t\tSecrets\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-office\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/authorities\"\n\t\t\t\t\t>\n\t\t\t\t\t\tAuthorities\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-database\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/storages\"\n\t\t\t\t\t>\n\t\t\t\t\t\tStorages\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-cloud\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/datacenters\"\n\t\t\t\t\t>\n\t\t\t\t\t\tDatacenters\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-layout-circle\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/zones\"\n\t\t\t\t\t>\n\t\t\t\t\t\tZones\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-notifications\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tto=\"/alerts\"\n\t\t\t\t\t>\n\t\t\t\t\t\tAlerts\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-history\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/logs\"\n\t\t\t\t\t>\n\t\t\t\t\t\tLogs\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-settings\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tto=\"/settings\"\n\t\t\t\t\t>\n\t\t\t\t\t\tSettings\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<RouterLink\n\t\t\t\t\t\tto=\"/subscription\"\n\t\t\t\t\t\tstyle={css.sub}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-credit-card\"\n\t\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tSubscriptionActions.sync(true);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>Subscription</button>\n\t\t\t\t\t</RouterLink>\n\t\t\t\t\t<a\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-comment\"\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\thref=\"https://forum.pritunl.com\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t>Community Forum</a>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-refresh\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tdisabled: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tRouter.refresh(() => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdisabled: false,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}}\n\t\t\t\t\t>Refresh</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-log-out\"\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\twindow.location.href = '/logout';\n\t\t\t\t\t\t}}\n\t\t\t\t\t>Logout</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-moon\"\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tTheme.toggle(evt.shiftKey);\n\t\t\t\t\t\t\tTheme.save();\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<LoadingBar intent=\"primary\"/>\n\t\t\t</nav>\n\t\t\t<div style={css.content}>\n\t\t\t\t<RouterRoutes>\n\t\t\t\t\t<RouterRoute path=\"/\" render={() => (\n\t\t\t\t\t\t<Instances/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/reload\" render={() => (\n\t\t\t\t\t\t<RouterRedirect to=\"/\"/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/instances\" render={() => (\n\t\t\t\t\t\t<Instances/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/pods\" render={() => (\n\t\t\t\t\t\t<Pods/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/disks\" render={() => (\n\t\t\t\t\t\t<Disks/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/images\" render={() => (\n\t\t\t\t\t\t<Images/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/firewalls\" render={() => (\n\t\t\t\t\t\t<Firewalls/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/vpcs\" render={() => (\n\t\t\t\t\t\t<Vpcs/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/domains\" render={() => (\n\t\t\t\t\t\t<Domains/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/balancers\" render={() => (\n\t\t\t\t\t\t<Balancers/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/pools\" render={() => (\n\t\t\t\t\t\t<Pools/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/blocks\" render={() => (\n\t\t\t\t\t\t<Blocks/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/nodes\" render={() => (\n\t\t\t\t\t\t<Nodes/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/shapes\" render={() => (\n\t\t\t\t\t\t<Shapes/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/plans\" render={() => (\n\t\t\t\t\t\t<Plans/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/organizations\" render={() => (\n\t\t\t\t\t\t<Organizations/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/users\" render={() => (\n\t\t\t\t\t\t<Users/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/user\" render={() => (\n\t\t\t\t\t\t<UserDetailed/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/user/:userId\" render={(data) => (\n\t\t\t\t\t\t<UserDetailed userId={data.params.userId}/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/policies\" render={() => (\n\t\t\t\t\t\t<Policies/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/certificates\" render={() => (\n\t\t\t\t\t\t<Certificates/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/secrets\" render={() => (\n\t\t\t\t\t\t<Secrets/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/authorities\" render={() => (\n\t\t\t\t\t\t<Authorities/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/storages\" render={() => (\n\t\t\t\t\t\t<Storages/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/datacenters\" render={() => (\n\t\t\t\t\t\t<Datacenters/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/zones\" render={() => (\n\t\t\t\t\t\t<Zones/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/alerts\" render={() => (\n\t\t\t\t\t\t<Alerts/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/logs\" render={() => (\n\t\t\t\t\t\t<Logs/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/settings\" render={() => (\n\t\t\t\t\t\t<Settings/>\n\t\t\t\t\t)}/>\n\t\t\t\t\t<RouterRoute path=\"/subscription\" render={() => (\n\t\t\t\t\t\t<Subscription/>\n\t\t\t\t\t)}/>\n\t\t\t\t</RouterRoutes>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/MarkdownMemo.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from \"react\"\nimport Markdown from 'react-markdown';\nimport hljs from \"highlight.js/lib/core\";\n\ninterface Props {\n\tvalue: string\n}\n\nconst langRe = /^language-(.+)$/\nconst codeBlockRe = /^\\{([^}]+)\\}?$/;\n\nfunction parseCodeBlockHeader(input: string): Record<string, string> {\n  const attrs: Record<string, string> = {};\n\n  const matches = input.match(codeBlockRe);\n  if (!matches) {\n    return attrs;\n  }\n\n  const attrPairs = matches[1].split(\",\");\n  for (let pair of attrPairs) {\n    pair = pair.trim();\n\n    const keyValue = pair.split(\"=\", 2);\n    if (keyValue.length === 2) {\n      const key = keyValue[0].trim();\n      const value = keyValue[1].trim().replace(/^\"|\"$/g, \"\");\n      attrs[key] = value;\n    }\n  }\n\n\treturn attrs;\n}\n\nconst MarkdownWrap = React.memo<Props>((props) => {\n  return <Markdown\n\t\tchildren={props.value}\n\t\tcomponents={{\n\t\t\tcode(args) {\n\t\t\t\tlet {children, className, node, ...rest} = args\n\t\t\t\tlet match = (className || \"\").match(langRe)\n\n\t\t\t\tlet phase = \"\"\n\t\t\t\tif (node && node.data) {\n\t\t\t\t\tlet nodeData = node.data as any\n\t\t\t\t\tif (nodeData && nodeData.meta) {\n\t\t\t\t\t\tlet metaAttrs = parseCodeBlockHeader(nodeData.meta)\n\t\t\t\t\t\tphase = metaAttrs[\"phase\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (match && !hljs.getLanguage(match[1])) {\n\t\t\t\t\tclassName = \"language-plaintext\"\n\t\t\t\t}\n\n\t\t\t\tif (phase === \"reboot\") {\n\t\t\t\t\tclassName += \" intent-secondary\"\n\t\t\t\t} else if (phase === \"reload\") {\n\t\t\t\t\tclassName += \" intent-primary\"\n\t\t\t\t}\n\n\t\t\t\tconst codeRef = React.useRef<HTMLElement>(null);\n\t\t\t\tReact.useEffect(() => {\n\t\t\t\t\t\tif (codeRef.current) {\n\t\t\t\t\t\t\t\thljs.highlightElement(codeRef.current);\n\t\t\t\t\t\t}\n\t\t\t\t}, [children]);\n\n\t\t\t\tlet elem = <code ref={codeRef} {...rest} className={className}>\n\t\t\t\t\t{children}\n\t\t\t\t</code>\n\n\t\t\t\treturn elem\n\t\t\t}\n\t\t}}\n\t/>\n});\n\nexport default class MarkdownMemo extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context)\n\t\tthis.state = {\n\t\t}\n\t}\n\n\trender() {\n\t\treturn <MarkdownWrap\n\t\t\tvalue={this.props.value}\n\t\t/>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Node.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as BlockTypes from '../types/BlockTypes';\nimport NodeDetailed from './NodeDetailed';\n\ninterface Props {\n\tnode: NodeTypes.NodeRo;\n\tcertificates: CertificateTypes.CertificatesRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tblocks: BlockTypes.BlocksRo;\n\tselected: boolean;\n\topen: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\tupdateIcon: {\n\t\tmarginRight: '5px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '85px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n};\n\nexport default class Node extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet node = this.props.node;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<NodeDetailed\n\t\t\t\t\tnode={this.props.node}\n\t\t\t\t\tcertificates={this.props.certificates}\n\t\t\t\t\tdatacenters={this.props.datacenters}\n\t\t\t\t\tzones={this.props.zones}\n\t\t\t\t\tblocks={this.props.blocks}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet active = node.requests_min !== 0 || node.memory !== 0 ||\n\t\t\tnode.load1 !== 0 || node.load5 !== 0 || node.load15 !== 0;\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\t\tif (!active) {\n\t\t\tcardStyle.opacity = 0.6;\n\t\t}\n\n\t\tlet updateElm: JSX.Element\n\t\tlet updateSev = 0\n\t\tlet updateClass = \"\"\n\t\tnode.updates?.forEach((update) => {\n\t\t\tswitch (update.severity) {\n\t\t\t\tcase \"moderate\":\n\t\t\t\t\tif (updateSev < 1) {\n\t\t\t\t\t\tupdateSev = 1\n\t\t\t\t\t\tupdateClass = \"bp5-icon-issue bp5-intent-primary\"\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase \"important\":\n\t\t\t\t\tif (updateSev < 2) {\n\t\t\t\t\t\tupdateSev = 2\n\t\t\t\t\t\tupdateClass = \"bp5-icon-issue bp5-intent-warning\"\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase \"critical\":\n\t\t\t\t\tupdateSev = 3\n\t\t\t\t\tupdateClass = \"bp5-icon-issue bp5-intent-danger\"\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t})\n\n\t\tif (updateClass) {\n\t\t\tupdateElm = <Blueprint.Tooltip\n\t\t\t\tcontent=\"Updates available\"\n\t\t\t\topenOnTargetFocus={false}\n\t\t\t\tcompact={true}\n\t\t\t\trenderTarget={({isOpen, ...tooltipProps}) => (\n\t\t\t\t\t<span\n\t\t\t\t\t\t{...tooltipProps}\n\t\t\t\t\t\tstyle={css.updateIcon}\n\t\t\t\t\t\tclassName={\"bp5-icon-standard \" + updateClass}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t/>\n\t\t}\n\n\t\tlet memoryStyle: React.CSSProperties = {\n\t\t\twidth: (node.memory || 0) + '%',\n\t\t};\n\t\tlet hugepagesStyle: React.CSSProperties = {\n\t\t\twidth: (node.hugepages_used || 0) + '%',\n\t\t\tbackgroundColor: '#7207d4',\n\t\t};\n\t\tlet load1Style: React.CSSProperties = {\n\t\t\twidth: (node.load1 || 0) + '%',\n\t\t};\n\t\tlet load5Style: React.CSSProperties = {\n\t\t\twidth: (node.load5 || 0) + '%',\n\t\t};\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{updateElm}\n\t\t\t\t\t\t{node.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t{node.requests_min + '/min'}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t{MiscUtils.formatDateShortTime(node.timestamp) || 'Inactive'}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.bars}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-progress-bar bp5-no-stripes bp5-intent-success\"\n\t\t\t\t\tstyle={css.bar}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bp5-progress-meter\" style={load1Style}/>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-progress-bar bp5-no-stripes bp5-intent-warning\"\n\t\t\t\t\tstyle={css.bar}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bp5-progress-meter\" style={load5Style}/>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-progress-bar bp5-no-stripes bp5-intent-primary\"\n\t\t\t\t\tstyle={css.barLast}\n\t\t\t\t\thidden={node.hugepages}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bp5-progress-meter\" style={memoryStyle}/>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-progress-bar bp5-no-stripes\"\n\t\t\t\t\tstyle={css.barLast}\n\t\t\t\t\thidden={!node.hugepages}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bp5-progress-meter\" style={hugepagesStyle}/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/NodeBlock.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as BlockTypes from '../types/BlockTypes';\n\ninterface Props {\n\tipv6?: boolean;\n\tinterfaces?: NodeTypes.Interface[];\n\tblocks: BlockTypes.BlocksRo;\n\tblock: NodeTypes.BlockAttachment;\n\tonChange: (state: NodeTypes.BlockAttachment) => void;\n\tonAdd: (prepend: boolean) => void;\n\tonRemove: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '400px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tsourceGroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '219px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tcontrol: {\n\t\tmarginTop: '0px',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tmarginTop: '0px',\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tportBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tother: {\n\t\tflex: '0 1 auto',\n\t\twidth: '52px',\n\t\tborderRadius: '0 3px 3px 0',\n\t} as React.CSSProperties,\n};\n\nexport default class NodeBlock extends React.Component<Props, {}> {\n\tclone(): NodeTypes.BlockAttachment {\n\t\treturn {\n\t\t\t...this.props.block,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet block = this.props.block;\n\n\t\tlet ifaceMatch = false;\n\t\tlet ifacesSelect: JSX.Element[] = [];\n\t\tfor (let iface of (this.props.interfaces || [])) {\n\t\t\tif (block.interface === iface.name) {\n\t\t\t\tifaceMatch = true;\n\t\t\t}\n\n\t\t\tifacesSelect.push(\n\t\t\t\t<option key={iface.name} value={iface.name}>\n\t\t\t\t\t{iface.name + (iface.address ? (` (${iface.address})`) : \"\")}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tif (!ifaceMatch) {\n\t\t\tifacesSelect.push(\n\t\t\t\t<option key={block.interface} value={block.interface}>\n\t\t\t\t\t{block.interface}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet blocksSelect: JSX.Element[] = [];\n\t\tfor (let blck of (this.props.blocks || [])) {\n\t\t\tif (!this.props.ipv6 && blck.type === 'ipv6') {\n\t\t\t\tcontinue;\n\t\t\t} else if (this.props.ipv6 && blck.type !== 'ipv6') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tblocksSelect.push(\n\t\t\t\t<option key={blck.id} value={blck.id}>\n\t\t\t\t\t{blck.name}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tif (blocksSelect.length === 0) {\n\t\t\tblocksSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Blocks</option>);\n\t\t}\n\n\t\treturn <div>\n\t\t\t<div className=\"bp5-control-group\" style={css.group}>\n\t\t\t\t<div className=\"bp5-select\" style={css.protocol}>\n\t\t\t\t\t<select\n\t\t\t\t\t\tvalue={block.interface}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.interface = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{ifacesSelect}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"bp5-select\" style={css.protocol}>\n\t\t\t\t\t<select\n\t\t\t\t\t\tvalue={block.block}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.block = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{blocksSelect}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tstyle={css.control}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tstyle={css.control}\n\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\tthis.props.onAdd(evt.shiftKey);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/NodeDeploy.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as NodeActions from '../actions/NodeActions';\nimport * as BlockTypes from '../types/BlockTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Theme from '../Theme';\nimport Help from './Help';\nimport PageInput from './PageInput';\nimport PageInputButton from './PageInputButton';\nimport PageSwitch from './PageSwitch';\nimport PageSelect from './PageSelect';\nimport CertificatesStore from \"../stores/CertificatesStore\";\nimport NodeBlock from \"./NodeBlock\";\n\ninterface Props {\n\thidden?: boolean;\n\tdisabled?: boolean;\n\tnode: NodeTypes.NodeRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tblocks: BlockTypes.BlocksRo;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tmessage: string;\n\tprovider: string;\n\tdatacenter: string;\n\tzone: string;\n\tfirewall: boolean;\n\tinternalIface: string;\n\texternalIface: string;\n\tnetwork: string;\n\tgateway: string;\n\tnetmask: string;\n\tsubnets: string[];\n\taddSubnet: string,\n\tpopover: boolean;\n}\n\nconst css = {\n\tbox: {\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tcallout: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n\tpopover: {\n\t\twidth: '230px',\n\t} as React.CSSProperties,\n\tpopoverTarget: {\n\t\ttop: '9px',\n\t\tleft: '18px',\n\t} as React.CSSProperties,\n\tdialog: {\n\t\tmaxWidth: '480px',\n\t\tmargin: '30px 20px',\n\t} as React.CSSProperties,\n\ttextarea: {\n\t\twidth: '100%',\n\t\tresize: 'none',\n\t\tfontSize: Theme.monospaceSize,\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n};\n\nexport default class NodeDeploy extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tmessage: '',\n\t\t\tprovider: '',\n\t\t\tdatacenter: '',\n\t\t\tzone: '',\n\t\t\tfirewall: true,\n\t\t\tinternalIface: '',\n\t\t\texternalIface: '',\n\t\t\tnetwork: '',\n\t\t\tgateway: '',\n\t\t\tnetmask: '',\n\t\t\tsubnets: [],\n\t\t\taddSubnet: '',\n\t\t\tpopover: false,\n\t\t};\n\t}\n\n\tifaces(): NodeTypes.Interface[] {\n\t\tlet node = this.props.node;\n\n\t\tlet dcId = node.datacenter;\n\t\tif (this.state.datacenter) {\n\t\t\tdcId = this.state.datacenter;\n\t\t}\n\n\t\tlet vxlan = false;\n\t\tfor (let dc of this.props.datacenters) {\n\t\t\tif (dc.id === dcId) {\n\t\t\t\tif (dc.network_mode === 'vxlan_vlan') {\n\t\t\t\t\tvxlan = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (vxlan) {\n\t\t\treturn NodeTypes.GetAllIfaces(node);\n\t\t} else {\n\t\t\treturn NodeTypes.GetAllIfaces(node);\n\t\t}\n\t}\n\n\tonSave = (): void => {\n\t\tlet internalIface = this.state.internalIface;\n\t\tif (!internalIface) {\n\t\t\tlet ifaces = this.ifaces();\n\t\t\tif (ifaces.length) {\n\t\t\t\tinternalIface = ifaces[0]?.name;\n\t\t\t}\n\t\t}\n\n\t\tlet externalIface = this.state.externalIface;\n\t\tif (!externalIface) {\n\t\t\tlet ifaces = this.ifaces();\n\t\t\tif (ifaces.length) {\n\t\t\t\texternalIface = ifaces[0]?.name;\n\t\t\t}\n\t\t}\n\n\t\tlet data: NodeTypes.NodeInit = {\n\t\t\tprovider: this.state.provider || 'other',\n\t\t\tzone: this.props.node.zone ? this.props.node.zone :\n\t\t\t\tthis.state.zone,\n\t\t\tfirewall: this.state.firewall,\n\t\t\tinternal_interface: internalIface,\n\t\t\texternal_interface: externalIface,\n\t\t\thost_network: this.state.network,\n\t\t\tblock_gateway: this.state.gateway,\n\t\t\tblock_netmask: this.state.netmask,\n\t\t\tblock_subnets: this.state.subnets,\n\t\t};\n\n\t\tNodeActions.init(this.props.node.id, data).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tpopover: !this.state.popover,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddSubnet = (): void => {\n\t\tif (!this.state.addSubnet) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet subnets = [\n\t\t\t...this.state.subnets,\n\t\t];\n\n\t\tlet addSubnet = this.state.addSubnet.trim();\n\t\tif (subnets.indexOf(addSubnet) === -1) {\n\t\t\tsubnets.push(addSubnet);\n\t\t}\n\n\t\tsubnets.sort();\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tsubnets: subnets,\n\t\t\taddSubnet: '',\n\t\t});\n\t}\n\n\tonRemoveSubnet = (subnet: string): void => {\n\t\tlet subnets = [\n\t\t\t...(this.state.subnets || []),\n\t\t];\n\n\t\tlet i = subnets.indexOf(subnet);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tsubnets.splice(i, 1);\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tsubnets: subnets,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet popoverElem: JSX.Element;\n\n\t\tif (this.state.popover) {\n\t\t\tlet callout = 'Initialize node, select the hosts public network interface.';\n\t\t\tlet errorMsg = '';\n\t\t\tlet errorMsgElem: JSX.Element;\n\n\t\t\tif (errorMsg) {\n\t\t\t\terrorMsgElem = <div className=\"bp5-dialog-body\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"bp5-callout bp5-intent-danger bp5-icon-ban-circle\"\n\t\t\t\t\t\tstyle={css.callout}\n\t\t\t\t\t>\n\t\t\t\t\t\t{errorMsg}\n\t\t\t\t\t</div>\n\t\t\t\t</div>;\n\t\t\t}\n\n\t\t\tlet defaultDatacenter = '';\n\t\t\tlet hasDatacenters = false;\n\t\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\t\tif (this.props.datacenters.length) {\n\t\t\t\thasDatacenters = true;\n\t\t\t\tdefaultDatacenter = this.props.datacenters[0].id;\n\t\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!hasDatacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t\t}\n\n\t\t\tlet datacenter = this.state.datacenter || defaultDatacenter;\n\t\t\tlet hasZones = false;\n\t\t\tlet zonesSelect: JSX.Element[] = [];\n\t\t\tif (this.props.zones.length) {\n\t\t\t\tzonesSelect.push(<option key=\"null\" value=\"\">Select Zone</option>);\n\n\t\t\t\tfor (let zone of this.props.zones) {\n\t\t\t\t\tif (!this.props.node.zone && zone.datacenter !== datacenter) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\thasZones = true;\n\n\t\t\t\t\tzonesSelect.push(\n\t\t\t\t\t\t<option\n\t\t\t\t\t\t\tkey={zone.id}\n\t\t\t\t\t\t\tvalue={zone.id}\n\t\t\t\t\t\t>{zone.name}</option>,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!hasZones) {\n\t\t\t\tzonesSelect = [<option key=\"null\" value=\"\">No Zones</option>];\n\t\t\t}\n\n\t\t\tlet availableIfaces = this.ifaces();\n\t\t\tlet ifacesSelect: JSX.Element[] = [];\n\t\t\tfor (let iface of (availableIfaces || [])) {\n\t\t\t\tifacesSelect.push(\n\t\t\t\t\t<option key={iface.name} value={iface.name}>\n\t\t\t\t\t\t{iface.name + (iface.address ? (\" (\" + iface.address + \")\") : \"\")}\n\t\t\t\t\t</option>,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet subnets: JSX.Element[] = [];\n\t\t\tfor (let subnet of (this.state.subnets || [])) {\n\t\t\t\tsubnets.push(\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\t\tstyle={css.item}\n\t\t\t\t\t\tkey={subnet}\n\t\t\t\t\t>\n\t\t\t\t\t\t{subnet}\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\t\tthis.onRemoveSubnet(subnet);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tpopoverElem = <Blueprint.Dialog\n\t\t\t\ttitle=\"Initialize Node\"\n\t\t\t\tstyle={css.dialog}\n\t\t\t\tisOpen={this.state.popover}\n\t\t\t\tusePortal={true}\n\t\t\t\tportalContainer={document.body}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tpopover: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{errorMsgElem}\n\t\t\t\t<div className=\"bp5-dialog-body\" hidden={!!errorMsgElem}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"bp5-callout bp5-intent-primary bp5-icon-info-sign\"\n\t\t\t\t\t\tstyle={css.callout}\n\t\t\t\t\t>\n\t\t\t\t\t\t{callout}\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Provider\"\n\t\t\t\t\t\thelp=\"Bare metal hosting provider.\"\n\t\t\t\t\t\tvalue={this.state.provider}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tprovider: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option key=\"other\" value=\"other\">Other</option>\n\t\t\t\t\t\t<option key=\"vultr\" value=\"vultr\">Vultr</option>\n\t\t\t\t\t\t<option key=\"phoenixnap\" value=\"phoenixnap\">phoenixNAP</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\thidden={!!this.props.node.zone}\n\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\thelp=\"Node datacenter, cannot be changed once set.\"\n\t\t\t\t\t\tvalue={this.state.datacenter}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tdatacenter: val,\n\t\t\t\t\t\t\t\tzone: '',\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={!!this.props.node.zone || this.state.disabled ||\n\t\t\t\t\t\t!hasZones}\n\t\t\t\t\t\tlabel=\"Zone\"\n\t\t\t\t\t\thelp=\"Node zone, cannot be changed once set.\"\n\t\t\t\t\t\tvalue={this.props.node.zone ? this.props.node.zone :\n\t\t\t\t\t\t\tthis.state.zone}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tzone: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{zonesSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !ifacesSelect.length}\n\t\t\t\t\t\thidden={this.state.provider === 'phoenixnap'}\n\t\t\t\t\t\tlabel=\"Public Network Interface\"\n\t\t\t\t\t\thelp=\"Network interface for instance public traffic.\"\n\t\t\t\t\t\tvalue={this.state.externalIface}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\texternalIface: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{ifacesSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !ifacesSelect.length}\n\t\t\t\t\t\thidden={this.state.provider !== 'phoenixnap'}\n\t\t\t\t\t\tlabel=\"Private Network Interface\"\n\t\t\t\t\t\thelp=\"Network interface for instance private VPC interface.\"\n\t\t\t\t\t\tvalue={this.state.internalIface}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tinternalIface: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{ifacesSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !ifacesSelect.length}\n\t\t\t\t\t\thidden={this.state.provider !== 'phoenixnap'}\n\t\t\t\t\t\tlabel=\"Public Network Interface\"\n\t\t\t\t\t\thelp=\"Network interface for instance public traffic.\"\n\t\t\t\t\t\tvalue={this.state.externalIface}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\texternalIface: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{ifacesSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={this.state.provider !== 'phoenixnap'}\n\t\t\t\t\t\tlabel=\"Public Gateway\"\n\t\t\t\t\t\thelp=\"Gateway address with prefix for public IP network.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter gateway\"\n\t\t\t\t\t\tvalue={this.state.gateway}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tgateway: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={true}\n\t\t\t\t\t\tlabel=\"Public Netmask\"\n\t\t\t\t\t\thelp=\"Netmask of of public IP addresses\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter netmask\"\n\t\t\t\t\t\tvalue={this.state.netmask}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tnetmask: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={this.state.provider !== 'phoenixnap'}\n\t\t\t\t\t>\n\t\t\t\t\t\tIP Addresses\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Public IP Addresses\"\n\t\t\t\t\t\t\tcontent=\"Public IP addresses that are available for instances.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{subnets}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={this.state.provider !== 'phoenixnap'}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add addresses\"\n\t\t\t\t\t\tvalue={this.state.addSubnet}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddSubnet: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddSubnet}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Host IPv4 Network\"\n\t\t\t\t\t\thelp=\"Host IPv4 network with prefix that is configured on the host to provide networking between the host and the instances. If left blank no host network will be created.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter network\"\n\t\t\t\t\t\tvalue={this.state.network}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tnetwork: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Node firewall\"\n\t\t\t\t\t\thelp=\"Configure a default firewall for the node allowing web and ssh traffic from all addresses. This should always be enabled unless an external firewall has been configured on the host system. The firewall can be modified after from the web console.\"\n\t\t\t\t\t\tchecked={this.state.firewall}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tfirewall: !this.state.firewall,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"bp5-dialog-footer\">\n\t\t\t\t\t<div className=\"bp5-dialog-footer-actions\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tpopover: !this.state.popover,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>Close</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-icon-cloud-upload bp5-intent-primary\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={this.onSave}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tInitialize Node\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</Blueprint.Dialog>;\n\t\t}\n\n\t\treturn <div hidden={this.props.hidden} style={css.box}>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-icon-cloud-upload bp5-intent-primary\"\n\t\t\t\tstyle={css.button}\n\t\t\t\thidden={true}\n\t\t\t\ttype=\"button\"\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tpopover: !this.state.popover,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\tInitialize Node\n\t\t\t</button>\n\t\t\t{popoverElem}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/NodeDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as NodeActions from '../actions/NodeActions';\nimport * as BlockTypes from '../types/BlockTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as PageInfos from './PageInfo';\nimport CompletionStore from '../stores/CompletionStore';\nimport NodeDeploy from './NodeDeploy';\nimport PageInput from './PageInput';\nimport PageSwitch from './PageSwitch';\nimport PageInputSwitch from './PageInputSwitch';\nimport PageSelect from './PageSelect';\nimport PageSelectButton from './PageSelectButton';\nimport PageInputButton from './PageInputButton';\nimport PageTextArea from './PageTextArea';\nimport PageNumInput from './PageNumInput';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport NodeBlock from './NodeBlock';\nimport NodeShare from './NodeShare';\nimport InstanceAdvDialog from './InstanceAdvDialog';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport Help from './Help';\n\ninterface Props {\n\tnode: NodeTypes.NodeRo;\n\tcertificates: CertificateTypes.CertificatesRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tblocks: BlockTypes.BlocksRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tdatacenter: string;\n\tzone: string;\n\tchanged: boolean;\n\tmessage: string;\n\tnode: NodeTypes.Node;\n\taddExternalIface: string;\n\taddInternalIface: string;\n\taddCloudSubnet: string;\n\taddCert: string;\n\taddNetworkRole: string;\n\taddDrive: string;\n\tforwardedChecked: boolean;\n\tforwardedProtoChecked: boolean;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\twordBreak: 'break-all',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\trestart: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tlabelWide: {\n\t\twidth: '100%',\n\t\tmaxWidth: '400px',\n\t} as React.CSSProperties,\n\tlabelShares: {\n\t\twidth: '100%',\n\t\tmaxWidth: '400px',\n\t\tmarginBottom: '0',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tminWidth: '90px',\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tminWidth: '120px',\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tblocks: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n\tdivider: {\n\t\twidth: '100%',\n\t\tmaxWidth: '270px',\n\t} as React.CSSProperties,\n};\n\nexport default class NodeDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tdatacenter: '',\n\t\t\tzone: '',\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tnode: null,\n\t\t\taddExternalIface: null,\n\t\t\taddInternalIface: null,\n\t\t\taddCloudSubnet: null,\n\t\t\taddCert: null,\n\t\t\taddNetworkRole: null,\n\t\t\taddDrive: null,\n\t\t\tforwardedChecked: false,\n\t\t\tforwardedProtoChecked: false,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet node: any;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tnode[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\ttoggleFirewall(): void {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tnode.firewall = !node.firewall;\n\t\tif (!node.firewall) {\n\t\t\tnode.roles = [];\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\ttoggleType(typ: string): void {\n\t\tlet node: NodeTypes.Node = this.state.node || this.props.node;\n\n\t\tlet vals = node.types;\n\n\t\tlet i = vals.indexOf(typ);\n\t\tif (i === -1) {\n\t\t\tvals.push(typ);\n\t\t} else {\n\t\t\tvals.splice(i, 1);\n\t\t}\n\n\t\tvals = vals.filter((val): boolean => {\n\t\t\treturn !!val;\n\t\t});\n\n\t\tvals.sort();\n\n\t\tthis.set('types', vals);\n\t}\n\n\tifaces(): NodeTypes.Interface[] {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet dcId = node.datacenter;\n\t\tif (this.state.datacenter) {\n\t\t\tdcId = this.state.datacenter;\n\t\t}\n\n\t\tlet vxlan = false;\n\t\tfor (let dc of this.props.datacenters) {\n\t\t\tif (dc.id === dcId) {\n\t\t\t\tif (dc.network_mode === 'vxlan_vlan') {\n\t\t\t\t\tvxlan = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (vxlan) {\n\t\t\treturn NodeTypes.GetAllIfaces(node);\n\t\t} else {\n\t\t\treturn NodeTypes.GetAllIfaces(node);\n\t\t}\n\t}\n\n\tsubnetLabel(subnetId: string): string {\n\t\tfor (let vpc of (this.props.node.available_vpcs || [])) {\n\t\t\tfor (let subnet of (vpc.subnets || [])) {\n\t\t\t\tif (subnet.id === subnetId) {\n\t\t\t\t\treturn vpc.name + ' - ' + subnet.name;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn subnetId;\n\t}\n\n\tonAddNetworkRole = (): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (!this.state.addNetworkRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(node.roles || []),\n\t\t];\n\n\t\tif (roles.indexOf(this.state.addNetworkRole) === -1) {\n\t\t\troles.push(this.state.addNetworkRole);\n\t\t}\n\n\t\troles.sort();\n\t\tnode.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonRemoveNetworkRole = (networkRole: string): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(node.roles || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(networkRole);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\t\tnode.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet node = {\n\t\t\t...this.state.node,\n\t\t};\n\n\t\tif (!this.props.node.zone) {\n\t\t\tlet zone = this.state.zone;\n\t\t\tif (!zone && this.props.datacenters.length &&\n\t\t\t\t\tthis.props.zones.length) {\n\t\t\t\tlet datacenter = this.state.datacenter ||\n\t\t\t\t\tthis.props.datacenters[0].id;\n\t\t\t\tfor (let zne of this.props.zones) {\n\t\t\t\t\tif (zne.datacenter === datacenter) {\n\t\t\t\t\t\tzone = zne.id;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (zone) {\n\t\t\t\tnode.zone = zone;\n\t\t\t}\n\t\t}\n\n\t\tNodeActions.commit(node).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tnode: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\toperation(state: string): void {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tNodeActions.operation(this.props.node.id, state).then((): void => {\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t});\n\t\t\t}, 250);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tNodeActions.remove(this.props.node.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddExternalIface = (): void => {\n\t\tlet node: NodeTypes.Node;\n\t\tlet availableIfaces = this.ifaces();\n\n\t\tif (!this.state.addExternalIface && !availableIfaces.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet index = this.state.addExternalIface || availableIfaces[0]?.name;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet ifaces = [\n\t\t\t...(node.external_interfaces || []),\n\t\t];\n\n\t\tif (ifaces.indexOf(index) === -1) {\n\t\t\tifaces.push(index);\n\t\t}\n\n\t\tifaces.sort();\n\n\t\tnode.external_interfaces = ifaces;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonRemoveExternalIface = (iface: string): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet ifaces = [\n\t\t\t...(node.external_interfaces || []),\n\t\t];\n\n\t\tlet i = ifaces.indexOf(iface);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tifaces.splice(i, 1);\n\n\t\tnode.external_interfaces = ifaces;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonAddInternalIface = (): void => {\n\t\tlet node: NodeTypes.Node;\n\t\tlet availableIfaces = this.ifaces();\n\n\t\tif (!this.state.addInternalIface && !availableIfaces.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet index = this.state.addInternalIface || availableIfaces[0]?.name;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet ifaces = [\n\t\t\t...(node.internal_interfaces || []),\n\t\t];\n\n\t\tif (ifaces.indexOf(index) === -1) {\n\t\t\tifaces.push(index);\n\t\t}\n\n\t\tifaces.sort();\n\n\t\tnode.internal_interfaces = ifaces;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonRemoveInternalIface = (iface: string): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet ifaces = [\n\t\t\t...(node.internal_interfaces || []),\n\t\t];\n\n\t\tlet i = ifaces.indexOf(iface);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tifaces.splice(i, 1);\n\n\t\tnode.internal_interfaces = ifaces;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonAddCert = (): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (!this.state.addCert && !this.props.certificates.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet certId = this.state.addCert;\n\t\tif (!certId) {\n\t\t\tfor (let certificate of this.props.certificates) {\n\t\t\t\tif (certificate.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tcertId = certificate.id;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tlet certificates = [\n\t\t\t...(node.certificates || []),\n\t\t];\n\n\t\tif (certificates.indexOf(certId) === -1) {\n\t\t\tcertificates.push(certId);\n\t\t}\n\n\t\tcertificates.sort();\n\n\t\tnode.certificates = certificates;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonRemoveCert = (certId: string): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet certificates = [\n\t\t\t...(node.certificates || []),\n\t\t];\n\n\t\tlet i = certificates.indexOf(certId);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tcertificates.splice(i, 1);\n\n\t\tnode.certificates = certificates;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tnewBlock = (ipv6: boolean): NodeTypes.BlockAttachment => {\n\t\tlet defBlock = '';\n\n\t\tfor (let block of (this.props.blocks || [])) {\n\t\t\tif ((ipv6 && block.type === 'ipv6') ||\n\t\t\t\t\t(!ipv6 && block.type === 'ipv4')) {\n\t\t\t\tdefBlock = block.id;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tinterface: NodeTypes.GetAllIfaces(this.props.node)?.[0]?.name,\n\t\t\tblock: defBlock,\n\t\t} as NodeTypes.BlockAttachment;\n\t}\n\n\tonNetworkMode = (mode: string): void => {\n\t\tlet node: any;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tif (mode === 'static' && (node.blocks || []).length === 0) {\n\t\t\tnode.blocks = [\n\t\t\t\tthis.newBlock(false),\n\t\t\t];\n\t\t}\n\n\t\tnode.network_mode = mode;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonNetworkMode6 = (mode: string): void => {\n\t\tlet node: any;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tif (mode === 'static' && (node.blocks6 || []).length === 0) {\n\t\t\tnode.blocks6 = [\n\t\t\t\tthis.newBlock(true),\n\t\t\t];\n\t\t}\n\n\t\tnode.network_mode6 = mode;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonAddCloudSubnet = (): void => {\n\t\tlet node: NodeTypes.Node;\n\t\tlet availabeVpcs = this.props.node.available_vpcs || [];\n\n\t\tif (!this.state.addCloudSubnet && !availabeVpcs.length &&\n\t\t\t\t!availabeVpcs[0].subnets.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet addCloudSubnet = this.state.addCloudSubnet;\n\t\tif (!addCloudSubnet) {\n\t\t\taddCloudSubnet = availabeVpcs[0].subnets[0].id;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet nodeCloudSubnets = [\n\t\t\t...(node.cloud_subnets || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < nodeCloudSubnets.length; i++) {\n\t\t\tif (nodeCloudSubnets[i] === addCloudSubnet) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (index === -1) {\n\t\t\tnodeCloudSubnets.push(addCloudSubnet);\n\t\t}\n\n\t\tnode.cloud_subnets = nodeCloudSubnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonRemoveCloudSubnet = (device: string): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet nodeCloudSubnets = [\n\t\t\t...(node.cloud_subnets || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < nodeCloudSubnets.length; i++) {\n\t\t\tif (nodeCloudSubnets[i] === device) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (index === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tnodeCloudSubnets.splice(index, 1);\n\t\tnode.cloud_subnets = nodeCloudSubnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonAddBlock = (i: number, prepend: boolean): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet blocks = [\n\t\t\t...node.blocks,\n\t\t];\n\n\t\tblocks.splice(prepend ? i : i + 1, 0, this.newBlock(false));\n\t\tnode.blocks = blocks;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonChangeBlock(i: number, block: NodeTypes.BlockAttachment): void {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet blocks = [\n\t\t\t...node.blocks,\n\t\t];\n\n\t\tblocks[i] = block;\n\n\t\tnode.blocks = blocks;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonRemoveBlock(i: number): void {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet blocks = [\n\t\t\t...node.blocks,\n\t\t];\n\n\t\tblocks.splice(i, 1);\n\n\t\tif (!blocks.length) {\n\t\t\tblocks = [\n\t\t\t\tthis.newBlock(false),\n\t\t\t];\n\t\t}\n\n\t\tnode.blocks = blocks;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonAddBlock6 = (i: number, prepend: boolean): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet blocks = [\n\t\t\t...node.blocks6,\n\t\t];\n\n\t\tblocks.splice(prepend ? i : i + 1, 0, this.newBlock(true));\n\t\tnode.blocks6 = blocks;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonChangeBlock6(i: number, block: NodeTypes.BlockAttachment): void {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet blocks = [\n\t\t\t...node.blocks6,\n\t\t];\n\n\t\tblocks[i] = block;\n\n\t\tnode.blocks6 = blocks;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonRemoveBlock6(i: number): void {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet blocks = [\n\t\t\t...node.blocks6,\n\t\t];\n\n\t\tblocks.splice(i, 1);\n\n\t\tif (!blocks.length) {\n\t\t\tblocks = [\n\t\t\t\tthis.newBlock(true),\n\t\t\t];\n\t\t}\n\n\t\tnode.blocks6 = blocks;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonAddShare = (i: number, prepend: boolean): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet shares = [\n\t\t\t...(node.shares || []),\n\t\t];\n\t\tif (!shares.length) {\n\t\t\tshares.push({})\n\t\t}\n\n\t\tshares.splice(prepend ? i : i + 1, 0, {});\n\t\tnode.shares = shares;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonChangeShare(i: number, block: NodeTypes.Share): void {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet shares = [\n\t\t\t...(node.shares || []),\n\t\t];\n\t\tif (!shares.length) {\n\t\t\tshares.push({})\n\t\t}\n\n\t\tshares[i] = block;\n\n\t\tnode.shares = shares;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonRemoveShare(i: number): void {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet shares = [\n\t\t\t...(node.shares || []),\n\t\t];\n\t\tif (!shares.length) {\n\t\t\tshares.push({})\n\t\t}\n\n\t\tshares.splice(i, 1);\n\n\t\tif (!shares.length) {\n\t\t\tshares = [\n\t\t\t\t{},\n\t\t\t];\n\t\t}\n\n\t\tnode.shares = shares;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonAddDrive = (): void => {\n\t\tlet node: NodeTypes.Node;\n\t\tlet availabeDrives = this.props.node.available_drives || [];\n\n\t\tif (!this.state.addDrive && !availabeDrives.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet addDrive = this.state.addDrive;\n\t\tif (!addDrive) {\n\t\t\taddDrive = availabeDrives[0].id;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet instanceDrives = [\n\t\t\t...(node.instance_drives || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < instanceDrives.length; i++) {\n\t\t\tlet dev = instanceDrives[i];\n\t\t\tif (dev.id === addDrive) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (index === -1) {\n\t\t\tinstanceDrives.push({\n\t\t\t\tid: addDrive,\n\t\t\t});\n\t\t}\n\n\t\tnode.instance_drives = instanceDrives;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\tonRemoveDrive = (device: string): void => {\n\t\tlet node: NodeTypes.Node;\n\n\t\tif (this.state.changed) {\n\t\t\tnode = {\n\t\t\t\t...this.state.node,\n\t\t\t};\n\t\t} else {\n\t\t\tnode = {\n\t\t\t\t...this.props.node,\n\t\t\t};\n\t\t}\n\n\t\tlet instanceDrives = [\n\t\t\t...(node.instance_drives || []),\n\t\t];\n\n\t\tlet index = -1;\n\t\tfor (let i = 0; i < instanceDrives.length; i++) {\n\t\t\tlet dev = instanceDrives[i];\n\t\t\tif (dev.id === device) {\n\t\t\t\tindex = i;\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (index === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tinstanceDrives.splice(index, 1);\n\t\tnode.instance_drives = instanceDrives;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tnode: node,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet node: NodeTypes.Node = this.state.node || this.props.node;\n\t\tlet active = node.requests_min !== 0 || node.memory !== 0 ||\n\t\t\t\tnode.load1 !== 0 || node.load5 !== 0 || node.load15 !== 0;\n\t\tlet types = node.types || [];\n\n\t\tlet publicIps: any = this.props.node.public_ips;\n\t\tif (!publicIps || !publicIps.length) {\n\t\t\tpublicIps = 'None';\n\t\t}\n\n\t\tlet publicIps6: any = this.props.node.public_ips6;\n\t\tif (!publicIps6 || !publicIps6.length) {\n\t\t\tpublicIps6 = 'None';\n\t\t}\n\n\t\tlet privateIps: string[] = [];\n\t\tObject.values(this.props.node?.private_ips).forEach((val: string) => {\n\t\t\tprivateIps.push(val)\n\t\t})\n\n\t\tif (!privateIps.length) {\n\t\t\tprivateIps = ['None'];\n\t\t}\n\n\t\tlet resourceBars: PageInfos.Bar[] = [\n\t\t\t{\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-success',\n\t\t\t\tlabel: 'Load1',\n\t\t\t\tvalue: this.props.node.load1 || 0,\n\t\t\t},\n\t\t\t{\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-warning',\n\t\t\t\tlabel: 'Load5',\n\t\t\t\tvalue: this.props.node.load5 || 0,\n\t\t\t},\n\t\t\t{\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-danger',\n\t\t\t\tlabel: 'Load15',\n\t\t\t\tvalue: this.props.node.load15 || 0,\n\t\t\t},\n\t\t\t{\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-primary',\n\t\t\t\tlabel: 'Memory',\n\t\t\t\tvalue: this.props.node.memory || 0,\n\t\t\t},\n\t\t];\n\t\tif (this.props.node.hugepages) {\n\t\t\tresourceBars.push({\n\t\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-primary',\n\t\t\t\tlabel: 'HugePages',\n\t\t\t\tvalue: this.props.node.hugepages_used || 0,\n\t\t\t\tcolor: '#7207d4',\n\t\t\t});\n\t\t}\n\n\t\tlet externalIfaces: JSX.Element[] = [];\n\t\tfor (let iface of (node.external_interfaces || [])) {\n\t\t\texternalIfaces.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={iface}\n\t\t\t\t>\n\t\t\t\t\t{iface}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveExternalIface(iface);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet internalIfaces: JSX.Element[] = [];\n\t\tfor (let iface of (node.internal_interfaces || [])) {\n\t\t\tinternalIfaces.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={iface}\n\t\t\t\t>\n\t\t\t\t\t{iface}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveInternalIface(iface);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet availableIfaces = this.ifaces();\n\t\tlet externalIfacesSelect: JSX.Element[] = [];\n\t\tfor (let iface of (availableIfaces || [])) {\n\t\t\texternalIfacesSelect.push(\n\t\t\t\t<option key={iface.name} value={iface.name}>\n\t\t\t\t\t{iface.name + (iface.address ? (\" (\" + iface.address + \")\") : \"\")}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet internalIfacesSelect: JSX.Element[] = [];\n\t\tfor (let iface of (availableIfaces || [])) {\n\t\t\tinternalIfacesSelect.push(\n\t\t\t\t<option key={iface.name} value={iface.name}>\n\t\t\t\t\t{iface.name + (iface.address ? (\" (\" + iface.address + \")\") : \"\")}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet cloudSubnets: JSX.Element[] = [];\n\t\tfor (let subnetId of (node.cloud_subnets || [])) {\n\t\t\tcloudSubnets.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={subnetId}\n\t\t\t\t>\n\t\t\t\t\t{this.subnetLabel(subnetId)}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveCloudSubnet(subnetId);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet availableSubnetsSelect: JSX.Element[] = [];\n\t\tfor (let vpc of (node.available_vpcs || [])) {\n\t\t\tfor (let subnet of (vpc.subnets || [])) {\n\t\t\t\tavailableSubnetsSelect.push(\n\t\t\t\t\t<option key={subnet.id} value={subnet.id}>\n\t\t\t\t\t\t{vpc.name + ' - ' + subnet.name}\n\t\t\t\t\t</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet availableDrives: JSX.Element[] = [];\n\t\tfor (let device of (node.instance_drives || [])) {\n\t\t\tavailableDrives.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={device.id}\n\t\t\t\t>\n\t\t\t\t\t{device.id}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveDrive(device.id);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet availableDrivesSelect: JSX.Element[] = [];\n\t\tfor (let device of (node.available_drives || [])) {\n\t\t\tavailableDrivesSelect.push(\n\t\t\t\t<option key={device.id} value={device.id}>\n\t\t\t\t\t{device.id}\n\t\t\t\t</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet certificates: JSX.Element[] = [];\n\t\tfor (let certId of (node.certificates || [])) {\n\t\t\tlet cert = CompletionStore.certificate(certId);\n\t\t\tif (!cert) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcertificates.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={cert.id}\n\t\t\t\t>\n\t\t\t\t\t{cert.name}\n\t\t\t\t\t<button\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveCert(cert.id);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet hasCertificates = false;\n\t\tlet certificatesSelect: JSX.Element[] = [];\n\t\tif (this.props.certificates) {\n\t\t\tfor (let certificate of this.props.certificates) {\n\t\t\t\tif (certificate.organization) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasCertificates = true;\n\n\t\t\t\tcertificatesSelect.push(\n\t\t\t\t\t<option key={certificate.id} value={certificate.id}>\n\t\t\t\t\t\t{certificate.name}\n\t\t\t\t\t</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasCertificates) {\n\t\t\tcertificatesSelect = [\n\t\t\t\t<option key=\"null\" value=\"\">\n\t\t\t\t\tNo Certificates\n\t\t\t\t</option>,\n\t\t\t];\n\t\t}\n\n\t\tlet defaultDatacenter = '';\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters.length) {\n\t\t\thasDatacenters = true;\n\t\t\tdefaultDatacenter = this.props.datacenters[0].id;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\tlet datacenter = this.state.datacenter || defaultDatacenter;\n\t\tlet hasZones = false;\n\t\tlet zonesSelect: JSX.Element[] = [];\n\t\tif (this.props.zones.length) {\n\t\t\tzonesSelect.push(<option key=\"null\" value=\"\">Select Zone</option>);\n\n\t\t\tfor (let zone of this.props.zones) {\n\t\t\t\tif (!this.props.node.zone && zone.datacenter !== datacenter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasZones = true;\n\n\t\t\t\tzonesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={zone.id}\n\t\t\t\t\t\tvalue={zone.id}\n\t\t\t\t\t>{zone.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasZones) {\n\t\t\tzonesSelect = [<option key=\"null\" value=\"\">No Zones</option>];\n\t\t}\n\n\t\tlet hasRenders = false;\n\t\tlet rendersSelect: JSX.Element[] = [];\n\t\tif (this.props.node.available_renders &&\n\t\t\tthis.props.node.available_renders.length) {\n\t\t\trendersSelect.push(<option key=\"null\" value=\"\">Select Render</option>);\n\n\t\t\tfor (let render of this.props.node.available_renders) {\n\t\t\t\thasRenders = true;\n\n\t\t\t\trendersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={render}\n\t\t\t\t\t\tvalue={render}\n\t\t\t\t\t>{render}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasRenders) {\n\t\t\trendersSelect = [<option key=\"null\" value=\"\">No Renders</option>];\n\t\t}\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let networkRole of (node.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={networkRole}\n\t\t\t\t>\n\t\t\t\t\t{networkRole}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveNetworkRole(networkRole);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet nodeBlocks = node.blocks || [];\n\t\tlet blocks: JSX.Element[] = [];\n\t\tfor (let i = 0; i < nodeBlocks.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tblocks.push(\n\t\t\t\t<NodeBlock\n\t\t\t\t\tkey={index}\n\t\t\t\t\tinterfaces={NodeTypes.GetAllIfaces(node)}\n\t\t\t\t\tblocks={this.props.blocks}\n\t\t\t\t\tblock={nodeBlocks[index]}\n\t\t\t\t\tipv6={false}\n\t\t\t\t\tonChange={(state: NodeTypes.BlockAttachment): void => {\n\t\t\t\t\t\tthis.onChangeBlock(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddBlock(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveBlock(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet nodeBlocks6 = node.blocks6 || [];\n\t\tlet blocks6: JSX.Element[] = [];\n\t\tfor (let i = 0; i < nodeBlocks6.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tblocks6.push(\n\t\t\t\t<NodeBlock\n\t\t\t\t\tkey={index}\n\t\t\t\t\tinterfaces={NodeTypes.GetAllIfaces(node)}\n\t\t\t\t\tblocks={this.props.blocks}\n\t\t\t\t\tblock={nodeBlocks6[index]}\n\t\t\t\t\tipv6={true}\n\t\t\t\t\tonChange={(state: NodeTypes.BlockAttachment): void => {\n\t\t\t\t\t\tthis.onChangeBlock6(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddBlock6(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveBlock6(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet nodeShares = node.shares || [];\n\t\tlet shares: JSX.Element[] = [];\n\t\tif (nodeShares.length === 0) {\n\t\t\tnodeShares.push({});\n\t\t}\n\t\tfor (let i = 0; i < nodeShares.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tshares.push(\n\t\t\t\t<NodeShare\n\t\t\t\t\tkey={index}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tshare={nodeShares[index]}\n\t\t\t\t\tonChange={(state: NodeTypes.Share): void => {\n\t\t\t\t\t\tthis.onChangeShare(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddShare(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveShare(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\n\t\t\tif (i < nodeShares.length-1) {\n\t\t\t\tshares.push(<Blueprint.Divider style={css.divider}/>);\n\t\t\t}\n\t\t}\n\n\t\tlet fields: PageInfos.Field[] = [\n\t\t\t{\n\t\t\t\tlabel: 'ID',\n\t\t\t\tvalue: this.props.node.id || 'None',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Version',\n\t\t\t\tvalue: node.software_version || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalueClass: active ? '' : 'bp5-text-intent-danger',\n\t\t\t\tlabel: 'Timestamp',\n\t\t\t\tvalue: MiscUtils.formatDate(\n\t\t\t\t\tthis.props.node.timestamp) || 'Inactive',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'CPU Units Reserved',\n\t\t\t\tvalue: (this.props.node.cpu_units_res || 0).toString(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'CPU Units',\n\t\t\t\tvalue: (this.props.node.cpu_units ||\n\t\t\t\t\t'Unknown').toString(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Memory Units Reserved',\n\t\t\t\tvalue: (this.props.node.memory_units_res || 0).toString(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Memory Units',\n\t\t\t\tvalue: (this.props.node.memory_units ?\n\t\t\t\t\tMath.floor(this.props.node.memory_units).toString() : 'Unkown'),\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Default Interface',\n\t\t\t\tvalue: this.props.node.default_interface || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Hostname',\n\t\t\t\tvalue: node.hostname || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Private IPv4',\n\t\t\t\tvalue: privateIps,\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Public IPv4',\n\t\t\t\tvalue: publicIps,\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Public IPv6',\n\t\t\t\tvalue: publicIps6,\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Requests',\n\t\t\t\tvalue: this.props.node.requests_min + '/min',\n\t\t\t},\n\t\t]\n\n\t\tif (node.updates?.length > 0) {\n\t\t\tfields.push({\n\t\t\t\tlabel: 'System Updates',\n\t\t\t\tvalue: 'Hover to Expand',\n\t\t\t\tvalueClass: 'bp5-text-intent-warning',\n\t\t\t\tembedded: {\n\t\t\t\t\tfields: NodeTypes.UpdateFields(node),\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={4}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<InstanceAdvDialog updates={this.props.node.updates}/>\n\t\t\t\t\t\t<Relations kind=\"node\" id={this.props.node.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Node\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this node\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[node.name]}\n\t\t\t\t\t\t\tdisabled={active || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of node\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={node.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Node comment.\"\n\t\t\t\t\t\tplaceholder=\"Node comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={node.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Admin\"\n\t\t\t\t\t\thelp=\"Provides access to the admin console on this node.\"\n\t\t\t\t\t\tchecked={types.indexOf('admin') !== -1}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.toggleType('admin');\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"User\"\n\t\t\t\t\t\thelp=\"Provides access to the user console on this node for SSH certificates.\"\n\t\t\t\t\t\tchecked={types.indexOf('user') !== -1}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.toggleType('user');\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Load Balancer\"\n\t\t\t\t\t\thelp=\"Provides access to load balancers.\"\n\t\t\t\t\t\tchecked={types.indexOf('balancer') !== -1}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.toggleType('balancer');\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Hypervisor\"\n\t\t\t\t\t\thelp=\"Run instances with hypervisor on this node.\"\n\t\t\t\t\t\tchecked={types.indexOf('hypervisor') !== -1}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.toggleType('hypervisor');\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={types.indexOf('balancer') === -1 && (\n\t\t\t\t\t\t\ttypes.indexOf('admin') === -1 ||\n\t\t\t\t\t\t\ttypes.indexOf('user') === -1)}\n\t\t\t\t\t\tlabel=\"Admin Domain\"\n\t\t\t\t\t\thelp=\"Domain that will be used to access the admin interface.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter admin domain\"\n\t\t\t\t\t\tvalue={node.admin_domain}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('admin_domain', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={types.indexOf('balancer') === -1 && (\n\t\t\t\t\t\t\ttypes.indexOf('admin') === -1 ||\n\t\t\t\t\t\t\ttypes.indexOf('user') === -1)}\n\t\t\t\t\t\tlabel=\"User Domain\"\n\t\t\t\t\t\thelp=\"Domain that will be used to access the user interface.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter user domain\"\n\t\t\t\t\t\tvalue={node.user_domain}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('user_domain', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={types.indexOf('admin') === -1 &&\n\t\t\t\t\t\t\ttypes.indexOf('user') === -1}\n\t\t\t\t\t\tlabel=\"WebAuthn Domain\"\n\t\t\t\t\t\thelp=\"Domain that will be used for WebAuthn relying party identifier. This domain should be the highest level domain for the relevant resources. All other Pritunl Cloud domains handling WebAuthn authentication must be a sub-domain of this domain. Changing this domain will invalidate all existing WebAuthn devices.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter WebAuthn domain\"\n\t\t\t\t\t\tvalue={node.webauthn_domain}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('webauthn_domain', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label className=\"bp5-label\" style={css.label}>\n\t\t\t\t\t\tProtocol and Port\n\t\t\t\t\t\t<div className=\"bp5-control-group\" style={css.inputGroup}>\n\t\t\t\t\t\t\t<div className=\"bp5-select\" style={css.protocol}>\n\t\t\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\t\t\tvalue={node.protocol || 'https'}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.set('protocol', evt.target.value);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<option value=\"http\">HTTP</option>\n\t\t\t\t\t\t\t\t\t<option value=\"https\">HTTPS</option>\n\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\t\tstyle={css.port}\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\t\t\tplaceholder=\"Port\"\n\t\t\t\t\t\t\t\tvalue={node.port || 443}\n\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\tthis.set('port', parseInt(evt.target.value, 10));\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Web server HTTP/2 support\"\n\t\t\t\t\t\thelp=\"Support HTTP/2 traffic to the web server.\"\n\t\t\t\t\t\tchecked={node.http2}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('http2', !node.http2);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Web redirect server\"\n\t\t\t\t\t\thelp=\"Enable redirect server for HTTP requests to HTTPS. Required for Lets Encrypt certificates.\"\n\t\t\t\t\t\tchecked={!node.no_redirect_server}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('no_redirect_server', !node.no_redirect_server);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\thidden={!!this.props.node.zone}\n\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\thelp=\"Node datacenter, cannot be changed once set.\"\n\t\t\t\t\t\tvalue={this.state.datacenter}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\t\t\tnode = {\n\t\t\t\t\t\t\t\t\t...this.state.node,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnode = {\n\t\t\t\t\t\t\t\t\t...this.props.node,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\t\t\tnode: node,\n\t\t\t\t\t\t\t\tdatacenter: val,\n\t\t\t\t\t\t\t\tzone: '',\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={!!this.props.node.zone || this.state.disabled ||\n\t\t\t\t\t\t\t!hasZones}\n\t\t\t\t\t\tlabel=\"Zone\"\n\t\t\t\t\t\thelp=\"Node zone, cannot be changed once set. Clear node ID in configuration file to reset node.\"\n\t\t\t\t\t\tvalue={this.props.node.zone ? this.props.node.zone :\n\t\t\t\t\t\t\tthis.state.zone}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tlet node: NodeTypes.Node;\n\t\t\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\t\t\tnode = {\n\t\t\t\t\t\t\t\t\t...this.state.node,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnode = {\n\t\t\t\t\t\t\t\t\t...this.props.node,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\t\t\tnode: node,\n\t\t\t\t\t\t\t\tzone: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{zonesSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Network IPv4 Mode\"\n\t\t\t\t\t\thelp=\"Network mode for public IP addresses. Cannot be changed with instances running.\"\n\t\t\t\t\t\tvalue={node.network_mode}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.onNetworkMode(val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"disabled\">Disabled</option>\n\t\t\t\t\t\t<option value=\"dhcp\">DHCP</option>\n\t\t\t\t\t\t<option value=\"static\">Static</option>\n\t\t\t\t\t\t<option value=\"cloud\">Oracle Cloud</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Network IPv6 Mode\"\n\t\t\t\t\t\thelp=\"Network mode for public IPv6 addresses. Cannot be changed with instances running. Default will use IPv4 network mode.\"\n\t\t\t\t\t\tvalue={node.network_mode6}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.onNetworkMode6(val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"disabled\">Disabled</option>\n\t\t\t\t\t\t<option value=\"dhcp\">DHCP</option>\n\t\t\t\t\t\t<option value=\"static\">Static</option>\n\t\t\t\t\t\t<option value=\"cloud\">Oracle Cloud</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\thidden={\n\t\t\t\t\t\t\tnode.network_mode !== 'dhcp' &&\n\t\t\t\t\t\t\tnode.network_mode !== '' &&\n\t\t\t\t\t\t\tnode.network_mode6 !== 'dhcp' &&\n\t\t\t\t\t\t\tnode.network_mode6 !== ''\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\tExternal Interfaces\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"External Interfaces\"\n\t\t\t\t\t\t\tcontent=\"External interfaces for instance public interface. If this is not a bridge interface the host will not be able to reach the external IP of instances running on the same host.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{externalIfaces}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\thidden={\n\t\t\t\t\t\t\tnode.network_mode !== 'dhcp' &&\n\t\t\t\t\t\t\tnode.network_mode !== '' &&\n\t\t\t\t\t\t\tnode.network_mode6 !== 'dhcp' &&\n\t\t\t\t\t\t\tnode.network_mode6 !== ''\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlabel=\"Add Interface\"\n\t\t\t\t\t\tvalue={this.state.addExternalIface}\n\t\t\t\t\t\tdisabled={!externalIfacesSelect.length || this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddExternalIface: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddExternalIface}\n\t\t\t\t\t>\n\t\t\t\t\t\t{externalIfacesSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t>\n\t\t\t\t\t\tInternal Interfaces\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Internal Interfaces\"\n\t\t\t\t\t\t\tcontent=\"Internal interfaces for instance private VPC interface. If zone network mode is default this must be a bridge interface. Set zone network mode to VXLan to use non-bridge interface. Leave blank to use external interface.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{internalIfaces}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\tlabel=\"Add Interface\"\n\t\t\t\t\t\tvalue={this.state.addInternalIface}\n\t\t\t\t\t\tdisabled={!internalIfacesSelect.length || this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddInternalIface: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddInternalIface}\n\t\t\t\t\t>\n\t\t\t\t\t\t{internalIfacesSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={node.network_mode !== 'static'}\n\t\t\t\t\t\tstyle={css.labelWide}\n\t\t\t\t\t>\n\t\t\t\t\t\tExternal IPv4 Block Attachments\n\t\t\t\t\t\t{blocks}\n\t\t\t\t\t</label>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={node.network_mode6 !== 'static'}\n\t\t\t\t\t\tstyle={css.labelWide}\n\t\t\t\t\t>\n\t\t\t\t\t\tExternal IPv6 Block Attachments\n\t\t\t\t\t\t{blocks6}\n\t\t\t\t\t</label>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\thidden={node.network_mode !== 'cloud'}\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t>\n\t\t\t\t\t\tOracle Cloud Subnets\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Oracle Cloud Subnets\"\n\t\t\t\t\t\t\tcontent=\"Oracle Cloud VCN subnets available to attach to instances.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{cloudSubnets}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\tlabel=\"Add Subnet\"\n\t\t\t\t\t\thidden={node.network_mode !== 'cloud'}\n\t\t\t\t\t\tvalue={this.state.addCloudSubnet}\n\t\t\t\t\t\tdisabled={!availableSubnetsSelect.length || this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddCloudSubnet: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddCloudSubnet}\n\t\t\t\t\t>\n\t\t\t\t\t\t{availableSubnetsSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Host Network\"\n\t\t\t\t\t\thelp=\"Enable host networking to allow host to instance communication. Required for instance NAT.\"\n\t\t\t\t\t\tchecked={!node.no_host_network}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('no_host_network', !node.no_host_network);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={node.no_host_network}\n\t\t\t\t\t\tlabel=\"Host Network NAT\"\n\t\t\t\t\t\thelp=\"Enable NAT to on the host network.\"\n\t\t\t\t\t\tchecked={node.host_nat}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('host_nat', !node.host_nat);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Node Port Network\"\n\t\t\t\t\t\thelp=\"Enable node port networking to allow instances to use node ports.\"\n\t\t\t\t\t\tchecked={!node.no_node_port_network}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('no_node_port_network', !node.no_node_port_network);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={node.network_mode !== 'cloud' &&\n\t\t\t\t\t\t\tnode.network_mode6 !== 'cloud'}\n\t\t\t\t\t\tlabel=\"Oracle Cloud User OCID\"\n\t\t\t\t\t\thelp=\"User OCID for Oracle Cloud API authentication.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter user OCID\"\n\t\t\t\t\t\tvalue={node.oracle_user}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('oracle_user', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={node.network_mode !== 'cloud' &&\n\t\t\t\t\t\t\tnode.network_mode6 !== 'cloud'}\n\t\t\t\t\t\tlabel=\"Oracle Cloud User Tenancy\"\n\t\t\t\t\t\thelp=\"Tenancy OCID for Oracle Cloud API authentication.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter tenancy OCID\"\n\t\t\t\t\t\tvalue={node.oracle_tenancy}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('oracle_tenancy', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={node.network_mode !== 'cloud' &&\n\t\t\t\t\t\t\tnode.network_mode6 !== 'cloud'}\n\t\t\t\t\t\tlabel=\"Oracle Cloud Public Key\"\n\t\t\t\t\t\thelp=\"Public key for Oracle Cloud API authentication.\"\n\t\t\t\t\t\tplaceholder=\"Oracle Cloud public key\"\n\t\t\t\t\t\treadOnly={true}\n\t\t\t\t\t\trows={6}\n\t\t\t\t\t\tvalue={node.oracle_public_key}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('oracle_public_key', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Default instance public IPv4 address\"\n\t\t\t\t\t\thelp=\"Enable or disable default option for instance public IPv4 address.\"\n\t\t\t\t\t\tchecked={!node.default_no_public_address}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('default_no_public_address',\n\t\t\t\t\t\t\t\t!node.default_no_public_address);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Default instance public IPv6 address\"\n\t\t\t\t\t\thelp=\"Enable or disable default option for instance public IPv6 address.\"\n\t\t\t\t\t\tchecked={!node.default_no_public_address6}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('default_no_public_address6',\n\t\t\t\t\t\t\t\t!node.default_no_public_address6);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Jumbo frames external\"\n\t\t\t\t\t\thelp=\"Enable jumbo frames on external interfaces, requires node restart when changed. Node external interfaces must be configured for 9000 MTU. Also requires internal jumbo frames. This is a rare configuration for systems run entirely on private networks.\"\n\t\t\t\t\t\tchecked={node.jumbo_frames}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('jumbo_frames', !node.jumbo_frames);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Jumbo frames internal\"\n\t\t\t\t\t\thelp=\"Enable jumbo frames on internal interfaces, requires node restart when changed. Node interal interfaces must be configured for 9000 MTU.\"\n\t\t\t\t\t\tchecked={node.jumbo_frames || node.jumbo_frames_internal}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('jumbo_frames_internal', !node.jumbo_frames_internal);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Instance iSCSI support\"\n\t\t\t\t\t\thelp=\"Enable iSCSI disk support for instances.\"\n\t\t\t\t\t\tchecked={node.iscsi}\n\t\t\t\t\t\thidden={!node.iscsi}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('iscsi', !node.iscsi);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!node.pci_passthrough}\n\t\t\t\t\t\tlabel=\"PCI Passthrough\"\n\t\t\t\t\t\thelp=\"Enable PCI passthrough support for instances.\"\n\t\t\t\t\t\tchecked={node.pci_passthrough}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('pci_passthrough', !node.pci_passthrough);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"USB Passthrough\"\n\t\t\t\t\t\thelp=\"Enable USB passthrough support for instances.\"\n\t\t\t\t\t\tchecked={node.usb_passthrough}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('usb_passthrough', !node.usb_passthrough);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"HugePages\"\n\t\t\t\t\t\thelp=\"Static hugepages provide a sector of the system memory to be dedicated for hugepages. This memory will be used for instances allowing higher memory performance and preventing the host system from disturbing memory dedicated for virtual instances. This option should always be used on production systems. The hugepages size must be set with the option below or manually with sysctl. Enabling this option while instances are running is likely to crash the system.\"\n\t\t\t\t\t\tchecked={node.hugepages}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('hugepages', !node.hugepages);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\tlabel=\"HugePages Size\"\n\t\t\t\t\t\thelp=\"Size of hugepages space in megabytes. Set this option to the size of memory that will be dedicated for virtual instances. It is recommended to leave 4GB of memory for the host system. Set to 0 if the hugepages size is being manually configured.\"\n\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\tminorStepSize={0}\n\t\t\t\t\t\tstepSize={1024}\n\t\t\t\t\t\tmajorStepSize={1024}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!node.hugepages}\n\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\tthis.set('hugepages_size', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tvalue={node.hugepages_size}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Firewall\"\n\t\t\t\t\t\thelp=\"Configure firewall on node. Incorrectly configuring the firewall can block access to the node.\"\n\t\t\t\t\t\tchecked={node.firewall}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.toggleFirewall();\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Desktop GUI\"\n\t\t\t\t\t\thelp=\"Enable support for desktop GUI display for instances. Requires Xorg or Wayland session to be running.\"\n\t\t\t\t\t\tchecked={node.gui}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('gui', !node.gui);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Desktop GUI Mode\"\n\t\t\t\t\t\thelp=\"Enable support for desktop GUI display for instances. Requires Xorg or Wayland session to be running.\"\n\t\t\t\t\t\tchecked={node.gui}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('gui', !node.gui);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\thidden={!node.gui}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Desktop GUI Mode\"\n\t\t\t\t\t\thelp=\"Desktop GUI display mode. SDL is recommended for better compatibility.\"\n\t\t\t\t\t\tvalue={node.gui_mode}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('gui_mode', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"sdl\">SDL</option>\n\t\t\t\t\t\t<option value=\"gtk\">GTK</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!node.gui}\n\t\t\t\t\t\tlabel=\"Desktop GUI User\"\n\t\t\t\t\t\thelp=\"Username of user to open desktop GUI window.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter username\"\n\t\t\t\t\t\tvalue={node.gui_user}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('gui_user', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={fields}\n\t\t\t\t\t\tbars={resourceBars}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\thidden={types.indexOf('hypervisor') === -1}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Hypervisor Mode\"\n\t\t\t\t\t\thelp=\"Hypervisor mode, select KVM if CPU has hardware virtualization support.\"\n\t\t\t\t\t\tvalue={node.hypervisor}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('hypervisor', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"qemu\">QEMU</option>\n\t\t\t\t\t\t<option value=\"kvm\">KVM</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\thidden={types.indexOf('hypervisor') === -1}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Hypervisor VGA Type\"\n\t\t\t\t\t\thelp={<div>\n\t\t\t\t\t\t\tType of VGA card to emulate. Generally [Virtio VGA OpenGL] for desktops or [Virtio VGA OpenGL Prime] for Nvidia laptops is the best option. The GPU based renders are very unstable and do not add any meaningful performance increase.\n\t\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t\t<li>Standard = --vga=std</li>\n\t\t\t\t\t\t\t\t<li>VMware = --vga=vmware</li>\n\t\t\t\t\t\t\t\t<li>Virtio = --display=virtio-vga</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU PCI = --display=virtio-gpu-pci</li>\n\t\t\t\t\t\t\t\t<li>Virtio VGA OpenGL = --display=virtio-vga-gl</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU OpenGL = --display=virtio-gpu-gl</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU Vulkan = --display=virtio-gpu-gl,venus=true</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU PCI OpenGL = --display=virtio-gpu-gl-pci</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU PCI Vulkan = --display=virtio-gpu-gl-pci,venus=true</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU PCI Prime = --display=virtio-gpu-pci DRI_PRIME=1</li>\n\t\t\t\t\t\t\t\t<li>Virtio VGA OpenGL Prime = --display=virtio-vga-gl DRI_PRIME=1</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU OpenGL Prime = --display=virtio-gpu-gl DRI_PRIME=1</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU Vulkan Prime = --display=virtio-gpu-gl,venus=true DRI_PRIME=1</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU PCI OpenGL Prime = --display=virtio-gpu-gl-pci DRI_PRIME=1</li>\n\t\t\t\t\t\t\t\t<li>Virtio GPU PCI Vulkan Prime = --display=virtio-gpu-gl-pci,venus=true DRI_PRIME=1</li>\n\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t</div>}\n\t\t\t\t\t\tvalue={node.vga}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('vga', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"std\">Standard</option>\n\t\t\t\t\t\t<option value=\"vmware\">VMware</option>\n\t\t\t\t\t\t<option value=\"virtio\">Virtio</option>\n\t\t\t\t\t\t<option value=\"virtio_pci\">Virtio GPU PCI</option>\n\t\t\t\t\t\t<option value=\"virtio_vga_gl\">Virtio VGA OpenGL</option>\n\t\t\t\t\t\t<option value=\"virtio_vga_gl_vulkan\">Virtio VGA Vulkan</option>\n\t\t\t\t\t\t<option value=\"virtio_gl\">Virtio GPU OpenGL</option>\n\t\t\t\t\t\t<option value=\"virtio_gl_vulkan\">Virtio GPU Vulkan</option>\n\t\t\t\t\t\t<option value=\"virtio_pci_gl\">Virtio GPU PCI OpenGL</option>\n\t\t\t\t\t\t<option value=\"virtio_pci_gl_vulkan\">Virtio GPU PCI Vulkan</option>\n\t\t\t\t\t\t<option value=\"virtio_pci_prime\">Virtio GPU PCI Prime</option>\n\t\t\t\t\t\t<option value=\"virtio_vga_gl_prime\">Virtio VGA OpenGL Prime</option>\n\t\t\t\t\t\t<option value=\"virtio_vga_gl_vulkan_prime\">Virtio VGA Vulkan Prime</option>\n\t\t\t\t\t\t<option value=\"virtio_gl_prime\">Virtio GPU OpenGL Prime</option>\n\t\t\t\t\t\t<option value=\"virtio_gl_vulkan_prime\">Virtio GPU Vulkan Prime</option>\n\t\t\t\t\t\t<option value=\"virtio_pci_gl_prime\">Virtio GPU PCI OpenGL Prime</option>\n\t\t\t\t\t\t<option value=\"virtio_pci_gl_vulkan_prime\">Virtio GPU PCI Vulkan Prime</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\thidden={types.indexOf('hypervisor') === -1 ||\n\t\t\t\t\t\t\t!NodeTypes.RenderModes.has(node.vga)}\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasRenders}\n\t\t\t\t\t\tlabel=\"Hypervisor EGL Render\"\n\t\t\t\t\t\thelp=\"Graphics card to use for EGL rendering.\"\n\t\t\t\t\t\tvalue={node.vga_render}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('vga_render', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{rendersSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t>\n\t\t\t\t\t\tInstance Passthrough Disks\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Instance Direct Disks\"\n\t\t\t\t\t\t\tcontent=\"Disk devices available to instances for passthrough.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{availableDrives}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\tlabel=\"Add Disk\"\n\t\t\t\t\t\tvalue={this.state.addDrive}\n\t\t\t\t\t\tdisabled={!availableDrivesSelect.length || this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddDrive: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddDrive}\n\t\t\t\t\t>\n\t\t\t\t\t\t{availableDrivesSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.labelShares}\n\t\t\t\t\t>\n\t\t\t\t\t\tShare Paths\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Share Paths\"\n\t\t\t\t\t\t\tcontent=\"Local paths on the host that are available for instances to access through VirtIO-FS sharing. Individual subdirectories do not need to be included. An instance can be configured for any path within the shared path. The instance's organization must have a matching role to access the share.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t{shares}\n\t\t\t\t\t</div>\n\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\tRoles\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\tcontent=\"Roles that will be matched with firewall rules and instance shapes. Roles are case-sensitive. Only node firewall roles without an organization will match.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\tvalue={this.state.addNetworkRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddNetworkRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddNetworkRole}\n\t\t\t\t\t/>\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\t\tstyle={css.label}\n\t\t\t\t\t\thidden={node.protocol === 'http'}\n\t\t\t\t\t>\n\t\t\t\t\t\tCertificates\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Certificates\"\n\t\t\t\t\t\t\tcontent=\"The certificates to use for this nodes web server. The certificates must be valid for all the domains that this node provides access to. This includes the management domain and any service domains.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{certificates}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\thidden={node.protocol === 'http'}\n\t\t\t\t\t\tlabel=\"Add Certificate\"\n\t\t\t\t\t\tvalue={this.state.addCert}\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasCertificates}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddCert: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddCert}\n\t\t\t\t\t>\n\t\t\t\t\t\t{certificatesSelect}\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<PageInputSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Forwarded for header\"\n\t\t\t\t\t\thelp=\"Enable when using a load balancer. This header value will be used to get the users IP address. It is important to only enable this when a load balancer is used. If it is enabled without a load balancer users can spoof their IP address by providing a value for the header that will not be overwritten by a load balancer. Additionally the nodes firewall should be configured to only accept requests from the load balancer to prevent requests being sent directly to the node bypassing the load balancer.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Forwarded for header\"\n\t\t\t\t\t\tvalue={node.forwarded_for_header}\n\t\t\t\t\t\tchecked={this.state.forwardedChecked}\n\t\t\t\t\t\tdefaultValue=\"X-Forwarded-For\"\n\t\t\t\t\t\tonChange={(state: boolean, val: string): void => {\n\t\t\t\t\t\t\tlet nde: NodeTypes.Node;\n\n\t\t\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\t\t\tnde = {\n\t\t\t\t\t\t\t\t\t...this.state.node,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnde = {\n\t\t\t\t\t\t\t\t\t...this.props.node,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tnde.forwarded_for_header = val;\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\t\t\tforwardedChecked: state,\n\t\t\t\t\t\t\t\tnode: nde,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInputSwitch\n\t\t\t\t\t\tlabel=\"Forwarded proto header\"\n\t\t\t\t\t\thelp=\"Enable when using a load balancer. This header value will be used to get the users protocol. This will redirect users to https when the forwarded protocol is http.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Forwarded proto header\"\n\t\t\t\t\t\tvalue={node.forwarded_proto_header}\n\t\t\t\t\t\tchecked={this.state.forwardedProtoChecked}\n\t\t\t\t\t\tdefaultValue=\"X-Forwarded-Proto\"\n\t\t\t\t\t\tonChange={(state: boolean, val: string): void => {\n\t\t\t\t\t\t\tlet nde: NodeTypes.Node;\n\n\t\t\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\t\t\tnde = {\n\t\t\t\t\t\t\t\t\t...this.state.node,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnde = {\n\t\t\t\t\t\t\t\t\t...this.props.node,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tnde.forwarded_proto_header = val;\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\t\t\tforwardedProtoChecked: state,\n\t\t\t\t\t\t\t\tnode: nde,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.node}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tforwardedChecked: false,\n\t\t\t\t\t\tforwardedProtoChecked: false,\n\t\t\t\t\t\tnode: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t>\n\t\t\t\t<NodeDeploy\n\t\t\t\t\tdisabled={this.state.disabled || this.state.changed}\n\t\t\t\t\tnode={this.props.node}\n\t\t\t\t\tdatacenters={this.props.datacenters}\n\t\t\t\t\tzones={this.props.zones}\n\t\t\t\t\tblocks={this.props.blocks}\n\t\t\t\t/>\n\t\t\t</PageSave>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/NodeShare.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as BlockTypes from '../types/BlockTypes';\nimport PageInputButton from './PageInputButton';\n\ninterface Props {\n\tdisabled: boolean;\n\tshare: NodeTypes.Share;\n\tonChange: (state: NodeTypes.Share) => void;\n\tonAdd: (prepend: boolean) => void;\n\tonRemove: () => void;\n}\n\ninterface State {\n\taddRole: string;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '216px',\n\t\tmarginTop: '5px',\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n\tsourceGroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '219px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tcontrol: {\n\t\tmarginTop: '0px',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tmarginTop: '0px',\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\troles: {\n\t\twidth: '100%',\n\t\tmaxWidth: '400px',\n\t\tmarginTop: '5px',\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n\tother: {\n\t\tflex: '0 1 auto',\n\t\twidth: '52px',\n\t\tborderRadius: '0 3px 3px 0',\n\t} as React.CSSProperties,\n};\n\nexport default class NodeShare extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\taddRole: null,\n\t\t};\n\t}\n\n\tclone(): NodeTypes.Share {\n\t\treturn {\n\t\t\t...this.props.share,\n\t\t};\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet state = this.clone();\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(state.roles || []),\n\t\t];\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\t\tstate.roles = roles;\n\n\t\tthis.props.onChange(state);\n\t}\n\n\tonRemoveRole = (role: string): void => {\n\t\tlet state = this.clone();\n\n\t\tlet roles = [\n\t\t\t...(state.roles || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\t\tstate.roles = roles;\n\n\t\tthis.props.onChange(state);\n\t}\n\n\trender(): JSX.Element {\n\t\tlet share = this.props.share;\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of (share.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <div>\n\t\t\t<div style={css.roles}>\n\t\t\t\t{roles}\n\t\t\t</div>\n\t\t\t<PageInputButton\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\tlabel=\"Add\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tlistStyle={true}\n\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\tvalue={this.state.addRole}\n\t\t\t\tonChange={(val): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t/>\n\t\t\t<div className=\"bp5-control-group\" style={css.group}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.input}\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder=\"Enter share path\"\n\t\t\t\t\tvalue={share.path || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\tstate.path = evt.target.value;\n\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tstyle={css.control}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tstyle={css.control}\n\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\tthis.props.onAdd(evt.shiftKey);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Nodes.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as BlockTypes from '../types/BlockTypes';\nimport NodesStore from '../stores/NodesStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as NodeActions from '../actions/NodeActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Node from './Node';\nimport NodesFilter from './NodesFilter';\nimport NodesPage from './NodesPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tnodes: NodeTypes.NodesRo;\n\tfilter: NodeTypes.Filter;\n\tcertificates: CertificateTypes.CertificatesRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tblocks: BlockTypes.BlocksRo;\n\tselected: Selected;\n\topened: Opened;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Nodes extends React.Component<{}, State> {\n\tsync: MiscUtils.SyncInterval;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tnodes: NodesStore.nodes,\n\t\t\tfilter: NodesStore.filter,\n\t\t\tcertificates: CompletionStore.certificates,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tblocks: CompletionStore.blocks,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tNodesStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);;\n\t\tNodeActions.sync();\n\t\tCompletionActions.sync();\n\n\t\tthis.sync = new MiscUtils.SyncInterval(\n\t\t\t() => NodeActions.sync(true),\n\t\t\t3000,\n\t\t)\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tNodesStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\n\t\tthis.sync?.stop()\n\t}\n\n\tonChange = (): void => {\n\t\tlet nodes = NodesStore.nodes;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tnodes.forEach((node: NodeTypes.Node): void => {\n\t\t\tif (curSelected[node.id]) {\n\t\t\t\tselected[node.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[node.id]) {\n\t\t\t\topened[node.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tnodes: nodes,\n\t\t\tfilter: NodesStore.filter,\n\t\t\tcertificates: CompletionStore.certificates,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tblocks: CompletionStore.blocks,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet nodesDom: JSX.Element[] = [];\n\n\t\tthis.state.nodes.forEach((node: NodeTypes.NodeRo): void => {\n\t\t\tnodesDom.push(<Node\n\t\t\t\tkey={node.id}\n\t\t\t\tnode={node}\n\t\t\t\tcertificates={this.state.certificates}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tzones={this.state.zones}\n\t\t\t\tblocks={this.state.blocks}\n\t\t\t\tselected={!!this.state.selected[node.id]}\n\t\t\t\topen={!!this.state.opened[node.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet nodes = this.state.nodes;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < nodes.length; i++) {\n\t\t\t\t\t\t\tlet usr = nodes[i];\n\n\t\t\t\t\t\t\tif (usr.id === node.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[nodes[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: node.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[node.id]) {\n\t\t\t\t\t\tdelete selected[node.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[node.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: node.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[node.id]) {\n\t\t\t\t\t\tdelete opened[node.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[node.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet sizeRow = <div style={{\"display\": \"table-row\"}}>\n\t\t\t<div style={{display: \"table-cell\", width: \"auto\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"auto\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"auto\"}}></div>\n\t\t\t<div style={{display: \"table-cell\", width: \"85px\"}}></div>\n\t\t</div>\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Nodes</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tNodeActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tNodeActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<NodesFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tNodeActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\tzones={this.state.zones}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{sizeRow}\n\t\t\t\t\t{nodesDom}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NodesPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/NodesFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as NodeTypes from '../types/NodeTypes';\nimport SearchInput from './SearchInput';\nimport SwitchNull from './SwitchNull';\nimport * as ZoneTypes from \"../types/ZoneTypes\";\n\ninterface Props {\n\tfilter: NodeTypes.Filter;\n\tonFilter: (filter: NodeTypes.Filter) => void;\n\tzones: ZoneTypes.ZonesRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class NodesFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet zonesSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.zones && this.props.zones.length) {\n\t\t\tfor (let zone of this.props.zones) {\n\t\t\t\tzonesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={zone.id}\n\t\t\t\t\t\tvalue={zone.id}\n\t\t\t\t\t>{zone.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Node ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.role}\n\t\t\t\tplaceholder=\"Network Role\"\n\t\t\t\tvalue={this.props.filter.role}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.role = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.role;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.zone || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.zone;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.zone = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{zonesSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<SwitchNull\n\t\t\t\tstyle={css.check}\n\t\t\t\tlabel=\"Admin\"\n\t\t\t\tchecked={this.props.filter.admin}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (filter.admin === undefined) {\n\t\t\t\t\t\tfilter.admin = true;\n\t\t\t\t\t} else if (filter.admin === true) {\n\t\t\t\t\t\tfilter.admin = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.admin;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SwitchNull\n\t\t\t\tstyle={css.check}\n\t\t\t\tlabel=\"User\"\n\t\t\t\tchecked={this.props.filter.user}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (filter.user === undefined) {\n\t\t\t\t\t\tfilter.user = true;\n\t\t\t\t\t} else if (filter.user === true) {\n\t\t\t\t\t\tfilter.user = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.user;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SwitchNull\n\t\t\t\tstyle={css.check}\n\t\t\t\tlabel=\"Hypervisor\"\n\t\t\t\tchecked={this.props.filter.hypervisor}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (filter.hypervisor === undefined) {\n\t\t\t\t\t\tfilter.hypervisor = true;\n\t\t\t\t\t} else if (filter.hypervisor === true) {\n\t\t\t\t\t\tfilter.hypervisor = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.hypervisor;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/NodesPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport NodesStore from '../stores/NodesStore';\nimport * as NodeActions from '../actions/NodeActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class NodesPages extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: NodesStore.page,\n\t\t\tpageCount: NodesStore.pageCount,\n\t\t\tpages: NodesStore.pages,\n\t\t\tcount: NodesStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tNodesStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tNodesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: NodesStore.page,\n\t\t\tpageCount: NodesStore.pageCount,\n\t\t\tpages: NodesStore.pages,\n\t\t\tcount: NodesStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tNodeActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tNodeActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tNodeActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tNodeActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tNodeActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/NonState.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\n\ninterface Props {\n\thidden: boolean;\n\ticonClass: string;\n\ttitle: string;\n\tnoDelay?: boolean;\n\tdescription?: string;\n}\n\ninterface State {\n\tinitialized: boolean;\n}\n\nconst css = {\n\tstate: {\n\t\theight: 'auto',\n\t} as React.CSSProperties,\n\tstateIcon: {\n\t\tfontSize: '48px',\n\t\tlineHeight: '48px',\n\t\tmarginBottom: '0',\n\t} as React.CSSProperties,\n};\n\nexport default class NonState extends React.Component<Props, State> {\n\ttimeout: number;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tinitialized: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tif (!this.props.noDelay) {\n\t\t\tthis.timeout = window.setTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tinitialized: true,\n\t\t\t\t});\n\t\t\t}, Constants.loadDelay);\n\t\t}\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tif (this.timeout) {\n\t\t\twindow.clearTimeout(this.timeout);\n\t\t}\n\t}\n\n\trender(): JSX.Element {\n\t\tlet description: JSX.Element;\n\t\tif (this.props.description) {\n\t\t\tdescription = <div className=\"bp5-non-ideal-state-description\">\n\t\t\t\t{this.props.description}\n\t\t\t</div>;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-non-ideal-state\"\n\t\t\tstyle={css.state}\n\t\t\thidden={this.props.hidden || (!this.state.initialized && !this.props.noDelay)}\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName=\"bp5-non-ideal-state-visual bp5-non-ideal-state-icon\"\n\t\t\t\tstyle={css.stateIcon}\n\t\t\t>\n\t\t\t\t<span className={'bp5-icon ' + this.props.iconClass}/>\n\t\t\t</div>\n\t\t\t<h4 className=\"bp5-non-ideal-state-title\">{this.props.title}</h4>\n\t\t\t{description}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Organization.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport OrganizationDetailed from './OrganizationDetailed';\n\ninterface Props {\n\torganization: OrganizationTypes.OrganizationRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Organization extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet organization = this.props.organization;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<OrganizationDetailed\n\t\t\t\t\torganization={this.props.organization}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{organization.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/OrganizationDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as OrganizationActions from '../actions/OrganizationActions';\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport PageInputButton from './PageInputButton';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganization: OrganizationTypes.OrganizationRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddRole: string;\n\torganization: OrganizationTypes.Organization;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class Organization extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\torganization: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet organization: any;\n\n\t\tif (this.state.changed) {\n\t\t\torganization = {\n\t\t\t\t...this.state.organization,\n\t\t\t};\n\t\t} else {\n\t\t\torganization = {\n\t\t\t\t...this.props.organization,\n\t\t\t};\n\t\t}\n\n\t\torganization[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\torganization: organization,\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet organization: OrganizationTypes.Organization;\n\n\t\tif (this.state.changed) {\n\t\t\torganization = {\n\t\t\t\t...this.state.organization,\n\t\t\t};\n\t\t} else {\n\t\t\torganization = {\n\t\t\t\t...this.props.organization,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...organization.roles,\n\t\t];\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\n\t\torganization.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\torganization: organization,\n\t\t});\n\t}\n\n\tonRemoveRole(role: string): void {\n\t\tlet organization: OrganizationTypes.Organization;\n\n\t\tif (this.state.changed) {\n\t\t\torganization = {\n\t\t\t\t...this.state.organization,\n\t\t\t};\n\t\t} else {\n\t\t\torganization = {\n\t\t\t\t...this.props.organization,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...organization.roles,\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\n\t\torganization.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\torganization: organization,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tOrganizationActions.commit(this.state.organization).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\torganization: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tOrganizationActions.remove(this.props.organization.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet org: OrganizationTypes.Organization = this.state.organization ||\n\t\t\tthis.props.organization;\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of (org.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"organization\" id={this.props.organization.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Organization\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this organization\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[org.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of organization\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Name\"\n\t\t\t\t\t\tvalue={org.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Organization comment.\"\n\t\t\t\t\t\tplaceholder=\"Organization comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={org.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.organization.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\tRoles\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\tcontent=\"User roles will be used to match with organization roles. A user must have a matching role to access an organization.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.organization}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\torganization: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/OrganizationNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as OrganizationActions from '../actions/OrganizationActions';\nimport PageInput from './PageInput';\nimport PageCreate from './PageCreate';\nimport PageInputButton from './PageInputButton';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddRole: string;\n\torganization: OrganizationTypes.Organization;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class Organization extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\torganization: {\n\t\t\t\tname: 'new-organization',\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet organization: any = {\n\t\t\t...this.state.organization,\n\t\t};\n\n\t\torganization[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\torganization: organization,\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet organization: OrganizationTypes.Organization;\n\n\t\torganization = {\n\t\t\t...this.state.organization,\n\t\t};\n\n\t\tlet roles = [\n\t\t\t...(organization.roles || []),\n\t\t];\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\n\t\torganization.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\torganization: organization,\n\t\t});\n\t}\n\n\tonRemoveRole(role: string): void {\n\t\tlet organization: OrganizationTypes.Organization;\n\n\t\torganization = {\n\t\t\t...this.state.organization,\n\t\t};\n\n\t\tlet roles = [\n\t\t\t...(organization.roles || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\n\t\torganization.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\torganization: organization,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet organization: any = {\n\t\t\t...this.state.organization,\n\t\t};\n\n\t\tOrganizationActions.create(organization).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Organization created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet org: OrganizationTypes.Organization = this.state.organization;\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of (org.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <div\n\t\tclassName=\"bp5-card bp5-row\"\n\t\tstyle={css.row}\n\t>\n\t\t<td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of organization\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Name\"\n\t\t\t\t\t\t\tvalue={org.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Organization comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Organization comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={org.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\t\tRoles\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\t\tcontent=\"User roles will be used to match with organization roles. A user must have a matching role to access an organization.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.organization}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/OrganizationSelect.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from \"@blueprintjs/core\"\nimport * as BpSelect from '@blueprintjs/select';\nimport * as Icons from '@blueprintjs/icons';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport CompletionStore from '../stores/CompletionStore';\n\ninterface Props {\n\thidden: boolean;\n}\n\ninterface State {\n\torganizations: OrganizationTypes.Organizations;\n\torganization: string;\n}\n\nconst css = {\n\tcardButton: {\n\t} as React.CSSProperties,\n\tselect: {\n\t\tdisplay: \"inline\",\n\t} as React.CSSProperties,\n};\n\nexport default class Organization extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\torganizations: [...CompletionStore.organizations],\n\t\t\torganization: null,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\torganizations: [...CompletionStore.organizations],\n\t\t\torganization: CompletionStore.userOrganization,\n\t\t});\n\t}\n\n\trenderOrg: BpSelect.ItemRenderer<OrganizationTypes.Organization> = (org,\n\t\t{handleClick, handleFocus, modifiers, query, index}): JSX.Element => {\n\n\t\tlet className = \"\"\n\t\tlet selected = false\n\t\tif (this.state.organization === org.id) {\n\t\t\tclassName = \"bp5-text-intent-primary bp5-intent-primary\"\n\t\t\tselected = true\n\t\t} else if (index === 0) {\n\t\t\tclassName = \"\"\n\t\t}\n\t\treturn <Blueprint.MenuItem\n\t\t\tkey={`org-${org.id}`}\n\t\t\tselected={selected}\n\t\t\troleStructure=\"listoption\"\n\t\t\ticon={<Icons.People\n\t\t\t\tclassName={className}\n\t\t\t/>}\n\t\t\tonFocus={handleFocus}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tevt.preventDefault()\n\t\t\t\tevt.stopPropagation()\n\t\t\t\thandleClick(evt)\n\t\t\t}}\n\t\t\ttext={org.name}\n\t\t\ttextClassName={className}\n\t\t/>\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.hidden) {\n\t\t\treturn <div/>\n\t\t}\n\n\t\treturn <BpSelect.Select<OrganizationTypes.OrganizationRo>\n\t\t\titems={this.state.organizations || []}\n\t\t\titemRenderer={this.renderOrg}\n\t\t\tpopoverTargetProps={{\n\t\t\t\tstyle: css.select,\n\t\t\t}}\n\t\t\tfilterable={false}\n\t\t\titemListRenderer={({items, itemsParentRef,\n\t\t\t\t\tquery, renderItem, menuProps}) => {\n\n\t\t\t\tconst renderedItems = items.map(renderItem).filter(\n\t\t\t\t\titem => item != null)\n\t\t\t\treturn <Blueprint.Menu\n\t\t\t\t\trole=\"listbox\"\n\t\t\t\t\tulRef={itemsParentRef}\n\t\t\t\t\t{...menuProps}\n\t\t\t\t>\n\t\t\t\t\t{renderedItems}\n\t\t\t\t</Blueprint.Menu>\n\t\t\t}}\n\t\t\tonItemSelect={(org) => {\n\t\t\t\tCompletionActions.setUserOrganization(org.id);\n\t\t\t}}\n\t\t>\n\t\t\t<Blueprint.Button\n\t\t\t\tstyle={css.cardButton}\n\t\t\t\talignText=\"left\"\n\t\t\t\tsmall={true}\n\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t>Organization</Blueprint.Button>\n\t\t</BpSelect.Select>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Organizations.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport OrganizationsStore from '../stores/OrganizationsStore';\nimport * as OrganizationActions from '../actions/OrganizationActions';\nimport Organization from './Organization';\nimport OrganizationNew from './OrganizationNew';\nimport OrganizationsFilter from './OrganizationsFilter';\nimport OrganizationsPage from './OrganizationsPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tfilter: OrganizationTypes.Filter;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Organizations extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\torganizations: OrganizationsStore.organizations,\n\t\t\tfilter: OrganizationsStore.filter,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tOrganizationsStore.addChangeListener(this.onChange);\n\t\tOrganizationActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tOrganizationsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet organizations = OrganizationsStore.organizations;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\torganizations.forEach((organization: OrganizationTypes.Organization): void => {\n\t\t\tif (curSelected[organization.id]) {\n\t\t\t\tselected[organization.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[organization.id]) {\n\t\t\t\topened[organization.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\torganizations: organizations,\n\t\t\tfilter: OrganizationsStore.filter,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tOrganizationActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet organizationsDom: JSX.Element[] = [];\n\n\t\tthis.state.organizations.forEach((\n\t\t\t\torganization: OrganizationTypes.OrganizationRo): void => {\n\t\t\torganizationsDom.push(<Organization\n\t\t\t\tkey={organization.id}\n\t\t\t\torganization={organization}\n\t\t\t\tselected={!!this.state.selected[organization.id]}\n\t\t\t\topen={!!this.state.opened[organization.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet organizations = this.state.organizations;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < organizations.length; i++) {\n\t\t\t\t\t\t\tlet usr = organizations[i];\n\n\t\t\t\t\t\t\tif (usr.id === organization.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[organizations[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: organization.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[organization.id]) {\n\t\t\t\t\t\tdelete selected[organization.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[organization.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: organization.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[organization.id]) {\n\t\t\t\t\t\tdelete opened[organization.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[organization.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = OrganizationsStore.organization(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newOrganizationDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewOrganizationDom = <OrganizationNew\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Organizations</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tOrganizationActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tOrganizationActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected organizations\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<OrganizationsFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tOrganizationActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newOrganizationDom}\n\t\t\t\t\t{organizationsDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!organizationsDom.length}\n\t\t\t\ticonClass=\"bp5-icon-people\"\n\t\t\t\ttitle=\"No organizations\"\n\t\t\t\tdescription=\"Add a new organization to get started.\"\n\t\t\t/>\n\t\t\t<OrganizationsPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/OrganizationsFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport SearchInput from './SearchInput';\n\ninterface Props {\n\tfilter: OrganizationTypes.Filter;\n\tonFilter: (filter: OrganizationTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class OrganizationsFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Organization ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/OrganizationsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport OrganizationsStore from '../stores/OrganizationsStore';\nimport * as OrganizationActions from '../actions/OrganizationActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class OrganizationsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: OrganizationsStore.page,\n\t\t\tpageCount: OrganizationsStore.pageCount,\n\t\t\tpages: OrganizationsStore.pages,\n\t\t\tcount: OrganizationsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tOrganizationsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tOrganizationsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: OrganizationsStore.page,\n\t\t\tpageCount: OrganizationsStore.pageCount,\n\t\t\tpages: OrganizationsStore.pages,\n\t\t\tcount: OrganizationsStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tOrganizationActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tOrganizationActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tOrganizationActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tOrganizationActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tOrganizationActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Page.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tchildren?: React.ReactNode\n\twide?: boolean;\n\tfull?: boolean;\n}\n\nconst css = {\n\tpage: {\n\t\tmargin: '0 auto',\n\t\tpadding: '30px 20px',\n\t\tminWidth: '200px',\n\t\tmaxWidth: '1100px',\n\t} as React.CSSProperties,\n\tpageWide: {\n\t\tmargin: '0 auto',\n\t\tpadding: '30px 20px',\n\t\tminWidth: '200px',\n\t\tmaxWidth: '1400px',\n\t} as React.CSSProperties,\n\tpageFull: {\n\t\theight: '100%',\n\t\tdisplay: 'flex',\n\t\tflexDirection: 'column',\n\t\toverflow: 'hidden',\n\t\tmargin: '0 auto',\n\t\tpadding: '30px 20px 0 20px',\n\t\tminWidth: '200px',\n\t\tmaxWidth: '1600px',\n\t} as React.CSSProperties,\n};\n\nexport default class Page extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div style={this.props.full ? css.pageFull :\n\t\t\t\t(this.props.wide ? css.pageWide : css.page)}>\n\t\t\t{this.props.children}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageButton.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport Help from './Help';\n\ninterface Props {\n\tchildren?: React.ReactNode\n\tclassName?: string;\n\thidden?: boolean;\n\tdisabled?: boolean;\n\tlabel: string;\n\thelp: string;\n\tvalue: string;\n\tonClick: () => void;\n}\n\nconst css = {\n\tlabel: {\n\t\tdisplay: 'inline-block',\n\t} as React.CSSProperties,\n};\n\nexport default class PageButton extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div hidden={this.props.hidden}>\n\t\t\t<label className=\"bp5-label\" style={css.label}>\n\t\t\t\t{this.props.label}\n\t\t\t\t<Help\n\t\t\t\t\ttitle={this.props.label}\n\t\t\t\t\tcontent={this.props.help}\n\t\t\t\t/>\n\t\t\t\t<div className=\"bp5-select\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={\"bp5-button \" + this.props.className}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\tonClick={this.props.onClick}\n\t\t\t\t\t>\n\t\t\t\t\t\t{this.props.children}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</label>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageCreate.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tstyle?: React.CSSProperties;\n\tmessage: string;\n\tchanged: boolean;\n\tdisabled: boolean;\n\tclosed?: boolean;\n\thidden?: boolean;\n\tlight?: boolean;\n\tonCancel: () => void;\n\tonCreate: () => void;\n}\n\nconst css = {\n\tmessage: {\n\t\tmarginTop: '6px',\n\t} as React.CSSProperties,\n\tbox: {\n\t\tmarginTop: '15px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmarginLeft: '10px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tflexShrink: 0,\n\t} as React.CSSProperties,\n};\n\nexport default class PageCreate extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet style: React.CSSProperties = this.props.light ? null : css.box;\n\n\t\tif (this.props.style) {\n\t\t\tstyle = {\n\t\t\t\t...style,\n\t\t\t\t...this.props.style,\n\t\t\t};\n\t\t}\n\n\t\tlet closedDom: JSX.Element;\n\t\tif (this.props.closed) {\n\t\t\tclosedDom = <button\n\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-cross\"\n\t\t\t\tstyle={css.button}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={this.props.onCancel}\n\t\t\t>\n\t\t\t\tClose\n\t\t\t</button>;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"layout horizontal\"\n\t\t\tstyle={style}\n\t\t\thidden={this.props.hidden}\n\t\t>\n\t\t\t<div className=\"flex\"/>\n\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t<span style={css.message} hidden={!this.props.message}>\n\t\t\t\t\t{this.props.message}\n\t\t\t\t</span>\n\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-icon-cross\"\n\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\thidden={this.props.closed}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\tonClick={this.props.onCancel}\n\t\t\t\t\t>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-tick\"\n\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\thidden={this.props.closed}\n\t\t\t\t\t\tdisabled={!this.props.changed || this.props.disabled}\n\t\t\t\t\t\tonClick={this.props.onCreate}\n\t\t\t\t\t>\n\t\t\t\t\t\tCreate\n\t\t\t\t\t</button>\n\t\t\t\t\t{closedDom}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageCustom.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport Help from './Help';\n\ninterface Props {\n\tchildren?: React.ReactNode\n\thidden?: boolean;\n\tlabel: string;\n\thelp: string | JSX.Element;\n}\n\nconst css = {\n\tlabel: {\n\t\tdisplay: 'inline-block',\n\t} as React.CSSProperties,\n};\n\nexport default class PageCustom extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div hidden={this.props.hidden}>\n\t\t\t<label className=\"bp5-label\" style={css.label}>\n\t\t\t\t{this.props.label}\n\t\t\t\t<Help\n\t\t\t\t\ttitle={this.props.label}\n\t\t\t\t\tcontent={this.props.help}\n\t\t\t\t/>\n\t\t\t\t{this.props.children}\n\t\t\t</label>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageDateTime.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as BlueprintDateTime from '@blueprintjs/datetime2';\nimport Help from './Help';\n\ninterface Props {\n\thidden?: boolean;\n\tdisabled?: boolean;\n\tlabel: string;\n\thelp: string;\n\tvalue: string;\n\tonChange: (val: string) => void;\n}\n\nconst minDate = new Date(2018, 0, 1);\nconst maxDate = new Date(new Date().getFullYear() + 5, 0, 0);\n\nconst css = {\n\tgroup: {\n\t\tdisplay: 'inline-block',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\tmarginBottom: '5px',\n\t} as React.CSSProperties,\n};\n\nexport default class PageDateTime extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet dateStyle: React.CSSProperties = {};\n\n\t\tlet date = new Date(this.props.value);\n\t\tif (!this.props.value ||\n\t\t\t\tthis.props.value.includes('0000-12-31') ||\n\t\t\t\tthis.props.value.includes('0001-01-01')) {\n\t\t\tdate = null;\n\t\t}\n\n\t\tif (!date || this.props.disabled) {\n\t\t\tdateStyle.opacity = 0.5;\n\t\t}\n\n\t\treturn <div hidden={this.props.hidden}>\n\t\t\t<div style={css.group}>\n\t\t\t\t<label className=\"bp5-label\" style={css.label}>\n\t\t\t\t\t{this.props.label}\n\t\t\t\t\t<Help\n\t\t\t\t\t\ttitle={this.props.label}\n\t\t\t\t\t\tcontent={this.props.help}\n\t\t\t\t\t/>\n\t\t\t\t</label>\n\t\t\t\t<div style={dateStyle}>\n\t\t\t\t\t<BlueprintDateTime.DatePicker3\n\t\t\t\t\t\tvalue={this.props.disabled ? null : date}\n\t\t\t\t\t\tshowActionsBar={true}\n\t\t\t\t\t\tminDate={minDate}\n\t\t\t\t\t\tmaxDate={maxDate}\n\t\t\t\t\t\ttimePickerProps={{\n\t\t\t\t\t\t\tshowArrowButtons: true,\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonChange={(newDate: Date): void => {\n\t\t\t\t\t\t\tif (this.props.disabled) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (newDate) {\n\t\t\t\t\t\t\t\tthis.props.onChange(newDate.toJSON());\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.props.onChange(null);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageHeader.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tchildren?: React.ReactNode\n\tlabel?: string;\n}\n\nconst css = {\n\theader: {\n\t\tfontSize: '24px',\n\t\tpaddingBottom: '8px',\n\t\tmarginBottom: '20px',\n\t\tborderBottomStyle: 'solid',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\tmargin: 0,\n\t} as React.CSSProperties,\n};\n\nexport default class PageHeader extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet label: JSX.Element;\n\t\tif (this.props.label) {\n\t\t\tlabel = <h2 style={css.label}>{this.props.label}</h2>;\n\t\t}\n\n\t\treturn <div className=\"bp5-border\" style={css.header}>\n\t\t\t{label}\n\t\t\t{this.props.children}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageInfo.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from \"@blueprintjs/core\"\nimport CopyButton from './CopyButton';\n\nexport interface Field {\n\tvalueClass?: string;\n\tvalueClasses?: string[];\n\tkey?: string;\n\tlabel: string;\n\tvalue?: string | number | string[];\n\thover?: JSX.Element;\n\tlink?: string;\n\tcopy?: boolean;\n\tembedded?: Props;\n\tmaxLines?: number;\n}\n\nexport interface Bar {\n\tprogressClass?: string;\n\tlabel?: string;\n\tvalue: number;\n\tcolor?: string;\n}\n\nexport interface Props {\n\tstyle?: React.CSSProperties;\n\thidden?: boolean;\n\tfields?: Field[];\n\tbars?: Bar[];\n\tcompact?: boolean;\n\tembedded?: boolean;\n}\n\nconst css = {\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '320px',\n\t} as React.CSSProperties,\n\tlabelEmbedded: {\n\t\twidth: '100%',\n\t\tmaxWidth: '320px',\n\t\tmarginBottom: '0px',\n\t} as React.CSSProperties,\n\tvalue: {\n\t\twordWrap: 'break-word',\n\t} as React.CSSProperties,\n\tvalueLimit: {\n\t\twordWrap: 'break-word',\n\t\tdisplay: '-webkit-box',\n\t\tWebkitBoxOrient: 'vertical',\n\t\toverflow: 'hidden',\n\t\ttextOverflow: 'ellipsis',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmarginTop: '0px',\n\t\tmarginBottom: '5px',\n\t} as React.CSSProperties,\n\titemLast: {\n\t\tmarginTop: '0px',\n\t\tmarginBottom: '0px',\n\t} as React.CSSProperties,\n\titemCompact: {\n\t\tmarginTop: '0px',\n\t\tmarginBottom: '2px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tembedded: {\n\t\tminWidth: '300px',\n\t\tpadding: '10px',\n\t\tmaxHeight: '320px',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tverticalAlign: 'middle',\n\t\tcursor: 'pointer',\n\t\tmarginLeft: '3px',\n\t\tposition: 'relative',\n\t\ttop: '-1px',\n\t\topacity: 0.8,\n\t} as React.CSSProperties,\n};\n\nexport default class PageInfo extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet fields: JSX.Element[] = [];\n\t\tlet bars: JSX.Element[] = [];\n\t\tlet itemStyle = this.props.compact ? css.itemCompact : css.item;\n\n\t\t(this.props.fields || []).forEach((field, index, array) => {\n\t\t\tif (field == null) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.props.embedded && index === array.length - 1) {\n\t\t\t\titemStyle = css.itemLast\n\t\t\t}\n\n\t\t\tlet value: string | JSX.Element[];\n\t\t\tlet copyBtn: JSX.Element;\n\t\t\tlet linkBtn: JSX.Element;\n\n\t\t\tif (typeof field.value === 'string') {\n\t\t\t\tvalue = field.value;\n\t\t\t\tif (field.copy) {\n\t\t\t\t\tcopyBtn = <CopyButton\n\t\t\t\t\t\tvalue={field.value}\n\t\t\t\t\t/>;\n\t\t\t\t}\n\t\t\t} else if (typeof field.value === 'number') {\n\t\t\t\tvalue = field.value.toString();\n\t\t\t\tif (field.copy) {\n\t\t\t\t\tcopyBtn = <CopyButton\n\t\t\t\t\t\tvalue={field.value.toString()}\n\t\t\t\t\t/>;\n\t\t\t\t}\n\t\t\t} else if (field.value) {\n\t\t\t\tvalue = [];\n\t\t\t\tfor (let i = 0; i < field.value.length; i++) {\n\t\t\t\t\tlet copyItemBtn: JSX.Element;\n\n\t\t\t\t\tif (field.copy) {\n\t\t\t\t\t\tcopyItemBtn = <CopyButton\n\t\t\t\t\t\t\tvalue={field.value[i]}\n\t\t\t\t\t\t/>;\n\t\t\t\t\t}\n\n\t\t\t\t\tvalue.push(\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\tfield.valueClasses ?\n\t\t\t\t\t\t\t\tfield.valueClasses[i] :\n\t\t\t\t\t\t\t\t(field.valueClass || 'bp5-text-muted')\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{field.value[i]}{copyItemBtn}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (field.link) {\n\t\t\t\tlinkBtn = <a\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\thref={field.link}\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\tstyle={css.link}\n\t\t\t\t\t\tclassName=\"bp5-icon-standard bp5-icon-document-open bp5-intent-primary\"\n\t\t\t\t\t/>\n\t\t\t\t</a>\n\t\t\t}\n\n\t\t\tif (field.hover || field.embedded) {\n\t\t\t\tfields.push(\n\t\t\t\t\t<Blueprint.Popover\n\t\t\t\t\t\tkey={field.label}\n\t\t\t\t\t\tinteractionKind=\"hover\"\n\t\t\t\t\t\tplacement=\"bottom\"\n\t\t\t\t\t\tminimal={true}\n\t\t\t\t\t\tcontent={field.hover || <div\n\t\t\t\t\t\t\tstyle={css.embedded}\n\t\t\t\t\t\t\tclassName=\"bp5-content-popover\">\n\t\t\t\t\t\t\t\t<PageInfo embedded={true} {...field.embedded}/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t\trenderTarget={({isOpen, ...targetProps}): JSX.Element => {\n\t\t\t\t\t\t\t\treturn <div {...targetProps} style={itemStyle}>\n\t\t\t\t\t\t\t\t{field.label}{linkBtn}\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclassName={field.valueClass || 'bp5-text-muted'}\n\t\t\t\t\t\t\t\t\tstyle={css.value}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{value}{copyBtn}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlet style = css.value\n\t\t\t\tif (field.maxLines) {\n\t\t\t\t\tstyle = {...css.valueLimit}\n\t\t\t\t\tstyle.WebkitLineClamp = field.maxLines\n\t\t\t\t}\n\n\t\t\t\tfields.push(\n\t\t\t\t\t<div key={field.key || field.label} style={itemStyle}>\n\t\t\t\t\t\t{field.label}{linkBtn}\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName={field.valueClass || 'bp5-text-muted'}\n\t\t\t\t\t\t\tstyle={style}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{value}{copyBtn}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>,\n\t\t\t\t);\n\t\t\t}\n\t\t})\n\n\t\tif (this.props.bars) {\n\t\t\tfor (let i = 0; i < this.props.bars.length; i++) {\n\t\t\t\tlet bar = this.props.bars[i]\n\n\t\t\t\tlet style: React.CSSProperties = {\n\t\t\t\t\twidth: (bar.value || 0) + '%',\n\t\t\t\t};\n\n\t\t\t\tif (bar.color) {\n\t\t\t\t\tstyle.backgroundColor = bar.color;\n\t\t\t\t}\n\n\t\t\t\tbars.push(\n\t\t\t\t\t<div key={bar.label || i} style={itemStyle}>\n\t\t\t\t\t\t{bar.label}\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName={'bp5-progress-bar ' + (bar.progressClass || '')}\n\t\t\t\t\t\t\tstyle={css.bar}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className=\"bp5-progress-meter\" style={style}/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet labelStyle: React.CSSProperties;\n\t\tif (this.props.embedded) {\n\t\t\tlabelStyle = css.labelEmbedded\n\t\t} else {\n\t\t\tlabelStyle = css.label\n\t\t}\n\n\t\tif (this.props.style) {\n\t\t\tlabelStyle = {\n\t\t\t\t...labelStyle,\n\t\t\t\t...this.props.style,\n\t\t\t};\n\t\t}\n\n\t\treturn <label\n\t\t\tclassName=\"bp5-label\"\n\t\t\tstyle={labelStyle}\n\t\t\thidden={this.props.hidden}\n\t\t>\n\t\t\t{fields}\n\t\t\t{bars}\n\t\t</label>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageInput.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport Help from './Help';\n\ninterface Props {\n\thidden?: boolean;\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\tautoSelect?: boolean;\n\tnarrow?: boolean;\n\tlabel: string;\n\thelp: string;\n\ttype: string;\n\tplaceholder: string;\n\tvalue: string | number;\n\tonChange?: (val: string) => void;\n}\n\nconst css = {\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tlabelNarrow: {\n\t\twidth: '100%',\n\t\tmaxWidth: '220px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n};\n\nexport default class PageInput extends React.Component<Props, {}> {\n\tautoSelect = (evt: React.MouseEvent<HTMLInputElement>): void => {\n\t\tevt.currentTarget.select();\n\t}\n\n\trender(): JSX.Element {\n\t\tlet value: any = this.props.value;\n\t\tvalue = isNaN(value) ? this.props.value || '' : this.props.value;\n\n\t\treturn <label\n\t\t\tclassName=\"bp5-label\"\n\t\t\tstyle={this.props.narrow ? css.labelNarrow : css.label}\n\t\t\thidden={this.props.hidden}\n\t\t>\n\t\t\t{this.props.label}\n\t\t\t<Help\n\t\t\t\ttitle={this.props.label}\n\t\t\t\tcontent={this.props.help}\n\t\t\t/>\n\t\t\t<input\n\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\tstyle={css.input}\n\t\t\t\ttype={this.props.type}\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\treadOnly={this.props.readOnly}\n\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\tspellCheck={false}\n\t\t\t\tplaceholder={this.props.placeholder}\n\t\t\t\tvalue={value}\n\t\t\t\tonClick={this.props.autoSelect ? this.autoSelect : null}\n\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\tif (this.props.onChange) {\n\t\t\t\t\t\tthis.props.onChange(evt.target.value);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</label>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageInputButton.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport Help from './Help';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Props {\n\tbuttonClass?: string;\n\thidden?: boolean;\n\tdisabled?: boolean;\n\tbuttonConfirm?: boolean;\n\tbuttonDisabled?: boolean;\n\treadOnly?: boolean;\n\tautoSelect?: boolean;\n\tlabel?: string;\n\tlabelTop?: boolean;\n\tlistStyle?: boolean;\n\thelp?: string;\n\ttype: string;\n\tplaceholder?: string;\n\tvalue: string;\n\tonChange?: (val: string) => void;\n\tonSubmit: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\tmarginBottom: '15px',\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tgroupList: {\n\t\tmarginTop: '5px',\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tgroupTop: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbuttonTop: {\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n};\n\nexport default class PageInputButton extends React.Component<Props, {}> {\n\tautoSelect = (evt: React.MouseEvent<HTMLInputElement>): void => {\n\t\tevt.currentTarget.select();\n\t}\n\n\trender(): JSX.Element {\n\t\tlet buttonClass = 'bp5-button';\n\t\tif (this.props.buttonClass) {\n\t\t\tbuttonClass += ' ' + this.props.buttonClass;\n\t\t}\n\n\t\tlet buttonLabel = '';\n\t\tlet buttonStyle: React.CSSProperties;\n\t\tif (this.props.labelTop) {\n\t\t\tbuttonStyle = css.buttonTop;\n\t\t} else {\n\t\t\tbuttonLabel = this.props.label || '';\n\t\t}\n\n\t\tlet button: JSX.Element;\n\t\tif (this.props.buttonConfirm) {\n\t\t\tbutton = <ConfirmButton\n\t\t\t\tclassName={buttonClass}\n\t\t\t\tstyle={buttonStyle}\n\t\t\t\tdisabled={this.props.disabled || this.props.buttonDisabled}\n\t\t\t\tonConfirm={this.props.onSubmit}\n\t\t\t\tlabel={buttonLabel}\n\t\t\t/>;\n\t\t} else {\n\t\t\tbutton = <button\n\t\t\t\tclassName={buttonClass}\n\t\t\t\tstyle={buttonStyle}\n\t\t\t\tdisabled={this.props.disabled || this.props.buttonDisabled}\n\t\t\t\tonClick={this.props.onSubmit}\n\t\t\t>{buttonLabel}</button>;\n\t\t}\n\n\t\tif (this.props.labelTop) {\n\t\t\treturn <label\n\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\tstyle={css.label}\n\t\t\t\thidden={this.props.hidden}\n\t\t\t>\n\t\t\t\t{this.props.label}\n\t\t\t\t<Help\n\t\t\t\t\ttitle={this.props.label}\n\t\t\t\t\tcontent={this.props.help}\n\t\t\t\t/>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-control-group\"\n\t\t\t\t\tstyle={css.groupTop}\n\t\t\t\t\thidden={this.props.hidden}\n\t\t\t\t>\n\t\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\t\ttype={this.props.type}\n\t\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\t\treadOnly={this.props.readOnly}\n\t\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\t\tplaceholder={this.props.placeholder}\n\t\t\t\t\t\t\tvalue={this.props.value || ''}\n\t\t\t\t\t\t\tonClick={this.props.autoSelect ? this.autoSelect : null}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\tif (this.props.onChange) {\n\t\t\t\t\t\t\t\t\tthis.props.onChange(evt.target.value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonKeyPress={(evt): void => {\n\t\t\t\t\t\t\t\tif (evt.key === 'Enter') {\n\t\t\t\t\t\t\t\t\tthis.props.onSubmit();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t{button}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</label>;\n\t\t} else {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-control-group\"\n\t\t\t\tstyle={this.props.listStyle ? css.groupList : css.group}\n\t\t\t\thidden={this.props.hidden}\n\t\t\t>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\ttype={this.props.type}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\treadOnly={this.props.readOnly}\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder={this.props.placeholder || ''}\n\t\t\t\t\t\tvalue={this.props.value || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tif (this.props.onChange) {\n\t\t\t\t\t\t\t\tthis.props.onChange(evt.target.value);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonKeyPress={(evt): void => {\n\t\t\t\t\t\t\tif (evt.key === 'Enter') {\n\t\t\t\t\t\t\t\tthis.props.onSubmit();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t{button}\n\t\t\t\t</div>\n\t\t\t</div>;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageInputSwitch.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport Help from './Help';\n\ninterface Props {\n\thidden?: boolean;\n\tdisabled?: boolean;\n\tlabel: string;\n\thelp: string;\n\ttype: string;\n\tplaceholder: string;\n\tvalue: string | number;\n\tchecked: boolean;\n\tdefaultValue: string;\n\tonChange: (state: boolean, val: string) => void;\n}\n\nconst css = {\n\tswitchLabel: {\n\t\tdisplay: 'inline-block',\n\t\tmarginBottom: 0,\n\t} as React.CSSProperties,\n\tinputLabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n};\n\nexport default class PageInputSwitch extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div hidden={this.props.hidden}>\n\t\t\t<label className=\"bp5-control bp5-switch\" style={css.switchLabel}>\n\t\t\t\t<input\n\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tchecked={!!this.props.value || this.props.checked}\n\t\t\t\t\tonChange={(): void => {\n\t\t\t\t\t\tif (!!this.props.value || this.props.checked) {\n\t\t\t\t\t\t\tthis.props.onChange(false, null);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.props.onChange(true, this.props.defaultValue);\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t{this.props.label}\n\t\t\t</label>\n\t\t\t<Help\n\t\t\t\ttitle={this.props.label}\n\t\t\t\tcontent={this.props.help}\n\t\t\t/>\n\t\t\t<label className=\"bp5-label\" style={css.inputLabel}>\n\t\t\t\t<input\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.input}\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\thidden={!this.props.value && !this.props.checked}\n\t\t\t\t\ttype={this.props.type}\n\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\tspellCheck={false}\n\t\t\t\t\tplaceholder={this.props.placeholder}\n\t\t\t\t\tvalue={this.props.value || ''}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tthis.props.onChange(true, evt.target.value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</label>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tmessage: string;\n\tchanged: boolean;\n\tdisabled: boolean;\n\tonSave: () => void;\n}\n\nconst css = {\n\tmessage: {\n\t\tmarginTop: '6px',\n\t} as React.CSSProperties,\n\tbox: {\n\t\tmarginTop: '15px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmarginLeft: '10px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tflexShrink: 0,\n\t} as React.CSSProperties,\n};\n\nexport default class PageNew extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div className=\"layout horizontal\" style={css.box}>\n\t\t\t<div className=\"flex\"/>\n\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t<span style={css.message} hidden={!this.props.message}>\n\t\t\t\t\t{this.props.message}\n\t\t\t\t</span>\n\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdisabled={!this.props.changed || this.props.disabled}\n\t\t\t\t\t\tonClick={this.props.onSave}\n\t\t\t\t\t>\n\t\t\t\t\t\tNew\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageNumInput.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport Help from './Help';\n\ninterface Props {\n\thidden?: boolean;\n\tdisabled?: boolean;\n\tmin?: number;\n\tmax?: number;\n\tminorStepSize?: number;\n\tstepSize?: number;\n\tmajorStepSize?: number;\n\tselectAllOnFocus?: true;\n\tplaceholder?: string;\n\tlabel: string;\n\thelp: string;\n\tvalue: number;\n\tonChange: (val: number) => void;\n}\n\nconst css = {\n\tlabel: {\n\t\tdisplay: 'inline-block',\n\t} as React.CSSProperties,\n};\n\nexport default class PageNumInput extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div hidden={this.props.hidden}>\n\t\t\t<label className=\"bp5-label no-select\" style={css.label}>\n\t\t\t\t{this.props.label}\n\t\t\t\t<Help\n\t\t\t\t\ttitle={this.props.label}\n\t\t\t\t\tcontent={this.props.help}\n\t\t\t\t/>\n\t\t\t\t<Blueprint.NumericInput\n\t\t\t\t\tallowNumericCharactersOnly={true}\n\t\t\t\t\tmin={this.props.min}\n\t\t\t\t\tminorStepSize={this.props.minorStepSize}\n\t\t\t\t\tmax={this.props.max}\n\t\t\t\t\tstepSize={this.props.stepSize}\n\t\t\t\t\tmajorStepSize={this.props.majorStepSize}\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tselectAllOnFocus={this.props.selectAllOnFocus}\n\t\t\t\t\tplaceholder={this.props.placeholder}\n\t\t\t\t\tonValueChange={(val: number): void => {\n\t\t\t\t\t\tif (this.props.max && val > this.props.max) {\n\t\t\t\t\t\t\tval = this.props.max;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.props.onChange(val);\n\t\t\t\t\t}}\n\t\t\t\t\tvalue={this.props.value}\n\t\t\t\t/>\n\t\t\t</label>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PagePanel.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tchildren?: React.ReactNode\n\tclassName?: string;\n\thidden?: boolean;\n}\n\nconst css = {\n\tpanel: {\n\t\tflex: 1,\n\t\tminWidth: '250px',\n\t\tpadding: '0 10px',\n\t} as React.CSSProperties,\n};\n\nexport default class PagePanel extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div\n\t\t\tclassName={this.props.className}\n\t\t\tstyle={css.panel}\n\t\t\thidden={this.props.hidden}\n\t\t>\n\t\t\t{this.props.children}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageSave.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tchildren?: React.ReactNode\n\tstyle?: React.CSSProperties;\n\tmessage: string;\n\tchanged: boolean;\n\tdisabled: boolean;\n\twrap?: boolean;\n\thidden?: boolean;\n\tlight?: boolean;\n\tonCancel: () => void;\n\tonSave: () => void;\n}\n\nconst css = {\n\tmessage: {\n\t\tmarginTop: '6px',\n\t} as React.CSSProperties,\n\tmessageWrap: {\n\t\tmarginTop: '6px',\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tbox: {\n\t\tmarginTop: '15px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmarginLeft: '10px',\n\t} as React.CSSProperties,\n\tbuttonWrap: {\n\t\tmarginLeft: '10px',\n\t\tmarginBottom: '10px',\n\t} as React.CSSProperties,\n\tbuttonWrapFirst: {\n\t\tmarginBottom: '10px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tflexShrink: 0,\n\t} as React.CSSProperties,\n};\n\nexport default class PageSave extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet style: React.CSSProperties = this.props.light ? null : css.box;\n\n\t\tif (this.props.style) {\n\t\t\tstyle = {\n\t\t\t\t...style,\n\t\t\t\t...this.props.style,\n\t\t\t};\n\t\t}\n\n\t\tlet containerClass = 'layout horizontal';\n\t\tlet buttonStyle: React.CSSProperties;\n\t\tlet buttonStyleFirst: React.CSSProperties;\n\t\tlet messageStyle: React.CSSProperties;\n\t\tif (this.props.wrap) {\n\t\t\tbuttonStyle = css.buttonWrap;\n\t\t\tbuttonStyleFirst = css.buttonWrapFirst;\n\t\t\tmessageStyle = css.messageWrap;\n\t\t} else {\n\t\t\tbuttonStyle = css.button;\n\t\t\tbuttonStyleFirst = css.button;\n\t\t\tmessageStyle = css.message;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName={'layout horizontal' + (this.props.wrap ? ' wrap': '')}\n\t\t\tstyle={style}\n\t\t\thidden={this.props.hidden && !this.props.children}\n\t\t>\n\t\t\t{this.props.children}\n\t\t\t<div className=\"flex\"/>\n\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t<span style={messageStyle} hidden={!this.props.message}>\n\t\t\t\t\t{this.props.message}\n\t\t\t\t</span>\n\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-icon-cross\"\n\t\t\t\t\t\tstyle={buttonStyleFirst}\n\t\t\t\t\t\thidden={this.props.hidden}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdisabled={!this.props.changed || this.props.disabled}\n\t\t\t\t\t\tonClick={this.props.onCancel}\n\t\t\t\t\t>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-tick\"\n\t\t\t\t\t\tstyle={buttonStyle}\n\t\t\t\t\t\thidden={this.props.hidden}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdisabled={!this.props.changed || this.props.disabled}\n\t\t\t\t\t\tonClick={this.props.onSave}\n\t\t\t\t\t>\n\t\t\t\t\t\tSave\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageSelect.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport Help from './Help';\n\ninterface Props {\n\tchildren?: React.ReactNode\n\thidden?: boolean;\n\tdisabled?: boolean;\n\tlabel: string;\n\thelp: string | JSX.Element;\n\tvalue: string;\n\tonChange: (val: string) => void;\n}\n\nconst css = {\n\tlabel: {\n\t\tdisplay: 'inline-block',\n\t} as React.CSSProperties,\n};\n\nexport default class PageSelect extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div hidden={this.props.hidden}>\n\t\t\t<label className=\"bp5-label\" style={css.label}>\n\t\t\t\t{this.props.label}\n\t\t\t\t<Help\n\t\t\t\t\ttitle={this.props.label}\n\t\t\t\t\tcontent={this.props.help}\n\t\t\t\t/>\n\t\t\t\t<div className=\"bp5-select\">\n\t\t\t\t\t<select\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\tvalue={this.props.value || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tthis.props.onChange(evt.target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{this.props.children}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t</label>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageSelectButton.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tchildren?: React.ReactNode\n\thidden?: boolean;\n\tlabel: string;\n\tvalue: string;\n\tdisabled?: boolean;\n\tbuttonClass?: string;\n\tonChange: (val: string) => void;\n\tonSubmit: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\tmarginBottom: '15px',\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\twidth: '100%',\n\t\tborderTopLeftRadius: '3px',\n\t\tborderBottomLeftRadius: '3px',\n\t} as React.CSSProperties,\n\tselectInner: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tselectBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class PageSelectButton extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet buttonClass = 'bp5-button';\n\t\tif (this.props.buttonClass) {\n\t\t\tbuttonClass += ' ' + this.props.buttonClass;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-control-group\"\n\t\t\tstyle={css.group}\n\t\t\thidden={this.props.hidden}\n\t\t>\n\t\t\t<div style={css.selectBox}>\n\t\t\t\t<div className=\"bp5-select\" style={css.select}>\n\t\t\t\t\t<select\n\t\t\t\t\t\tstyle={css.selectInner}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\tvalue={this.props.value || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tthis.props.onChange(evt.target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{this.props.children}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<button\n\t\t\t\tclassName={buttonClass}\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\tonClick={this.props.onSubmit}\n\t\t\t>{this.props.label}</button>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageSelectButtonConfirm.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Props {\n\tchildren?: React.ReactNode\n\thidden?: boolean;\n\tlabel: string;\n\tvalue: string;\n\tconfirmMsg?: string;\n\tdisabled?: boolean;\n\tbuttonClass?: string;\n\tprogressClassName?: string;\n\tonChange: (val: string) => void;\n\tonSubmit: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\tmarginBottom: '15px',\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\twidth: '100%',\n\t\tborderTopLeftRadius: '3px',\n\t\tborderBottomLeftRadius: '3px',\n\t} as React.CSSProperties,\n\tselectInner: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tselectBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class PageSelectButton extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet buttonClass = 'bp5-button';\n\t\tif (this.props.buttonClass) {\n\t\t\tbuttonClass += ' ' + this.props.buttonClass;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-control-group\"\n\t\t\tstyle={css.group}\n\t\t\thidden={this.props.hidden}\n\t\t>\n\t\t\t<div style={css.selectBox}>\n\t\t\t\t<div className=\"bp5-select\" style={css.select}>\n\t\t\t\t\t<select\n\t\t\t\t\t\tstyle={css.selectInner}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\tvalue={this.props.value || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tthis.props.onChange(evt.target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{this.props.children}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<ConfirmButton\n\t\t\t\tlabel={this.props.label}\n\t\t\t\tclassName={buttonClass}\n\t\t\t\tprogressClassName={this.props.progressClassName}\n\t\t\t\tconfirmMsg={this.props.confirmMsg}\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\tonConfirm={this.props.onSubmit}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageSelector.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from \"react\"\nimport * as Blueprint from \"@blueprintjs/core\"\nimport * as Icons from \"@blueprintjs/icons\"\nimport Help from \"./Help\"\nimport PageSelectButton from \"./PageSelectButton\"\n\ninterface Props {\n\thidden?: boolean\n\tdisabled?: boolean\n\ttitle: string\n\thelp: string\n\taddLabel: string\n\tmenuLabel: string\n\tlistMax?: number\n\tselected: Item[]\n\toptions: Item[]\n\ticon: JSX.Element\n\tonAdd: (id: string) => void\n\tonRemove: (id: string) => void\n}\n\ninterface State {\n\tselected: string\n}\n\nexport interface Item {\n\tid: string\n\tname: string\n}\n\nconst css = {\n\tlabel: {\n\t\tdisplay: 'inline-block',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tmenu: {\n\t\tmaxHeight: '400px',\n\t\toverflowY: \"auto\",\n\t} as React.CSSProperties,\n\tmenuOpen: {\n\t\tmarginLeft: '0',\n\t} as React.CSSProperties,\n\tmenuLabel: {\n\t} as React.CSSProperties,\n\tmenuRemove: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n}\n\nexport class PageSelector extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context)\n\t\tthis.state = {\n\t\t\tselected: \"\",\n\t\t}\n\t}\n\n\trender(): JSX.Element {\n\t\tlet itemsBox: JSX.Element\n\n\t\tif ((this.props.selected || []).length > (this.props.listMax || 6)) {\n\t\t\tconst itemMap = new Map<string, Array<Item>>();\n\n\t\t\t(this.props.selected || []).forEach((item) => {\n\t\t\t\tif (!itemMap.has(item.name)) {\n\t\t\t\t\titemMap.set(item.name, [])\n\t\t\t\t}\n\t\t\t\titemMap.get(item.name)!.push(item)\n\t\t\t})\n\n\t\t\tconst items: JSX.Element[] = Array\n\t\t\t\t.from(itemMap.keys())\n\t\t\t\t.sort((a, b) => a.localeCompare(b))\n\t\t\t\t.flatMap((name) =>\n\t\t\t\t\titemMap.get(name)!.map((item) =>\n\t\t\t\t\t\t<Blueprint.MenuItem\n\t\t\t\t\t\t\tkey={item.id}\n\t\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\t\tselected={false}\n\t\t\t\t\t\t\troleStructure=\"menuitem\"\n\t\t\t\t\t\t\ticon={<Icons.Remove\n\t\t\t\t\t\t\t\tstyle={css.menuRemove}\n\t\t\t\t\t\t\t/>}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tevt.stopPropagation()\n\t\t\t\t\t\t\t\tthis.props.onRemove(item.id)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\ttext={item.name}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)\n\t\t\t\t)\n\n\t\t\titemsBox = <Blueprint.Popover\n\t\t\t\tcontent={<Blueprint.Menu style={css.menu}>\n\t\t\t\t\t{items}\n\t\t\t\t</Blueprint.Menu>}\n\t\t\t\tplacement=\"bottom\"\n\t\t\t>\n\t\t\t\t<Blueprint.Button\n\t\t\t\t\talignText=\"left\"\n\t\t\t\t\ticon={this.props.icon}\n\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\ttext={this.props.menuLabel}\n\t\t\t\t\tstyle={css.menuOpen}\n\t\t\t\t/>\n\t\t\t</Blueprint.Popover>\n\t\t} else {\n\t\t\tconst itemMap = new Map<string, Array<Item>>();\n\n\t\t\t(this.props.selected || []).forEach((item) => {\n\t\t\t\tif (!itemMap.has(item.name)) {\n\t\t\t\t\titemMap.set(item.name, []);\n\t\t\t\t}\n\t\t\t\titemMap.get(item.name)!.push(item)\n\t\t\t})\n\n\t\t\tconst items: JSX.Element[] = Array\n\t\t\t\t.from(itemMap.keys())\n\t\t\t\t.sort((a, b) => a.localeCompare(b))\n\t\t\t\t.flatMap((name) =>\n\t\t\t\t\titemMap.get(name)!.map((item) =>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\t\t\tstyle={css.item}\n\t\t\t\t\t\t\tkey={item.id}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{item.name}\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\t\t\tthis.props.onRemove(item.id)\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)\n\t\t\t\t)\n\n\t\t\titemsBox = <div>{items}</div>\n\t\t}\n\n\t\tlet selects: JSX.Element[] = [];\n\t\tif ((this.props.options || []).length === 0) {\n\t\t\tselects.push(<option key=\"null\" value=\"\">None</option>)\n\t\t} else {\n\t\t\tconst optionsMap = new Map<string, Array<Item>>();\n\n\t\t\t(this.props.options || []).forEach((item) => {\n\t\t\t\tif (!optionsMap.has(item.name)) {\n\t\t\t\t\toptionsMap.set(item.name, [])\n\t\t\t\t}\n\t\t\t\toptionsMap.get(item.name)!.push(item)\n\t\t\t})\n\n\t\t\tselects = Array\n\t\t\t\t.from(optionsMap.keys())\n\t\t\t\t.sort((a, b) => a.localeCompare(b))\n\t\t\t\t.flatMap((name) =>\n\t\t\t\t\toptionsMap.get(name)!.map((item) =>\n\t\t\t\t\t\t<option key={item.id} value={item.id}>{item.name}</option>\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t}\n\n\t\treturn <div>\n\t\t\t<label\n\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\tstyle={css.label}\n\t\t\t\thidden={this.props.hidden}\n\t\t\t>\n\t\t\t\t{this.props.title}\n\t\t\t\t<Help\n\t\t\t\t\ttitle={this.props.title}\n\t\t\t\t\tcontent={this.props.help}\n\t\t\t\t/>\n\t\t\t\t<div>\n\t\t\t\t\t{itemsBox}\n\t\t\t\t</div>\n\t\t\t</label>\n\t\t\t<PageSelectButton\n\t\t\t\thidden={this.props.hidden}\n\t\t\t\tlabel={this.props.addLabel}\n\t\t\t\tvalue={this.state.selected}\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: val,\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t\tonSubmit={() => {\n\t\t\t\t\tlet id = this.state.selected\n\t\t\t\t\tif (!id && this.props.options) {\n\t\t\t\t\t\tid = this.props.options[0].id\n\t\t\t\t\t}\n\n\t\t\t\t\tif (id) {\n\t\t\t\t\t\tthis.props.onAdd(id)\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{selects}\n\t\t\t</PageSelectButton>\n\t\t</div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageSplit.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tchildren?: React.ReactNode\n}\n\nexport default class PageSplit extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div className=\"layout horizontal wrap\">\n\t\t\t{this.props.children}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageSwitch.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport Help from './Help';\n\ninterface Props {\n\thidden?: boolean;\n\tdisabled?: boolean;\n\tlabel: string;\n\thelp: string;\n\tchecked: boolean;\n\tonToggle: () => void;\n}\n\nconst css = {\n\tlabel: {\n\t\tdisplay: 'inline-block',\n\t} as React.CSSProperties,\n};\n\nexport default class PageSwitch extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div hidden={this.props.hidden}>\n\t\t\t<label className=\"bp5-control bp5-switch\" style={css.label}>\n\t\t\t\t<input\n\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tchecked={!!this.props.checked}\n\t\t\t\t\tonChange={this.props.onToggle}\n\t\t\t\t/>\n\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t{this.props.label}\n\t\t\t</label>\n\t\t\t<Help\n\t\t\t\ttitle={this.props.label}\n\t\t\t\tcontent={this.props.help}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PageTextArea.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport Help from './Help';\nimport * as Theme from '../Theme';\n\ninterface Props {\n\thidden?: boolean;\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\tlabel: string;\n\thelp: string;\n\tplaceholder: string;\n\trows: number;\n\tvalue: string;\n\tonChange: (val: string) => void;\n}\n\nconst css = {\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\ttextarea: {\n\t\twidth: '100%',\n\t\tresize: 'none',\n\t\tfontSize: Theme.monospaceSize,\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n};\n\nexport default class PageTextArea extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <label\n\t\t\tclassName=\"bp5-label\"\n\t\t\tstyle={css.label}\n\t\t\thidden={this.props.hidden}\n\t\t>\n\t\t\t{this.props.label}\n\t\t\t<Help\n\t\t\t\ttitle={this.props.label}\n\t\t\t\tcontent={this.props.help}\n\t\t\t/>\n\t\t\t<textarea\n\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\tstyle={css.textarea}\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\treadOnly={this.props.readOnly}\n\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\tspellCheck={false}\n\t\t\t\tplaceholder={this.props.placeholder}\n\t\t\t\trows={this.props.rows}\n\t\t\t\tvalue={this.props.value || ''}\n\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\tthis.props.onChange(evt.target.value);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</label>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Plan.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PlanTypes from '../types/PlanTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\nimport PlanDetailed from './PlanDetailed';\nimport * as SecretTypes from \"../types/SecretTypes\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tplan: PlanTypes.PlanRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Plan extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet plan = this.props.plan;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<PlanDetailed\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tsecrets={this.props.secrets}\n\t\t\t\t\tplan={this.props.plan}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet orgName = '';\n\t\tif (plan.organization) {\n\t\t\tlet org = CompletionStore.organization(plan.organization);\n\t\t\torgName = org ? org.name : plan.organization;\n\t\t} else {\n\t\t\torgName = 'Unknown Organization';\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{plan.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName={'bp5-icon-standard bp5-text-muted ' + (\n\t\t\t\t\t\tplan.organization ? 'bp5-icon-people' : 'bp5-icon-layers')}\n\t\t\t\t/>\n\t\t\t\t{orgName}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PlanDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PlanTypes from '../types/PlanTypes';\nimport * as PlanActions from '../actions/PlanActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport OrganizationsStore from \"../stores/OrganizationsStore\";\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport PageSelect from \"./PageSelect\";\nimport PageTextArea from \"./PageTextArea\";\nimport PlanStatement from \"./PlanStatement\";\nimport PlanEditor from \"./PlanEditor\";\nimport * as Constants from \"../Constants\";\nimport * as SecretTypes from \"../types/SecretTypes\";\nimport Help from \"./Help\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tplan: PlanTypes.PlanRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tplan: PlanTypes.Plan;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\twideGroup: {\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class PlanDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tplan: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet plan: any;\n\n\t\tif (this.state.changed) {\n\t\t\tplan = {\n\t\t\t\t...this.state.plan,\n\t\t\t};\n\t\t} else {\n\t\t\tplan = {\n\t\t\t\t...this.props.plan,\n\t\t\t};\n\t\t}\n\n\t\tplan[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tplan: plan,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPlanActions.commit(this.state.plan).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tplan: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPlanActions.remove(this.props.plan.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet plan: PlanTypes.Plan = this.state.plan ||\n\t\t\tthis.props.plan;\n\n\t\tlet hasOrganizations = false\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\thasOrganizations = true\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Plan\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this plan\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[plan.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Plan name.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={plan.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Plan comment.\"\n\t\t\t\t\t\tplaceholder=\"Plan comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={plan.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.plan.id || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\thelp=\"Organization for plan.\"\n\t\t\t\t\t\tvalue={plan.organization}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div className=\"flex\" style={css.wideGroup}>\n\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\tStatements\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Statements\"\n\t\t\t\t\t\t\tcontent=\"Plan statements.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PlanEditor\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tstatements={plan.statements}\n\t\t\t\t\t\tonChange={(statements) => {\n\t\t\t\t\t\t\tlet plan: PlanTypes.Plan;\n\n\t\t\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\t\t\tplan = {\n\t\t\t\t\t\t\t\t\t...this.state.plan,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tplan = {\n\t\t\t\t\t\t\t\t\t...this.props.plan,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tplan.statements = statements;\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\t\t\tplan: plan,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.plan && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tplan: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PlanEditor.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PlanTypes from '../types/PlanTypes';\nimport * as Theme from \"../Theme\";\nimport * as MonacoEditor from \"@monaco-editor/react\"\n\ninterface Props {\n\tdisabled?: boolean;\n\tstatements: PlanTypes.Statement[];\n\tonChange: (statements: PlanTypes.Statement[]) => void;\n}\n\nconst css = {\n\tgroup: {\n\t\tmargin: '5px 0 20px 0',\n\t} as React.CSSProperties,\n};\n\nexport default class PlanStatement extends React.Component<Props, {}> {\n\tonChange = (val: string): void => {\n\t\tlet curStatements = this.props.statements\n\t\tlet newStatements: PlanTypes.Statement[] = []\n\t\tlet lines = val.split(\"\\n\")\n\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tlet line = lines[i]\n\t\t\tif (!line) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlet newStatement: PlanTypes.Statement = {\n\t\t\t\tstatement: line,\n\t\t\t}\n\n\t\t\tlet curStatement = curStatements[i]\n\t\t\tif (curStatement) {\n\t\t\t\tnewStatement.id = curStatement.id\n\t\t\t}\n\n\t\t\tnewStatements.push(newStatement)\n\t\t}\n\n\t\tthis.props.onChange(newStatements);\n\t}\n\n\trender(): JSX.Element {\n\t\tlet statements = (this.props.statements || [])\n\t\tlet statementsStr: string[] = []\n\n\t\tfor (let statement of statements) {\n\t\t\tstatementsStr.push(statement.statement)\n\t\t}\n\t\tlet val = statementsStr.join(\"\\n\")\n\n\t\treturn <div className=\"layout horizontal\" style={css.group}>\n\t\t\t<MonacoEditor.Editor\n\t\t\t\theight=\"192px\"\n\t\t\t\twidth=\"100%\"\n\t\t\t\tdefaultLanguage=\"markdown\"\n\t\t\t\ttheme={Theme.getEditorTheme()}\n\t\t\t\tdefaultValue={val}\n\t\t\t\toptions={{\n\t\t\t\t\tfolding: false,\n\t\t\t\t\tfontSize: 12,\n\t\t\t\t\tfontFamily: Theme.monospaceFont,\n\t\t\t\t\tfontWeight: Theme.monospaceWeight,\n\t\t\t\t\ttabSize: 4,\n\t\t\t\t\tdetectIndentation: false,\n\t\t\t\t\tscrollBeyondLastLine: false,\n\t\t\t\t\tminimap: {\n\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t},\n\t\t\t\t\twordWrap: \"on\",\n\t\t\t\t\tautomaticLayout: true,\n\t\t\t\t}}\n\t\t\t\tonChange={(val): void => {\n\t\t\t\t\tthis.onChange(val)\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PlanNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as PlanActions from '../actions/PlanActions';\nimport PageInput from './PageInput';\nimport PageCreate from './PageCreate';\nimport PageSelect from './PageSelect';\nimport * as SecretTypes from \"../types/SecretTypes\";\nimport * as PlanTypes from \"../types/PlanTypes\";\nimport * as Constants from \"../Constants\";\nimport OrganizationsStore from \"../stores/OrganizationsStore\";\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tplan: PlanTypes.Plan;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class PlanNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tplan: {},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet plan: any = {\n\t\t\t...this.state.plan,\n\t\t};\n\n\t\tplan[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tplan: plan,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet plan: any = {\n\t\t\t...this.state.plan,\n\t\t};\n\n\t\tif (this.props.organizations.length && !plan.organization) {\n\t\t\tplan.organization = this.props.organizations[0].id;\n\t\t}\n\n\t\tPlanActions.create(plan).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Plan created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet plan = this.state.plan;\n\t\tif (this.props.organizations.length && !plan.organization) {\n\t\t\tplan.organization = this.props.organizations[0].id;\n\t\t}\n\n\t\tlet hasOrganizations = false\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\thasOrganizations = true\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Plan name.\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={plan.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Plan comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Plan comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={plan.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for plan.\"\n\t\t\t\t\t\t\tvalue={plan.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.plan}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PlanStatement.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PlanTypes from '../types/PlanTypes';\nimport * as Theme from \"../Theme\";\nimport * as MonacoEditor from \"@monaco-editor/react\"\n\ninterface Props {\n\tdisabled?: boolean;\n\tstatement: PlanTypes.Statement;\n\tonChange: (statement: PlanTypes.Statement) => void;\n\tonRemove: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\ttextarea: {\n\t\twidth: '100%',\n\t\tresize: 'none',\n\t\tfontSize: Theme.monospaceSize,\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n};\n\nexport default class PlanStatement extends React.Component<Props, {}> {\n\tclone(): PlanTypes.Statement {\n\t\treturn {\n\t\t\t...this.props.statement,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet statement = this.props.statement;\n\n\t\treturn <div className=\"bp5-control-group layout horizontal\" style={css.group}>\n\t\t\t<textarea\n\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\tstyle={css.textarea}\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\tspellCheck={false}\n\t\t\t\trows={3}\n\t\t\t\tvalue={statement.statement || ''}\n\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.statement = evt.target.value;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Plans.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PlanTypes from '../types/PlanTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport PlansStore from '../stores/PlansStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as PlanActions from '../actions/PlanActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Plan from './Plan';\nimport PlansFilter from './PlansFilter';\nimport PlansPage from './PlansPage';\nimport PlanNew from './PlanNew';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\nimport * as SecretTypes from \"../types/SecretTypes\";\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tplans: PlanTypes.PlansRo;\n\tfilter: PlanTypes.Filter;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tsecrets: SecretTypes.SecretsRo;\n\torganization: string;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tgroupBox: {\n\t\tmargin: '16px 0 0 8px',\n\t\twidth: '100%',\n\t\tmaxWidth: '200px',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tselectFirst: {\n\t\twidth: '100%',\n\t\tborderTopLeftRadius: '3px',\n\t\tborderBottomLeftRadius: '3px',\n\t} as React.CSSProperties,\n\tselectInner: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tselectBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Plans extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tplans: PlansStore.plans,\n\t\t\tfilter: PlansStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tsecrets: CompletionStore.secrets,\n\t\t\torganization: '',\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tPlansStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tPlanActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tPlansStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet plans = PlansStore.plans;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tplans.forEach((plan: PlanTypes.Plan): void => {\n\t\t\tif (curSelected[plan.id]) {\n\t\t\t\tselected[plan.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[plan.id]) {\n\t\t\t\topened[plan.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tplans: plans,\n\t\t\tfilter: PlansStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tsecrets: CompletionStore.secrets,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPlanActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet plansDom: JSX.Element[] = [];\n\n\t\tlet hasOrganizations = false;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.state.organizations.length) {\n\t\t\thasOrganizations = true;\n\t\t\tfor (let organization of this.state.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey=\"null\"\n\t\t\t\t\tvalue=\"\"\n\t\t\t\t>No Organizations</option>,\n\t\t\t);\n\t\t}\n\n\t\tthis.state.plans.forEach((\n\t\t\t\tplan: PlanTypes.PlanRo): void => {\n\t\t\tplansDom.push(<Plan\n\t\t\t\tkey={plan.id}\n\t\t\t\tplan={plan}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tsecrets={this.state.secrets}\n\t\t\t\tselected={!!this.state.selected[plan.id]}\n\t\t\t\topen={!!this.state.opened[plan.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet plans = this.state.plans;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < plans.length; i++) {\n\t\t\t\t\t\t\tlet usr = plans[i];\n\n\t\t\t\t\t\t\tif (usr.id === plan.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[plans[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: plan.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[plan.id]) {\n\t\t\t\t\t\tdelete selected[plan.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[plan.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: plan.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[plan.id]) {\n\t\t\t\t\t\tdelete opened[plan.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[plan.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let planId of Object.keys(this.state.selected)) {\n\t\t\tlet plan = PlansStore.plan(planId);\n\t\t\tif (plan) {\n\t\t\t\tselectedNames.push(plan.name || planId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(planId);\n\t\t\t}\n\t\t}\n\n\t\tlet newDiskDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewDiskDom = <PlanNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tsecrets={this.state.secrets}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Plans</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tPlanActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tPlanActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected plans\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<PlansFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tPlanActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newDiskDom}\n\t\t\t\t\t{plansDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!plansDom.length}\n\t\t\t\ticonClass=\"bp5-icon-map-marker\"\n\t\t\t\ttitle=\"No plans\"\n\t\t\t\tdescription=\"Add a new plan to get started.\"\n\t\t\t/>\n\t\t\t<PlansPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PlansFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as PlanTypes from '../types/PlanTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: PlanTypes.Filter;\n\tonFilter: (filter: PlanTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class PlansFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Plan ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PlansPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport PlansStore from '../stores/PlansStore';\nimport * as PlanActions from '../actions/PlanActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class PlansPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: PlansStore.page,\n\t\t\tpageCount: PlansStore.pageCount,\n\t\t\tpages: PlansStore.pages,\n\t\t\tcount: PlansStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tPlansStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tPlansStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: PlansStore.page,\n\t\t\tpageCount: PlansStore.pageCount,\n\t\t\tpages: PlansStore.pages,\n\t\t\tcount: PlansStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPlanActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPlanActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPlanActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPlanActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPlanActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Pod.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as PodTypes from '../types/PodTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tpod: PodTypes.PodRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tcursor: 'pointer',\n\t\tmargin: '5px 0',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tpadding: '6px',\n\t\tlineHeight: '1.3',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Pod extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet pod = this.props.pod;\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet orgName = '';\n\t\tif (!MiscUtils.objectIdNil(pod.organization)) {\n\t\t\tlet org = CompletionStore.organization(pod.organization);\n\t\t\torgName = org ? org.name : pod.organization;\n\t\t} else {\n\t\t\torgName = 'Node Pod';\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className={this.props.open ? \"bp5-callout\" : \"\"} style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{pod.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodDeploy.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as BpSelect from '@blueprintjs/select';\nimport * as Icons from '@blueprintjs/icons';\nimport * as PodTypes from '../types/PodTypes';\nimport * as PodActions from '../actions/PodActions';\nimport * as Alert from '../Alert';\nimport * as Theme from '../Theme';\nimport * as MiscUtils from '../utils/MiscUtils';\n\ninterface Props {\n\tpod: PodTypes.PodRo;\n\tunit: PodTypes.PodUnit;\n\tcommits: PodTypes.Commit[];\n}\n\ninterface State {\n\tdialog: boolean;\n\tdisabled: boolean;\n\tspecId: string;\n\tdeployCommit: PodTypes.Commit;\n\tcount: number;\n}\n\nconst css = {\n\tdialog: {\n\t\twidth: '430px',\n\t\tposition: 'absolute',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmargin: '18px 0 0 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tsettingsOpen: {\n\t\tmarginLeft: '10px',\n\t} as React.CSSProperties,\n\tcommit: {\n\t\tfontFamily: Theme.monospaceFont,\n\t} as React.CSSProperties,\n\tmuted: {\n\t\topacity: 0.75,\n\t} as React.CSSProperties,\n\tcommitButton: {\n\t\tmarginTop: \"5px\",\n\t\twidth: '400px',\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n\tcommitsMenu: {\n\t\tmaxHeight: '400px',\n\t\toverflowY: \"auto\",\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n};\n\nexport default class PodDeploy extends React.Component<Props, State> {\n\tinterval: NodeJS.Timer;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdialog: false,\n\t\t\tdisabled: false,\n\t\t\tspecId: \"\",\n\t\t\tdeployCommit: null,\n\t\t\tcount: 1,\n\t\t};\n\t}\n\n\topenDialog = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdialog: true,\n\t\t});\n\t}\n\n\tcloseDialog = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdialog: false,\n\t\t\tspecId: \"\",\n\t\t\tcount: 1,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet deployCommit = this.state.deployCommit?.id ||\n\t\t\tthis.props.commits?.[0]?.id\n\n\t\tPodActions.deployUnit(\n\t\t\t\tthis.props.pod.id, this.props.unit.id,\n\t\t\t\tdeployCommit, this.state.count).then((): void => {\n\n\t\t\tAlert.success('Successfully created deployments');\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdialog: false,\n\t\t\t\tdisabled: false,\n\t\t\t\tspecId: \"\",\n\t\t\t\tcount: 1,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trenderDeploy(): JSX.Element {\n\t\tlet commitSelect: JSX.Element\n\t\tif (this.props.commits) {\n\t\t\tlet deployCommit = this.state.deployCommit || this.props.commits?.[0]\n\t\t\tlet selectButtonClass = \"\"\n\t\t\tlet selectLabelClass = \"\"\n\t\t\tlet selectLabelStyle: React.CSSProperties\n\t\t\tif (deployCommit && deployCommit.id === this.props.commits?.[0]?.id) {\n\t\t\t\tselectButtonClass = \"bp5-text-intent-success\"\n\t\t\t\tselectLabelStyle = css.muted\n\t\t\t} else {\n\t\t\t\tselectLabelClass = \"bp5-text-muted\"\n\t\t\t}\n\n\t\t\tlet commitMenuItems: JSX.Element[] = []\n\t\t\tthis.props.commits.forEach((commit, index): void => {\n\t\t\t\tlet className = \"\"\n\t\t\t\tlet styles: React.CSSProperties\n\t\t\t\tlet disabled = false\n\t\t\t\tlet selected = false\n\n\t\t\t\tif (this.state.deployCommit?.id == commit.id ||\n\t\t\t\t\t(!this.state.deployCommit && index === 0)) {\n\t\t\t\t\tclassName = \"bp5-text-intent-primary bp5-intent-primary\"\n\t\t\t\t\tstyles = css.muted\n\t\t\t\t\tselected = true\n\t\t\t\t} else if (index === 0) {\n\t\t\t\t\tclassName = \"bp5-text-intent-success bp5-intent-success\"\n\t\t\t\t\tstyles = css.muted\n\t\t\t\t}\n\n\t\t\t\tcommitMenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\tkey={\"diff-\" + commit.id}\n\t\t\t\t\tdisabled={disabled || this.state.disabled}\n\t\t\t\t\tselected={selected}\n\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t\ticon={<Icons.GitCommit\n\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t/>}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tdeployCommit: commit,\n\t\t\t\t\t\t})\n\t\t\t\t\t}}\n\t\t\t\t\ttext={commit.id.substring(12)}\n\t\t\t\t\ttextClassName={className}\n\t\t\t\t\tlabelElement={<span\n\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\tstyle={styles}\n\t\t\t\t\t>{MiscUtils.formatDateLocal(commit.timestamp)}</span>}\n\t\t\t\t/>)\n\t\t\t})\n\n\t\t\tcommitSelect = <Blueprint.Popover\n\t\t\t\tcontent={<div>\n\t\t\t\t\t<Blueprint.Menu style={css.commitsMenu}>\n\t\t\t\t\t\t{commitMenuItems}\n\t\t\t\t\t</Blueprint.Menu>\n\t\t\t\t</div>}\n\t\t\t\tplacement=\"bottom\"\n\t\t\t>\n\t\t\t\t<Blueprint.Button\n\t\t\t\t\talignText=\"left\"\n\t\t\t\t\ticon={<Icons.GitCommit/>}\n\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\tstyle={css.commitButton}\n\t\t\t\t\ttextClassName={selectButtonClass}\n\t\t\t\t>\n\t\t\t\t\t<span>{deployCommit?.id.substring(12)}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName={selectLabelClass}\n\t\t\t\t\t\tstyle={selectLabelStyle}\n\t\t\t\t\t>\n\t\t\t\t\t\t{\" \" + MiscUtils.formatDateLocal(deployCommit?.timestamp)}\n\t\t\t\t\t</span>\n\t\t\t\t</Blueprint.Button>\n\t\t\t</Blueprint.Popover>\n\t\t}\n\n\t\tlet dialogElem = <Blueprint.Dialog\n\t\t\ttitle=\"Create Deployment\"\n\t\t\tstyle={css.dialog}\n\t\t\tisOpen={this.state.dialog}\n\t\t\tusePortal={true}\n\t\t\tportalContainer={document.body}\n\t\t\tonClose={this.closeDialog}\n\t\t>\n\t\t\t<div className=\"bp5-dialog-body\">\n\t\t\t\t<label\n\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\tstyle={css.label}\n\t\t\t\t>\n\t\t\t\t\tTarget Commit\n\t\t\t\t</label>\n\t\t\t\t<div\n\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{commitSelect}\n\t\t\t\t</div>\n\t\t\t\t<label\n\t\t\t\t\tclassName=\"bp5-label no-select\"\n\t\t\t\t\tstyle={css.label}\n\t\t\t\t>\n\t\t\t\t\tDeployment Count\n\t\t\t\t\t<Blueprint.NumericInput\n\t\t\t\t\t\tvalue={this.state.count}\n\t\t\t\t\t\tonValueChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tcount: val,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-dialog-footer\">\n\t\t\t\t<div className=\"bp5-dialog-footer-actions\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={this.closeDialog}\n\t\t\t\t\t>Cancel</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonClick={this.onCreate}\n\t\t\t\t\t>Create</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Blueprint.Dialog>\n\n\t\treturn <div>\n\t\t\t<Blueprint.MenuItem\n\t\t\t\tkey=\"menu-new-deployment\"\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\ticon={<Icons.Plus/>}\n\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\tevt.preventDefault()\n\t\t\t\t\tevt.stopPropagation()\n\t\t\t\t\tthis.openDialog()\n\t\t\t\t}}\n\t\t\t\ttext=\"New Deployment\"\n\t\t\t/>\n\t\t\t{dialogElem}\n\t\t</div>\n\t}\n\n\trenderImage(): JSX.Element {\n\t\tlet commitSelect: JSX.Element\n\t\tif (this.props.commits) {\n\t\t\tlet deployCommit = this.state.deployCommit || this.props.commits?.[0]\n\t\t\tlet selectButtonClass = \"\"\n\t\t\tlet selectLabelClass = \"\"\n\t\t\tlet selectLabelStyle: React.CSSProperties\n\t\t\tif (deployCommit && deployCommit.id === this.props.commits?.[0]?.id) {\n\t\t\t\tselectButtonClass = \"bp5-text-intent-success\"\n\t\t\t\tselectLabelStyle = css.muted\n\t\t\t} else {\n\t\t\t\tselectLabelClass = \"bp5-text-muted\"\n\t\t\t}\n\n\t\t\tlet commitMenuItems: JSX.Element[] = []\n\t\t\tthis.props.commits.forEach((commit, index): void => {\n\t\t\t\tlet className = \"\"\n\t\t\t\tlet styles: React.CSSProperties\n\t\t\t\tlet disabled = false\n\t\t\t\tlet selected = false\n\n\t\t\t\tif (this.state.deployCommit?.id == commit.id ||\n\t\t\t\t\t(!this.state.deployCommit && index === 0)) {\n\n\t\t\t\t\tclassName = \"bp5-text-intent-primary bp5-intent-primary\"\n\t\t\t\t\tstyles = css.muted\n\t\t\t\t\tselected = true\n\t\t\t\t} else if (index === 0) {\n\t\t\t\t\tclassName = \"bp5-text-intent-success bp5-intent-success\"\n\t\t\t\t\tstyles = css.muted\n\t\t\t\t}\n\n\t\t\t\tcommitMenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\tkey={\"diff-\" + commit.id}\n\t\t\t\t\tdisabled={disabled || this.state.disabled}\n\t\t\t\t\tselected={selected}\n\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t\ticon={<Icons.GitCommit\n\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t/>}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tdeployCommit: commit,\n\t\t\t\t\t\t})\n\t\t\t\t\t}}\n\t\t\t\t\ttext={commit.id.substring(12)}\n\t\t\t\t\ttextClassName={className}\n\t\t\t\t\tlabelElement={<span\n\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\tstyle={styles}\n\t\t\t\t\t>{MiscUtils.formatDateLocal(commit.timestamp)}</span>}\n\t\t\t\t/>)\n\t\t\t})\n\n\t\t\tcommitSelect = <Blueprint.Popover\n\t\t\t\tcontent={<div>\n\t\t\t\t\t<Blueprint.Menu style={css.commitsMenu}>\n\t\t\t\t\t\t{commitMenuItems}\n\t\t\t\t\t</Blueprint.Menu>\n\t\t\t\t</div>}\n\t\t\t\tplacement=\"bottom\"\n\t\t\t>\n\t\t\t\t<Blueprint.Button\n\t\t\t\t\talignText=\"left\"\n\t\t\t\t\ticon={<Icons.GitCommit/>}\n\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\tstyle={css.commitButton}\n\t\t\t\t\ttextClassName={selectButtonClass}\n\t\t\t\t>\n\t\t\t\t\t<span>{deployCommit?.id.substring(12)}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName={selectLabelClass}\n\t\t\t\t\t\tstyle={selectLabelStyle}\n\t\t\t\t\t>\n\t\t\t\t\t\t{\" \" + MiscUtils.formatDateLocal(deployCommit?.timestamp)}\n\t\t\t\t\t</span>\n\t\t\t\t</Blueprint.Button>\n\t\t\t</Blueprint.Popover>\n\t\t}\n\n\t\tlet dialogElem = <Blueprint.Dialog\n\t\t\ttitle=\"Create Image Build\"\n\t\t\tstyle={css.dialog}\n\t\t\tisOpen={this.state.dialog}\n\t\t\tusePortal={true}\n\t\t\tportalContainer={document.body}\n\t\t\tonClose={this.closeDialog}\n\t\t>\n\t\t\t<div className=\"bp5-dialog-body\">\n\t\t\t\t<label\n\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\tstyle={css.label}\n\t\t\t\t>\n\t\t\t\t\tTarget Commit\n\t\t\t\t</label>\n\t\t\t\t<div\n\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{commitSelect}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-dialog-footer\">\n\t\t\t\t<div className=\"bp5-dialog-footer-actions\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={this.closeDialog}\n\t\t\t\t\t>Cancel</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonClick={this.onCreate}\n\t\t\t\t\t>Create</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Blueprint.Dialog>\n\n\t\treturn <div>\n\t\t\t<Blueprint.MenuItem\n\t\t\t\tkey=\"menu-new-deployment\"\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\ticon={<Icons.Plus/>}\n\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\tevt.preventDefault()\n\t\t\t\t\tevt.stopPropagation()\n\t\t\t\t\tthis.openDialog()\n\t\t\t\t}}\n\t\t\t\ttext=\"New Build\"\n\t\t\t/>\n\t\t\t{dialogElem}\n\t\t</div>\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.unit.kind === \"image\") {\n\t\t\treturn this.renderImage()\n\t\t} else {\n\t\t\treturn this.renderDeploy()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodDeployment.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from \"react\"\nimport * as Blueprint from \"@blueprintjs/core\"\nimport * as BpSelect from '@blueprintjs/select';\nimport * as Icons from '@blueprintjs/icons';\nimport * as Theme from '../Theme';\nimport * as PodTypes from \"../types/PodTypes\"\nimport * as PodActions from \"../actions/PodActions\"\nimport * as InstanceActions from '../actions/InstanceActions';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport PodDeploymentEdit from \"./PodDeploymentEdit\";\nimport PageInfo from \"./PageInfo\"\nimport Editor from \"./Editor\"\nimport * as Router from \"../Router\";\nimport * as PageInfos from './PageInfo';\n\ninterface Props {\n\thidden: boolean\n\tdisabled: boolean\n\tselected: boolean\n\tcommitMap: Record<string, PodTypes.Commit>\n\tdeployment: PodTypes.Deployment\n\tonSelect: (shift: boolean) => void\n}\n\ninterface State {\n\tdisabled: boolean\n\tlogsResource: string\n\teditOpen: boolean\n}\n\nconst css = {\n\tcontainer: {\n\t\theight: \"900px\",\n\t\toverflowY: \"auto\",\n\t\tmarginBottom: \"10px\",\n\t} as React.CSSProperties,\n\tbox: {\n\t\tfontSize: \"11px\",\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n\tboxEmpty: {\n\t\tflex: 1,\n\t\tmargin: \"20px 10px 20px 10px\",\n\t} as React.CSSProperties,\n\tcard: {\n\t\tpadding: \"5px 5px 3px 5px\",\n\t} as React.CSSProperties,\n\tcardInactive: {\n\t\tpadding: \"5px 5px 3px 5px\",\n\t\topacity: 0.6,\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: \"0 0 0 0\",\n\t} as React.CSSProperties,\n\tcheckBox: {\n\t\tdisplay: \"flex\",\n\t\tpaddingBottom: \"2px\",\n\t} as React.CSSProperties,\n\tselect: {\n\t\tdisplay: \"inline\",\n\t} as React.CSSProperties,\n\tinfo: {\n\t\tmarginBottom: \"0px\",\n\t\tfontSize: \"11px\",\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n\titemFirst: {\n\t\tflex: \"1 1 auto\",\n\t\tminWidth: \"100px\",\n\t\tmaxWidth: \"170px\",\n\t\tmargin: \"0 5px 0 -4px\",\n\t} as React.CSSProperties,\n\titem: {\n\t\tflex: \"1 1 auto\",\n\t\twidth: 0,\n\t\tmargin: \" 0 5px\",\n\t} as React.CSSProperties,\n\titemSmall: {\n\t\tflex: \"0.7 1 auto\",\n\t\twidth: 0,\n\t\tmargin: \" 0 5px\",\n\t} as React.CSSProperties,\n\titemMedium: {\n\t\tflex: \"0.8 1 auto\",\n\t\twidth: 0,\n\t\tmargin: \" 0 5px\",\n\t} as React.CSSProperties,\n\titemFull: {\n\t\tflex: \"2 1 auto\",\n\t\twidth: 0,\n\t\tmargin: \" 0 5px\",\n\t} as React.CSSProperties,\n\titemLast: {\n\t\tflex: \"0 1 auto\",\n\t\tminWidth: \"123px\",\n\t\tmargin: \" 0 5px\",\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\thoverInfo: {\n\t\tpadding: \"10px\",\n\t\tfontSize: \"12px\",\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n\tcardButton: {\n\t\tmarginTop: \"1px\",\n\t\tmarginRight: \"5px\",\n\t} as React.CSSProperties,\n\tcardButtonRight: {\n\t\tmarginTop: \"6px\",\n\t} as React.CSSProperties,\n\teditor: {\n\t\tmarginTop: \"5px\",\n\t} as React.CSSProperties,\n}\n\nexport default class PodDeployment extends React.Component<Props, State> {\n\teditorRef = React.createRef<Editor>()\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context)\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tlogsResource: \"\",\n\t\t\teditOpen: false,\n\t\t}\n\t}\n\n\tonLogsClose = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tlogsResource: \"\",\n\t\t})\n\t}\n\n\tonEditToggle = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\teditOpen: !this.state.editOpen,\n\t\t})\n\t}\n\n\tonEditClose = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\teditOpen: false,\n\t\t})\n\t}\n\n\trenderJournal: BpSelect.ItemRenderer<PodTypes.Journal> = (jrnl,\n\t\t{handleClick, handleFocus, modifiers, query, index}): JSX.Element => {\n\n\t\tlet className = \"\"\n\t\tlet selected = false\n\t\tif (this.state.logsResource === jrnl.key) {\n\t\t\tclassName = \"bp5-text-intent-primary bp5-intent-primary\"\n\t\t\tselected = true\n\t\t} else if (index === 0) {\n\t\t\tclassName = \"\"\n\t\t}\n\t\treturn <Blueprint.MenuItem\n\t\t\tkey={`journal-${jrnl.index}`}\n\t\t\tselected={selected}\n\t\t\tdisabled={this.state.disabled}\n\t\t\troleStructure=\"listoption\"\n\t\t\ticon={<Icons.Document\n\t\t\t\tclassName={className}\n\t\t\t/>}\n\t\t\tonFocus={handleFocus}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tevt.preventDefault()\n\t\t\t\tevt.stopPropagation()\n\t\t\t\thandleClick(evt)\n\t\t\t}}\n\t\t\ttext={jrnl.key}\n\t\t\ttextClassName={className}\n\t\t/>\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.hidden) {\n\t\t\treturn <div></div>\n\t\t}\n\n\t\tlet deployment = this.props.deployment\n\n\t\tlet label = \"deployment\"\n\t\tlet labelTitle = \"Deployment\"\n\t\tif (deployment.kind == \"image\") {\n\t\t\tlabel = \"image\"\n\t\t\tlabelTitle = \"Image\"\n\t\t}\n\n\t\tlet cardStyle = css.card\n\t\tif (deployment.state === \"archived\") {\n\t\t\tcardStyle = css.cardInactive\n\t\t}\n\n\t\tlet stateValue = MiscUtils.capitalize(deployment.state) || \"-\"\n\t\tlet stateClass = \"\"\n\t\tswitch (deployment.state) {\n\t\t\tcase \"deployed\":\n\t\t\t\tstateClass = \"bp5-text-intent-success\"\n\t\t\t\tbreak\n\t\t\tcase \"reserved\":\n\t\t\t\tstateClass = \"bp5-text-intent-primary\"\n\t\t\t\tbreak\n\t\t\tcase \"archived\":\n\t\t\t\tstateClass = \"bp5-text-intent-warning\"\n\t\t\t\tbreak\n\t\t}\n\n\t\tswitch (deployment.action) {\n\t\t\tcase \"migrate\":\n\t\t\t\tstateValue = \"Migrating\"\n\t\t\t\tstateClass = \"bp5-text-intent-warning\"\n\t\t\t\tbreak\n\t\t\tcase \"restore\":\n\t\t\t\tstateValue = \"Restoring\"\n\t\t\t\tstateClass = \"bp5-text-intent-primary\"\n\t\t\t\tbreak\n\t\t\tcase \"destroy\":\n\t\t\t\tstateValue = \"Destroying\"\n\t\t\t\tstateClass = \"bp5-text-intent-danger\"\n\t\t\t\tbreak\n\t\t\tcase \"archive\":\n\t\t\t\tstateValue = \"Archiving\"\n\t\t\t\tstateClass = \"bp5-text-intent-warning\"\n\t\t\t\tbreak\n\t\t}\n\n\t\tlet statusClass = \"\"\n\t\tswitch (deployment.instance_status) {\n\t\t\tcase \"Running\":\n\t\t\t\tstatusClass = \"bp5-text-intent-success\"\n\t\t\t\tbreak\n\t\t\tcase \"Starting\":\n\t\t\tcase \"Restarting\":\n\t\t\tcase \"Updating\":\n\t\t\tcase \"Provisioning\":\n\t\t\t\tstatusClass = \"bp5-text-intent-primary\"\n\t\t\t\tbreak\n\t\t\tcase \"Failed\":\n\t\t\tcase \"Stopping\":\n\t\t\tcase \"Destroying\":\n\t\t\tcase \"Stopped\":\n\t\t\t\tstatusClass = \"bp5-text-intent-danger\"\n\t\t\t\tbreak\n\t\t}\n\n\t\tlet instData = deployment.instance_data || {}\n\n\t\tlet publicIps = instData.public_ips\n\t\tif (!publicIps || !publicIps.length) {\n\t\t\tpublicIps = [\"-\"]\n\t\t}\n\n\t\tlet publicIps6 = instData.public_ips6\n\t\tif (!publicIps6 || !publicIps6.length) {\n\t\t\tpublicIps6 = [\"-\"]\n\t\t}\n\n\t\tlet privateIps = instData.private_ips\n\t\tif (!privateIps || !privateIps.length) {\n\t\t\tprivateIps = [\"-\"]\n\t\t}\n\n\t\tlet privateIps6 = instData.private_ips6\n\t\tif (!privateIps6 || !privateIps6.length) {\n\t\t\tprivateIps6 = [\"-\"]\n\t\t}\n\n\t\tlet commitClass = deployment.spec_offset === 0 ?\n\t\t\t\"bp5-text-intent-success\" :\t\"bp5-text-intent-danger\"\n\n\t\tlet specHover = <div\n\t\t\tclassName=\"bp5-content-popover\"\n\t\t\tstyle={css.hoverInfo}\n\t\t>\n\t\t\t<PageInfo\n\t\t\t\tcompact={true}\n\t\t\t\tstyle={css.info}\n\t\t\t\tfields={[\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"Commit\",\n\t\t\t\t\t\tvalue: deployment.spec.substring(12) || \"-\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"Timestamp\",\n\t\t\t\t\t\tvalue: MiscUtils.formatDateLocal(deployment.spec_timestamp),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"Behind\",\n\t\t\t\t\t\tvalue: deployment.spec_offset,\n\t\t\t\t\t\tvalueClass: commitClass,\n\t\t\t\t\t},\n\t\t\t\t]}\n\t\t\t/>\n\t\t</div>\n\n\t\tlet domainInfo: PageInfos.Field\n\t\tif (deployment?.domain_data?.records) {\n\t\t\tlet domainFields: PageInfos.Field[] = [];\n\n\t\t\tfor (let rec of deployment.domain_data.records) {\n\t\t\t\tdomainFields.push({\n\t\t\t\t\tlabel: rec.domain,\n\t\t\t\t\tvalue: rec.value,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tlet domainHover = <div\n\t\t\t\tclassName=\"bp5-content-popover\"\n\t\t\t\tstyle={css.hoverInfo}\n\t\t\t>\n\t\t\t\t<PageInfo\n\t\t\t\t\tcompact={true}\n\t\t\t\t\tstyle={css.info}\n\t\t\t\t\tfields={domainFields}\n\t\t\t\t/>\n\t\t\t</div>\n\n\t\t\tdomainInfo = {\n\t\t\t\tlabel: \"Domains\",\n\t\t\t\tvalue: \"Registered\",\n\t\t\t\tvalueClass: \"bp5-text-intent-success\",\n\t\t\t\thover: domainHover,\n\t\t\t}\n\t\t}\n\n\t\tlet deplyStatus = MiscUtils.capitalize(deployment.status) || \"-\"\n\t\tlet heartbeatClass = \"\"\n\t\tif (deployment.status === \"healthy\") {\n\t\t\theartbeatClass = \"bp5-text-intent-success\"\n\t\t} else {\n\t\t\theartbeatClass = \"bp5-text-intent-danger\"\n\t\t}\n\n\t\tlet agentStatus = MiscUtils.capitalize(\n\t\t\tdeployment.instance_guest_status) || \"-\"\n\t\tlet agentClass = heartbeatClass\n\t\tswitch (deployment.instance_guest_status) {\n\t\t\tcase \"initializing\":\n\t\t\t\tagentClass = \"bp5-text-intent-primary\"\n\t\t\t\tbreak\n\t\t\tcase \"reloading_clean\":\n\t\t\t\tagentStatus = \"Reloading\"\n\t\t\t\tagentClass = \"bp5-text-intent-primary\"\n\t\t\t\tbreak\n\t\t\tcase \"reloading_fault\":\n\t\t\t\tagentStatus = \"Reloading\"\n\t\t\t\tagentClass = \"bp5-text-intent-danger\"\n\t\t\t\tbreak\n\t\t\tcase \"fault\":\n\t\t\t\tagentClass = \"bp5-text-intent-danger\"\n\t\t\t\tbreak\n\t\t}\n\n\t\tlet heartbeatHover = <div\n\t\t\tclassName=\"bp5-content-popover\"\n\t\t\tstyle={css.hoverInfo}\n\t\t>\n\t\t\t<PageInfo\n\t\t\t\tcompact={true}\n\t\t\t\tstyle={css.info}\n\t\t\t\tfields={[\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"Status\",\n\t\t\t\t\t\tvalue: deplyStatus,\n\t\t\t\t\t\tvalueClass: heartbeatClass,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: \"Heartbeat Timestamp\",\n\t\t\t\t\t\tvalue: MiscUtils.formatDateLocal(\n\t\t\t\t\t\t\tdeployment.instance_heartbeat) || \"-\",\n\t\t\t\t\t\tvalueClass: heartbeatClass,\n\t\t\t\t\t},\n\t\t\t\t]}\n\t\t\t/>\n\t\t</div>\n\n\t\tlet resourceBars: PageInfos.Bar[] = []\n\t\tresourceBars.push({\n\t\t\tlabel: \"Instance Resources\",\n\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-success',\n\t\t\tvalue: deployment.instance_load1 || 0,\n\t\t})\n\t\tresourceBars.push({\n\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-warning',\n\t\t\tvalue: deployment.instance_load5 || 0,\n\t\t})\n\t\tresourceBars.push({\n\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-danger',\n\t\t\tvalue: deployment.instance_load15 || 0,\n\t\t})\n\t\tresourceBars.push({\n\t\t\tprogressClass: 'bp5-no-stripes bp5-intent-primary',\n\t\t\tvalue: deployment.instance_memory_usage || 0,\n\t\t})\n\n\t\tlet editor: JSX.Element\n\t\tif (this.state.logsResource) {\n\t\t\teditor = <Editor\n\t\t\t\tref={this.editorRef}\n\t\t\t\theight=\"500px\"\n\t\t\t\tinterval={2000}\n\t\t\t\tstyle={css.editor}\n\t\t\t\tautoScroll={true}\n\t\t\t\treadOnly={true}\n\t\t\t\tfontSize={11}\n\t\t\t\trefresh={async (first: boolean): Promise<string> => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet logs = await PodActions.log(\n\t\t\t\t\t\t\tthis.props.deployment, this.state.logsResource, !first)\n\t\t\t\t\t\treturn logs.join(\"\")\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn \"\"\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t}\n\n\t\tlet logsSelect = <BpSelect.Select<PodTypes.Journal>\n\t\t\titems={this.props.deployment.journals || []}\n\t\t\titemRenderer={this.renderJournal}\n\t\t\tpopoverTargetProps={{\n\t\t\t\tstyle: css.select,\n\t\t\t}}\n\t\t\tfilterable={false}\n\t\t\titemListRenderer={({items, itemsParentRef,\n\t\t\t\t\tquery, renderItem, menuProps}) => {\n\n\t\t\t\tconst renderedItems = items.map(renderItem).filter(\n\t\t\t\t\titem => item != null)\n\t\t\t\treturn <Blueprint.Menu\n\t\t\t\t\trole=\"listbox\"\n\t\t\t\t\tulRef={itemsParentRef}\n\t\t\t\t\t{...menuProps}\n\t\t\t\t>\n\t\t\t\t\t{renderedItems}\n\t\t\t\t</Blueprint.Menu>\n\t\t\t}}\n\t\t\tonItemSelect={(jrnl) => {\n\t\t\t\tif (this.state.logsResource === jrnl.key) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlogsResource: \"\",\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlogsResource: jrnl.key,\n\t\t\t\t\t}, () => {\n\t\t\t\t\t\tthis.editorRef?.current?.refresh()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}}\n\t\t>\n\t\t\t<Blueprint.Button\n\t\t\t\tstyle={css.cardButton}\n\t\t\t\talignText=\"left\"\n\t\t\t\tsmall={true}\n\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t>Logs</Blueprint.Button>\n\t\t</BpSelect.Select>\n\n\t\tif (deployment.kind === \"image\" && deployment.image_id) {\n\t\t\treturn <Blueprint.Card\n\t\t\t\tkey={deployment.id}\n\t\t\t\tcompact={true}\n\t\t\t\tstyle={cardStyle}\n\t\t\t>\n\t\t\t\t<div className=\"layout vertical flex\">\n\t\t\t\t\t<div className=\"layout horizontal flex\" style={css.box}>\n\t\t\t\t\t\t<div className=\"layout center\" style={css.checkBox}>\n\t\t\t\t\t\t\t<Blueprint.Checkbox\n\t\t\t\t\t\t\t\tstyle={css.check}\n\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey)\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.itemFirst}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Build ID\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.id,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{logsSelect}\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"bp5-button bp5-small\"\n\t\t\t\t\t\t\t\tstyle={css.cardButton}\n\t\t\t\t\t\t\t\thidden={this.state.editOpen}\n\t\t\t\t\t\t\t\tonClick={this.onEditToggle}\n\t\t\t\t\t\t\t>Settings</button>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"bp5-button bp5-small bp5-active bp5-intent-danger\"\n\t\t\t\t\t\t\t\tstyle={css.cardButton}\n\t\t\t\t\t\t\t\thidden={!this.state.editOpen}\n\t\t\t\t\t\t\t\tonClick={this.onEditToggle}\n\t\t\t\t\t\t\t>Settings</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.item}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Commit ID\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.spec.substring(12),\n\t\t\t\t\t\t\t\t\t\thover: specHover,\n\t\t\t\t\t\t\t\t\t\tvalueClass: commitClass,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.item}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Image ID\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.image_id || \"-\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.item}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Image Name\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.image_name || \"-\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.item}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Tags\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.tags.length ? deployment.tags : \"-\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"layout horizontal flex\">\n\t\t\t\t\t\t{editor}\n\t\t\t\t\t</div>\n\t\t\t\t\t<PodDeploymentEdit\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\tdeployment={this.props.deployment}\n\t\t\t\t\t\topen={this.state.editOpen}\n\t\t\t\t\t\tonClose={this.onEditClose}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</Blueprint.Card>\n\t\t} else {\n\t\t\treturn <Blueprint.Card\n\t\t\t\tkey={deployment.id}\n\t\t\t\tcompact={true}\n\t\t\t\tstyle={cardStyle}\n\t\t\t>\n\t\t\t\t<div className=\"layout vertical flex\">\n\t\t\t\t\t<div className=\"layout horizontal flex\" style={css.box}>\n\t\t\t\t\t\t<div className=\"layout center\" style={css.checkBox}>\n\t\t\t\t\t\t\t<Blueprint.Checkbox\n\t\t\t\t\t\t\t\tstyle={css.check}\n\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey)\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.itemFirst}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Deployment ID\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.id,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Instance\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.instance_name || \"-\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{logsSelect}\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"bp5-button bp5-small\"\n\t\t\t\t\t\t\t\tstyle={css.cardButton}\n\t\t\t\t\t\t\t\thidden={this.state.editOpen}\n\t\t\t\t\t\t\t\tonClick={this.onEditToggle}\n\t\t\t\t\t\t\t>Settings</button>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"bp5-button bp5-small bp5-active bp5-intent-danger\"\n\t\t\t\t\t\t\t\tstyle={css.cardButton}\n\t\t\t\t\t\t\t\thidden={!this.state.editOpen}\n\t\t\t\t\t\t\t\tonClick={this.onEditToggle}\n\t\t\t\t\t\t\t>Settings</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.itemMedium}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Commit ID\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.spec.substring(12),\n\t\t\t\t\t\t\t\t\t\thover: specHover,\n\t\t\t\t\t\t\t\t\t\tvalueClass: commitClass,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Zone\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.zone_name || \"-\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Node\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.node_name || \"-\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.itemSmall}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"State\",\n\t\t\t\t\t\t\t\t\t\tvalue: stateValue,\n\t\t\t\t\t\t\t\t\t\tvalueClass: stateClass,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Status\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.instance_status || \"-\",\n\t\t\t\t\t\t\t\t\t\tvalueClass: statusClass,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tdomainInfo,\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.item}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Agent Status\",\n\t\t\t\t\t\t\t\t\t\tvalue: agentStatus,\n\t\t\t\t\t\t\t\t\t\tvalueClass: agentClass,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Last Heartbeat\",\n\t\t\t\t\t\t\t\t\t\tvalue: MiscUtils.formatSinceLocal(\n\t\t\t\t\t\t\t\t\t\t\tdeployment.instance_heartbeat) || \"-\",\n\t\t\t\t\t\t\t\t\t\thover: heartbeatHover,\n\t\t\t\t\t\t\t\t\t\tvalueClass: heartbeatClass,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: \"Uptime\",\n\t\t\t\t\t\t\t\t\t\tvalue: deployment.instance_uptime || \"-\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.item}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: 'Private IPv4',\n\t\t\t\t\t\t\t\t\t\tvalue: privateIps,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: 'Public IPv4',\n\t\t\t\t\t\t\t\t\t\tvalue: publicIps,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.itemFull}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: 'Private IPv6',\n\t\t\t\t\t\t\t\t\t\tvalue: privateIps6,\n\t\t\t\t\t\t\t\t\t\tmaxLines: 2,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlabel: 'Public IPv6',\n\t\t\t\t\t\t\t\t\t\tvalue: publicIps6,\n\t\t\t\t\t\t\t\t\t\tmaxLines: 2,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={css.itemLast}>\n\t\t\t\t\t\t\t<PageInfo\n\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\t\t\tbars={resourceBars}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"bp5-button bp5-small\"\n\t\t\t\t\t\t\t\tstyle={css.cardButtonRight}\n\t\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\t\tInstanceActions.filter({\n\t\t\t\t\t\t\t\t\t\tid: deployment.instance\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\tRouter.setLocation(\"/instances\")\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>View Instance</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"layout horizontal flex\">\n\t\t\t\t\t\t{editor}\n\t\t\t\t\t</div>\n\t\t\t\t\t<PodDeploymentEdit\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\tdeployment={this.props.deployment}\n\t\t\t\t\t\topen={this.state.editOpen}\n\t\t\t\t\t\tonClose={this.onEditClose}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</Blueprint.Card>\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodDeploymentEdit.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from \"react\"\nimport * as Blueprint from \"@blueprintjs/core\"\nimport * as Theme from '../Theme';\nimport * as PodTypes from \"../types/PodTypes\"\nimport * as PodActions from \"../actions/PodActions\"\nimport PageInput from './PageInput';\nimport PageSave from './PageSave';\nimport PageInputButton from './PageInputButton';\nimport Help from \"./Help\"\n\ninterface Props {\n\tdisabled: boolean\n\tdeployment: PodTypes.Deployment\n\topen: boolean\n\tonClose: () => void\n}\n\ninterface State {\n\tdisabled: boolean\n\tchanged: boolean\n\tmessage: string\n\tdeployment: PodTypes.Deployment\n\taddTag: string\n}\n\nconst css = {\n\tcontainer: {\n\t\theight: \"900px\",\n\t\toverflowY: \"auto\",\n\t\tmarginBottom: \"10px\",\n\t} as React.CSSProperties,\n\tcard: {\n\t\tpadding: \"5px 5px 3px 5px\",\n\t} as React.CSSProperties,\n\tcardInactive: {\n\t\tpadding: \"5px 5px 3px 5px\",\n\t\topacity: 0.6,\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: \"0 0 0 0\",\n\t} as React.CSSProperties,\n\tcheckBox: {\n\t\tdisplay: \"flex\",\n\t\tpaddingBottom: \"2px\",\n\t} as React.CSSProperties,\n\tinfo: {\n\t\tmarginBottom: \"0px\",\n\t} as React.CSSProperties,\n\tsettings: {\n\t\tpadding: \"0 7px\",\n\t\tmarginTop: \"12px\",\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\titemFirst: {\n\t\tflex: \"1 1 auto\",\n\t\tminWidth: \"100px\",\n\t\tmargin: \"0 5px 0 -4px\",\n\t} as React.CSSProperties,\n\titem: {\n\t\tflex: \"1 1 auto\",\n\t\tminWidth: \"30px\",\n\t\tmargin: \" 0 5px\",\n\t} as React.CSSProperties,\n\titemLast: {\n\t\tflex: \"0 1 auto\",\n\t\tminWidth: \"30px\",\n\t\tmargin: \" 0 5px\",\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n}\n\nexport default class PodDeploymentEdit extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context)\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: \"\",\n\t\t\tdeployment: null,\n\t\t\taddTag: \"\",\n\t\t}\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPodActions.commitDeployment(this.state.deployment).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tdeployment: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddTag = (): void => {\n\t\tlet deployment: PodTypes.Deployment;\n\n\t\tif (!this.state.addTag) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tdeployment = {\n\t\t\t\t...this.state.deployment,\n\t\t\t};\n\t\t} else {\n\t\t\tdeployment = {\n\t\t\t\t...this.props.deployment,\n\t\t\t};\n\t\t}\n\n\t\tlet tags = [\n\t\t\t...(deployment.tags || []),\n\t\t];\n\n\t\tif (tags.indexOf(this.state.addTag) === -1) {\n\t\t\ttags.push(this.state.addTag);\n\t\t}\n\n\t\ttags.sort();\n\t\tdeployment.tags = tags;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddTag: '',\n\t\t\tdeployment: deployment,\n\t\t});\n\t}\n\n\tonRemoveTag = (tag: string): void => {\n\t\tlet deployment: PodTypes.Deployment;\n\n\t\tif (this.state.changed) {\n\t\t\tdeployment = {\n\t\t\t\t...this.state.deployment,\n\t\t\t};\n\t\t} else {\n\t\t\tdeployment = {\n\t\t\t\t...this.props.deployment,\n\t\t\t};\n\t\t}\n\n\t\tlet tags = [\n\t\t\t...(deployment.tags || []),\n\t\t];\n\n\t\tlet i = tags.indexOf(tag);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\ttags.splice(i, 1);\n\t\tdeployment.tags = tags;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddTag: '',\n\t\t\tdeployment: deployment,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tif (!this.props.open) {\n\t\t\treturn <div></div>\n\t\t}\n\n\t\tlet deployment = this.state.deployment || this.props.deployment\n\n\t\tlet tags: JSX.Element[] = [];\n\t\tfor (let tag of (deployment.tags || [])) {\n\t\t\ttags.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={tag}\n\t\t\t\t>\n\t\t\t\t\t{tag}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveTag(tag);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <div style={css.settings}>\n\t\t\t<div className=\"layout vertical wrap\">\n\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\tTags\n\t\t\t\t\t<Help\n\t\t\t\t\t\ttitle=\"Tags\"\n\t\t\t\t\t\tcontent=\"Deployment tags.\"\n\t\t\t\t\t/>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t{tags}\n\t\t\t\t\t</div>\n\t\t\t\t</label>\n\t\t\t\t<PageInputButton\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\tvalue={this.state.addTag}\n\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\taddTag: val,\n\t\t\t\t\t\t});\n\t\t\t\t\t}}\n\t\t\t\t\tonSubmit={this.onAddTag}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.deployment && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tdeployment: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as Styles from '../Styles';\nimport * as PodTypes from '../types/PodTypes';\nimport * as PodActions from '../actions/PodActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\nimport PodsStore from '../stores/PodsStore';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageSwitch from './PageSwitch';\nimport PageInfo from './PageInfo';\nimport Relations from './Relations';\nimport PodWorkspace from './PodWorkspace';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tpod: PodTypes.PodRo;\n\tmode: string;\n\tonMode: (mode: string) => void;\n\tsettings: boolean;\n\ttoggleSettings: () => void;\n\tsidebar: boolean;\n\ttoggleSidebar: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tunitChanged: boolean;\n\tmessage: string;\n\tpod: PodTypes.Pod;\n}\n\nconst css = {\n\tcard: {\n\t\twidth: '100%',\n\t\theight: '100%',\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '5px 4px 4px 4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\ttitle: {\n\t\tcursor: 'pointer',\n\t\tmargin: '3px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tmarginBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class PodDetailed extends React.Component<Props, State> {\n\tdraftsSyncTimeout: NodeJS.Timeout\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tunitChanged: false,\n\t\t\tmessage: '',\n\t\t\tpod: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet pod: any;\n\n\t\tif (this.state.changed) {\n\t\t\tpod = {\n\t\t\t\t...this.state.pod,\n\t\t\t};\n\t\t} else {\n\t\t\tpod = {\n\t\t\t\t...this.props.pod,\n\t\t\t};\n\t\t}\n\n\t\tpod[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tpod: pod,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet changed = false\n\t\tPodsStore.addChangeListen((): void => {\n\t\t\tchanged = true\n\t\t\tif (!this.state.changed) {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tpod: null,\n\t\t\t\t\tchanged: false,\n\t\t\t\t\tunitChanged: false,\n\t\t\t\t});\n\t\t\t\tthis.props.onMode(this.props.mode === \"edit\" ?\n\t\t\t\t\t\"view\" : this.props.mode);\n\t\t\t}\n\t\t});\n\n\t\tlet pod = this.state.pod\n\t\tif (!pod && this.props.pod.drafts?.length) {\n\t\t\tpod = {\n\t\t\t\t...this.props.pod,\n\t\t\t\tunits: this.props.pod.drafts,\n\t\t\t}\n\t\t}\n\n\t\tfor (let unit of pod.units) {\n\t\t\tunit.deploy_spec = \"\"\n\t\t}\n\n\t\tPodActions.commit(pod).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tunitChanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!changed && !this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tpod: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tunitChanged: false,\n\t\t\t\t\t});\n\t\t\t\t\tthis.props.onMode(this.props.mode === \"edit\" ?\n\t\t\t\t\t\t\"view\" : this.props.mode);\n\t\t\t\t} else {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonChangeCommit = (unitId: string, commit: string): void => {\n\t\tlet pod: PodTypes.Pod\n\n\t\tif (this.state.changed) {\n\t\t\tpod = {\n\t\t\t\t...this.state.pod,\n\t\t\t};\n\t\t\tpod.units = [...(pod.units || [])]\n\n\t\t\tfor (let i = 0; i < pod.units.length; i++) {\n\t\t\t\tif (pod.units[i].id === unitId) {\n\t\t\t\t\tpod.units[i].deploy_spec = commit\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: true,\n\t\t\t\tchanged: true,\n\t\t\t\tunitChanged: true,\n\t\t\t\tpod: pod,\n\t\t\t})\n\t\t} else {\n\t\t\tpod = this.props.pod\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: true,\n\t\t\t})\n\t\t}\n\n\t\tlet deployPod: PodTypes.Pod = {\n\t\t\t...this.props.pod,\n\t\t\tunits: [],\n\t\t}\n\n\t\tfor (let i = 0; i < pod.units.length; i++) {\n\t\t\tif (pod.units[i].id === unitId) {\n\t\t\t\tdeployPod.units = [{\n\t\t\t\t\t...pod.units[i],\n\t\t\t\t\tdeploy_spec: commit,\n\t\t\t\t}]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tPodActions.commitDeploy(deployPod).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t})\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t})\n\t\t})\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPodActions.remove(this.props.pod.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet pod: PodTypes.Pod\n\t\tlet podOrig = this.props.pod\n\t\tlet hasDrafts = !!this.props.pod.drafts?.length\n\n\t\tif (this.state.pod) {\n\t\t\tpod = this.state.pod\n\t\t} else if (hasDrafts) {\n\t\t\tpod = {\n\t\t\t\t...this.props.pod,\n\t\t\t\tunits: this.props.pod.drafts,\n\t\t\t}\n\t\t} else {\n\t\t\tpod = this.props.pod\n\t\t}\n\n\t\tlet hasOrganizations = !!this.props.organizations.length;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Organization</option>);\n\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet orgName = '';\n\t\tif (pod.organization) {\n\t\t\tlet org = CompletionStore.organization(pod.organization);\n\t\t\torgName = org ? org.name : pod.organization;\n\t\t}\n\n\t\treturn <div\n\t\t\tstyle={css.card}\n\t\t\tclassName=\"bp5-card layout vertical flex\"\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={\"bp5-button bp5-minimal \" + (\n\t\t\t\t\t\t\t\tthis.props.sidebar ? \"bp5-icon-drawer-right\" :\n\t\t\t\t\t\t\t\t\"bp5-icon-drawer-left\")}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={this.props.toggleSidebar}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"bp5-tag bp5-intent-primary no-select\"\n\t\t\t\t\t\t\tstyle={css.title}\n\t\t\t\t\t\t\tonClick={this.props.toggleSettings}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<b>Name:</b>&nbsp;{pod.name}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\thidden={!orgName}\n\t\t\t\t\t\t\tclassName=\"bp5-tag no-select\"\n\t\t\t\t\t\t\tstyle={css.title}\n\t\t\t\t\t\t\tonClick={this.props.toggleSettings}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<b>Organization:</b>&nbsp;{orgName}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t\t<Relations kind=\"pod\" id={this.props.pod.id}/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={\"bp5-button bp5-minimal bp5-icon-cog\" + (\n\t\t\t\t\t\t\t\tthis.props.settings ? \" bp5-intent-danger\" : \" bp5-intent-primary\")}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={this.props.toggleSettings}\n\t\t\t\t\t\t>{this.props.settings ? \"Close\" : \"\"} Pod Settings</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Pod\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this pod\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[pod.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\thidden={!this.props.settings}\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of pod\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={pod.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasOrganizations}\n\t\t\t\t\t\thidden={!this.props.settings || Constants.user}\n\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\thelp=\"Organization for pod.\"\n\t\t\t\t\t\tvalue={pod.organization}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\thidden={!this.props.settings}\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.pod.id || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={!this.props.settings}\n\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\thelp=\"Block pod and any attached disks from being deleted.\"\n\t\t\t\t\t\tchecked={pod.delete_protection}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('delete_protection', !pod.delete_protection);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PodWorkspace\n\t\t\t\tpod={pod}\n\t\t\t\tpodOrig={podOrig}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tunitChanged={this.state.unitChanged || hasDrafts}\n\t\t\t\tmode={this.props.mode}\n\t\t\t\tonMode={(mode: string): void => {\n\t\t\t\t\tthis.props.onMode(mode)\n\t\t\t\t}}\n\t\t\t\tonChangeCommit={this.onChangeCommit}\n\t\t\t\tonEdit={(units: PodTypes.Unit[]): void => {\n\t\t\t\t\tlet pod: any;\n\n\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\tpod = {\n\t\t\t\t\t\t\t...this.state.pod,\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpod = {\n\t\t\t\t\t\t\t...this.props.pod,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tpod.units = units\n\n\t\t\t\t\tlet newMode = \"view\"\n\t\t\t\t\tfor (let unit of units) {\n\t\t\t\t\t\tif (!unit.delete) {\n\t\t\t\t\t\t\tnewMode = \"edit\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\tunitChanged: true,\n\t\t\t\t\t\tpod: pod,\n\t\t\t\t\t});\n\t\t\t\t\tthis.props.onMode(newMode)\n\n\t\t\t\t\tif (this.draftsSyncTimeout) {\n\t\t\t\t\t\tclearTimeout(this.draftsSyncTimeout)\n\t\t\t\t\t}\n\n\t\t\t\t\tPodsStore.setDrafts(pod.id, units)\n\t\t\t\t\tthis.draftsSyncTimeout = setTimeout(() => {\n\t\t\t\t\t\tif (pod.id != this.props.pod?.id) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tPodActions.commitDrafts({\n\t\t\t\t\t\t\t...this.props.pod,\n\t\t\t\t\t\t\tdrafts: units,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tthis.draftsSyncTimeout = null\n\t\t\t\t\t}, 500)\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.pod && !this.state.message && !hasDrafts}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed || hasDrafts}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tPodsStore.setDrafts(this.props.pod.id, [])\n\t\t\t\t\tPodActions.commitDrafts({\n\t\t\t\t\t\t...this.props.pod,\n\t\t\t\t\t\tdrafts: [],\n\t\t\t\t\t}, true)\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tunitChanged: false,\n\t\t\t\t\t\tpod: null,\n\t\t\t\t\t});\n\t\t\t\t\tthis.props.onMode(\"view\")\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodEditor.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from \"react\"\nimport * as Styles from \"../Styles\"\nimport Help from \"./Help\"\nimport MarkdownMemo from \"./MarkdownMemo\"\nimport * as Theme from \"../Theme\"\nimport * as PodActions from \"../actions/PodActions\"\nimport * as CompletionEngine from \"../completion/Engine\"\n\nimport * as MonacoEditor from \"@monaco-editor/react\"\nimport * as Monaco from \"monaco-editor\"\n\ninterface Props {\n\tpodId: string\n\thidden: boolean\n\treadOnly: boolean\n\texpandLeft: boolean\n\texpandRight: boolean\n\tdisabled?: boolean\n\tuuid: string\n\tvalue: string\n\tdiffValue: string\n\tonEdit?: () => void\n\tonChange?: (value: string) => void\n\tonDiffChange?: (value: string) => void\n}\n\ninterface State {\n}\n\ninterface EditorState {\n\tmodel: Monaco.editor.ITextModel\n\tview: Monaco.editor.ICodeEditorViewState\n}\n\nconst css = {\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: \"280px\",\n\t\theight: \"100%\",\n\t\toverflowY: \"auto\",\n\t\tmargin: \"0\",\n\t\tfontSize: \"12px\",\n\t} as React.CSSProperties,\n\tgroupSpaced: {\n\t\tflex: 1,\n\t\tminWidth: \"280px\",\n\t\theight: \"100%\",\n\t\toverflowY: \"auto\",\n\t\tmargin: \"0\",\n\t\tpadding: \"8px 0 0 0 \",\n\t\tfontSize: \"12px\",\n\t} as React.CSSProperties,\n\tgroupSpacedExt: {\n\t\tflex: 1,\n\t\tminWidth: \"280px\",\n\t\theight: \"100%\",\n\t\toverflowY: \"auto\",\n\t\tmargin: \"0\",\n\t\tpadding: \"0 0 0 0 \",\n\t\tfontSize: \"12px\",\n\t} as React.CSSProperties,\n\tgroupSplit: {\n\t\tflex: 1,\n\t\tminWidth: \"280px\",\n\t\theight: \"100%\",\n\t\toverflowY: \"auto\",\n\t\tmargin: \"0 0 0 10px\",\n\t} as React.CSSProperties,\n\tgroupEditor: {\n\t\tflex: 1,\n\t\tminWidth: \"280px\",\n\t\theight: \"100%\",\n\t\tmargin: \"0\",\n\t\tfontSize: \"12px\",\n\t} as React.CSSProperties,\n\tgroupEditorSplit: {\n\t\tflex: 1,\n\t\tminWidth: \"280px\",\n\t\theight: \"100%\",\n\t\tmargin: \"0 0 0 10px\",\n\t} as React.CSSProperties,\n\tgroupEdit: {\n\t\tflex: 1,\n\t\tminWidth: \"280px\",\n\t\theight: \"100%\",\n\t\tmargin: \"0\",\n\t\tfontSize: \"12px\",\n\t} as React.CSSProperties,\n\tgroupEditSplit: {\n\t\tflex: 1,\n\t\tminWidth: \"280px\",\n\t\theight: \"100%\",\n\t\tmargin: \"0 0 0 10px\",\n\t} as React.CSSProperties,\n\teditorBox: {\n\t\tflexGrow: 1,\n\t\tminHeight: 0,\n\t\tmaxHeight: \"100%\",\n\t\tmargin: \"0\",\n\t} as React.CSSProperties,\n\teditor: {\n\t\tmargin: \"0\",\n\t\tborderRadius: \"3px\",\n\t} as React.CSSProperties,\n\tbuttonEdit: {\n\t\tposition: \"absolute\",\n\t\ttop: \"2px\",\n\t\tright: \"0px\",\n\t\tpadding: \"7px\",\n\t} as React.CSSProperties,\n\tbuttonLeft: {\n\t\tposition: \"absolute\",\n\t\ttop: \"-4px\",\n\t\tright: \"0px\",\n\t\tpadding: \"7px\",\n\t} as React.CSSProperties,\n\tbuttonRight: {\n\t\tposition: \"absolute\",\n\t\ttop: \"-4px\",\n\t\tright: \"0px\",\n\t\tpadding: \"7px\",\n\t} as React.CSSProperties,\n};\n\nconst hashRe = /^( {0,3})#+\\s+\\S+/\nconst blockRe = /^( {4}|\\s*`)/\nconst yamlBlockRe = /```yaml([\\s\\S]*?)```/g;\nconst kindRe = /kind:\\s*(\\w+)/\nconst markdownUri = Monaco.Uri.from({\n\tscheme: \"file\",\n\tpath: \"/markdown.md\",\n});\nconst kindsAll = [\"instance\", \"domain\", \"firewall\"]\nconst kindsUri: Record<string, Monaco.Uri> = {\n\t\"instance\": Monaco.Uri.from({\n\t\tscheme: \"file\",\n\t\tpath: \"/instance.yaml\",\n\t}),\n\t\"domain\": Monaco.Uri.from({\n\t\tscheme: \"file\",\n\t\tpath: \"/domain.yaml\",\n\t}),\n\t\"firewall\": Monaco.Uri.from({\n\t\tscheme: \"file\",\n\t\tpath: \"/firewall.yaml\",\n\t}),\n}\nconst pathsKind: Record<string, string> = {\n\t\"/instance.yaml\": \"instance\",\n\t\"/domain.yaml\": \"domain\",\n\t\"/firewall.yaml\": \"firewall\",\n}\n\nexport default class PodEditor extends React.Component<Props, State> {\n\tcurUuid: string\n\teditor: Monaco.editor.IStandaloneCodeEditor\n\tmonaco: MonacoEditor.Monaco\n\tdiffEditor: Monaco.editor.IStandaloneDiffEditor\n\tdiffMonaco: MonacoEditor.Monaco\n\tstates: Record<string, EditorState>\n\tmarkerListener: Monaco.IDisposable\n\tmarkersOffset: Record<string, number> = {}\n\tsyncMarkersTimeout: NodeJS.Timeout\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t}\n\n\t\tthis.states = {}\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tTheme.removeChangeListener(this.onThemeChange);\n\t\tthis.curUuid = undefined\n\t\tthis.editor = undefined\n\t\tthis.monaco = undefined\n\t\tthis.diffEditor = undefined\n\t\tthis.diffMonaco = undefined\n\t\tthis.states = {}\n\t\tif (this.markerListener) {\n\t\t\tthis.markerListener.dispose()\n\t\t}\n\t}\n\n\tonThemeChange = (): void => {\n\t\tif (this.monaco) {\n\t\t\tthis.monaco.editor.setTheme(Theme.getEditorTheme())\n\t\t}\n\t\tif (this.diffMonaco) {\n\t\t\tthis.diffMonaco.editor.setTheme(Theme.getEditorTheme())\n\t\t}\n\t}\n\n\tsyncMarkers(val: string): void {\n    if (this.syncMarkersTimeout) {\n      clearTimeout(this.syncMarkersTimeout)\n    }\n\n    this.syncMarkersTimeout = setTimeout(() => {\n\t\t\tthis._syncMarkers(val)\n      this.syncMarkersTimeout = null\n    }, 200)\n\t}\n\n\t_syncMarkers(val: string): void {\n\t\tlet monaco = this.monaco\n\t\tlet editor = this.editor\n\t\tlet kinds = new Set(kindsAll)\n\n\t\tif (!monaco?.editor) {\n\t\t\treturn\n\t\t}\n\n\t\tlet markdownModel = monaco.editor.getModel(markdownUri)\n\t\tif (markdownModel) {\n\t\t\tmarkdownModel.setValue(val)\n\t\t} else {\n\t\t\tmarkdownModel = monaco.editor.createModel(\n\t\t\t\tval,\n\t\t\t\t\"markdown\",\n\t\t\t\tmarkdownUri,\n\t\t\t)\n\t\t}\n\n\t\tconst matches = [...val.matchAll(yamlBlockRe)]\n\t\tif (matches.length > 0) {\n\t\t\tlet match = matches[0]\n\n\t\t\tconst yamlContent = match[1]\n\t\t\tconst yamlContentLines = yamlContent.split(\"\\n\")\n\n\t\t\tconst baseLineOffset = markdownModel.getValue().substr(\n\t\t\t\t0, match.index).split(\"\\n\").length - 1\n\n\t\t\tconst docLineOffsets: number[] = []\n\t\t\tconst yamlDocuments: string[] = []\n\n\t\t\tif (!yamlContent.trimStart().startsWith(\"---\")) {\n\t\t\t\tdocLineOffsets.push(0)\n\t\t\t\tyamlDocuments.push(yamlContent)\n\t\t\t} else {\n\t\t\t\tlet inDocument = false\n\t\t\t\tlet currentDocLines: string[] = []\n\t\t\t\tyamlContentLines.forEach((line, lineIndex) => {\n\t\t\t\t\tif (line.trim() === \"---\") {\n\t\t\t\t\t\tif (inDocument && currentDocLines.length > 0) {\n\t\t\t\t\t\t\tyamlDocuments.push(currentDocLines.join(\"\\n\"))\n\t\t\t\t\t\t\tcurrentDocLines = []\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tinDocument = true\n\t\t\t\t\t\tdocLineOffsets.push(lineIndex + 1)\n\t\t\t\t\t} else if (inDocument) {\n\t\t\t\t\t\tcurrentDocLines.push(line)\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tif (inDocument && currentDocLines.length > 0) {\n\t\t\t\t\tyamlDocuments.push(currentDocLines.join(\"\\n\").trim())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tyamlDocuments.forEach((docContent, docIndex) => {\n\t\t\t\tlet kind = docContent.match(kindRe)?.[1]\n\t\t\t\tif (!kinds.delete(kind)) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst modelUri = kindsUri[kind]\n\t\t\t\tlet yamlModel = monaco.editor.getModel(modelUri)\n\t\t\t\tif (yamlModel) {\n\t\t\t\t\tyamlModel.setValue(docContent)\n\t\t\t\t} else {\n\t\t\t\t\tyamlModel = monaco.editor.createModel(\n\t\t\t\t\t\tdocContent,\n\t\t\t\t\t\t\"yaml\",\n\t\t\t\t\t\tmodelUri,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tthis.markersOffset[kind] = baseLineOffset + docLineOffsets[docIndex]\n\t\t\t})\n\n\t\t\tfor (const kind of kinds.keys()) {\n\t\t\t\tmonaco.editor.setModelMarkers(\n\t\t\t\t\teditor.getModel(),\n\t\t\t\t\t`yaml-${kind}`,\n\t\t\t\t\t[],\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tupdateState(): void {\n\t\tif (!this.editor?.getModel()) {\n\t\t\treturn\n\t\t}\n\n\t\tif (!this.curUuid) {\n\t\t\tthis.curUuid = this.props.uuid\n\t\t}\n\n\t\tlet model: Monaco.editor.ITextModel\n\t\tif (this.curUuid != this.props.uuid) {\n\t\t\tthis.states[this.curUuid] = {\n\t\t\t\tmodel: this.editor.getModel(),\n\t\t\t\tview: this.editor.saveViewState(),\n\t\t\t}\n\n\t\t\tlet newState = this.states[this.props.uuid]\n\t\t\tif (newState) {\n\t\t\t\tmodel = newState.model\n\t\t\t\tthis.editor.setModel(newState.model)\n\t\t\t\tthis.editor.restoreViewState(newState.view)\n\t\t\t} else {\n\t\t\t\tmodel = this.monaco.editor.createModel(\n\t\t\t\t\tthis.props.value, \"markdown\",\n\t\t\t\t)\n\t\t\t\tthis.editor.setModel(model)\n\t\t\t}\n\n\t\t\tthis.curUuid = this.props.uuid\n\t\t} else {\n\t\t\tmodel = this.editor.getModel()\n\t\t}\n\t}\n\n\trender(): JSX.Element {\n\t\tthis.updateState()\n\n\t\tif (this.props.hidden) {\n\t\t\treturn <div></div>\n\t\t}\n\n\t\tlet expandLeft = this.props.expandLeft\n\t\tlet expandRight = this.props.expandRight\n\t\tlet markdown: JSX.Element\n\t\tlet leftGroupStyle: React.CSSProperties = css.group\n\n\t\tif (!expandRight) {\n\t\t\tmarkdown = <MarkdownMemo value={this.props.value}/>\n\t\t}\n\n\t\tlet val = (this.props.value || \"\")\n\t\tlet valTrim = val.trimStart()\n\n\t\tif (blockRe.test(val)) {\n\t\t\tleftGroupStyle = css.groupSpacedExt\n\t\t} else if (!hashRe.test(val)) {\n\t\t\tleftGroupStyle = css.groupSpaced\n\t\t} else {\n\t\t\tlet valFirst = valTrim.split(\"\\n\")[0] || \"\"\n\t\t\tvalFirst = valFirst.replace(/#/g, \"\").trim()\n\t\t\tif (!valFirst) {\n\t\t\t\tleftGroupStyle = css.groupSpacedExt\n\t\t\t}\n\t\t}\n\n\t\tlet rightStyle: React.CSSProperties\n\t\tif (!this.props.readOnly) {\n\t\t\trightStyle = expandRight ? css.groupEdit : css.groupEditSplit\n\t\t} else {\n\t\t\trightStyle = expandRight ? css.groupEditor : css.groupEditorSplit\n\t\t}\n\n\t\tlet editor: JSX.Element\n\t\tif (!this.props.readOnly && !this.props.diffValue) {\n\t\t\teditor = <MonacoEditor.Editor\n\t\t\t\theight=\"100%\"\n\t\t\t\twidth=\"100%\"\n\t\t\t\tdefaultLanguage=\"markdown\"\n\t\t\t\ttheme={Theme.getEditorTheme()}\n\t\t\t\tdefaultValue={this.props.value}\n\t\t\t\tbeforeMount={CompletionEngine.handleBeforeMount}\n\t\t\t\tonMount={(editor: Monaco.editor.IStandaloneCodeEditor,\n\t\t\t\t\t\tmonaco: MonacoEditor.Monaco): void => {\n\t\t\t\t\tthis.editor = editor\n\t\t\t\t\tthis.monaco = monaco\n\n\t\t\t\t\teditor.addCommand(\n\t\t\t\t\t\tmonaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,\n\t\t\t\t\t\t() => {},\n\t\t\t\t\t)\n\n\t\t\t\t\tif (this.markerListener) {\n\t\t\t\t\t\tthis.markerListener.dispose()\n\t\t\t\t\t}\n\t\t\t\t\tthis.markerListener = monaco.editor.onDidChangeMarkers((uris) => {\n\t\t\t\t\t\turis.forEach((uri) => {\n\t\t\t\t\t\t\tlet kind = pathsKind[uri.path]\n\t\t\t\t\t\t\tif (!kind) {\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst markers = monaco.editor.getModelMarkers({\n\t\t\t\t\t\t\t\tresource: uri,\n\t\t\t\t\t\t\t})\n\n\t\t\t\t\t\t\tconst offset = this.markersOffset[kind] || 0\n\t\t\t\t\t\t\tconst adjustedMarkers = markers.map(marker => ({\n\t\t\t\t\t\t\t\t...marker,\n\t\t\t\t\t\t\t\tstartLineNumber: marker.startLineNumber + offset,\n\t\t\t\t\t\t\t\tendLineNumber: marker.endLineNumber + offset,\n\t\t\t\t\t\t\t\tresource: uri,\n\t\t\t\t\t\t\t}))\n\n\t\t\t\t\t\t\tmonaco.editor.setModelMarkers(\n\t\t\t\t\t\t\t\teditor.getModel(),\n\t\t\t\t\t\t\t\t`yaml-${kind}`,\n\t\t\t\t\t\t\t\tadjustedMarkers\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\tthis.editor.onDidDispose((): void => {\n\t\t\t\t\t\tthis.editor = undefined\n\t\t\t\t\t\tthis.monaco = undefined\n\t\t\t\t\t\tthis.states = {}\n\t\t\t\t\t\tthis.curUuid = undefined\n\t\t\t\t\t})\n\t\t\t\t\tthis.updateState()\n\n\t\t\t\t\tCompletionEngine.handleAfterMount(editor, monaco)\n\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tthis.syncMarkers(val)\n\t\t\t\t\t}, 500)\n\t\t\t\t}}\n\t\t\t\toptions={{\n\t\t\t\t\tfolding: false,\n\t\t\t\t\tfontSize: 12,\n\t\t\t\t\tfontFamily: Theme.monospaceFont,\n\t\t\t\t\tfontWeight: Theme.monospaceWeight,\n\t\t\t\t\tautomaticLayout: true,\n\t\t\t\t\tformatOnPaste: true,\n\t\t\t\t\tformatOnType: true,\n\t\t\t\t\ttabSize: 4,\n\t\t\t\t\tdetectIndentation: false,\n\t\t\t\t\trulers: [80],\n\t\t\t\t\tscrollBeyondLastLine: false,\n\t\t\t\t\tminimap: {\n\t\t\t\t\t\tenabled: expandRight,\n\t\t\t\t\t},\n\t\t\t\t\twordWrap: \"on\",\n\t\t\t\t}}\n\t\t\t\tonChange={(val): void => {\n\t\t\t\t\tthis.syncMarkers(val)\n\t\t\t\t\tthis.props.onChange(val)\n\t\t\t\t}}\n\t\t\t/>\n\t\t} else if (!this.props.readOnly && this.props.diffValue) {\n\t\t\teditor = <MonacoEditor.DiffEditor\n\t\t\t\theight=\"100%\"\n\t\t\t\twidth=\"100%\"\n\t\t\t\ttheme={Theme.getEditorTheme()}\n\t\t\t\toriginal={this.props.diffValue}\n\t\t\t\tmodified={this.props.value}\n\t\t\t\toriginalLanguage=\"markdown\"\n\t\t\t\tmodifiedLanguage=\"markdown\"\n\t\t\t\tbeforeMount={CompletionEngine.handleBeforeMount}\n\t\t\t\tonMount={(editor: Monaco.editor.IStandaloneDiffEditor,\n\t\t\t\t\t\tmonaco: MonacoEditor.Monaco): void => {\n\t\t\t\t\tthis.diffEditor = editor\n\t\t\t\t\tthis.diffMonaco = monaco\n\t\t\t\t\tthis.diffEditor.onDidDispose((): void => {\n\t\t\t\t\t\tthis.diffEditor = undefined\n\t\t\t\t\t\tthis.diffMonaco = undefined\n\t\t\t\t\t})\n\n\t\t\t\t\teditor.addCommand(\n\t\t\t\t\t\tmonaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,\n\t\t\t\t\t\t() => {},\n\t\t\t\t\t)\n\n\t\t\t\t\tlet modifiedEditor = editor.getModifiedEditor()\n\t\t\t\t\tmodifiedEditor.onDidChangeModelContent((): void => {\n\t\t\t\t\t\tthis.props.onDiffChange(modifiedEditor.getValue())\n\t\t\t\t\t})\n\n\t\t\t\t\tthis.updateState()\n\t\t\t\t}}\n\t\t\t\toptions={{\n\t\t\t\t\tfolding: false,\n\t\t\t\t\tfontSize: 12,\n\t\t\t\t\tfontFamily: Theme.monospaceFont,\n\t\t\t\t\tfontWeight: Theme.monospaceWeight,\n\t\t\t\t\trenderSideBySide: true,\n\t\t\t\t\tautomaticLayout: true,\n\t\t\t\t\tformatOnPaste: true,\n\t\t\t\t\tformatOnType: true,\n\t\t\t\t\trulers: [80],\n\t\t\t\t\tscrollBeyondLastLine: false,\n\t\t\t\t\tminimap: {\n\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t},\n\t\t\t\t\twordWrap: \"on\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t}\n\n\t\treturn <div className=\"layout horizontal\" style={css.editorBox}>\n\t\t\t<div\n\t\t\t\tstyle={leftGroupStyle}\n\t\t\t\thidden={expandRight}\n\t\t\t>\n\t\t\t\t{markdown}\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={expandRight ? css.groupEditor : css.groupEditorSplit}\n\t\t\t\thidden={expandLeft}\n\t\t\t>\n\t\t\t\t<div style={rightStyle}>\n\t\t\t\t\t{editor}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodMigrate.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as BpSelect from '@blueprintjs/select';\nimport * as Icons from '@blueprintjs/icons';\nimport * as PodTypes from '../types/PodTypes';\nimport * as PodActions from '../actions/PodActions';\nimport * as Alert from '../Alert';\nimport * as Theme from '../Theme';\nimport * as MiscUtils from '../utils/MiscUtils';\n\nimport * as MonacoEditor from \"@monaco-editor/react\"\nimport * as Monaco from \"monaco-editor\"\n\ninterface Props {\n\tdisabled: boolean;\n\tpod: PodTypes.PodRo;\n\tunit: PodTypes.PodUnit;\n\tcommits: PodTypes.Commit[];\n\tselectedDeployments: {[key: string]: boolean};\n\tonClear: () => void;\n}\n\ninterface State {\n\tdialog: boolean;\n\tdisabled: boolean;\n\tspecId: string;\n\tcurrentCommit: PodTypes.Commit;\n\tmigrateCommit: PodTypes.Commit;\n\tmismatchCommits: boolean;\n}\n\nconst css = {\n\tdialog: {\n\t\twidth: '630px',\n\t\tposition: 'absolute',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmargin: '18px 0 0 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tsettingsOpen: {\n\t\tmarginLeft: '10px',\n\t} as React.CSSProperties,\n\tcommit: {\n\t\tfontFamily: Theme.monospaceFont,\n\t} as React.CSSProperties,\n\tmuted: {\n\t\topacity: 0.75,\n\t} as React.CSSProperties,\n\tcommitButton: {\n\t\tmarginTop: \"5px\",\n\t\twidth: '400px',\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n\tcommitsMenu: {\n\t\tmaxHeight: '400px',\n\t\toverflowY: \"auto\",\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n\teditorBox: {\n\t\tmarginTop: \"10px\",\n\t} as React.CSSProperties,\n};\n\nexport default class PodMigrate extends React.Component<Props, State> {\n\tinterval: NodeJS.Timer;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdialog: false,\n\t\t\tdisabled: false,\n\t\t\tspecId: \"\",\n\t\t\tcurrentCommit: null,\n\t\t\tmigrateCommit: null,\n\t\t\tmismatchCommits: false,\n\t\t};\n\t}\n\n\topenDialog = (): void => {\n\t\tlet migrateCommit = this.state.migrateCommit || this.props.commits?.[0]\n\t\tthis.onSelectCommit(migrateCommit)\n\t}\n\n\tcloseDialog = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdialog: false,\n\t\t\tspecId: \"\",\n\t\t});\n\t}\n\n\tonSelectCommit = async (newCommit: PodTypes.Commit): Promise<void> => {\n\t\tlet curCommitId: string\n\t\tlet mismatchCommits: boolean = false\n\t\tthis.props.unit.deployments.forEach((deploy): void => {\n\t\t\tif (this.props.selectedDeployments[deploy.id]) {\n\t\t\t\tif (!curCommitId && !mismatchCommits) {\n\t\t\t\t\tcurCommitId = deploy.spec\n\t\t\t\t} else if (deploy.spec != curCommitId) {\n\t\t\t\t\tcurCommitId = null\n\t\t\t\t\tmismatchCommits = true\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tif (curCommitId) {\n\t\t\tlet [curSpec, newSpec] = await Promise.all([\n\t\t\t\tPodActions.spec(this.props.pod.id, this.props.unit.id, curCommitId),\n\t\t\t\tPodActions.spec(this.props.pod.id, this.props.unit.id, newCommit.id),\n\t\t\t]);\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdialog: true,\n\t\t\t\tcurrentCommit: curSpec,\n\t\t\t\tmigrateCommit: newSpec,\n\t\t\t\tmismatchCommits: mismatchCommits,\n\t\t\t});\n\t\t} else {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdialog: true,\n\t\t\t\tcurrentCommit: null,\n\t\t\t\tmigrateCommit: newCommit,\n\t\t\t\tmismatchCommits: mismatchCommits,\n\t\t\t});\n\t\t}\n\t}\n\n\tfilterCommit: BpSelect.ItemPredicate<PodTypes.Commit> = (\n\t\tquery, commit, _index, exactMatch) => {\n\t\tif (exactMatch) {\n\t\t\treturn commit.id == query\n\t\t} else {\n\t\t\treturn commit.id.indexOf(query) !== -1\n\t\t}\n\t}\n\n\trenderCommit: BpSelect.ItemRenderer<PodTypes.Commit> = (commit,\n\t\t{handleClick, handleFocus, modifiers, query, index}): JSX.Element => {\n\t\tif (!modifiers.matchesPredicate) {\n\t\t\treturn null;\n\t\t}\n\t\tlet className = \"\"\n\t\tlet selected = false\n\t\tif (this.state.migrateCommit?.id == commit.id ||\n\t\t\t(!this.state.migrateCommit && index === 0)) {\n\t\t\tclassName = \"bp5-text-intent-primary bp5-intent-primary\"\n\t\t\tselected = true\n\t\t} else if (index === 0) {\n\t\t\tclassName = \"bp5-text-intent-success bp5-intent-success\"\n\t\t}\n\t\treturn <Blueprint.MenuItem\n\t\t\tkey={\"diff-\" + commit.id}\n\t\t\tstyle={css.commit}\n\t\t\tselected={selected}\n\t\t\tdisabled={this.state.disabled}\n\t\t\troleStructure=\"listoption\"\n\t\t\ticon={<Icons.GitCommit\n\t\t\t\tclassName={className}\n\t\t\t/>}\n\t\t\tonFocus={handleFocus}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tevt.preventDefault()\n\t\t\t\tevt.stopPropagation()\n\t\t\t\thandleClick(evt)\n\t\t\t}}\n\t\t\ttext={MiscUtils.highlightMatch(commit.id.substring(12), query)}\n\t\t\ttextClassName={className}\n\t\t\tlabelElement={<span\n\t\t\t\tclassName={className}\n\t\t\t>{MiscUtils.formatDateLocal(commit.timestamp)}</span>}\n\t\t/>\n\t}\n\n\trenderCommitSelect = () => {\n\t\tlet commitSelect: JSX.Element\n\t\tif (this.props.commits) {\n\t\t\tlet migrateCommit = this.state.migrateCommit || this.props.commits?.[0]\n\t\t\tlet deployClass = \"\"\n\t\t\tif (migrateCommit && migrateCommit.id === this.props.commits?.[0]?.id) {\n\t\t\t\tdeployClass = \"bp5-text-intent-success\"\n\t\t\t}\n\n\t\t\tcommitSelect = <BpSelect.Select<PodTypes.Commit>\n\t\t\t\titems={this.props.commits}\n\t\t\t\titemPredicate={this.filterCommit}\n\t\t\t\titemRenderer={this.renderCommit}\n\t\t\t\titemListRenderer={({items, itemsParentRef,\n\t\t\t\t\t\tquery, renderItem, menuProps}) => {\n\n\t\t\t\t\tconst renderedItems = items.map(renderItem).filter(\n\t\t\t\t\t\titem => item != null)\n\t\t\t\t\treturn <Blueprint.Menu\n\t\t\t\t\t\trole=\"listbox\"\n\t\t\t\t\t\tulRef={itemsParentRef}\n\t\t\t\t\t\t{...menuProps}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Blueprint.MenuItem\n\t\t\t\t\t\t\tdisabled={true}\n\t\t\t\t\t\t\ttext={`Found ${renderedItems.length} items matching \"${query}\"`}\n\t\t\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{renderedItems}\n\t\t\t\t\t</Blueprint.Menu>\n\t\t\t\t}}\n\t\t\t\tnoResults={<Blueprint.MenuItem\n\t\t\t\t\tdisabled={true}\n\t\t\t\t\tstyle={css.commit}\n\t\t\t\t\ttext=\"No results.\"\n\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t/>}\n\t\t\t\tonItemSelect={(commit) => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmigrateCommit: commit,\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<Blueprint.Button\n\t\t\t\t\talignText=\"left\"\n\t\t\t\t\ticon={<Icons.GitCommit/>}\n\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\tstyle={css.commitButton}\n\t\t\t\t\ttextClassName={deployClass}\n\t\t\t\t\ttext={migrateCommit?.id.substring(12) + \" \" +\n\t\t\t\t\t\tMiscUtils.formatDateLocal(migrateCommit?.timestamp)}\n\t\t\t\t/>\n\t\t\t</BpSelect.Select>\n\t\t}\n\t}\n\n\tonMigrate = (): void => {\n\t\tlet migrateCommit = this.state.migrateCommit || this.props.commits?.[0]\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPodActions.updateMultiUnitAction(\n\t\t\t\tthis.props.pod.id, this.props.unit.id,\n\t\t\t\tObject.keys(this.props.selectedDeployments),\n\t\t\t\t\"migrate\", migrateCommit.id).then((): void => {\n\n\t\t\tAlert.success('Successfully initiated deployment migration');\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdialog: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t\tthis.props.onClear();\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet commitSelect: JSX.Element\n\t\tif (this.props.commits) {\n\t\t\tlet migrateCommit = this.state.migrateCommit || this.props.commits?.[0]\n\t\t\tlet selectButtonClass = \"\"\n\t\t\tlet selectLabelClass = \"\"\n\t\t\tlet selectLabelStyle: React.CSSProperties\n\t\t\tif (migrateCommit && migrateCommit.id === this.props.commits?.[0]?.id) {\n\t\t\t\tselectButtonClass = \"bp5-text-intent-success\"\n\t\t\t\tselectLabelStyle = css.muted\n\t\t\t} else {\n\t\t\t\tselectLabelClass = \"bp5-text-muted\"\n\t\t\t}\n\n\t\t\tlet commitMenuItems: JSX.Element[] = []\n\t\t\tthis.props.commits.forEach((commit, index): void => {\n\t\t\t\tlet className = \"\"\n\t\t\t\tlet styles: React.CSSProperties\n\t\t\t\tlet disabled = false\n\t\t\t\tlet selected = false\n\n\t\t\t\tif (this.state.migrateCommit?.id == commit.id ||\n\t\t\t\t\t(!this.state.migrateCommit && index === 0)) {\n\n\t\t\t\t\tclassName = \"bp5-text-intent-primary bp5-intent-primary\"\n\t\t\t\t\tstyles = css.muted\n\t\t\t\t\tselected = true\n\t\t\t\t} else if (index === 0) {\n\t\t\t\t\tclassName = \"bp5-text-intent-success bp5-intent-success\"\n\t\t\t\t\tstyles = css.muted\n\t\t\t\t}\n\n\t\t\t\tcommitMenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\tkey={\"diff-\" + commit.id}\n\t\t\t\t\tdisabled={disabled || this.props.disabled || this.state.disabled}\n\t\t\t\t\tselected={selected}\n\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t\ticon={<Icons.GitCommit\n\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t/>}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.onSelectCommit(commit)\n\t\t\t\t\t}}\n\t\t\t\t\ttext={commit.id.substring(12)}\n\t\t\t\t\ttextClassName={className}\n\t\t\t\t\tlabelElement={<span\n\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\tstyle={styles}\n\t\t\t\t\t>{MiscUtils.formatDateLocal(commit.timestamp)}</span>}\n\t\t\t\t/>)\n\t\t\t})\n\n\t\t\tcommitSelect = <Blueprint.Popover\n\t\t\t\tcontent={<div>\n\t\t\t\t\t<Blueprint.Menu style={css.commitsMenu}>\n\t\t\t\t\t\t{commitMenuItems}\n\t\t\t\t\t</Blueprint.Menu>\n\t\t\t\t</div>}\n\t\t\t\tplacement=\"bottom\"\n\t\t\t>\n\t\t\t\t<Blueprint.Button\n\t\t\t\t\talignText=\"left\"\n\t\t\t\t\ticon={<Icons.GitCommit/>}\n\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\tstyle={css.commitButton}\n\t\t\t\t\ttextClassName={selectButtonClass}\n\t\t\t\t>\n\t\t\t\t\t<span>{migrateCommit?.id.substring(12)}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName={selectLabelClass}\n\t\t\t\t\t\tstyle={selectLabelStyle}\n\t\t\t\t\t>\n\t\t\t\t\t\t{\" \" + MiscUtils.formatDateLocal(migrateCommit?.timestamp)}\n\t\t\t\t\t</span>\n\t\t\t\t</Blueprint.Button>\n\t\t\t</Blueprint.Popover>\n\t\t}\n\n\t\tlet itemsList: JSX.Element;\n\t\tif (this.props.selectedDeployments) {\n\t\t\tlet items: JSX.Element[] = [];\n\t\t\tfor (let item in this.props.selectedDeployments) {\n\t\t\t\titems.push(<li key={item}>{item}</li>);\n\t\t\t}\n\t\t\titemsList = <ul>{items}</ul>;\n\t\t}\n\n\t\tlet editor: JSX.Element\n\t\tif (this.state.currentCommit?.data && this.state.migrateCommit?.data) {\n\t\t\teditor = <div style={css.editorBox}>\n\t\t\t\t<MonacoEditor.DiffEditor\n\t\t\t\t\theight=\"500px\"\n\t\t\t\t\twidth=\"100%\"\n\t\t\t\t\ttheme={Theme.getEditorTheme()}\n\t\t\t\t\toriginal={this.state.currentCommit.data}\n\t\t\t\t\tmodified={this.state.migrateCommit.data}\n\t\t\t\t\toriginalLanguage=\"markdown\"\n\t\t\t\t\tmodifiedLanguage=\"markdown\"\n\t\t\t\t\toptions={{\n\t\t\t\t\t\tfolding: false,\n\t\t\t\t\t\tfontSize: 10,\n\t\t\t\t\t\tfontFamily: Theme.monospaceFont,\n\t\t\t\t\t\tfontWeight: Theme.monospaceWeight,\n\t\t\t\t\t\trenderSideBySide: false,\n\t\t\t\t\t\treadOnly: true,\n\t\t\t\t\t\tautomaticLayout: true,\n\t\t\t\t\t\tformatOnPaste: true,\n\t\t\t\t\t\tformatOnType: true,\n\t\t\t\t\t\trulers: [80],\n\t\t\t\t\t\tscrollBeyondLastLine: false,\n\t\t\t\t\t\tminimap: {\n\t\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\twordWrap: \"on\",\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t} else if (this.state.mismatchCommits) {\n\t\t\teditor = <div style={css.editorBox}>\n\t\t\t\t<Blueprint.Callout\n\t\t\t\t\tintent=\"primary\"\n\t\t\t\t>Selected deployments have mismatched current\n\t\t\t\tcommits, diff unavailable</Blueprint.Callout>\n\t\t\t</div>\n\t\t} else {\n\t\t\teditor = <div style={css.editorBox}>\n\t\t\t\t<Blueprint.Callout\n\t\t\t\t\tintent=\"primary\"\n\t\t\t\t>No migrate commit diff available</Blueprint.Callout>\n\t\t\t</div>\n\t\t}\n\n\t\tlet dialogElem = <Blueprint.Dialog\n\t\t\ttitle=\"Migrate Selected Deployments\"\n\t\t\tstyle={css.dialog}\n\t\t\tisOpen={this.state.dialog}\n\t\t\tusePortal={true}\n\t\t\tportalContainer={document.body}\n\t\t\tonClose={this.closeDialog}\n\t\t>\n\t\t\t<div className=\"bp5-dialog-body\">\n\t\t\t\tMigrate the selected deployments\n\t\t\t\t{itemsList}\n\t\t\t\t<label\n\t\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\t\tstyle={css.label}\n\t\t\t\t>\n\t\t\t\t\tTarget Commit\n\t\t\t\t</label>\n\t\t\t\t<div\n\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{commitSelect}\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t{editor}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-dialog-footer\">\n\t\t\t\t<div className=\"bp5-dialog-footer-actions\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={this.closeDialog}\n\t\t\t\t\t>Cancel</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonClick={this.onMigrate}\n\t\t\t\t\t>Migrate</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Blueprint.Dialog>\n\n\t\treturn <div>\n\t\t\t<Blueprint.MenuItem\n\t\t\t\tkey=\"menu-new-deployment\"\n\t\t\t\tclassName=\"bp5-intent-primary\"\n\t\t\t\tdisabled={this.state.disabled || this.props.disabled}\n\t\t\t\ticon={<Icons.Updated/>}\n\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\tevt.preventDefault()\n\t\t\t\t\tevt.stopPropagation()\n\t\t\t\t\tthis.openDialog()\n\t\t\t\t}}\n\t\t\t\ttext=\"Migrate Selected\"\n\t\t\t/>\n\t\t\t{dialogElem}\n\t\t</div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as PodTypes from '../types/PodTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as PodActions from '../actions/PodActions';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport PodsStore from '../stores/PodsStore';\nimport PageInput from './PageInput';\nimport PageInputButton from './PageInputButton';\nimport PageCreate from './PageCreate';\nimport PageSelect from './PageSelect';\nimport PageSwitch from \"./PageSwitch\";\nimport PageNumInput from './PageNumInput';\nimport PodWorkspace from './PodWorkspace';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tonClose: () => void;\n\tsidebar: boolean;\n\ttoggleSidebar: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tunitChanged: boolean;\n\tmessage: string;\n\tmode: string;\n\tpod: PodTypes.Pod;\n}\n\nconst css = {\n\trow: {\n\t\twidth: '100%',\n\t\theight: '100%',\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class PodNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tunitChanged: false,\n\t\t\tmessage: '',\n\t\t\tmode: \"view\",\n\t\t\tpod: this.default,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t}\n\n\tcomponentWillUnmount(): void {\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t});\n\t}\n\n\tget default(): PodTypes.Pod {\n\t\treturn {\n\t\t\tname: 'new-pod',\n\t\t\tunits: [],\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet pod: any = {\n\t\t\t...this.state.pod,\n\t\t};\n\n\t\tpod[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tpod: pod,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet pod: any = {\n\t\t\t...this.state.pod,\n\t\t};\n\n\t\tPodActions.create(pod).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Pod created successfully',\n\t\t\t\tchanged: false,\n\t\t\t\tunitChanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet pod = this.state.pod;\n\n\t\tlet hasOrganizations = !!this.props.organizations.length;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Organization</option>);\n\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\treturn <div\n\t\t\tstyle={css.row}\n\t\t\tclassName=\"bp5-card layout vertical flex\"\n\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"layout horizontal bp5-card-header\"\n\t\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName={\"bp5-button bp5-minimal \" + (\n\t\t\t\t\t\t\t\t\tthis.props.sidebar ? \"bp5-icon-drawer-right\" : \"bp5-icon-drawer-left\")}\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={this.props.toggleSidebar}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of pod. String formatting such as %d or %02d can be used to add the pod number or zero padded number.\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tvalue={pod.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasOrganizations}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for pod.\"\n\t\t\t\t\t\t\tvalue={pod.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\t\thelp=\"Block pod and any attached disks from being deleted.\"\n\t\t\t\t\t\t\tchecked={pod.delete_protection}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('delete_protection', !pod.delete_protection);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.pod}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodUnit.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from \"react\"\nimport * as Blueprint from \"@blueprintjs/core\"\nimport * as Theme from '../Theme';\nimport Help from \"./Help\"\nimport * as PodTypes from \"../types/PodTypes\"\nimport * as PodActions from \"../actions/PodActions\"\nimport * as MiscUtils from '../utils/MiscUtils';\nimport NonState from './NonState';\nimport PageInfo from \"./PageInfo\"\nimport PodDeployment from \"./PodDeployment\"\nimport * as PageInfos from './PageInfo';\n\ninterface Props {\n\thidden: boolean\n\tdisabled?: boolean\n\tselected: Selected\n\tlastSelected: string\n\tonSelect: (selected: Selected, lastSelected: string) => void\n\tunit: PodTypes.PodUnit\n}\n\ninterface State {\n}\n\ninterface Selected {\n\t[key: string]: boolean\n}\n\nconst css = {\n\tcontainer: {\n\t\theight: \"100%\",\n\t\toverflowY: \"auto\",\n\t} as React.CSSProperties,\n\tbox: {\n\t\tflex: 1,\n\t\tminWidth: \"280px\",\n\t\tmargin: \"10px 0 0 0\",\n\t} as React.CSSProperties,\n\tboxEmpty: {\n\t\tflex: 1,\n\t\tmargin: \"20px 10px 20px 10px\",\n\t} as React.CSSProperties,\n\tcard: {\n\t\tpadding: \"5px 5px 3px 5px\",\n\t} as React.CSSProperties,\n\tcardInactive: {\n\t\tpadding: \"5px 5px 3px 5px\",\n\t\topacity: 0.6,\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: \"0 0 0 0\",\n\t} as React.CSSProperties,\n\tcheckBox: {\n\t\tdisplay: \"flex\",\n\t\tpaddingBottom: \"2px\",\n\t} as React.CSSProperties,\n\tinfo: {\n\t\tmarginBottom: \"0px\",\n\t} as React.CSSProperties,\n\titemFirst: {\n\t\tflex: \"1 1 auto\",\n\t\tminWidth: \"100px\",\n\t\tmargin: \"0 5px 0 -4px\",\n\t} as React.CSSProperties,\n\titem: {\n\t\tflex: \"1 1 auto\",\n\t\tminWidth: \"30px\",\n\t\tmargin: \" 0 5px\",\n\t} as React.CSSProperties,\n\titemLast: {\n\t\tflex: \"0 1 auto\",\n\t\tminWidth: \"30px\",\n\t\tmargin: \" 0 5px\",\n\t} as React.CSSProperties,\n\tspecHover: {\n\t\tpadding: \"10px\",\n\t\tfontSize: \"12px\",\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n}\n\nexport default class PodUnit extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context)\n\t\tthis.state = {\n\t\t}\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.hidden) {\n\t\t\treturn <div></div>\n\t\t}\n\n\t\tif (!this.props.unit) {\n\t\t\treturn <div style={css.container}></div>\n\t\t}\n\n\t\tlet label = \"deployment\"\n\t\tlet labelTitle = \"Deployment\"\n\t\tif (this.props.unit.kind == \"image\") {\n\t\t\tlabel = \"image\"\n\t\t\tlabelTitle = \"Image\"\n\t\t}\n\n\t\tlet cards: JSX.Element[] = []\n\t\tlet deployments: PodTypes.Deployment[]\n\t\tif (this.props.unit && this.props.unit.deployments) {\n\t\t\tdeployments = this.props.unit.deployments\n\t\t} else {\n\t\t\tdeployments = []\n\t\t}\n\n\t\tif (!deployments.length) {\n\t\t\treturn <div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.boxEmpty}>\n\t\t\t\t\t<NonState\n\t\t\t\t\t\thidden={false}\n\t\t\t\t\t\ticonClass=\"bp5-icon-dashboard\"\n\t\t\t\t\t\ttitle={\"No \" + label + \"s\"}\n\t\t\t\t\t\tdescription={\"Update unit spec to create \" +\n\t\t\t\t\t\t\tlabel + \"s.\"}\n\t\t\t\t\t\tnoDelay={true}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t}\n\n\t\tlet commitMap: Record<string, PodTypes.Commit> = {}\n\t\tif (this.props.unit.commits) {\n\t\t\tfor (let commit of this.props.unit.commits) {\n\t\t\t\tcommitMap[commit.id] = commit\n\t\t\t}\n\t\t}\n\n\t\tdeployments.forEach((deployment: PodTypes.Deployment): void => {\n\t\t\tcards.push(<PodDeployment\n\t\t\t\tkey={deployment.id}\n\t\t\t\thidden={this.props.hidden}\n\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\tselected={!!this.props.selected[deployment.id]}\n\t\t\t\tcommitMap={commitMap}\n\t\t\t\tdeployment={deployment}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.props.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet deployments = this.props.unit.deployments;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < deployments.length; i++) {\n\t\t\t\t\t\t\tlet deply = deployments[i];\n\n\t\t\t\t\t\t\tif (deply.id === deployment.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (deply.id === this.props.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[deployments[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.props.onSelect(selected, deployment.id)\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[deployment.id]) {\n\t\t\t\t\t\tdelete selected[deployment.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[deployment.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onSelect(selected, deployment.id)\n\t\t\t\t}}\n\t\t\t/>)\n\t\t})\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.container}>\n\t\t\t<div style={css.box}>\n\t\t\t\t<Blueprint.CardList>\n\t\t\t\t\t{cards}\n\t\t\t\t</Blueprint.CardList>\n\t\t\t</div>\n\t\t</div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodWorkspace.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport EventDispatcher from '../dispatcher/EventDispatcher';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as Icons from '@blueprintjs/icons';\nimport * as Constants from '../Constants';\nimport * as PodTypes from '../types/PodTypes';\nimport * as PodActions from '../actions/PodActions';\nimport PodsUnitStore from '../stores/PodsUnitStore';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as Theme from '../Theme';\nimport * as Alert from '../Alert';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageInputButton from './PageInputButton';\nimport PodEditor from './PodEditor';\nimport PodUnit from './PodUnit';\nimport PodDeploy from './PodDeploy';\nimport PodMigrate from './PodMigrate';\nimport NonState from './NonState';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tpod: PodTypes.PodRo;\n\tpodOrig: PodTypes.PodRo;\n\tdisabled: boolean;\n\tunitChanged: boolean;\n\tmode: string;\n\tonMode: (mode: string) => void;\n\tonChangeCommit: (unitId: string, commit: string) => void;\n\tonEdit: (units: PodTypes.Unit[]) => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\texpandLeft: boolean;\n\texpandRight: boolean;\n\tactiveUnitId: string;\n\tselectedDeployments: Selected;\n\tlastSelectedDeployment: string;\n\tunit: PodTypes.PodUnit;\n\tcommits: PodTypes.CommitData;\n\tcommitData: PodTypes.Commit\n\tdiffCommitData: PodTypes.Commit\n\tdiffCommit: PodTypes.Commit\n\tdiffChanged: boolean\n\tviewCommit: PodTypes.Commit\n}\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\nconst css = {\n\tcard: {\n\t\tpadding: '0 0 10px 0',\n\t\twidth: '100%',\n\t\tflexGrow: 1,\n\t\tminHeight: 0,\n\t\tmaxHeight: '100%',\n\t} as React.CSSProperties,\n\teditButton: {\n\t\tmargin: \"0 0 0 3px\",\n\t\tminHeight: \"18px\",\n\t\tminWidth: \"18px\",\n\t\theight: \"18px\",\n\t\twidth: \"18px\",\n\t\tpadding: \"0 0 0 1px\",\n\t} as React.CSSProperties,\n\ttab: {\n\t\tfontWeight: \"bold\",\n\t\tmarginRight: \"10px\",\n\t} as React.CSSProperties,\n\tdocumentIcon: {\n\t\tmargin: \"2px 0 0 0\",\n\t\tfontSize: \"12px\",\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tdivider : {\n\t\tmarginRight: \"0\",\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tnavbar: {\n\t\theight: '52px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\ttabsBox: {\n\t\toverflowX: 'auto',\n\t\toverflowY: 'hidden',\n\t\tscrollbarWidth: 'thin',\n\t} as React.CSSProperties,\n\tnavButtons: {\n\t\theight: '52px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n\tnavButton: {\n\t\tmarginLeft: '10px',\n\t} as React.CSSProperties,\n\tsettingsOpen: {\n\t\tmarginLeft: '10px',\n\t} as React.CSSProperties,\n\tsettingsMenu: {\n\t\tmaxHeight: '400px',\n\t\toverflowY: \"auto\",\n\t} as React.CSSProperties,\n\tmuted: {\n\t\topacity: 0.75,\n\t} as React.CSSProperties,\n\tcommitsButton: {\n\t\tmarginLeft: '10px',\n\t\tmaxHeight: '400px',\n\t\toverflowY: \"auto\",\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n\tcommitsMenu: {\n\t\tmaxHeight: '400px',\n\t\toverflowY: \"auto\",\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n\tnonState: {\n\t\tpadding: \"80px 0\",\n\t} as React.CSSProperties,\n};\n\nexport default class PodWorkspace extends React.Component<Props, State> {\n\tsync: MiscUtils.SyncInterval;\n\teventToken: string;\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\texpandLeft: null,\n\t\t\texpandRight: null,\n\t\t\tactiveUnitId: \"\",\n\t\t\tselectedDeployments: {},\n\t\t\tlastSelectedDeployment: null,\n\t\t\tunit: null,\n\t\t\tcommits: null,\n\t\t\tcommitData: null,\n\t\t\tdiffCommitData: null,\n\t\t\tdiffCommit: null,\n\t\t\tdiffChanged: false,\n\t\t\tviewCommit: null,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tPodsUnitStore.addChangeListener(this.onChange);\n\t\tlet activeUnit = this.getActiveUnit()\n\t\tif (activeUnit && !activeUnit.new) {\n\t\t\tthis.syncUnit(activeUnit.id)\n\t\t}\n\n\t\tthis.sync = new MiscUtils.SyncInterval(\n\t\t\tasync () => {\n\t\t\t\tlet activeUnit = this.getActiveUnit()\n\t\t\t\tif (activeUnit && !activeUnit.new) {\n\t\t\t\t\tawait PodActions.syncUnit(this.props.pod.id, activeUnit.id);\n\t\t\t\t}\n\t\t\t},\n\t\t\t3000,\n\t\t)\n\n\t\tthis.eventToken = EventDispatcher.register((action: PodTypes.PodDispatch) => {\n\t\t\tswitch (action.type) {\n\t\t\t\tcase PodTypes.CHANGE:\n\t\t\t\t\tlet activeUnit = this.getActiveUnit()\n\t\t\t\t\tif (activeUnit && !activeUnit.new) {\n\t\t\t\t\t\tthis.syncUnit(activeUnit.id)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tlet activeUnit = this.getActiveUnit()\n\t\t\t\t\t\t\tif (activeUnit && !activeUnit.new) {\n\t\t\t\t\t\t\t\tthis.syncUnit(activeUnit.id)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 500)\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t});\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tPodsUnitStore.removeChangeListener(this.onChange);\n\t\tthis.sync?.stop()\n\t\tEventDispatcher.unregister(this.eventToken)\n\t}\n\n\tget selectedDeployments(): boolean {\n\t\treturn !!Object.keys(this.state.selectedDeployments).length;\n\t}\n\n\tsyncUnit = async (unitId: string, more?: boolean): Promise<void> => {\n\t\tPodActions.syncUnit(this.props.pod.id, unitId);\n\n\t\tlet specsPage = 0\n\t\tif (more && this.state.commits?.unit == unitId) {\n\t\t\tspecsPage += this.state.commits.page + 1\n\t\t}\n\n\t\tlet commitData = await PodActions.syncSpecs(\n\t\t\tthis.props.pod.id, unitId, specsPage)\n\n\t\tif (more && this.state.commits?.unit == unitId &&\n\t\t\tcommitData.page > this.state.commits?.page) {\n\n\t\t\tcommitData.specs = [\n\t\t\t\t...this.state.commits.specs,\n\t\t\t\t...commitData.specs,\n\t\t\t]\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tcommits: commitData,\n\t\t})\n\t}\n\n\tsyncCommit = async (unitId: string, specId: string): Promise<void> => {\n\t\tlet spec = await PodActions.spec(\n\t\t\tthis.props.pod.id, unitId, specId)\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tcommitData: spec,\n\t\t})\n\t}\n\n\tsyncDiffCommit = async (unitId: string, specId: string): Promise<void> => {\n\t\tlet spec = await PodActions.spec(\n\t\t\tthis.props.pod.id, unitId, specId)\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdiffCommitData: spec,\n\t\t})\n\t}\n\n\tonChange = (): void => {\n\t\tlet unit: PodTypes.PodUnit\n\t\tlet activeUnit = this.getActiveUnit()\n\n\t\tif (activeUnit && !activeUnit.new) {\n\t\t\tunit = PodsUnitStore.unit(activeUnit.id)\n\t\t} else {\n\t\t\tunit = null\n\t\t}\n\n\t\tlet selectedDeployments: Selected = {};\n\t\tlet curSelectedDeployments = this.state.selectedDeployments;\n\n\t\tif (activeUnit && unit) {\n\t\t\tlet deployments = unit.deployments || []\n\t\t\tdeployments.forEach((deployment: PodTypes.Deployment): void => {\n\t\t\t\tif (curSelectedDeployments[deployment.id]) {\n\t\t\t\t\tselectedDeployments[deployment.id] = true;\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tselectedDeployments: selectedDeployments,\n\t\t\tunit: unit,\n\t\t});\n\t}\n\n\tonArchiveDeployments = (): void => {\n\t\tlet activeUnit = this.getActiveUnit()\n\t\tif (!activeUnit) {\n\t\t\treturn\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPodActions.updateMultiUnitAction(\n\t\t\t\tthis.props.pod.id, activeUnit.id,\n\t\t\t\tObject.keys(this.state.selectedDeployments),\n\t\t\t  \"archive\").then((): void => {\n\n\t\t\tAlert.success('Successfully archived deployments');\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselectedDeployments: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonRestoreDeployments = (): void => {\n\t\tlet activeUnit = this.getActiveUnit()\n\t\tif (!activeUnit) {\n\t\t\treturn\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPodActions.updateMultiUnitAction(\n\t\t\t\tthis.props.pod.id, activeUnit.id,\n\t\t\t\tObject.keys(this.state.selectedDeployments),\n\t\t\t  \"restore\").then((): void => {\n\n\t\t\tAlert.success('Successfully restored deployments');\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselectedDeployments: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDeleteDeployments = (): void => {\n\t\tlet activeUnit = this.getActiveUnit()\n\t\tif (!activeUnit) {\n\t\t\treturn\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPodActions.updateMultiUnitAction(\n\t\t\t\tthis.props.pod.id, activeUnit.id,\n\t\t\t\tObject.keys(this.state.selectedDeployments),\n\t\t\t  \"destroy\").then((): void => {\n\n\t\t\tAlert.success('Successfully deleted deployments');\n\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselectedDeployments: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tgetActiveUnit = (): PodTypes.Unit => {\n\t\tlet units = [\n\t\t\t...(this.props.pod.units || []),\n\t\t]\n\n\t\tlet activeUnit = units.find(unit => unit.id === this.state.activeUnitId)\n\t\tif (!activeUnit) {\n\t\t\tfor (let unit of units) {\n\t\t\t\tif (!unit.delete) {\n\t\t\t\t\tactiveUnit = unit\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn activeUnit\n\t}\n\n\tgetActiveUnitOrig = (): PodTypes.Unit => {\n\t\tlet units = (this.props.podOrig.units || [])\n\n\t\tlet activeUnit = units.find(unit => unit.id === this.state.activeUnitId)\n\t\tif (!activeUnit) {\n\t\t\tfor (let unit of units) {\n\t\t\t\tif (!unit.delete) {\n\t\t\t\t\tactiveUnit = unit\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn activeUnit\n\t}\n\n\tgetActiveUnitIndex = (): number => {\n\t\tlet units = [\n\t\t\t...(this.props.pod.units || []),\n\t\t]\n\n\t\tlet activeIndex = units.findIndex(\n\t\t\tunit => unit.id === this.state.activeUnitId)\n\t\tif (activeIndex === -1) {\n\t\t\tfor (let i = 0; i < units.length; i++) {\n\t\t\t\tif (!units[i].delete) {\n\t\t\t\t\tactiveIndex = i\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn activeIndex\n\t}\n\n\tonEdit = (): void => {\n\t\tlet units = [\n\t\t\t...(this.props.pod.units || []),\n\t\t]\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\texpandLeft: false,\n\t\t\texpandRight: true,\n\t\t})\n\t\tthis.props.onEdit(units)\n\t}\n\n\tonView = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\texpandLeft: true,\n\t\t\texpandRight: false,\n\t\t})\n\t\tthis.props.onMode(\"view\")\n\t}\n\n\tonUnit = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\texpandLeft: true,\n\t\t\texpandRight: false,\n\t\t})\n\t\tthis.props.onMode(\"unit\")\n\t}\n\n\tonNew = (): void => {\n\t\tlet units = [\n\t\t\t...(this.props.pod.units || []),\n\t\t]\n\n\t\tunits.push({\n\t\t\tid: MiscUtils.objectId(),\n\t\t\tname: \"new-unit\",\n\t\t\tspec: \"\",\n\t\t\tnew: true,\n\t\t})\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\texpandLeft: false,\n\t\t\texpandRight: true,\n\t\t})\n\t\tthis.props.onEdit(units)\n\t}\n\n\tonDelete = (): void => {\n\t\tlet units = [\n\t\t\t...(this.props.pod.units || []),\n\t\t]\n\n\t\tlet index = this.getActiveUnitIndex()\n\t\tif (index !== -1) {\n\t\t\tlet unit = units[index]\n\t\t\tunits[index] = {\n\t\t\t\tid: unit.id,\n\t\t\t\tdelete: true,\n\t\t\t}\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tactiveUnitId: \"\",\n\t\t})\n\t\tthis.props.onEdit(units)\n\n\t\tlet activeUnit = this.getActiveUnit()\n\t\tif (activeUnit && !activeUnit.new) {\n\t\t\tPodActions.syncUnit(this.props.pod.id, activeUnit.id);\n\t\t}\n\t}\n\n\tonUnitEdit = (val: string): void => {\n\t\tlet units = [\n\t\t\t...(this.props.pod.units || []),\n\t\t]\n\n\t\tlet index = this.getActiveUnitIndex()\n\t\tif (index !== -1) {\n\t\t\tunits[index] = {\n\t\t\t\t...units[index],\n\t\t\t\tspec: val,\n\t\t\t}\n\t\t}\n\n\t\tthis.props.onEdit(units)\n\t}\n\n\tonUnitDeploy = (val: string): void => {\n\t\tlet index = this.getActiveUnitIndex()\n\t\tif (index !== -1) {\n\t\t\tthis.props.onChangeCommit(this.props.pod.units[index].id, val)\n\t\t}\n\t}\n\n\tonViewCommit = (unit: PodTypes.Unit, commit: PodTypes.Commit): void => {\n\t\tif (commit.index === unit.spec_index) {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tviewCommit: null,\n\t\t\t})\n\t\t} else {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tviewCommit: commit,\n\t\t\t})\n\t\t}\n\t\tthis.syncCommit(unit.id, commit.id)\n\t}\n\n\trender(): JSX.Element {\n\t\tlet units = [\n\t\t\t...(this.props.pod.units || []),\n\t\t]\n\t\tlet activeUnit = this.getActiveUnit()\n\t\tlet diffCommit: PodTypes.Commit\n\t\tif (this.state.diffCommitData?.id === this.state.diffCommit?.id) {\n\t\t\tdiffCommit = this.state.diffCommitData\n\t\t}\n\t\tlet mode = this.props.mode\n\t\tlet noUnits = true\n\t\tfor (let unit of units) {\n\t\t\tif (!unit.delete) {\n\t\t\t\tnoUnits = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (noUnits) {\n\t\t\tmode = \"view\"\n\t\t}\n\n\t\tlet expandLeft = this.state.expandLeft\n\t\tlet expandRight = this.state.expandRight\n\t\tif (expandLeft === null) {\n\t\t\tif (mode === \"edit\") {\n\t\t\t\texpandLeft = false\n\t\t\t\texpandRight = true\n\t\t\t} else {\n\t\t\t\texpandLeft = true\n\t\t\t\texpandRight = false\n\t\t\t}\n\t\t}\n\n\t\tif (!this.props.unitChanged && mode !== \"edit\") {\n\t\t\texpandLeft = true\n\t\t\texpandRight = false\n\t\t}\n\n\t\tlet expandIconClass: string\n\t\tif (!expandLeft && !expandRight) {\n\t\t\texpandIconClass = \"bp5-button bp5-minimal bp5-icon-maximize\"\n\t\t} else {\n\t\t\texpandIconClass = \"bp5-button bp5-minimal bp5-icon-minimize\"\n\t\t}\n\n\t\tlet tabsElem: JSX.Element[] = []\n\t\tfor (let i = 0; i < units.length; ++i) {\n\t\t\tlet unit = units[i]\n\t\t\tif (unit.delete) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttabsElem.push(<Blueprint.Tab id={unit.id} style={css.tab} key={unit.id}>\n\t\t\t\t{unit.name}\n\t\t\t</Blueprint.Tab>)\n\t\t}\n\n\t\tlet menuItems: JSX.Element[] = []\n\n\t\tmenuItems.push(<li\n\t\t\tkey=\"menu-unit-header\"\n\t\t\tclassName=\"bp5-menu-header\"\n\t\t>\n\t\t\t<h6 className=\"bp5-heading\">Units</h6>\n\t\t</li>)\n\t\tmenuItems.push(<Blueprint.MenuDivider\n\t\t\tkey=\"menu-unit-divider\"\n\t\t/>)\n\n\t\tmenuItems.push(<Blueprint.MenuItem\n\t\t\tkey=\"menu-new-unit\"\n\t\t\tclassName=\"\"\n\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\ticon={<Icons.Plus/>}\n\t\t\tonClick={(): void => {\n\t\t\t\tthis.onNew()\n\t\t\t}}\n\t\t\ttext={\"New Unit\"}\n\t\t/>)\n\n\t\tmenuItems.push(<ConfirmButton\n\t\t\tkey=\"menu-delete-unit\"\n\t\t\tsafe={true}\n\t\t\tmenuItem={true}\n\t\t\tclassName=\"bp5-intent-danger bp5-icon-trash\"\n\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\tdialogLabel=\"Delete Unit\"\n\t\t\tlabel=\"Delete Unit\"\n\t\t\tconfirmMsg=\"Permanently delete this unit\"\n\t\t\tconfirmInput={false}\n\t\t\titems={[activeUnit ? activeUnit.name : \"null\"]}\n\t\t\thidden={!activeUnit}\n\t\t\tstyle={css.navButton}\n\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\tonConfirm={(): void => {\n\t\t\t\tthis.onDelete()\n\t\t\t}}\n\t\t/>)\n\n\t\tlet deploymentsLabel = \"Deployments\"\n\t\tif (activeUnit && activeUnit.kind === \"image\") {\n\t\t\tdeploymentsLabel = \"Builds\"\n\t\t}\n\n\t\tif (!noUnits) {\n\t\t\tmenuItems.push(<li\n\t\t\t\tkey=\"menu-deployments-header\"\n\t\t\t\tclassName=\"bp5-menu-header\"\n\t\t\t>\n\t\t\t\t<h6 className=\"bp5-heading\">{deploymentsLabel}</h6>\n\t\t\t</li>)\n\t\t\tmenuItems.push(<Blueprint.MenuDivider\n\t\t\t\tkey=\"menu-deployments-divider\"\n\t\t\t/>)\n\n\t\t\tif (mode !== \"unit\") {\n\t\t\t\tmenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\tkey=\"menu-deployments\"\n\t\t\t\t\tclassName=\"\"\n\t\t\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\t\t\ticon={<Icons.Dashboard/>}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.onUnit()\n\t\t\t\t\t}}\n\t\t\t\t\ttext={\"View \" + deploymentsLabel}\n\t\t\t\t/>)\n\t\t\t}\n\t\t}\n\n\t\tlet commits: PodTypes.Commit[];\n\t\tif (this.state.commits?.unit === activeUnit?.id) {\n\t\t\tcommits = this.state.commits?.specs\n\t\t}\n\n\t\tlet commitMenu: JSX.Element\n\t\tif (mode === \"unit\") {\n\t\t\tif (commits && this.state.unit?.kind !== \"image\") {\n\t\t\t\tlet activeUnitOrig = this.getActiveUnitOrig()\n\t\t\t\tlet commitMenuItems: JSX.Element[] = []\n\n\t\t\t\tcommits.forEach((commit): void => {\n\t\t\t\t\tlet className = \"\"\n\t\t\t\t\tlet disabled = false\n\t\t\t\t\tlet selected = false\n\t\t\t\t\tif (activeUnit && activeUnitOrig.deploy_spec == commit.id) {\n\t\t\t\t\t\t// disabled = true\n\t\t\t\t\t\tclassName = \"bp5-text-intent-primary bp5-intent-primary\"\n\t\t\t\t\t\tselected = true\n\t\t\t\t\t}\n\n\t\t\t\t\tcommitMenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\t\tkey={\"diff-\" + commit.id}\n\t\t\t\t\t\tdisabled={disabled || this.props.disabled || this.state.disabled}\n\t\t\t\t\t\tselected={selected}\n\t\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t\t\ticon={<Icons.GitCommit\n\t\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\t/>}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onUnitDeploy(commit.id)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\ttext={commit.id.substring(12)}\n\t\t\t\t\t\ttextClassName={className}\n\t\t\t\t\t\tlabelElement={<span\n\t\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\t>{MiscUtils.formatDateLocal(commit.timestamp)}</span>}\n\t\t\t\t\t/>)\n\t\t\t\t})\n\n\t\t\t\tif (commits.length < this.state.commits?.count) {\n\t\t\t\t\tcommitMenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\t\tkey={\"diff-more\"}\n\t\t\t\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t\t\ticon={<Icons.BringData/>}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.syncUnit(activeUnit.id, true)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\ttext=\"Load More...\"\n\t\t\t\t\t\ttextClassName=\"bp5-text-muted\"\n\t\t\t\t\t\tshouldDismissPopover={false}\n\t\t\t\t\t/>)\n\t\t\t\t}\n\n\t\t\t\tcommitMenu = <Blueprint.Popover\n\t\t\t\t\tcontent={<Blueprint.Menu style={css.commitsMenu}>\n\t\t\t\t\t\t{commitMenuItems}\n\t\t\t\t\t</Blueprint.Menu>}\n\t\t\t\t\tplacement=\"bottom\"\n\t\t\t\t>\n\t\t\t\t\t<Blueprint.Button\n\t\t\t\t\t\talignText=\"left\"\n\t\t\t\t\t\ticon={<Icons.GitRepo/>}\n\t\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\t\ttext=\"Deployment Commit\"\n\t\t\t\t\t\tstyle={css.settingsOpen}\n\t\t\t\t\t/>\n\t\t\t\t</Blueprint.Popover>\n\t\t\t} else {\n\t\t\t\tcommitMenu = <Blueprint.Popover\n\t\t\t\t\tcontent={<Blueprint.Menu style={css.commitsMenu}>\n\t\t\t\t\t</Blueprint.Menu>}\n\t\t\t\t\tplacement=\"bottom\"\n\t\t\t\t>\n\t\t\t\t\t<Blueprint.Button\n\t\t\t\t\t\talignText=\"left\"\n\t\t\t\t\t\ticon={<Icons.GitRepo/>}\n\t\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\t\ttext=\"Deployment Commit\"\n\t\t\t\t\t\tstyle={css.settingsOpen}\n\t\t\t\t\t\tdisabled={true}\n\t\t\t\t\t/>\n\t\t\t\t</Blueprint.Popover>\n\t\t\t}\n\n\t\t\tlet selectedNames: string[] = [];\n\t\t\tfor (let deploymentId of Object.keys(this.state.selectedDeployments)) {\n\t\t\t\tselectedNames.push(deploymentId)\n\t\t\t}\n\t\t\tmenuItems.push(<PodDeploy\n\t\t\t\tkey=\"menu-pod-deploy\"\n\t\t\t\tpod={this.props.pod}\n\t\t\t\tunit={activeUnit}\n\t\t\t\tcommits={commits}\n\t\t\t/>)\n\t\t\tif (this.state.unit?.kind !== \"image\") {\n\t\t\t\tmenuItems.push(<ConfirmButton\n\t\t\t\t\tkey=\"menu-restore-deployments\"\n\t\t\t\t\tlabel=\"Restore Selected\"\n\t\t\t\t\tclassName=\"bp5-intent-success bp5-icon-unarchive\"\n\t\t\t\t\tsafe={true}\n\t\t\t\t\tmenuItem={true}\n\t\t\t\t\tstyle={css.navButton}\n\t\t\t\t\tconfirmMsg=\"Restore the selected archived deployments\"\n\t\t\t\t\tconfirmInput={false}\n\t\t\t\t\titems={selectedNames}\n\t\t\t\t\tdisabled={!this.selectedDeployments || this.state.disabled}\n\t\t\t\t\tonConfirm={this.onRestoreDeployments}\n\t\t\t\t/>)\n\t\t\t\tmenuItems.push(<ConfirmButton\n\t\t\t\t\tkey=\"menu-archive-deployments\"\n\t\t\t\t\tlabel=\"Archive Selected\"\n\t\t\t\t\tclassName=\"bp5-intent-warning bp5-icon-archive\"\n\t\t\t\t\tsafe={true}\n\t\t\t\t\tmenuItem={true}\n\t\t\t\t\tstyle={css.navButton}\n\t\t\t\t\tconfirmMsg=\"Archive the selected deployments, use restore to reactivate\"\n\t\t\t\t\tconfirmInput={false}\n\t\t\t\t\titems={selectedNames}\n\t\t\t\t\tdisabled={!this.selectedDeployments || this.state.disabled}\n\t\t\t\t\tonConfirm={this.onArchiveDeployments}\n\t\t\t\t/>)\n\t\t\t\tmenuItems.push(<PodMigrate\n\t\t\t\t\tkey=\"menu-migrate\"\n\t\t\t\t\tdisabled={!this.selectedDeployments || this.state.disabled}\n\t\t\t\t\tpod={this.props.pod}\n\t\t\t\t\tunit={this.state.unit}\n\t\t\t\t\tcommits={commits}\n\t\t\t\t\tselectedDeployments={this.state.selectedDeployments}\n\t\t\t\t\tonClear={(): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tselectedDeployments: {},\n\t\t\t\t\t\t\tdisabled: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t}}\n\t\t\t\t/>)\n\t\t\t}\n\t\t\tmenuItems.push(<ConfirmButton\n\t\t\t\tkey=\"menu-delete-deployments\"\n\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\tsafe={true}\n\t\t\t\tmenuItem={true}\n\t\t\t\tstyle={css.navButton}\n\t\t\t\tconfirmMsg=\"Permanently delete the selected deployments\"\n\t\t\t\tconfirmInput={true}\n\t\t\t\titems={selectedNames}\n\t\t\t\tdisabled={!this.selectedDeployments || this.state.disabled}\n\t\t\t\tonConfirm={this.onDeleteDeployments}\n\t\t\t/>)\n\t\t}\n\n\t\tlet newUnit: JSX.Element\n\t\tif (noUnits) {\n\t\t\tnewUnit = <Blueprint.Button\n\t\t\t\talignText=\"left\"\n\t\t\t\ticon={<Icons.Plus/>}\n\t\t\t\ttext=\"New Unit\"\n\t\t\t\tstyle={css.settingsOpen}\n\t\t\t\tonClick={() => {\n\t\t\t\t\tthis.onNew()\n\t\t\t\t}}\n\t\t\t/>\n\t\t}\n\n\t\tif (!noUnits) {\n\t\t\tmenuItems.push(<li\n\t\t\t\tkey=\"menu-spec-header\"\n\t\t\t\tclassName=\"bp5-menu-header\"\n\t\t\t>\n\t\t\t\t<h6 className=\"bp5-heading\">Specs</h6>\n\t\t\t</li>)\n\t\t\tmenuItems.push(<Blueprint.MenuDivider\n\t\t\t\tkey=\"menu-spec-divider\"\n\t\t\t/>)\n\t\t}\n\n\t\tif (mode !== \"view\") {\n\t\t\tmenuItems.push(<Blueprint.MenuItem\n\t\t\t\tkey=\"menu-view-spec\"\n\t\t\t\tclassName=\"\"\n\t\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\t\ticon={<Icons.DocumentOpen/>}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.onView()\n\t\t\t\t}}\n\t\t\t\ttext={\"View Spec\"}\n\t\t\t/>)\n\t\t}\n\n\t\tif (mode !== \"edit\") {\n\t\t\tmenuItems.push(<Blueprint.MenuItem\n\t\t\t\tkey=\"menu-edit-spec\"\n\t\t\t\tclassName=\"\"\n\t\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\t\thidden={!activeUnit}\n\t\t\t\ticon={<Icons.Edit/>}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.onEdit()\n\t\t\t\t}}\n\t\t\t\ttext={\"Edit Spec\"}\n\t\t\t/>)\n\t\t}\n\n\t\tif (mode === \"edit\") {\n\t\t\tif (this.state.unit && activeUnit &&\n\t\t\t\tthis.state.unit.id === activeUnit.id && commits) {\n\n\t\t\t\tlet commitMenuItems: JSX.Element[] = []\n\n\t\t\t\tcommits.forEach((commit): void => {\n\t\t\t\t\tlet className = \"\"\n\t\t\t\t\tlet disabled = false\n\t\t\t\t\tif (activeUnit && activeUnit.last_spec == commit.id) {\n\t\t\t\t\t\tif (diffCommit) {\n\t\t\t\t\t\t\tclassName = \"bp5-text-intent-success\"\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tclassName = \"bp5-text-intent-primary\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (diffCommit && diffCommit.id == commit.id) {\n\t\t\t\t\t\tclassName = \"bp5-text-intent-danger\"\n\t\t\t\t\t\tdisabled = true\n\t\t\t\t\t}\n\n\t\t\t\t\tcommitMenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\t\tkey={\"diff-\" + commit.id}\n\t\t\t\t\t\tdisabled={disabled || this.props.disabled || this.state.disabled}\n\t\t\t\t\t\ticon={<Icons.GitCommit/>}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tdiffCommit: commit,\n\t\t\t\t\t\t\t\tdiffChanged: false,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tthis.syncDiffCommit(activeUnit.id, commit.id)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\ttext={commit.id.substring(12)}\n\t\t\t\t\t\ttextClassName={className}\n\t\t\t\t\t\tlabel={MiscUtils.formatDateLocal(commit.timestamp)}\n\t\t\t\t\t/>)\n\t\t\t\t})\n\n\t\t\t\tif (commits.length < this.state.commits?.count) {\n\t\t\t\t\tcommitMenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\t\tkey={\"diff-more\"}\n\t\t\t\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t\t\ticon={<Icons.BringData/>}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.syncUnit(activeUnit.id, true)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\ttext=\"Load More...\"\n\t\t\t\t\t\ttextClassName=\"bp5-text-muted\"\n\t\t\t\t\t\tshouldDismissPopover={false}\n\t\t\t\t\t/>)\n\t\t\t\t}\n\n\t\t\t\tcommitMenu = <Blueprint.Popover\n\t\t\t\t\tcontent={<Blueprint.Menu style={css.commitsMenu}>\n\t\t\t\t\t\t{commitMenuItems}\n\t\t\t\t\t</Blueprint.Menu>}\n\t\t\t\t\tplacement=\"bottom\"\n\t\t\t\t>\n\t\t\t\t\t<Blueprint.Button\n\t\t\t\t\t\talignText=\"left\"\n\t\t\t\t\t\ticon={<Icons.GitRepo/>}\n\t\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\t\ttext=\"Diff\"\n\t\t\t\t\t\tstyle={css.settingsOpen}\n\t\t\t\t\t/>\n\t\t\t\t</Blueprint.Popover>\n\t\t\t} else {\n\t\t\t\tcommitMenu = <Blueprint.Popover\n\t\t\t\t\tcontent={<Blueprint.Menu style={css.commitsMenu}>\n\t\t\t\t\t</Blueprint.Menu>}\n\t\t\t\t\tplacement=\"bottom\"\n\t\t\t\t>\n\t\t\t\t\t<Blueprint.Button\n\t\t\t\t\t\talignText=\"left\"\n\t\t\t\t\t\ticon={<Icons.GitRepo/>}\n\t\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\t\ttext=\"Diff\"\n\t\t\t\t\t\tstyle={css.settingsOpen}\n\t\t\t\t\t\tdisabled={true}\n\t\t\t\t\t/>\n\t\t\t\t</Blueprint.Popover>\n\t\t\t}\n\n\t\t\tmenuItems.push(<li\n\t\t\t\tkey=\"menu-theme-header\"\n\t\t\t\tclassName=\"bp5-menu-header\"\n\t\t\t>\n\t\t\t\t<h6 className=\"bp5-heading\">Editor Theme</h6>\n\t\t\t</li>)\n\t\t\tmenuItems.push(<Blueprint.MenuDivider\n\t\t\t\tkey=\"menu-theme-divider\"\n\t\t\t/>)\n\n\t\t\tlet curEditorTheme = Theme.getEditorTheme()\n\t\t\tfor (let editorTheme in Theme.editorThemeNames) {\n\t\t\t\tlet className = \"\"\n\t\t\t\tlet themeName = Theme.editorThemeNames[editorTheme]\n\n\t\t\t\tif (editorTheme === curEditorTheme) {\n\t\t\t\t\tclassName = \"bp5-intent-primary\"\n\t\t\t\t}\n\n\t\t\t\tlet menuItem = <Blueprint.MenuItem\n\t\t\t\t\tkey={\"menu-theme-\" + editorTheme}\n\t\t\t\t\tclassName={className}\n\t\t\t\t\ticon={<Icons.Font/>}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tTheme.setEditorTheme(editorTheme)\n\t\t\t\t\t\tTheme.save()\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t})\n\t\t\t\t\t}}\n\t\t\t\t\ttext={themeName}\n\t\t\t\t/>\n\t\t\t\tmenuItems.push(menuItem)\n\t\t\t}\n\t\t}\n\n\t\tlet editorVal = \"\"\n\t\tlet viewLatestCommit = true\n\t\tif (activeUnit) {\n\t\t\tif (mode === \"view\" &&\n\t\t\t\tthis.state.viewCommit?.unit === activeUnit.id) {\n\t\t\t\t\tif (this.state.commitData?.id == this.state.viewCommit.id) {\n\t\t\t\t\t\teditorVal = this.state.commitData.data\n\t\t\t\t\t}\n\t\t\t\t\tviewLatestCommit = false\n\t\t\t} else {\n\t\t\t\teditorVal = activeUnit.spec\n\t\t\t}\n\t\t}\n\n\t\tif (mode === \"view\") {\n\t\t\tif (commits) {\n\t\t\t\tlet commitMenuItems: JSX.Element[] = []\n\n\t\t\t\tlet selectButton = <Blueprint.Button\n\t\t\t\t\talignText=\"left\"\n\t\t\t\t\ticon={<Icons.GitRepo/>}\n\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\ttext=\"View Commit\"\n\t\t\t\t\tstyle={css.settingsOpen}\n\t\t\t\t/>\n\n\t\t\t\tcommits.forEach((commit, index): void => {\n\t\t\t\t\tlet className = \"\"\n\t\t\t\t\tlet selected = false\n\t\t\t\t\tif (viewLatestCommit && index === 0) {\n\t\t\t\t\t\tclassName = \"bp5-text-intent-primary bp5-intent-primary\"\n\t\t\t\t\t\tselected = true\n\t\t\t\t\t} else if (!viewLatestCommit && commit.unit === activeUnit?.id &&\n\t\t\t\t\t\tthis.state.viewCommit?.id === commit.id) {\n\n\t\t\t\t\t\tclassName = \"bp5-text-intent-primary bp5-intent-primary\"\n\t\t\t\t\t\tselected = true\n\n\t\t\t\t\t\tselectButton = <Blueprint.Button\n\t\t\t\t\t\t\talignText=\"left\"\n\t\t\t\t\t\t\ticon={<Icons.GitCommit/>}\n\t\t\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\t\t\tstyle={css.commitsButton}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span>{this.state.viewCommit.id.substring(12)}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName=\"bp5-text-muted\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{\" \" + MiscUtils.formatDateLocal(\n\t\t\t\t\t\t\t\t\tthis.state.viewCommit.timestamp)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</Blueprint.Button>\n\t\t\t\t\t}\n\n\t\t\t\t\tcommitMenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\t\tkey={\"diff-\" + commit.id}\n\t\t\t\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\t\t\t\tselected={selected}\n\t\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t\t\ticon={<Icons.GitCommit\n\t\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\t/>}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.onViewCommit(activeUnit, commit)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\ttext={commit.id.substring(12)}\n\t\t\t\t\t\ttextClassName={className}\n\t\t\t\t\t\tlabelElement={<span\n\t\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\t>{MiscUtils.formatDateLocal(commit.timestamp)}</span>}\n\t\t\t\t\t/>)\n\t\t\t\t})\n\n\t\t\t\tif (commits.length < this.state.commits?.count) {\n\t\t\t\t\tcommitMenuItems.push(<Blueprint.MenuItem\n\t\t\t\t\t\tkey={\"diff-more\"}\n\t\t\t\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\t\t\t\troleStructure=\"listoption\"\n\t\t\t\t\t\ticon={<Icons.BringData/>}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.syncUnit(activeUnit.id, true)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\ttext=\"Load More...\"\n\t\t\t\t\t\ttextClassName=\"bp5-text-muted\"\n\t\t\t\t\t\tshouldDismissPopover={false}\n\t\t\t\t\t/>)\n\t\t\t\t}\n\n\t\t\t\tcommitMenu = <Blueprint.Popover\n\t\t\t\t\tcontent={<Blueprint.Menu style={css.commitsMenu}>\n\t\t\t\t\t\t{commitMenuItems}\n\t\t\t\t\t</Blueprint.Menu>}\n\t\t\t\t\tplacement=\"bottom\"\n\t\t\t\t>\n\t\t\t\t\t{selectButton}\n\t\t\t\t</Blueprint.Popover>\n\t\t\t} else {\n\t\t\t\tcommitMenu = <Blueprint.Popover\n\t\t\t\t\tcontent={<Blueprint.Menu style={css.commitsMenu}>\n\t\t\t\t\t</Blueprint.Menu>}\n\t\t\t\t\tplacement=\"bottom\"\n\t\t\t\t>\n\t\t\t\t\t<Blueprint.Button\n\t\t\t\t\talignText=\"left\"\n\t\t\t\t\ticon={<Icons.GitRepo/>}\n\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\ttext=\"View Commit\"\n\t\t\t\t\tstyle={css.settingsOpen}\n\t\t\t\t\tdisabled={true}\n\t\t\t\t/>\n\t\t\t\t</Blueprint.Popover>\n\t\t\t}\n\t\t}\n\n\t\tlet settingsMenu = <Blueprint.Menu style={css.settingsMenu}>\n\t\t\t{menuItems}\n\t\t</Blueprint.Menu>\n\n\t\treturn <div\n\t\t\tclassName=\"layout vertical\"\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<Blueprint.Navbar className=\"layout horizontal\" style={css.navbar}>\n\t\t\t\t<Blueprint.NavbarGroup\n\t\t\t\t\tclassName=\"flex thin-scroll\"\n\t\t\t\t\tstyle={css.tabsBox}\n\t\t\t\t\talign={\"left\"}\n\t\t\t\t>\n\t\t\t\t\t<Blueprint.Tabs\n\t\t\t\t\t\tid={this.props.pod.id}\n\t\t\t\t\t\tselectedTabId={activeUnit ? activeUnit.id : null}\n\t\t\t\t\t\tfill={true}\n\t\t\t\t\t\tonChange={(newTabId): void => {\n\t\t\t\t\t\t\tlet activeUnitId = newTabId.valueOf() as string\n\t\t\t\t\t\t\tlet activeUnit = units.find(unit => unit.id === activeUnitId)\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tactiveUnitId: activeUnitId,\n\t\t\t\t\t\t\t\tdiffCommit: null,\n\t\t\t\t\t\t\t\tdiffChanged: false,\n\t\t\t\t\t\t\t})\n\n\t\t\t\t\t\t\tif (activeUnit && !activeUnit.new) {\n\t\t\t\t\t\t\t\tthis.syncUnit(activeUnitId)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{tabsElem}\n\t\t\t\t\t</Blueprint.Tabs>\n\t\t\t\t</Blueprint.NavbarGroup>\n\t\t\t\t<Blueprint.NavbarGroup style={css.navButtons} align={\"right\"}>\n\t\t\t\t\t<Blueprint.NavbarDivider\n\t\t\t\t\t\tstyle={css.divider}\n\t\t\t\t\t/>\n\t\t\t\t\t<button\n\t\t\t\t\t\thidden={!(mode === \"edit\" && !this.state.diffCommit)}\n\t\t\t\t\t\tstyle={css.navButton}\n\t\t\t\t\t\tclassName={expandIconClass}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\texpandLeft: false,\n\t\t\t\t\t\t\t\texpandRight: !expandRight,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<button\n\t\t\t\t\t\thidden={!(mode === \"edit\" && this.state.diffCommit)}\n\t\t\t\t\t\tstyle={css.navButton}\n\t\t\t\t\t\tclassName=\"bp5-button bp5-icon-cross bp5-intent-danger\"\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tdiffCommit: null,\n\t\t\t\t\t\t\t\tdiffChanged: false,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}}\n\t\t\t\t\t>Close Diff</button>\n\t\t\t\t\t{newUnit}\n\t\t\t\t\t{commitMenu}\n\t\t\t\t\t<Blueprint.Popover\n\t\t\t\t\t\tcontent={settingsMenu}\n\t\t\t\t\t\tplacement=\"bottom-start\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Blueprint.Button\n\t\t\t\t\t\t\talignText=\"left\"\n\t\t\t\t\t\t\ticon={<Icons.Application/>}\n\t\t\t\t\t\t\trightIcon={<Icons.CaretDown/>}\n\t\t\t\t\t\t\ttext=\"Settings\"\n\t\t\t\t\t\t\tstyle={css.settingsOpen}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</Blueprint.Popover>\n\t\t\t\t</Blueprint.NavbarGroup>\n\t\t\t</Blueprint.Navbar>\n\t\t\t<div style={css.nonState} hidden={!noUnits}>\n\t\t\t\t<NonState\n\t\t\t\t\thidden={!noUnits}\n\t\t\t\t\tnoDelay={true}\n\t\t\t\t\ticonClass=\"bp5-icon-server\"\n\t\t\t\t\ttitle=\"No units\"\n\t\t\t\t\tdescription=\"Add a new unit to get started.\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<PodEditor\n\t\t\t\tpodId={this.props.pod.id}\n\t\t\t\thidden={mode === \"unit\"}\n\t\t\t\texpandLeft={expandLeft}\n\t\t\t\texpandRight={expandRight}\n\t\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\t\treadOnly={mode === \"view\"}\n\t\t\t\tuuid={activeUnit ? activeUnit.id : null}\n\t\t\t\tvalue={editorVal}\n\t\t\t\tdiffValue={diffCommit ? diffCommit.data : null}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tthis.onUnitEdit(val)\n\t\t\t\t}}\n\t\t\t\tonDiffChange={(val: string): void => {\n\t\t\t\t\tif (this.state.diffCommit && !this.state.diffChanged) {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tdiffChanged: true,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tthis.onUnitEdit(val)\n\t\t\t\t}}\n\t\t\t\tonEdit={this.onEdit}\n\t\t\t/>\n\t\t\t<PodUnit\n\t\t\t\thidden={mode !== \"unit\"}\n\t\t\t\tdisabled={this.props.disabled || this.state.disabled}\n\t\t\t\tselected={this.state.selectedDeployments}\n\t\t\t\tlastSelected={this.state.lastSelectedDeployment}\n\t\t\t\tunit={this.state.unit}\n\t\t\t\tonSelect={(selected: Selected, lastSelected: string): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelectedDeployment: lastSelected,\n\t\t\t\t\t\tselectedDeployments: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Pods.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PodTypes from '../types/PodTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport PodsStore from '../stores/PodsStore';\nimport * as PodActions from '../actions/PodActions';\nimport Pod from './Pod';\nimport PodDetailed from './PodDetailed';\nimport PodNew from './PodNew';\nimport PodsFilter from './PodsFilter';\nimport PodsPage from './PodsPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport CompletionCache from \"../completion/Cache\"\nimport * as CompletionTypes from \"../types/CompletionTypes\";\nimport * as CompletionActions from \"../actions/CompletionActions\";\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tpodId: string;\n\tpods: PodTypes.PodsRo;\n\tfilter: PodTypes.Filter;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tcompletion: CompletionTypes.Completion;\n\tselected: Selected;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n\tsidebar: boolean;\n\tsettings: boolean;\n\tmode: string;\n}\n\nconst css = {\n\tlayout: {\n\t\tminHeight: 0,\n\t\tpaddingBottom: '20px'\n\t} as React.CSSProperties,\n\titems: {\n\t\twidth: '200px',\n\t\tborderSpacing: '0 5px',\n\t\tpadding: '0 5px',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tpods: {\n\t\tminWidth: 0,\n\t\tmaxWidth: '100%',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\theadingBeta: {\n\t\tposition: \"relative\",\n\t\ttop: \"-4px\",\n\t\tleft: \"6px\",\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n\tnonState: {\n\t\tjustifyContent: 'center',\n\t\theight: '100%',\n\t} as React.CSSProperties,\n};\n\nexport default class Pods extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpodId: null,\n\t\t\tpods: PodsStore.pods,\n\t\t\tfilter: PodsStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tcompletion: CompletionStore.completion,\n\t\t\tselected: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t\tsidebar: true,\n\t\t\tsettings: false,\n\t\t\tmode: \"view\",\n\t\t};\n\t\tthis.handleKeyDown = this.handleKeyDown.bind(this);\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tPodsStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tPodActions.sync();\n\t\tCompletionActions.sync();\n\t\tdocument.addEventListener('keydown', this.handleKeyDown);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tPodsStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t\tdocument.removeEventListener('keydown', this.handleKeyDown);\n\t}\n\n\thandleKeyDown(event: KeyboardEvent) {\n\t\tif (event.ctrlKey && event.key === \"e\") {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tsidebar: !this.state.sidebar,\n\t\t\t})\n\t\t\tevent.preventDefault();\n\t\t}\n\t}\n\n\tonChange = (): void => {\n\t\tlet pods = PodsStore.pods;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\n\t\tpods.forEach((pod: PodTypes.Pod): void => {\n\t\t\tif (curSelected[pod.id]) {\n\t\t\t\tselected[pod.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tCompletionCache.update(CompletionStore.completion)\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpods: PodsStore.pods,\n\t\t\tfilter: PodsStore.filter,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: selected,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPodActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet activePod: PodTypes.Pod;\n\t\tlet podsDom: JSX.Element[] = [];\n\n\t\tthis.state.pods.forEach((\n\t\t\t\tpod: PodTypes.PodRo): void => {\n\t\t\tif (pod.id === this.state.podId) {\n\t\t\t\tactivePod = {\n\t\t\t\t\t...pod,\n\t\t\t\t\tdrafts: PodsStore.getDrafts(pod.id),\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpodsDom.push(<Pod\n\t\t\t\tkey={pod.id}\n\t\t\t\tpod={pod}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tselected={!!this.state.selected[pod.id]}\n\t\t\t\topen={activePod?.id === pod.id}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet pods = this.state.pods;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < pods.length; i++) {\n\t\t\t\t\t\t\tlet usr = pods[i];\n\n\t\t\t\t\t\t\tif (usr.id === pod.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[pods[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: pod.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[pod.id]) {\n\t\t\t\t\t\tdelete selected[pod.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[pod.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: pod.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet newMode = this.state.mode\n\t\t\t\t\tif (this.state.mode === \"edit\") {\n\t\t\t\t\t\tnewMode = \"view\"\n\t\t\t\t\t}\n\n\t\t\t\t\tif (newMode === \"view\" && PodsStore.getDrafts(pod.id)?.length) {\n\t\t\t\t\t\tnewMode = \"edit\"\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tpodId: pod.id,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t\tmode: newMode,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet podElem: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tpodElem = <PodNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tsidebar={this.state.sidebar}\n\t\t\t\ttoggleSidebar={() => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tsidebar: !this.state.sidebar,\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t/>;\n\t\t} else if (!podsDom.length) {\n\t\t\tpodElem = <div className=\"layout vertical flex\" style={css.nonState}>\n\t\t\t\t<NonState\n\t\t\t\t\thidden={false}\n\t\t\t\t\ticonClass=\"bp5-icon-cube\"\n\t\t\t\t\ttitle=\"No pods\"\n\t\t\t\t\tdescription=\"Add a new pod to get started.\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t} else if (activePod) {\n\t\t\tpodElem = <PodDetailed\n\t\t\t\tkey={activePod?.id}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tpod={activePod}\n\t\t\t\tmode={this.state.mode}\n\t\t\t\tonMode={(mode: string) => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmode: mode,\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t\tsettings={this.state.settings}\n\t\t\t\ttoggleSettings={() => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tsettings: !this.state.settings,\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t\tsidebar={this.state.sidebar}\n\t\t\t\ttoggleSidebar={() => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tsidebar: !this.state.sidebar,\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t/>\n\t\t}\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = PodsStore.pod(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\treturn <Page full={true}>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Pods\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tstyle={css.headingBeta}\n\t\t\t\t\t\t\tclassName=\"bp5-tag bp5-intent-primary\"\n\t\t\t\t\t\t>Beta</span>\n\t\t\t\t\t</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tPodActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tPodActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected pods\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<PodsFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tPodActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t/>\n\t\t\t<div className=\"layout horizontal flex\" style={css.layout}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-menu\"\n\t\t\t\t\tstyle={css.items}\n\t\t\t\t\thidden={!this.state.sidebar}\n\t\t\t\t>\n\t\t\t\t\t{podsDom}\n\t\t\t\t\t<PodsPage\n\t\t\t\t\t\tminimal={true}\n\t\t\t\t\t\tonPage={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"layout vertical flex\" style={css.pods}>\n\t\t\t\t\t{podElem}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodsFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as PodTypes from '../types/PodTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: PodTypes.Filter;\n\tonFilter: (filter: PodTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class PodsFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Pod ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.role}\n\t\t\t\tplaceholder=\"Role\"\n\t\t\t\tvalue={this.props.filter.role}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.role = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.role;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PodsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport PodsStore from '../stores/PodsStore';\nimport * as PodActions from '../actions/PodActions';\n\ninterface Props {\n\tminimal?: boolean;\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonMinimal: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 3px 0 0',\n\t\twidth: \"20px\",\n\t\tminWidth: \"20px\",\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tlinkMinimal: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 2px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n\tellipsis: {\n\t\tmargin: '7px 2px 0 0',\n\t} as React.CSSProperties,\n};\n\nexport default class PodsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: PodsStore.page,\n\t\t\tpageCount: PodsStore.pageCount,\n\t\t\tpages: PodsStore.pages,\n\t\t\tcount: PodsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tPodsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tPodsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: PodsStore.page,\n\t\t\tpageCount: PodsStore.pageCount,\n\t\t\tpages: PodsStore.pages,\n\t\t\tcount: PodsStore.count,\n\t\t});\n\t}\n\n\tcreatePageButton = (pageNum: number): JSX.Element => {\n\t\tconst page = this.state.page;\n\t\tconst linkStyle = this.props.minimal ? css.linkMinimal : css.link;\n\n\t\treturn (\n\t\t\t<span\n\t\t\t\tkey={pageNum}\n\t\t\t\tstyle={page === pageNum ? {\n\t\t\t\t\t...linkStyle,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : linkStyle}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPodActions.traverse(pageNum);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{pageNum + 1}\n\t\t\t</span>\n\t\t);\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\n\t\tif (this.props.minimal) {\n\t\t\tlinks.push(this.createPageButton(0));\n\n\t\t\tif (pages <= 3) {\n\t\t\t\tfor (let i = 1; i < pages; i++) {\n\t\t\t\t\tlinks.push(this.createPageButton(i));\n\t\t\t\t}\n\t\t\t} else if (page <= 1) {\n\t\t\t\tif (page === 1) {\n\t\t\t\t\tlinks.push(this.createPageButton(1));\n\t\t\t\t}\n\t\t\t\tlinks.push(<span key=\"ellipsis1\" style={css.ellipsis}>...</span>);\n\t\t\t\tlinks.push(this.createPageButton(pages - 1));\n\t\t\t} else if (page >= pages - 2) {\n\t\t\t\tlinks.push(<span key=\"ellipsis1\" style={css.ellipsis}>...</span>);\n\t\t\t\tif (page === pages - 2) {\n\t\t\t\t\tlinks.push(this.createPageButton(pages - 2));\n\t\t\t\t}\n\t\t\t\tlinks.push(this.createPageButton(pages - 1));\n\t\t\t} else {\n\t\t\t\tlinks.push(<span key=\"ellipsis1\" style={css.ellipsis}>...</span>);\n\t\t\t\tlinks.push(this.createPageButton(page));\n\t\t\t\tlinks.push(<span key=\"ellipsis2\" style={css.ellipsis}>...</span>);\n\t\t\t\tlinks.push(this.createPageButton(pages - 1));\n\t\t\t}\n\t\t} else {\n\t\t\tlet start = Math.max(0, page - 7);\n\t\t\tlet end = Math.min(pages, start + 15);\n\n\t\t\tfor (let i = start; i < end; i++) {\n\t\t\t\tlinks.push(this.createPageButton(i));\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPodActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={this.props.minimal ? css.buttonMinimal : css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPodActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={this.props.minimal ? css.buttonMinimal : css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPodActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPodActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Policies.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PolicyTypes from '../types/PolicyTypes';\nimport * as SettingsTypes from '../types/SettingsTypes';\nimport PoliciesStore from '../stores/PoliciesStore';\nimport SettingsStore from '../stores/SettingsStore';\nimport * as PolicyActions from '../actions/PolicyActions';\nimport * as SettingsActions from '../actions/SettingsActions';\nimport Policy from './Policy';\nimport PolicyNew from './PolicyNew';\nimport PoliciesFilter from './PoliciesFilter';\nimport PoliciesPage from './PoliciesPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tpolicies: PolicyTypes.PoliciesRo;\n\tproviders: SettingsTypes.SecondaryProviders;\n\tfilter: PolicyTypes.Filter;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Policies extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpolicies: PoliciesStore.policies,\n\t\t\tproviders: SettingsStore.settings ?\n\t\t\t\tSettingsStore.settings.auth_secondary_providers : [],\n\t\t\tfilter: PoliciesStore.filter,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tPoliciesStore.addChangeListener(this.onChange);\n\t\tSettingsStore.addChangeListener(this.onChange);\n\t\tPolicyActions.sync();\n\t\tSettingsActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tPoliciesStore.removeChangeListener(this.onChange);\n\t\tSettingsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet policies = PoliciesStore.policies;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tpolicies.forEach((policy: PolicyTypes.Policy): void => {\n\t\t\tif (curSelected[policy.id]) {\n\t\t\t\tselected[policy.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[policy.id]) {\n\t\t\t\topened[policy.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpolicies: policies,\n\t\t\tproviders: SettingsStore.settings ?\n\t\t\t\tSettingsStore.settings.auth_secondary_providers : [],\n\t\t\tfilter: PoliciesStore.filter,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPolicyActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet policiesDom: JSX.Element[] = [];\n\n\t\tthis.state.policies.forEach((\n\t\t\t\tpolicy: PolicyTypes.PolicyRo): void => {\n\t\t\tpoliciesDom.push(<Policy\n\t\t\t\tkey={policy.id}\n\t\t\t\tpolicy={policy}\n\t\t\t\tproviders={this.state.providers}\n\t\t\t\tselected={!!this.state.selected[policy.id]}\n\t\t\t\topen={!!this.state.opened[policy.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet policies = this.state.policies;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < policies.length; i++) {\n\t\t\t\t\t\t\tlet usr = policies[i];\n\n\t\t\t\t\t\t\tif (usr.id === policy.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[policies[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: policy.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[policy.id]) {\n\t\t\t\t\t\tdelete selected[policy.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[policy.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: policy.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[policy.id]) {\n\t\t\t\t\t\tdelete opened[policy.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[policy.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = PoliciesStore.policy(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newPolicyDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewPolicyDom = <PolicyNew\n\t\t\t\tproviders={this.state.providers}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Policies</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tPolicyActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tPolicyActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected policies\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<PoliciesFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tPolicyActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newPolicyDom}\n\t\t\t\t\t{policiesDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!policiesDom.length}\n\t\t\t\ticonClass=\"bp5-icon-filter\"\n\t\t\t\ttitle=\"No policies\"\n\t\t\t\tdescription=\"Add a new policy to get started.\"\n\t\t\t/>\n\t\t\t<PoliciesPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PoliciesFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as PolicyTypes from '../types/PolicyTypes';\nimport SearchInput from './SearchInput';\n\ninterface Props {\n\tfilter: PolicyTypes.Filter;\n\tonFilter: (filter: PolicyTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class PoliciesFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Policy ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PoliciesPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport PoliciesStore from '../stores/PoliciesStore';\nimport * as PolicyActions from '../actions/PolicyActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class PoliciesPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: PoliciesStore.page,\n\t\t\tpageCount: PoliciesStore.pageCount,\n\t\t\tpages: PoliciesStore.pages,\n\t\t\tcount: PoliciesStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tPoliciesStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tPoliciesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: PoliciesStore.page,\n\t\t\tpageCount: PoliciesStore.pageCount,\n\t\t\tpages: PoliciesStore.pages,\n\t\t\tcount: PoliciesStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPolicyActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPolicyActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPolicyActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPolicyActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPolicyActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Policy.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as SettingsTypes from '../types/SettingsTypes';\nimport * as PolicyTypes from '../types/PolicyTypes';\nimport PolicyDetailed from './PolicyDetailed';\n\ninterface Props {\n\tpolicy: PolicyTypes.PolicyRo;\n\tproviders: SettingsTypes.SecondaryProviders;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Policy extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet policy = this.props.policy;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<PolicyDetailed\n\t\t\t\t\tpolicy={this.props.policy}\n\t\t\t\t\tproviders={this.props.providers}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{policy.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PolicyDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Styles from \"../Styles\";\nimport * as PolicyTypes from '../types/PolicyTypes';\nimport * as SettingsTypes from '../types/SettingsTypes';\nimport * as PolicyActions from '../actions/PolicyActions';\nimport PolicyRule from './PolicyRule';\nimport PageInput from './PageInput';\nimport PageSwitch from './PageSwitch';\nimport PageSelect from './PageSelect';\nimport PageInputButton from './PageInputButton';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport Help from './Help';\nimport * as Alert from '../Alert';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tpolicy: PolicyTypes.PolicyRo;\n\tproviders: SettingsTypes.SecondaryProviders;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tpolicy: PolicyTypes.Policy;\n\taddRole: string;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class PolicyDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tpolicy: null,\n\t\t\taddRole: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet policy: any;\n\n\t\tif (this.state.changed) {\n\t\t\tpolicy = {\n\t\t\t\t...this.state.policy,\n\t\t\t};\n\t\t} else {\n\t\t\tpolicy = {\n\t\t\t\t...this.props.policy,\n\t\t\t};\n\t\t}\n\n\t\tpolicy[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tpolicy: policy,\n\t\t});\n\t}\n\n\tsetRule(name: string, rule: PolicyTypes.Rule): void {\n\t\tlet policy: any;\n\n\t\tif (this.state.changed) {\n\t\t\tpolicy = {\n\t\t\t\t...this.state.policy,\n\t\t\t};\n\t\t} else {\n\t\t\tpolicy = {\n\t\t\t\t...this.props.policy,\n\t\t\t};\n\t\t}\n\n\t\tlet rules = {\n\t\t\t...(policy.rules || {}),\n\t\t};\n\n\t\tif (rule.values == null) {\n\t\t\tdelete rules[name];\n\t\t} else {\n\t\t\trules[name] = rule;\n\t\t}\n\n\t\tpolicy.rules = rules;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tpolicy: policy,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPolicyActions.commit(this.state.policy).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tpolicy: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPolicyActions.remove(this.props.policy.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet policy: PolicyTypes.Policy;\n\n\t\tif (this.state.changed) {\n\t\t\tpolicy = {\n\t\t\t\t...this.state.policy,\n\t\t\t};\n\t\t} else {\n\t\t\tpolicy = {\n\t\t\t\t...this.props.policy,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(policy.roles || []),\n\t\t];\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\n\t\tpolicy.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tpolicy: policy,\n\t\t});\n\t}\n\n\tonRemoveRole(role: string): void {\n\t\tlet policy: PolicyTypes.Policy;\n\n\t\tif (this.state.changed) {\n\t\t\tpolicy = {\n\t\t\t\t...this.state.policy,\n\t\t\t};\n\t\t} else {\n\t\t\tpolicy = {\n\t\t\t\t...this.props.policy,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(policy.roles || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\n\t\tpolicy.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tpolicy: policy,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet policy: PolicyTypes.Policy = this.state.policy ||\n\t\t\tthis.props.policy;\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of (policy.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet rules = policy.rules || {};\n\t\tlet operatingSystem = rules.operating_system || {\n\t\t\ttype: 'operating_system',\n\t\t};\n\t\tlet browser = rules.browser || {\n\t\t\ttype: 'browser',\n\t\t};\n\t\tlet location = rules.location || {\n\t\t\ttype: 'location',\n\t\t};\n\t\tlet whitelistNetworks = rules.whitelist_networks || {\n\t\t\ttype: 'whitelist_networks',\n\t\t};\n\t\tlet blacklistNetworks = rules.blacklist_networks || {\n\t\t\ttype: 'blacklist_networks',\n\t\t};\n\n\t\tlet providerIds: string[] = [];\n\t\tlet adminProviders: JSX.Element[] = [];\n\t\tlet userProviders: JSX.Element[] = [];\n\t\tif (this.props.providers.length) {\n\t\t\tfor (let provider of this.props.providers) {\n\t\t\t\tproviderIds.push(provider.id);\n\t\t\t\tadminProviders.push(<option\n\t\t\t\t\tkey={provider.id}\n\t\t\t\t\tvalue={provider.id}\n\t\t\t\t>{provider.name}</option>);\n\t\t\t\tuserProviders.push(<option\n\t\t\t\t\tkey={provider.id}\n\t\t\t\t\tvalue={provider.id}\n\t\t\t\t>{provider.name}</option>);\n\t\t\t}\n\t\t} else {\n\t\t\tadminProviders.push(<option\n\t\t\t\tkey=\"null\"\n\t\t\t\tvalue=\"\"\n\t\t\t>None</option>);\n\t\t\tuserProviders.push(<option\n\t\t\t\tkey=\"null\"\n\t\t\t\tvalue=\"\"\n\t\t\t>None</option>);\n\t\t}\n\t\tlet adminProvider = policy.admin_secondary &&\n\t\t\tproviderIds.indexOf(policy.admin_secondary) !== -1;\n\t\tlet userProvider = policy.user_secondary &&\n\t\t\tproviderIds.indexOf(policy.user_secondary) !== -1;\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"policy\" id={this.props.policy.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Policy\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this policy\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[policy.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of policy\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={policy.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Policy comment.\"\n\t\t\t\t\t\tplaceholder=\"Policy comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={policy.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\tRoles\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\tcontent=\"Roles associated with this policy. All requests from users with associated roles must pass this policy check.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Admin two-factor authentication\"\n\t\t\t\t\t\thelp=\"Require admins to use two-factor authentication.\"\n\t\t\t\t\t\tchecked={adminProvider}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tif (adminProvider) {\n\t\t\t\t\t\t\t\tthis.set('admin_secondary', null);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.props.providers.length === 0) {\n\t\t\t\t\t\t\t\t\tAlert.warning(\n\t\t\t\t\t\t\t\t\t\t'No two-factor authentication providers exist');\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.set('admin_secondary', this.props.providers[0].id);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Admin Two-Factor Provider\"\n\t\t\t\t\t\thelp=\"Two-factor authentication provider that will be used. For users matching multiple policies the first provider will be used.\"\n\t\t\t\t\t\thidden={!adminProvider}\n\t\t\t\t\t\tvalue={policy.admin_secondary}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('admin_secondary', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{adminProviders}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"User two-factor authentication\"\n\t\t\t\t\t\thelp=\"Require users to use two-factor authentication.\"\n\t\t\t\t\t\tchecked={userProvider}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tif (userProvider) {\n\t\t\t\t\t\t\t\tthis.set('user_secondary', null);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.props.providers.length === 0) {\n\t\t\t\t\t\t\t\t\tAlert.warning(\n\t\t\t\t\t\t\t\t\t\t'No two-factor authentication providers exist');\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.set('user_secondary', this.props.providers[0].id);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"User Two-Factor Provider\"\n\t\t\t\t\t\thelp=\"Two-factor authentication provider that will be used. For users matching multiple policies the first provider will be used.\"\n\t\t\t\t\t\thidden={!userProvider}\n\t\t\t\t\t\tvalue={policy.user_secondary}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('user_secondary', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{userProviders}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\trule={whitelistNetworks}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setRule('whitelist_networks', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\trule={blacklistNetworks}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setRule('blacklist_networks', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.policy.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Enabled\"\n\t\t\t\t\t\thelp=\"Enable or disable policy.\"\n\t\t\t\t\t\tchecked={!policy.disabled}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('disabled', !policy.disabled)\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\trule={location}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setRule('location', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\trule={operatingSystem}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setRule('operating_system', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\trule={browser}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setRule('browser', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Admin WebAuthn device authentication\"\n\t\t\t\t\t\thelp=\"Require admins to use WebAuthn device authentication.\"\n\t\t\t\t\t\tchecked={policy.admin_device_secondary}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('admin_device_secondary',\n\t\t\t\t\t\t\t\t!policy.admin_device_secondary)\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"User WebAuthn device authentication\"\n\t\t\t\t\t\thelp=\"Require users to use WebAuthn device authentication.\"\n\t\t\t\t\t\tchecked={policy.user_device_secondary}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('user_device_secondary',\n\t\t\t\t\t\t\t\t!policy.user_device_secondary)\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.policy}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tpolicy: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PolicyNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PolicyTypes from '../types/PolicyTypes';\nimport * as SettingsTypes from '../types/SettingsTypes';\nimport * as PolicyActions from '../actions/PolicyActions';\nimport PolicyRule from './PolicyRule';\nimport PageInput from './PageInput';\nimport PageSwitch from './PageSwitch';\nimport PageSelect from './PageSelect';\nimport PageInputButton from './PageInputButton';\nimport PageCreate from './PageCreate';\nimport Help from './Help';\nimport * as Alert from '../Alert';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tproviders: SettingsTypes.SecondaryProviders;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tpolicy: PolicyTypes.Policy;\n\taddRole: string;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class PolicyDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddRole: null,\n\t\t\tpolicy: {\n\t\t\t\tname: \"new-policy\",\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet policy: any = {\n\t\t\t...this.state.policy,\n\t\t};\n\n\t\tpolicy[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tpolicy: policy,\n\t\t});\n\t}\n\n\tsetRule(name: string, rule: PolicyTypes.Rule): void {\n\t\tlet policy: any = {\n\t\t\t...this.state.policy,\n\t\t};\n\n\t\tlet rules = {\n\t\t\t...(policy.rules || {}),\n\t\t};\n\n\t\tif (rule.values == null) {\n\t\t\tdelete rules[name];\n\t\t} else {\n\t\t\trules[name] = rule;\n\t\t}\n\n\t\tpolicy.rules = rules;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tpolicy: policy,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet policy: any = {\n\t\t\t...this.state.policy,\n\t\t};\n\n\t\tPolicyActions.create(policy).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Policy created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet policy: PolicyTypes.Policy;\n\n\t\tpolicy = {\n\t\t\t...this.state.policy,\n\t\t};\n\n\t\tlet roles = [\n\t\t\t...(policy.roles || []),\n\t\t];\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\n\t\tpolicy.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tpolicy: policy,\n\t\t});\n\t}\n\n\tonRemoveRole(role: string): void {\n\t\tlet policy: PolicyTypes.Policy;\n\n\t\tpolicy = {\n\t\t\t...this.state.policy,\n\t\t};\n\n\t\tlet roles = [\n\t\t\t...(policy.roles || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\n\t\tpolicy.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tpolicy: policy,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet policy: PolicyTypes.Policy = this.state.policy;\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of (policy.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet rules = policy.rules || {};\n\t\tlet operatingSystem = rules.operating_system || {\n\t\t\ttype: 'operating_system',\n\t\t};\n\t\tlet browser = rules.browser || {\n\t\t\ttype: 'browser',\n\t\t};\n\t\tlet location = rules.location || {\n\t\t\ttype: 'location',\n\t\t};\n\t\tlet whitelistNetworks = rules.whitelist_networks || {\n\t\t\ttype: 'whitelist_networks',\n\t\t};\n\t\tlet blacklistNetworks = rules.blacklist_networks || {\n\t\t\ttype: 'blacklist_networks',\n\t\t};\n\n\t\tlet providerIds: string[] = [];\n\t\tlet adminProviders: JSX.Element[] = [];\n\t\tlet userProviders: JSX.Element[] = [];\n\t\tif (this.props.providers.length) {\n\t\t\tfor (let provider of this.props.providers) {\n\t\t\t\tproviderIds.push(provider.id);\n\t\t\t\tadminProviders.push(<option\n\t\t\t\t\tkey={provider.id}\n\t\t\t\t\tvalue={provider.id}\n\t\t\t\t>{provider.name}</option>);\n\t\t\t\tuserProviders.push(<option\n\t\t\t\t\tkey={provider.id}\n\t\t\t\t\tvalue={provider.id}\n\t\t\t\t>{provider.name}</option>);\n\t\t\t}\n\t\t} else {\n\t\t\tadminProviders.push(<option\n\t\t\t\tkey=\"null\"\n\t\t\t\tvalue=\"\"\n\t\t\t>None</option>);\n\t\t\tuserProviders.push(<option\n\t\t\t\tkey=\"null\"\n\t\t\t\tvalue=\"\"\n\t\t\t>None</option>);\n\t\t}\n\t\tlet adminProvider = policy.admin_secondary &&\n\t\t\tproviderIds.indexOf(policy.admin_secondary) !== -1;\n\t\tlet userProvider = policy.user_secondary &&\n\t\t\tproviderIds.indexOf(policy.user_secondary) !== -1;\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of policy\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={policy.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Policy comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Policy comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={policy.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\t\tRoles\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\t\tcontent=\"Roles associated with this policy. All requests from users with associated roles must pass this policy check.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"Admin two-factor authentication\"\n\t\t\t\t\t\t\thelp=\"Require admins to use two-factor authentication.\"\n\t\t\t\t\t\t\tchecked={adminProvider}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tif (adminProvider) {\n\t\t\t\t\t\t\t\t\tthis.set('admin_secondary', null);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tif (this.props.providers.length === 0) {\n\t\t\t\t\t\t\t\t\t\tAlert.warning(\n\t\t\t\t\t\t\t\t\t\t\t'No two-factor authentication providers exist');\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tthis.set('admin_secondary', this.props.providers[0].id);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Admin Two-Factor Provider\"\n\t\t\t\t\t\t\thelp=\"Two-factor authentication provider that will be used. For users matching multiple policies the first provider will be used.\"\n\t\t\t\t\t\t\thidden={!adminProvider}\n\t\t\t\t\t\t\tvalue={policy.admin_secondary}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('admin_secondary', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{adminProviders}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"User two-factor authentication\"\n\t\t\t\t\t\t\thelp=\"Require users to use two-factor authentication.\"\n\t\t\t\t\t\t\tchecked={userProvider}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tif (userProvider) {\n\t\t\t\t\t\t\t\t\tthis.set('user_secondary', null);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tif (this.props.providers.length === 0) {\n\t\t\t\t\t\t\t\t\t\tAlert.warning(\n\t\t\t\t\t\t\t\t\t\t\t'No two-factor authentication providers exist');\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tthis.set('user_secondary', this.props.providers[0].id);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"User Two-Factor Provider\"\n\t\t\t\t\t\t\thelp=\"Two-factor authentication provider that will be used. For users matching multiple policies the first provider will be used.\"\n\t\t\t\t\t\t\thidden={!userProvider}\n\t\t\t\t\t\t\tvalue={policy.user_secondary}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('user_secondary', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{userProviders}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\t\trule={whitelistNetworks}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setRule('whitelist_networks', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\t\trule={blacklistNetworks}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setRule('blacklist_networks', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"Enabled\"\n\t\t\t\t\t\t\thelp=\"Enable or disable policy.\"\n\t\t\t\t\t\t\tchecked={!policy.disabled}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('disabled', !policy.disabled)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\t\trule={location}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setRule('location', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\t\trule={operatingSystem}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setRule('operating_system', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PolicyRule\n\t\t\t\t\t\t\trule={browser}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setRule('browser', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"Admin WebAuthn device authentication\"\n\t\t\t\t\t\t\thelp=\"Require admins to use WebAuthn device authentication.\"\n\t\t\t\t\t\t\tchecked={policy.admin_device_secondary}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('admin_device_secondary',\n\t\t\t\t\t\t\t\t\t!policy.admin_device_secondary)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"User WebAuthn device authentication\"\n\t\t\t\t\t\t\thelp=\"Require users to use WebAuthn device authentication.\"\n\t\t\t\t\t\t\tchecked={policy.user_device_secondary}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('user_device_secondary',\n\t\t\t\t\t\t\t\t\t!policy.user_device_secondary)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.policy}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PolicyRule.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PolicyTypes from '../types/PolicyTypes';\nimport * as Constants from '../Constants';\nimport PageSwitch from './PageSwitch';\nimport PageInputButton from './PageInputButton';\nimport PageSelectButton from './PageSelectButton';\nimport Help from './Help';\n\ninterface Props {\n\trule: PolicyTypes.Rule;\n\tonChange: (state: PolicyTypes.Rule) => void;\n}\n\ninterface State {\n\taddValue: string;\n}\n\nconst css = {\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class PolicyRule extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\taddValue: '',\n\t\t};\n\t}\n\n\tclone(): PolicyTypes.Rule {\n\t\treturn {\n\t\t\t...this.props.rule,\n\t\t};\n\t}\n\n\tonAddValue = (value: string): void => {\n\t\tif (!value) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet rule = this.clone();\n\n\t\tlet values = [\n\t\t\t...rule.values,\n\t\t];\n\n\t\tif (values.indexOf(value) === -1) {\n\t\t\tvalues.push(value);\n\t\t}\n\n\t\tvalues.sort();\n\n\t\trule.values = values;\n\n\t\tthis.props.onChange(rule);\n\t}\n\n\tonRemoveValue(value: string): void {\n\t\tlet rule = this.clone();\n\n\t\tlet values = [\n\t\t\t...rule.values,\n\t\t];\n\n\t\tlet i = values.indexOf(value);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\tvalues.splice(i, 1);\n\n\t\trule.values = values;\n\n\t\tthis.props.onChange(rule);\n\t}\n\n\trender(): JSX.Element {\n\t\tlet rule = this.props.rule;\n\t\tlet defaultOption: string;\n\n\t\tlet label: string;\n\t\tlet selectLabel: string;\n\t\tlet selectPlaceholder: string;\n\t\tlet options: {[key: string]: string};\n\t\tswitch (this.props.rule.type) {\n\t\t\tcase 'operating_system':\n\t\t\t\tlabel = 'Permitted Operating Systems';\n\t\t\t\tselectLabel = 'Operating system policies';\n\t\t\t\toptions = Constants.operatingSystems;\n\t\t\t\tbreak;\n\t\t\tcase 'browser':\n\t\t\t\tlabel = 'Permitted Browsers';\n\t\t\t\tselectLabel = 'Browser policies';\n\t\t\t\toptions = Constants.browsers;\n\t\t\t\tbreak;\n\t\t\tcase 'location':\n\t\t\t\tlabel = 'Permitted Locations';\n\t\t\t\tselectLabel = 'Location policies';\n\t\t\t\toptions = Constants.locations;\n\t\t\t\tbreak;\n\t\t\tcase 'whitelist_networks':\n\t\t\t\tlabel = 'Permitted Networks';\n\t\t\t\tselectLabel = 'Permitted network policies';\n\t\t\t\tselectPlaceholder = 'Add network';\n\t\t\t\tbreak;\n\t\t\tcase 'blacklist_networks':\n\t\t\t\tlabel = 'Blocked Networks';\n\t\t\t\tselectLabel = 'Blocked network policies';\n\t\t\t\tselectPlaceholder = 'Add network';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet optionsSelect: JSX.Element[] = [];\n\t\tfor (let option in options) {\n\t\t\tif (!options.hasOwnProperty(option)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!defaultOption) {\n\t\t\t\tdefaultOption = option;\n\t\t\t}\n\n\t\t\toptionsSelect.push(\n\t\t\t\t<option key={option} value={option}>{options[option]}</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet values: JSX.Element[] = [];\n\t\tfor (let value of rule.values || []) {\n\t\t\tvalues.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.item}\n\t\t\t\t\tkey={value}\n\t\t\t\t>\n\t\t\t\t\t{options ? options[value] || value : value}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveValue(value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet inputElem: JSX.Element;\n\t\tif (options) {\n\t\t\tinputElem = <PageSelectButton\n\t\t\t\thidden={rule.values == null}\n\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\tlabel=\"Add\"\n\t\t\t\tvalue={this.state.addValue}\n\t\t\t\tonChange={(val): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\taddValue: val,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSubmit={(): void => {\n\t\t\t\t\tthis.onAddValue(this.state.addValue || defaultOption);\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{optionsSelect}\n\t\t\t</PageSelectButton>;\n\t\t} else {\n\t\t\tinputElem = <PageInputButton\n\t\t\t\thidden={rule.values == null}\n\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\tlabel=\"Add\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder={selectPlaceholder}\n\t\t\t\tvalue={this.state.addValue}\n\t\t\t\tonChange={(val): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\taddValue: val,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSubmit={(): void => {\n\t\t\t\t\tthis.onAddValue(this.state.addValue);\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\taddValue: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <div>\n\t\t\t<PageSwitch\n\t\t\t\tlabel={selectLabel}\n\t\t\t\thelp=\"Turn on to enable policy.\"\n\t\t\t\tchecked={rule.values != null}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.values = rule.values == null ? [] : null;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"Disabled user on failure\"\n\t\t\t\thelp=\"This will disable the user when the policy check fails. It is generally only useful for the location check to disable a user account when an authentication occurs from a foreign country. It is important to consider that the policy check is the last check that occurs during authentication. An authentication attempt with an incorrect password from a foreign country would not trigger a policy failure or disable the user.\"\n\t\t\t\tchecked={rule.disable}\n\t\t\t\thidden={rule.values == null}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.disable = !state.disable;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<label\n\t\t\t\tclassName=\"bp5-label\"\n\t\t\t\thidden={rule.values == null}\n\t\t\t>\n\t\t\t\t{label}\n\t\t\t\t<Help\n\t\t\t\t\ttitle={label}\n\t\t\t\t\tcontent=\"One of the values must match for the check to pass.\"\n\t\t\t\t/>\n\t\t\t\t<div>\n\t\t\t\t\t{values}\n\t\t\t\t</div>\n\t\t\t</label>\n\t\t\t{inputElem}\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Pool.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as PoolTypes from '../types/PoolTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport PoolDetailed from './PoolDetailed';\nimport ZonesStore from \"../stores/ZonesStore\";\n\ninterface Props {\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tpool: PoolTypes.PoolRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Pool extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet pool = this.props.pool;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<PoolDetailed\n\t\t\t\t\tdatacenters={this.props.datacenters}\n\t\t\t\t\tzones={this.props.zones}\n\t\t\t\t\tpool={this.props.pool}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet zone = ZonesStore.zone(this.props.pool.zone);\n\t\tlet zoneName = zone ? zone.name : null;\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{pool.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\thidden={!zoneName}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-layout-circle\"\n\t\t\t\t/>\n\t\t\t\t{zoneName}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PoolDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as PoolTypes from '../types/PoolTypes';\nimport * as PoolActions from '../actions/PoolActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\nimport * as ZoneTypes from \"../types/ZoneTypes\";\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageInputButton from './PageInputButton';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\nimport PageSwitch from \"./PageSwitch\";\n\ninterface Props {\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tpool: PoolTypes.PoolRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tpool: PoolTypes.Pool;\n\tdatacenter: string;\n\tzone: string;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class PoolDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tpool: null,\n\t\t\tdatacenter: '',\n\t\t\tzone: '',\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet pool: any;\n\n\t\tif (this.state.changed) {\n\t\t\tpool = {\n\t\t\t\t...this.state.pool,\n\t\t\t};\n\t\t} else {\n\t\t\tpool = {\n\t\t\t\t...this.props.pool,\n\t\t\t};\n\t\t}\n\n\t\tpool[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tpool: pool,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPoolActions.commit(this.state.pool).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tpool: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPoolActions.remove(this.props.pool.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet pool: PoolTypes.Pool = this.state.pool ||\n\t\t\tthis.props.pool;\n\n\t\tlet defaultDatacenter = '';\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters.length) {\n\t\t\thasDatacenters = true;\n\t\t\tdefaultDatacenter = this.props.datacenters[0].id;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\tlet datacenter = this.state.datacenter || defaultDatacenter;\n\t\tlet hasZones = false;\n\t\tlet zonesSelect: JSX.Element[] = [];\n\t\tif (this.props.zones.length) {\n\t\t\tzonesSelect.push(<option key=\"null\" value=\"\">Select Zone</option>);\n\n\t\t\tfor (let zone of this.props.zones) {\n\t\t\t\tif (!this.props.pool.zone && zone.datacenter !== datacenter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasZones = true;\n\n\t\t\t\tzonesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={zone.id}\n\t\t\t\t\t\tvalue={zone.id}\n\t\t\t\t\t>{zone.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasZones) {\n\t\t\tzonesSelect = [<option key=\"null\" value=\"\">No Zones</option>];\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Pool\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this pool\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[pool.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of pool\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={pool.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Pool comment.\"\n\t\t\t\t\t\tplaceholder=\"Pool comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={pool.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\thidden={!!this.props.pool.zone}\n\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\thelp=\"Pool datacenter.\"\n\t\t\t\t\t\tvalue={this.state.datacenter}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\t\t\tpool = {\n\t\t\t\t\t\t\t\t\t...this.state.pool,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tpool = {\n\t\t\t\t\t\t\t\t\t...this.props.pool,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\t\t\tpool: pool,\n\t\t\t\t\t\t\t\tdatacenter: val,\n\t\t\t\t\t\t\t\tzone: '',\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={!!this.props.pool.zone || this.state.disabled ||\n\t\t\t\t\t\t\t!hasZones}\n\t\t\t\t\t\tlabel=\"Zone\"\n\t\t\t\t\t\thelp=\"Pool zone, cannot be changed once set. Clear pool ID in configuration file to reset pool.\"\n\t\t\t\t\t\tvalue={this.props.pool.zone ? this.props.pool.zone :\n\t\t\t\t\t\t\tthis.state.zone}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tlet pool: PoolTypes.Pool;\n\t\t\t\t\t\t\tif (this.state.changed) {\n\t\t\t\t\t\t\t\tpool = {\n\t\t\t\t\t\t\t\t\t...this.state.pool,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tpool = {\n\t\t\t\t\t\t\t\t\t...this.props.pool,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\t\t\tpool: pool,\n\t\t\t\t\t\t\t\tzone: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{zonesSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.pool.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={true}\n\t\t\t\t\t\tlabel=\"Volume Group Name\"\n\t\t\t\t\t\thelp=\"LVM volume group name. Name will be used to match nodes that have the LVM volume group available.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={pool.vg_name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('vg_name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\thelp=\"Block pool from being deleted.\"\n\t\t\t\t\t\tchecked={pool.delete_protection}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('delete_protection', !pool.delete_protection);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.pool && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tpool: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PoolNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PoolTypes from '../types/PoolTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as PoolActions from '../actions/PoolActions';\nimport * as ImageActions from '../actions/ImageActions';\nimport * as InstanceActions from '../actions/InstanceActions';\nimport * as NodeActions from '../actions/NodeActions';\nimport ImagesDatacenterStore from '../stores/ImagesDatacenterStore';\nimport InstancesNodeStore from '../stores/InstancesNodeStore';\nimport NodesZoneStore from '../stores/NodesZoneStore';\nimport PageInput from './PageInput';\nimport PageInputButton from './PageInputButton';\nimport PageCreate from './PageCreate';\nimport PageSelect from './PageSelect';\nimport PageSwitch from \"./PageSwitch\";\nimport PageNumInput from './PageNumInput';\nimport Help from './Help';\n\ninterface Props {\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tpool: PoolTypes.Pool;\n\tdatacenter: string;\n\tzone: string;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class PoolNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tpool: {\n\t\t\t\tname: 'new-pool',\n\t\t\t},\n\t\t\tdatacenter: '',\n\t\t\tzone: '',\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet pool: any = {\n\t\t\t...this.state.pool,\n\t\t};\n\n\t\tpool[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tpool: pool,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet pool: any = {\n\t\t\t...this.state.pool,\n\t\t};\n\n\t\tlet zone = this.state.zone;\n\t\tif (!zone && this.props.datacenters.length &&\n\t\t\tthis.props.zones.length) {\n\t\t\tlet datacenter = this.state.datacenter ||\n\t\t\t\tthis.props.datacenters[0].id;\n\t\t\tfor (let zne of this.props.zones) {\n\t\t\t\tif (zne.datacenter === datacenter) {\n\t\t\t\t\tzone = zne.id;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (zone) {\n\t\t\tpool.zone = zone;\n\t\t}\n\n\t\tPoolActions.create(pool).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Pool created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet pool = this.state.pool;\n\n\t\tlet defaultDatacenter = '';\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters.length) {\n\t\t\thasDatacenters = true;\n\t\t\tdefaultDatacenter = this.props.datacenters[0].id;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\tlet datacenter = this.state.datacenter || defaultDatacenter;\n\t\tlet hasZones = false;\n\t\tlet zonesSelect: JSX.Element[] = [];\n\t\tif (this.props.zones.length) {\n\t\t\tzonesSelect.push(<option key=\"null\" value=\"\">Select Zone</option>);\n\n\t\t\tfor (let zone of this.props.zones) {\n\t\t\t\tif (!this.state.zone && zone.datacenter !== datacenter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasZones = true;\n\n\t\t\t\tzonesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={zone.id}\n\t\t\t\t\t\tvalue={zone.id}\n\t\t\t\t\t>{zone.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasZones) {\n\t\t\tzonesSelect = [<option key=\"null\" value=\"\">No Zones</option>];\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of pool\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tvalue={pool.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\t\thelp=\"Datacenter for pool.\"\n\t\t\t\t\t\t\tvalue={this.state.datacenter}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdatacenter: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasZones}\n\t\t\t\t\t\t\tlabel=\"Zone\"\n\t\t\t\t\t\t\thelp=\"Zone for pool.\"\n\t\t\t\t\t\t\tvalue={this.state.zone}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tzone: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{zonesSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Volume Group Name\"\n\t\t\t\t\t\t\thelp=\"LVM volume group name. Name will be used to match nodes that have the LVM volume group available.\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={pool.vg_name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('vg_name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\t\thelp=\"Block pool from being deleted.\"\n\t\t\t\t\t\t\tchecked={pool.delete_protection}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('delete_protection', !pool.delete_protection);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.pool}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Pools.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as PoolTypes from '../types/PoolTypes';\nimport PoolsStore from '../stores/PoolsStore';\nimport * as PoolActions from '../actions/PoolActions';\nimport Pool from './Pool';\nimport PoolNew from './PoolNew';\nimport PoolsFilter from './PoolsFilter';\nimport PoolsPage from './PoolsPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\nimport * as ZoneTypes from \"../types/ZoneTypes\";\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as CompletionActions from \"../actions/CompletionActions\";\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tpools: PoolTypes.PoolsRo;\n\tfilter: PoolTypes.Filter;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Pools extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpools: PoolsStore.pools,\n\t\t\tfilter: PoolsStore.filter,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tPoolsStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tPoolActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tPoolsStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet pools = PoolsStore.pools;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tpools.forEach((pool: PoolTypes.Pool): void => {\n\t\t\tif (curSelected[pool.id]) {\n\t\t\t\tselected[pool.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[pool.id]) {\n\t\t\t\topened[pool.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpools: pools,\n\t\t\tfilter: PoolsStore.filter,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tPoolActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet poolsDom: JSX.Element[] = [];\n\n\t\tthis.state.pools.forEach((\n\t\t\t\tpool: PoolTypes.PoolRo): void => {\n\t\t\tpoolsDom.push(<Pool\n\t\t\t\tkey={pool.id}\n\t\t\t\tpool={pool}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tzones={this.state.zones}\n\t\t\t\tselected={!!this.state.selected[pool.id]}\n\t\t\t\topen={!!this.state.opened[pool.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet pools = this.state.pools;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < pools.length; i++) {\n\t\t\t\t\t\t\tlet usr = pools[i];\n\n\t\t\t\t\t\t\tif (usr.id === pool.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[pools[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: pool.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[pool.id]) {\n\t\t\t\t\t\tdelete selected[pool.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[pool.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: pool.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[pool.id]) {\n\t\t\t\t\t\tdelete opened[pool.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[pool.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = PoolsStore.pool(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newPoolDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewPoolDom = <PoolNew\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tzones={this.state.zones}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Disk Pools</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tPoolActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tPoolActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected pools\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<PoolsFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tPoolActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newPoolDom}\n\t\t\t\t\t{poolsDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!poolsDom.length}\n\t\t\t\ticonClass=\"bp5-icon-control\"\n\t\t\t\ttitle=\"No pools\"\n\t\t\t\tdescription=\"Add a new pool to get started.\"\n\t\t\t/>\n\t\t\t<PoolsPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PoolsFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as PoolTypes from '../types/PoolTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: PoolTypes.Filter;\n\tonFilter: (filter: PoolTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class PoolsFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Pool ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Volume Group Name\"\n\t\t\t\tvalue={this.props.filter.vg_name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.vg_name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.vg_name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/PoolsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport PoolsStore from '../stores/PoolsStore';\nimport * as PoolActions from '../actions/PoolActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class PoolsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: PoolsStore.page,\n\t\t\tpageCount: PoolsStore.pageCount,\n\t\t\tpages: PoolsStore.pages,\n\t\t\tcount: PoolsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tPoolsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tPoolsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: PoolsStore.page,\n\t\t\tpageCount: PoolsStore.pageCount,\n\t\t\tpages: PoolsStore.pages,\n\t\t\tcount: PoolsStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPoolActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPoolActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPoolActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPoolActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tPoolActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Relations.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\nimport * as RelationTypes from '../types/RelationTypes';\nimport * as RelationsActions from '../actions/RelationsActions';\nimport * as Alert from '../Alert';\nimport * as Theme from '../Theme';\n\nimport * as MonacoEditor from \"@monaco-editor/react\"\nimport * as Monaco from \"monaco-editor\"\n\ninterface State {\n\tdata: RelationTypes.Relation;\n\tdisabled: boolean;\n}\n\ninterface Props {\n\tkind: string;\n\tid: string;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t} as React.CSSProperties,\n\ttimestamp: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '6px',\n\t} as React.CSSProperties,\n\tlevel: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '6px',\n\t} as React.CSSProperties,\n\tmessage: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '6px',\n\t} as React.CSSProperties,\n\tfields: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '6px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tkey: {\n\t\tfontWeight: 'bold',\n\t} as React.CSSProperties,\n\tvalue: {\n\t} as React.CSSProperties,\n\tdialog: {\n\t\theight: '610px',\n\t\twidth: '90%',\n\t\tmaxWidth: '600px',\n\t} as React.CSSProperties,\n\tdialogBody: {\n\t\theight: '100%',\n\t} as React.CSSProperties,\n\ttextarea: {\n\t\tpadding: '10px 10px 0 10px',\n\t} as React.CSSProperties,\n}\n\nexport default class Relations extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdata: null,\n\t\t\tdisabled: false,\n\t\t}\n\t}\n\n\tload = async () => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t})\n\n\t\tlet data: RelationTypes.Relation\n\t\ttry {\n\t\t\tdata = await RelationsActions.load(this.props.kind, this.props.id)\n\t\t} catch (error) {\n\t\t\tAlert.error('Failed to load relation');\n\t\t}\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: false,\n\t\t\tdata: data,\n\t\t})\n\t}\n\n\trender(): JSX.Element {\n\t\tlet dialog: JSX.Element\n\t\tif (this.state.data) {\n\t\t\tdialog = <Blueprint.Dialog\n\t\t\t\ttitle=\"Resource Overview\"\n\t\t\t\tstyle={css.dialog}\n\t\t\t\tisOpen={!!this.state.data}\n\t\t\t\tusePortal={true}\n\t\t\t\tportalContainer={document.body}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tdata: null,\n\t\t\t\t\t})\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={css.textarea}>\n\t\t\t\t\t<MonacoEditor.Editor\n\t\t\t\t\t\theight=\"500px\"\n\t\t\t\t\t\twidth=\"100%\"\n\t\t\t\t\t\ttheme={Theme.getEditorTheme()}\n\t\t\t\t\t\tvalue={this.state.data?.data || \"\"}\n\t\t\t\t\t\tlanguage=\"yaml\"\n\t\t\t\t\t\toptions={{\n\t\t\t\t\t\t\tfolding: false,\n\t\t\t\t\t\t\tfontSize: 10,\n\t\t\t\t\t\t\tfontFamily: Theme.monospaceFont,\n\t\t\t\t\t\t\tfontWeight: Theme.monospaceWeight,\n\t\t\t\t\t\t\treadOnly: true,\n\t\t\t\t\t\t\tautomaticLayout: true,\n\t\t\t\t\t\t\tformatOnPaste: true,\n\t\t\t\t\t\t\tformatOnType: true,\n\t\t\t\t\t\t\tscrollBeyondLastLine: false,\n\t\t\t\t\t\t\tminimap: {\n\t\t\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\twordWrap: \"on\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"bp5-dialog-footer\">\n\t\t\t\t\t<div className=\"bp5-dialog-footer-actions\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-danger\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdata: null,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>Close</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</Blueprint.Dialog>\n\t\t}\n\n\t\treturn <div>\n\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-locate bp5-intent-primary\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={this.load}\n\t\t\t>Resource Overview</button>\n\t\t\t{dialog}\n\t\t</div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/RouterLink.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Router from '../Router';\n\ninterface Props {\n\tclassName?: string;\n\tstyle?: React.CSSProperties;\n\thidden?: boolean;\n\tto: string;\n\tchildren?: React.ReactNode\n}\n\nexport default class RouterLink extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <a\n\t\t\tclassName={this.props.className}\n\t\t\tstyle={this.props.style}\n\t\t\thidden={this.props.hidden}\n\t\t\thref={\"#\" + this.props.to}\n\t\t\tonClick={(): void => {\n\t\t\t\tRouter.setLocation(this.props.to);\n\t\t\t}}\n\t\t>{this.props.children}</a>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/RouterRedirect.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Router from '../Router';\n\ninterface Props {\n\tto: string;\n}\n\nexport default class RouterRedirect extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tRouter.setLocation(this.props.to);\n\t\treturn <div></div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/RouterRoute.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\nimport * as RouterTypes from '../types/RouterTypes';\n\ninterface Props {\n\tpath: string;\n\trender: (data: RouterTypes.State) => JSX.Element;\n}\n\nexport default class RouterRoute extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet data = RouterTypes.getState()\n\n\t\treturn this.props.render(data)\n\t}\n}\n"
  },
  {
    "path": "www/app/components/RouterRoutes.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as RouterTypes from '../types/RouterTypes';\n\ninterface Props {\n\tchildren?: React.ReactNode\n}\n\ninterface State {\n\tpath: string\n}\n\nexport default class RouterRoutes extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpath: \"\",\n\t\t}\n\t}\n\n\tcomponentDidMount() {\n\t\twindow.addEventListener(\"router_update\", this.refresh)\n\t\twindow.addEventListener(\"hashchange\", this.refresh)\n\t}\n\n\tcomponentWillUnmount() {\n\t\twindow.removeEventListener(\"router_update\", this.refresh)\n\t\twindow.removeEventListener(\"hashchange\", this.refresh)\n\t}\n\n\trefresh = () => {\n\t\tlet newPath = window.location.hash\n\t\tif (this.state.path !== newPath) {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tpath: window.location.hash,\n\t\t\t})\n\t\t}\n\t}\n\n\trender(): JSX.Element {\n\t\tlet path = window.location.hash.replace(/^#/, '')\n\n\t\tlet curElem: JSX.Element;\n\n\t\tReact.Children.forEach(this.props.children, (elem) => {\n\t\t\tif (React.isValidElement(elem)) {\n\t\t\t\tlet data = RouterTypes.match(\n\t\t\t\t\telem.props.path, path)\n\n\t\t\t\tif (data.matched) {\n\t\t\t\t\tRouterTypes.setState(data)\n\t\t\t\t\tcurElem = elem\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tif (!curElem) {\n\t\t\tRouterTypes.setState(null)\n\t\t\tconsole.log(`Failed to match ${path}`)\n\t\t}\n\n\t\treturn <>{curElem}</>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/SearchInput.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Blueprint from '@blueprintjs/core';\n\ntype OnChange = (val: string) => void;\n\ninterface Props {\n\tstyle: React.CSSProperties;\n\tplaceholder: string;\n\tvalue: string;\n\tdynamic?: boolean;\n\texactDefault?: boolean;\n\tonChange: OnChange;\n}\n\ninterface State {\n\texact: boolean\n}\n\nexport default class SearchInput extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\texact: !!this.props.exactDefault,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet val = this.props.value || \"\"\n\n\t\tif (this.props.dynamic) {\n\t\t\tif (val.startsWith(\"~\")) {\n\t\t\t\tval = val.substring(1)\n\t\t\t}\n\t\t}\n\n\t\treturn <div style={this.props.style}>\n\t\t\t<Blueprint.InputGroup\n\t\t\t\ttype=\"text\"\n\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\tspellCheck={false}\n\t\t\t\tplaceholder={this.props.placeholder}\n\t\t\t\tvalue={val}\n\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\tif (this.props.dynamic && !this.state.exact && evt.target.value) {\n\t\t\t\t\t\tthis.props.onChange(\"~\" + evt.target.value)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.props.onChange(evt.target.value)\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tleftElement={<span className=\"bp5-icon bp5-icon-search\"/>}\n\t\t\t\trightElement={<Blueprint.Tooltip\n\t\t\t\t\tcontent={this.state.exact ? \"Exact match\" : \"Partial match\"}\n\t\t\t\t>\n\t\t\t\t\t<Blueprint.Button\n\t\t\t\t\t\thidden={!this.props.dynamic}\n\t\t\t\t\t\ticon={this.state.exact ? \"link\" : \"unlink\"}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tlet exact = !this.state.exact\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\texact: exact,\n\t\t\t\t\t\t\t})\n\n\t\t\t\t\t\t\tif (!exact && val) {\n\t\t\t\t\t\t\t\tthis.props.onChange(\"~\" + val)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.props.onChange(val)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tminimal={true}\n\t\t\t\t\t/>\n\t\t\t\t</Blueprint.Tooltip>}\n\t\t\t/>\n\t\t</div>\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Secret.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as SecretTypes from '../types/SecretTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport SecretDetailed from './SecretDetailed';\n\ninterface Props {\n\tsecret: SecretTypes.SecretRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Secret extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet secret = this.props.secret;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<SecretDetailed\n\t\t\t\t\tsecret={this.props.secret}\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet secType: string\n\t\tswitch (secret.type) {\n\t\t\tcase 'aws':\n\t\t\t\tsecType = 'AWS';\n\t\t\t\tbreak;\n\t\t\tcase 'cloudflare':\n\t\t\t\tsecType = 'Cloudflare';\n\t\t\t\tbreak;\n\t\t\tcase 'oracle_cloud':\n\t\t\t\tsecType = 'Oracle Cloud';\n\t\t\t\tbreak;\n\t\t\tcase 'google_cloud':\n\t\t\t\tsecType = 'Google Cloud';\n\t\t\t\tbreak;\n\t\t\tcase 'json':\n\t\t\t\tsecType = 'JSON';\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tsecType = 'Unknown';\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{secret.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-key\"\n\t\t\t\t/>\n\t\t\t\t{secType}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/SecretDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Theme from '../Theme';\nimport * as MonacoEditor from \"@monaco-editor/react\"\nimport * as Monaco from \"monaco-editor\";\nimport * as SecretTypes from '../types/SecretTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as SecretActions from '../actions/SecretActions';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageTextArea from './PageTextArea';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport Help from './Help';\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\tsecret: SecretTypes.SecretRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tsecret: SecretTypes.Secret;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tdomain: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class SecretDetailed extends React.Component<Props, State> {\n\teditor: Monaco.editor.IStandaloneCodeEditor\n\tmonaco: MonacoEditor.Monaco\n\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tsecret: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet secret: any;\n\n\t\tif (this.state.changed) {\n\t\t\tsecret = {\n\t\t\t\t...this.state.secret,\n\t\t\t};\n\t\t} else {\n\t\t\tsecret = {\n\t\t\t\t...this.props.secret,\n\t\t\t};\n\t\t}\n\n\t\tsecret[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tsecret: secret,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tSecretActions.commit(this.state.secret).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tsecret: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tSecretActions.remove(this.props.secret.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet secr: SecretTypes.Secret = this.state.secret ||\n\t\t\tthis.props.secret;\n\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\torganizationsSelect.push(\n\t\t\t<option key=\"null\" value=\"\">\n\t\t\t\tNode Secret\n\t\t\t</option>,\n\t\t);\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet keyLabel = \"\";\n\t\tlet keyHelp = \"\";\n\t\tlet keyPlaceholder = \"\";\n\t\tlet keyArea = false;\n\t\tlet valLabel = \"\";\n\t\tlet valHelp = \"\";\n\t\tlet valPlaceholder = \"\";\n\t\tlet regionLabel = \"\";\n\t\tlet regionHelp = \"\";\n\t\tlet regionPlaceholder = \"\";\n\t\tlet publicKeyLabel = \"\";\n\t\tlet publicKeyHelp = \"\";\n\t\tlet publicKeyPlaceholder = \"\";\n\t\tlet editor: JSX.Element;\n\n\t\tswitch (secr.type) {\n\t\t\tcase \"aws\":\n\t\t\tcase \"\":\n\t\t\t\tkeyLabel = \"AWS Key ID\";\n\t\t\t\tkeyHelp = \"Key for AWS API authentication.\";\n\t\t\t\tkeyPlaceholder = \"Key ID\";\n\t\t\t\tkeyArea = false;\n\t\t\t\tvalLabel = \"AWS Secret ID\";\n\t\t\t\tvalHelp = \"Key secret for AWS API authentication.\";\n\t\t\t\tvalPlaceholder = \"Key ID\";\n\t\t\t\tregionLabel = \"AWS Region\";\n\t\t\t\tregionHelp = \"Region for AWS API.\";\n\t\t\t\tregionPlaceholder = \"Region\";\n\t\t\t\tpublicKeyLabel = \"\";\n\t\t\t\tpublicKeyHelp = \"\";\n\t\t\t\tpublicKeyPlaceholder = \"\";\n\t\t\t\tbreak;\n\t\t\tcase \"cloudflare\":\n\t\t\t\tkeyLabel = \"Cloudflare Token\";\n\t\t\t\tkeyHelp = \"Cloudflare API token.\";\n\t\t\t\tkeyPlaceholder = \"Token\";\n\t\t\t\tkeyArea = false;\n\t\t\t\tvalLabel = \"\";\n\t\t\t\tvalHelp = \"\";\n\t\t\t\tvalPlaceholder = \"\";\n\t\t\t\tregionLabel = \"\";\n\t\t\t\tregionHelp = \"\";\n\t\t\t\tregionPlaceholder = \"\";\n\t\t\t\tpublicKeyLabel = \"\";\n\t\t\t\tpublicKeyHelp = \"\";\n\t\t\t\tpublicKeyPlaceholder = \"\";\n\t\t\t\tbreak;\n\t\t\tcase \"oracle_cloud\":\n\t\t\t\tkeyLabel = \"Oracle Cloud Tenancy OCID\";\n\t\t\t\tkeyHelp = \"Tenancy OCID for Oracle Cloud API authentication.\";\n\t\t\t\tkeyPlaceholder = \"Tenancy OCID\";\n\t\t\t\tkeyArea = false;\n\t\t\t\tvalLabel = \"Oracle Cloud User OCID\";\n\t\t\t\tvalHelp = \"User OCID for Oracle Cloud API authentication.\";\n\t\t\t\tvalPlaceholder = \"User OCID\";\n\t\t\t\tregionLabel = \"Oracle Cloud Region\";\n\t\t\t\tregionHelp = \"Region for Oracle Cloud API.\";\n\t\t\t\tregionPlaceholder = \"Region\";\n\t\t\t\tpublicKeyLabel = \"Oracle Cloud Public Key\";\n\t\t\t\tpublicKeyHelp = \"Public key for Oracle Cloud API authentication.\";\n\t\t\t\tpublicKeyPlaceholder = \"Oracle Cloud Public Key\";\n\t\t\t\tbreak;\n\t\t\tcase \"google_cloud\":\n\t\t\t\tkeyLabel = \"Google Cloud Service Account JSON\";\n\t\t\t\tkeyHelp = \"Google Cloud service account JSON authentication key.\";\n\t\t\t\tkeyPlaceholder = \"Service Account\";\n\t\t\t\tkeyArea = true;\n\t\t\t\tvalLabel = \"\";\n\t\t\t\tvalHelp = \"\";\n\t\t\t\tvalPlaceholder = \"\";\n\t\t\t\tregionLabel = \"\";\n\t\t\t\tregionHelp = \"\";\n\t\t\t\tregionPlaceholder = \"\";\n\t\t\t\tpublicKeyLabel = \"\";\n\t\t\t\tpublicKeyHelp = \"\";\n\t\t\t\tpublicKeyPlaceholder = \"\";\n\t\t\t\tbreak;\n\t\t\tcase \"json\":\n\t\t\t\tkeyLabel = \"\";\n\t\t\t\tkeyHelp = \"\";\n\t\t\t\tkeyPlaceholder = \"\";\n\t\t\t\tkeyArea = false;\n\t\t\t\tvalLabel = \"\";\n\t\t\t\tvalHelp = \"\";\n\t\t\t\tvalPlaceholder = \"\";\n\t\t\t\tregionLabel = \"\";\n\t\t\t\tregionHelp = \"\";\n\t\t\t\tregionPlaceholder = \"\";\n\t\t\t\tpublicKeyLabel = \"\";\n\t\t\t\tpublicKeyHelp = \"\";\n\t\t\t\tpublicKeyPlaceholder = \"\";\n\t\t\t\teditor = <MonacoEditor.Editor\n\t\t\t\t\theight=\"400px\"\n\t\t\t\t\twidth=\"100%\"\n\t\t\t\t\tdefaultLanguage=\"json\"\n\t\t\t\t\ttheme={Theme.getEditorTheme()}\n\t\t\t\t\tdefaultValue={secr.data}\n\t\t\t\t\tonMount={(editor: Monaco.editor.IStandaloneCodeEditor,\n\t\t\t\t\t\t\tmonaco: MonacoEditor.Monaco): void => {\n\t\t\t\t\t\tthis.monaco = monaco\n\t\t\t\t\t\tthis.editor = editor\n\t\t\t\t\t}}\n\t\t\t\t\toptions={{\n\t\t\t\t\t\tfolding: false,\n\t\t\t\t\t\tfontSize: 12,\n\t\t\t\t\t\tfontFamily: Theme.monospaceFont,\n\t\t\t\t\t\tfontWeight: Theme.monospaceWeight,\n\t\t\t\t\t\ttabSize: 4,\n\t\t\t\t\t\tdetectIndentation: false,\n\t\t\t\t\t\tscrollBeyondLastLine: false,\n\t\t\t\t\t\tminimap: {\n\t\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsuggestOnTriggerCharacters: false,\n\t\t\t\t\t\twordWrap: \"on\",\n\t\t\t\t\t\tautomaticLayout: true,\n\t\t\t\t\t}}\n\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\tthis.set(\"data\", val)\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"secret\" id={this.props.secret.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Secret\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this secret\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[secr.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of secret\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Name\"\n\t\t\t\t\t\tvalue={secr.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Secret comment.\"\n\t\t\t\t\t\tplaceholder=\"Secret comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={secr.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel={keyLabel}\n\t\t\t\t\t\thelp={keyHelp}\n\t\t\t\t\t\thidden={keyLabel === \"\" || keyArea}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder={keyPlaceholder}\n\t\t\t\t\t\tvalue={secr.key}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('key', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel={keyLabel}\n\t\t\t\t\t\thelp={keyHelp}\n\t\t\t\t\t\thidden={keyLabel === \"\" || !keyArea}\n\t\t\t\t\t\tplaceholder={keyPlaceholder}\n\t\t\t\t\t\trows={6}\n\t\t\t\t\t\tvalue={secr.key}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('key', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel={valLabel}\n\t\t\t\t\t\thelp={valHelp}\n\t\t\t\t\t\thidden={valLabel === \"\"}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder={valPlaceholder}\n\t\t\t\t\t\tvalue={secr.value}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('value', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel={regionLabel}\n\t\t\t\t\t\thelp={regionHelp}\n\t\t\t\t\t\thidden={regionLabel === \"\"}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder={regionPlaceholder}\n\t\t\t\t\t\tvalue={secr.region}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('region', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t{editor}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.secret.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thelp=\"Secret data type. Select provider specific type or JSON. The JSON type must be a flat object containing only string, boolean, number, or null values. The data.keyname selector is used to select data from the JSON.\"\n\t\t\t\t\t\tvalue={secr.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"aws\">AWS</option>\n\t\t\t\t\t\t<option value=\"cloudflare\">Cloudflare</option>\n\t\t\t\t\t\t<option value=\"oracle_cloud\">Oracle Cloud</option>\n\t\t\t\t\t\t<option value=\"google_cloud\">Google Cloud</option>\n\t\t\t\t\t\t<option value=\"json\">JSON</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\thelp=\"Organization for secret. Select node to create a secret for nodes.\"\n\t\t\t\t\t\tvalue={secr.organization}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={publicKeyLabel === \"\"}\n\t\t\t\t\t\tlabel={publicKeyLabel}\n\t\t\t\t\t\thelp={publicKeyHelp}\n\t\t\t\t\t\tplaceholder={publicKeyPlaceholder}\n\t\t\t\t\t\treadOnly={true}\n\t\t\t\t\t\trows={6}\n\t\t\t\t\t\tvalue={secr.public_key}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('public_key', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.secret}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tif (this.editor) {\n\t\t\t\t\t\tthis.editor.setValue(this.props.secret.data)\n\t\t\t\t\t}\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tsecret: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/SecretNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Theme from '../Theme';\nimport * as MonacoEditor from \"@monaco-editor/react\"\nimport * as Monaco from \"monaco-editor\";\nimport * as SecretTypes from '../types/SecretTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as SecretActions from '../actions/SecretActions';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageTextArea from './PageTextArea';\nimport PageCreate from './PageCreate';\nimport Help from './Help';\nimport * as Constants from \"../Constants\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tsecret: SecretTypes.Secret;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tdomain: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class SecretNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tsecret: {\n\t\t\t\tname: 'new-secret'\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet secret: any = {\n\t\t\t...this.state.secret,\n\t\t};\n\n\t\tsecret[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tsecret: secret,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet secret: any = {\n\t\t\t...this.state.secret,\n\t\t};\n\n\t\tSecretActions.create(secret).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Secret created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet secr: SecretTypes.Secret = this.state.secret;\n\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\torganizationsSelect.push(\n\t\t\t<option key=\"null\" value=\"\">\n\t\t\t\tNode Secret\n\t\t\t</option>,\n\t\t);\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet keyLabel = \"\";\n\t\tlet keyHelp = \"\";\n\t\tlet keyPlaceholder = \"\";\n\t\tlet keyArea = false;\n\t\tlet valLabel = \"\";\n\t\tlet valHelp = \"\";\n\t\tlet valPlaceholder = \"\";\n\t\tlet regionLabel = \"\";\n\t\tlet regionHelp = \"\";\n\t\tlet regionPlaceholder = \"\";\n\t\tlet publicKeyLabel = \"\";\n\t\tlet publicKeyHelp = \"\";\n\t\tlet publicKeyPlaceholder = \"\";\n\t\tlet editor: JSX.Element;\n\n\t\tswitch (secr.type || \"aws\") {\n\t\t\tcase \"aws\":\n\t\t\t\tkeyLabel = \"AWS Key ID\";\n\t\t\t\tkeyHelp = \"Key for AWS API authentication.\";\n\t\t\t\tkeyPlaceholder = \"Key ID\";\n\t\t\t\tkeyArea = false;\n\t\t\t\tvalLabel = \"AWS Secret ID\";\n\t\t\t\tvalHelp = \"Key secret for AWS API authentication.\";\n\t\t\t\tvalPlaceholder = \"Key ID\";\n\t\t\t\tregionLabel = \"AWS Region\";\n\t\t\t\tregionHelp = \"Region for AWS API.\";\n\t\t\t\tregionPlaceholder = \"Region\";\n\t\t\t\tpublicKeyLabel = \"\";\n\t\t\t\tpublicKeyHelp = \"\";\n\t\t\t\tpublicKeyPlaceholder = \"\";\n\t\t\t\tbreak;\n\t\t\tcase \"cloudflare\":\n\t\t\t\tkeyLabel = \"Cloudflare Token\";\n\t\t\t\tkeyHelp = \"Cloudflare API token.\";\n\t\t\t\tkeyPlaceholder = \"Token\";\n\t\t\t\tkeyArea = false;\n\t\t\t\tvalLabel = \"\";\n\t\t\t\tvalHelp = \"\";\n\t\t\t\tvalPlaceholder = \"\";\n\t\t\t\tregionLabel = \"\";\n\t\t\t\tregionHelp = \"\";\n\t\t\t\tregionPlaceholder = \"\";\n\t\t\t\tpublicKeyLabel = \"\";\n\t\t\t\tpublicKeyHelp = \"\";\n\t\t\t\tpublicKeyPlaceholder = \"\";\n\t\t\t\tbreak;\n\t\t\tcase \"oracle_cloud\":\n\t\t\t\tkeyLabel = \"Oracle Cloud Tenancy OCID\";\n\t\t\t\tkeyHelp = \"Tenancy OCID for Oracle Cloud API authentication.\";\n\t\t\t\tkeyPlaceholder = \"Tenancy OCID\";\n\t\t\t\tkeyArea = false;\n\t\t\t\tvalLabel = \"Oracle Cloud User OCID\";\n\t\t\t\tvalHelp = \"User OCID for Oracle Cloud API authentication.\";\n\t\t\t\tvalPlaceholder = \"User OCID\";\n\t\t\t\tregionLabel = \"Oracle Cloud Region\";\n\t\t\t\tregionHelp = \"Region for Oracle Cloud API.\";\n\t\t\t\tregionPlaceholder = \"Region\";\n\t\t\t\tpublicKeyLabel = \"Oracle Cloud Public Key\";\n\t\t\t\tpublicKeyHelp = \"Public key for Oracle Cloud API authentication.\";\n\t\t\t\tpublicKeyPlaceholder = \"Oracle Cloud Public Key\";\n\t\t\t\tbreak;\n\t\t\tcase \"google_cloud\":\n\t\t\t\tkeyLabel = \"Google Cloud Service Account JSON\";\n\t\t\t\tkeyHelp = \"Google Cloud service account JSON authentication key.\";\n\t\t\t\tkeyPlaceholder = \"Service Account\";\n\t\t\t\tkeyArea = true;\n\t\t\t\tvalLabel = \"\";\n\t\t\t\tvalHelp = \"\";\n\t\t\t\tvalPlaceholder = \"\";\n\t\t\t\tregionLabel = \"\";\n\t\t\t\tregionHelp = \"\";\n\t\t\t\tregionPlaceholder = \"\";\n\t\t\t\tpublicKeyLabel = \"\";\n\t\t\t\tpublicKeyHelp = \"\";\n\t\t\t\tpublicKeyPlaceholder = \"\";\n\t\t\t\tbreak;\n\t\t\tcase \"json\":\n\t\t\t\tkeyLabel = \"\";\n\t\t\t\tkeyHelp = \"\";\n\t\t\t\tkeyPlaceholder = \"\";\n\t\t\t\tkeyArea = false;\n\t\t\t\tvalLabel = \"\";\n\t\t\t\tvalHelp = \"\";\n\t\t\t\tvalPlaceholder = \"\";\n\t\t\t\tregionLabel = \"\";\n\t\t\t\tregionHelp = \"\";\n\t\t\t\tregionPlaceholder = \"\";\n\t\t\t\tpublicKeyLabel = \"\";\n\t\t\t\tpublicKeyHelp = \"\";\n\t\t\t\tpublicKeyPlaceholder = \"\";\n\t\t\t\teditor = <MonacoEditor.Editor\n\t\t\t\t\theight=\"400px\"\n\t\t\t\t\twidth=\"100%\"\n\t\t\t\t\tdefaultLanguage=\"json\"\n\t\t\t\t\ttheme={Theme.getEditorTheme()}\n\t\t\t\t\tdefaultValue={secr.data}\n\t\t\t\t\toptions={{\n\t\t\t\t\t\tfolding: false,\n\t\t\t\t\t\tfontSize: 12,\n\t\t\t\t\t\tfontFamily: Theme.monospaceFont,\n\t\t\t\t\t\tfontWeight: Theme.monospaceWeight,\n\t\t\t\t\t\ttabSize: 4,\n\t\t\t\t\t\tdetectIndentation: false,\n\t\t\t\t\t\tscrollBeyondLastLine: false,\n\t\t\t\t\t\tminimap: {\n\t\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsuggestOnTriggerCharacters: false,\n\t\t\t\t\t\twordWrap: \"on\",\n\t\t\t\t\t\tautomaticLayout: true,\n\t\t\t\t\t}}\n\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\tthis.set(\"data\", val)\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of secret\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Name\"\n\t\t\t\t\t\t\tvalue={secr.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Secret comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Secret comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={secr.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel={keyLabel}\n\t\t\t\t\t\t\thelp={keyHelp}\n\t\t\t\t\t\t\thidden={keyLabel === \"\" || keyArea}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder={keyPlaceholder}\n\t\t\t\t\t\t\tvalue={secr.key}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('key', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel={keyLabel}\n\t\t\t\t\t\t\thelp={keyHelp}\n\t\t\t\t\t\t\thidden={keyLabel === \"\" || !keyArea}\n\t\t\t\t\t\t\tplaceholder={keyPlaceholder}\n\t\t\t\t\t\t\trows={6}\n\t\t\t\t\t\t\tvalue={secr.key}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('key', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel={valLabel}\n\t\t\t\t\t\t\thelp={valHelp}\n\t\t\t\t\t\t\thidden={valLabel === \"\"}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder={valPlaceholder}\n\t\t\t\t\t\t\tvalue={secr.value}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('value', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel={regionLabel}\n\t\t\t\t\t\t\thelp={regionHelp}\n\t\t\t\t\t\t\thidden={regionLabel === \"\"}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder={regionPlaceholder}\n\t\t\t\t\t\t\tvalue={secr.region}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('region', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{editor}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thelp=\"Secret data type. Select provider specific type or JSON. The JSON type must be a flat object containing only string, boolean, number, or null values. The data.keyname selector is used to select data from the JSON.\"\n\t\t\t\t\t\t\tvalue={secr.type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"aws\">AWS</option>\n\t\t\t\t\t\t\t<option value=\"cloudflare\">Cloudflare</option>\n\t\t\t\t\t\t\t<option value=\"oracle_cloud\">Oracle Cloud</option>\n\t\t\t\t\t\t\t<option value=\"google_cloud\">Google Cloud</option>\n\t\t\t\t\t\t\t<option value=\"json\">JSON</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for secret. Select node to create a secret for nodes.\"\n\t\t\t\t\t\t\tvalue={secr.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={publicKeyLabel === \"\"}\n\t\t\t\t\t\t\tlabel={publicKeyLabel}\n\t\t\t\t\t\t\thelp={publicKeyHelp}\n\t\t\t\t\t\t\tplaceholder={publicKeyPlaceholder}\n\t\t\t\t\t\t\treadOnly={true}\n\t\t\t\t\t\t\trows={6}\n\t\t\t\t\t\t\tvalue={secr.public_key}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('public_key', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.secret}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Secrets.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as SecretTypes from '../types/SecretTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport SecretsStore from '../stores/SecretsStore';\nimport CompletionStore from '../stores/CompletionStore';\nimport * as SecretActions from '../actions/SecretActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Secret from './Secret';\nimport SecretNew from './SecretNew';\nimport SecretsFilter from './SecretsFilter';\nimport SecretsPage from './SecretsPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tsecrets: SecretTypes.SecretsRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tfilter: SecretTypes.Filter;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Secrets extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tsecrets: SecretsStore.secrets,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tfilter: SecretsStore.filter,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tSecretsStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tSecretActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tSecretsStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet secrets = SecretsStore.secrets;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tsecrets.forEach((secret: SecretTypes.Secret): void => {\n\t\t\tif (curSelected[secret.id]) {\n\t\t\t\tselected[secret.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[secret.id]) {\n\t\t\t\topened[secret.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tsecrets: secrets,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tfilter: SecretsStore.filter,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tSecretActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet secretsDom: JSX.Element[] = [];\n\n\t\tthis.state.secrets.forEach((\n\t\t\t\tsecret: SecretTypes.SecretRo): void => {\n\t\t\tsecretsDom.push(<Secret\n\t\t\t\tkey={secret.id}\n\t\t\t\tsecret={secret}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tselected={!!this.state.selected[secret.id]}\n\t\t\t\topen={!!this.state.opened[secret.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet secrets = this.state.secrets;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < secrets.length; i++) {\n\t\t\t\t\t\t\tlet usr = secrets[i];\n\n\t\t\t\t\t\t\tif (usr.id === secret.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[secrets[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: secret.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[secret.id]) {\n\t\t\t\t\t\tdelete selected[secret.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[secret.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: secret.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[secret.id]) {\n\t\t\t\t\t\tdelete opened[secret.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[secret.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = SecretsStore.secret(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newSecretDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewSecretDom = <SecretNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Secrets</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tSecretActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tSecretActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected secrets\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<SecretsFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tSecretActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newSecretDom}\n\t\t\t\t\t{secretsDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!secretsDom.length}\n\t\t\t\ticonClass=\"bp5-icon-key\"\n\t\t\t\ttitle=\"No secrets\"\n\t\t\t\tdescription=\"Add a new secret to get started.\"\n\t\t\t/>\n\t\t\t<SecretsPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/SecretsFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as SecretTypes from '../types/SecretTypes';\nimport SearchInput from './SearchInput';\n\ninterface Props {\n\tfilter: SecretTypes.Filter;\n\tonFilter: (filter: SecretTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class SecretsFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Secret ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/SecretsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport SecretsStore from '../stores/SecretsStore';\nimport * as SecretActions from '../actions/SecretActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class SecretsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: SecretsStore.page,\n\t\t\tpageCount: SecretsStore.pageCount,\n\t\t\tpages: SecretsStore.pages,\n\t\t\tcount: SecretsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tSecretsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tSecretsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: SecretsStore.page,\n\t\t\tpageCount: SecretsStore.pageCount,\n\t\t\tpages: SecretsStore.pages,\n\t\t\tcount: SecretsStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tSecretActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tSecretActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tSecretActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tSecretActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tSecretActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Session.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as SessionTypes from '../types/SessionTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as AgentUtils from '../utils/AgentUtils';\nimport * as Constants from '../Constants';\nimport * as SessionActions from '../actions/SessionActions';\nimport PageInfo from './PageInfo';\n\ninterface Props {\n\tsession: SessionTypes.SessionRo;\n}\n\ninterface State {\n\tdisabled: boolean;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px',\n\t\tmarginBottom: '5px',\n\t} as React.CSSProperties,\n\tinfo: {\n\t\tmarginBottom: '-5px',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '290px',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n};\n\nexport default class Session extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tSessionActions.remove(this.props.session.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet session = this.props.session;\n\t\tlet agent = session.agent || {};\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\t\tif (session.removed) {\n\t\t\tcardStyle.opacity = 0.6;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card\"\n\t\t\tstyle={cardStyle}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div style={css.remove}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\thidden={session.removed}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonClick={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: session.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Created',\n\t\t\t\t\t\t\t\tvalue: MiscUtils.formatDate(session.timestamp) || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Last Active',\n\t\t\t\t\t\t\t\tvalue: MiscUtils.formatDate(session.last_active) || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Session Type',\n\t\t\t\t\t\t\t\tvalue: Constants.sessionTypes[session.type] || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Browser',\n\t\t\t\t\t\t\t\tvalue: (Constants.operatingSystems[agent.operating_system] ||\n\t\t\t\t\t\t\t\t\t'Unknown') + ' ' + (Constants.browsers[agent.browser] ||\n\t\t\t\t\t\t\t\t\t'Unknown'),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ISP',\n\t\t\t\t\t\t\t\tvalue: agent.isp || 'Unknown',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tstyle={css.info}\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Location',\n\t\t\t\t\t\t\t\tvalue: [\n\t\t\t\t\t\t\t\t\tAgentUtils.formatLocation(agent),\n\t\t\t\t\t\t\t\t\tAgentUtils.formatCountry(agent),\n\t\t\t\t\t\t\t\t\tAgentUtils.formatContinent(agent),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Coordinates',\n\t\t\t\t\t\t\t\tvalue: AgentUtils.formatCoordinates(agent),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Sessions.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as SessionTypes from '../types/SessionTypes';\nimport SessionsStore from '../stores/SessionsStore';\nimport * as SessionActions from '../actions/SessionActions';\nimport NonState from './NonState';\nimport Session from './Session';\nimport PageHeader from './PageHeader';\n\ninterface Props {\n\tuserId: string;\n}\n\ninterface State {\n\tsessions: SessionTypes.SessionsRo;\n\tshowEnded: boolean;\n\tdisabled: boolean;\n}\n\nconst css = {\n\theader: {\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '15px 0 -5px 0',\n\t} as React.CSSProperties,\n};\n\nexport default class Sessions extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tsessions: SessionsStore.sessions,\n\t\t\tshowEnded: false,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tSessionsStore.addChangeListener(this.onChange);\n\t\tif (this.props.userId) {\n\t\t\tSessionActions.load(this.props.userId);\n\t\t}\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tSessionsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tsessions: SessionsStore.sessions,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tif (!this.props.userId) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet sessions: JSX.Element[] = [];\n\n\t\tthis.state.sessions.forEach((\n\t\t\t\tsession: SessionTypes.SessionRo): void => {\n\t\t\tif (session.removed && !this.state.showEnded) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsessions.push(<Session\n\t\t\t\tkey={session.id}\n\t\t\t\tsession={session}\n\t\t\t/>);\n\t\t});\n\n\t\treturn <div>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>User Sessions</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-minimal\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tshowEnded: !this.state.showEnded,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tSessionActions.showRemoved(!this.state.showEnded);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{(this.state.showEnded ? 'Hide' : 'Show') + ' ended sessions'}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<div>\n\t\t\t\t{sessions}\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!sessions.length}\n\t\t\t\ticonClass=\"bp5-icon-user\"\n\t\t\t\ttitle=\"No sessions\"\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Settings.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as SettingsTypes from '../types/SettingsTypes';\nimport SettingsStore from '../stores/SettingsStore';\nimport SubscriptionStore from '../stores/SubscriptionStore';\nimport * as SettingsActions from '../actions/SettingsActions';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport PagePanel from './PagePanel';\nimport PageSplit from './PageSplit';\nimport PageInput from './PageInput';\nimport PageSwitch from './PageSwitch';\nimport PageSelectButton from './PageSelectButton';\nimport PageSave from './PageSave';\nimport SettingsProvider from './SettingsProvider';\nimport SettingsSecondaryProvider from './SettingsSecondaryProvider';\nimport NonState from './NonState';\n\ninterface State {\n\tchanged: boolean;\n\tdisabled: boolean;\n\tmessage: string;\n\tprovider: string;\n\tsecondaryProvider: string;\n\tsettings: SettingsTypes.Settings;\n}\n\nconst css = {\n\tproviders: {\n\t\tpaddingBottom: '6px',\n\t\tmarginBottom: '5px',\n\t\tborderBottomStyle: 'solid',\n\t} as React.CSSProperties,\n\tprovidersLabel: {\n\t\tmargin: 0,\n\t} as React.CSSProperties,\n\tsecondaryProviders: {\n\t\tpaddingBottom: '6px',\n\t\tmarginBottom: '5px',\n\t\tborderBottomStyle: 'solid',\n\t} as React.CSSProperties,\n};\n\nexport default class Settings extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tchanged: false,\n\t\t\tdisabled: false,\n\t\t\tmessage: '',\n\t\t\tprovider: 'google',\n\t\t\tsecondaryProvider: 'duo',\n\t\t\tsettings: SettingsStore.settingsM,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tSettingsStore.addChangeListener(this.onChange);\n\t\tSettingsActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tSettingsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: false,\n\t\t\tsettings: SettingsStore.settingsM,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tSettingsActions.commit(this.state.settings).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tset = (name: string, val: any): void => {\n\t\tlet settings: any = {\n\t\t\t...this.state.settings,\n\t\t};\n\n\t\tsettings[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tsettings: settings,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet settings = this.state.settings;\n\n\t\tif (!settings) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet subscriptionActive = SubscriptionStore.subscription ?\n\t\t\tSubscriptionStore.subscription.active : false;\n\n\t\tlet providers: JSX.Element[] = [];\n\t\tfor (let i = 0; i < settings.auth_providers.length; i++) {\n\t\t\tproviders.push(<SettingsProvider\n\t\t\t\tkey={i}\n\t\t\t\tprovider={settings.auth_providers[i]}\n\t\t\t\tonChange={(state): void => {\n\t\t\t\t\tlet prvdrs = [\n\t\t\t\t\t\t...this.state.settings.auth_providers,\n\t\t\t\t\t];\n\t\t\t\t\tprvdrs[i] = state;\n\t\t\t\t\tthis.set('auth_providers', prvdrs);\n\t\t\t\t}}\n\t\t\t\tonRemove={(): void => {\n\t\t\t\t\tlet prvdrs = [\n\t\t\t\t\t\t...this.state.settings.auth_providers,\n\t\t\t\t\t];\n\t\t\t\t\tprvdrs.splice(i, 1);\n\t\t\t\t\tthis.set('auth_providers', prvdrs);\n\t\t\t\t}}\n\t\t\t/>);\n\t\t}\n\n\t\tlet secondaryProviders: JSX.Element[] = [];\n\t\tfor (let i = 0; i < settings.auth_secondary_providers.length; i++) {\n\t\t\tsecondaryProviders.push(<SettingsSecondaryProvider\n\t\t\t\tkey={i}\n\t\t\t\tprovider={settings.auth_secondary_providers[i]}\n\t\t\t\tonChange={(state): void => {\n\t\t\t\t\tlet prvdrs = [\n\t\t\t\t\t\t...this.state.settings.auth_secondary_providers,\n\t\t\t\t\t];\n\t\t\t\t\tprvdrs[i] = state;\n\t\t\t\t\tthis.set('auth_secondary_providers', prvdrs);\n\t\t\t\t}}\n\t\t\t\tonRemove={(): void => {\n\t\t\t\t\tlet prvdrs = [\n\t\t\t\t\t\t...this.state.settings.auth_secondary_providers,\n\t\t\t\t\t];\n\t\t\t\t\tprvdrs.splice(i, 1);\n\t\t\t\t\tthis.set('auth_secondary_providers', prvdrs);\n\t\t\t\t}}\n\t\t\t/>);\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader label=\"Settings\"/>\n\t\t\t<PageSplit>\n\t\t\t\t<PagePanel hidden={subscriptionActive}>\n\t\t\t\t\t<NonState\n\t\t\t\t\t\thidden={false}\n\t\t\t\t\t\ticonClass=\"bp5-icon-credit-card\"\n\t\t\t\t\t\ttitle=\"Subscription Required\"\n\t\t\t\t\t\tdescription=\"Subscription required for single sign-on.\"\n\t\t\t\t\t/>\n\t\t\t\t</PagePanel>\n\t\t\t\t<PagePanel hidden={!subscriptionActive}>\n\t\t\t\t\t<div className=\"bp5-border\" style={css.providers}>\n\t\t\t\t\t\t<h5 style={css.providersLabel}>Authentication Providers</h5>\n\t\t\t\t\t</div>\n\t\t\t\t\t{providers}\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\tlabel=\"Add Provider\"\n\t\t\t\t\t\tvalue={this.state.provider}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tprovider: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={(): void => {\n\t\t\t\t\t\t\tlet authProviders: SettingsTypes.Providers = [\n\t\t\t\t\t\t\t\t...settings.auth_providers,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: this.state.provider,\n\t\t\t\t\t\t\t\t\tdefault_roles: [],\n\t\t\t\t\t\t\t\t\tauto_create: true,\n\t\t\t\t\t\t\t\t\trole_management: 'set_on_insert',\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\tthis.set('auth_providers', authProviders);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"authzero\">Auth0</option>\n\t\t\t\t\t\t<option value=\"azure\">Azure</option>\n\t\t\t\t\t\t<option value=\"google\">Google</option>\n\t\t\t\t\t\t<option value=\"onelogin\">OneLogin</option>\n\t\t\t\t\t\t<option value=\"okta\">Okta</option>\n\t\t\t\t\t\t<option value=\"jumpcloud\">JumpCloud</option>\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t</PagePanel>\n\t\t\t\t<PagePanel>\n\t\t\t\t\t<div className=\"bp5-border\" style={css.secondaryProviders}>\n\t\t\t\t\t\t<h5 style={css.providersLabel}>Two-Factor Providers</h5>\n\t\t\t\t\t</div>\n\t\t\t\t\t{secondaryProviders}\n\t\t\t\t\t<PageSelectButton\n\t\t\t\t\t\tlabel=\"Add Secondary Provider\"\n\t\t\t\t\t\tvalue={this.state.secondaryProvider}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success\"\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tsecondaryProvider: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={(): void => {\n\t\t\t\t\t\t\tlet authProviders: SettingsTypes.SecondaryProviders = [\n\t\t\t\t\t\t\t\t...settings.auth_secondary_providers,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: this.state.secondaryProvider,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\tthis.set('auth_secondary_providers', authProviders);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"duo\">Duo</option>\n\t\t\t\t\t\t<option value=\"one_login\">OneLogin</option>\n\t\t\t\t\t\t<option value=\"okta\">Okta</option>\n\t\t\t\t\t</PageSelectButton>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Admin Session Expire Minutes\"\n\t\t\t\t\t\thelp=\"Number of inactive minutes before a admin session expires\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Session expire\"\n\t\t\t\t\t\tvalue={this.state.settings.auth_admin_expire}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('auth_admin_expire', parseInt(val, 10));\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Admin Session Max Duration Minutes\"\n\t\t\t\t\t\thelp=\"Number of minutes from start of a admin session until expiration\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Session max duration\"\n\t\t\t\t\t\tvalue={this.state.settings.auth_admin_max_duration}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('auth_admin_max_duration', parseInt(val, 10));\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"User Session Expire Minutes\"\n\t\t\t\t\t\thelp=\"Number of inactive minutes before a user session expires\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Session expire\"\n\t\t\t\t\t\tvalue={this.state.settings.auth_user_expire}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('auth_user_expire', parseInt(val, 10));\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"User Session Max Duration Minutes\"\n\t\t\t\t\t\thelp=\"Number of minutes from start of a user session until expiration\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Session max duration\"\n\t\t\t\t\t\tvalue={this.state.settings.auth_user_max_duration}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('auth_user_max_duration', parseInt(val, 10));\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Twilio Account SID\"\n\t\t\t\t\t\thelp=\"Twilio account SID for SMS and phone alerts.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Twilio account\"\n\t\t\t\t\t\tvalue={this.state.settings.twilio_account}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('twilio_account', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Twilio Secret\"\n\t\t\t\t\t\thelp=\"Twilio account secret for SMS and phone alerts.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Twilio account secret\"\n\t\t\t\t\t\tvalue={this.state.settings.twilio_secret}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('twilio_secret', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"NIST NVD API Key\"\n\t\t\t\t\t\thelp=\"API key to use NIST National Vulnerability Database for faster CVE queries. Without an API key CVE information queries are limited to one every 8 seconds.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"NIST API key\"\n\t\t\t\t\t\tvalue={this.state.settings.nvd_api_key}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('nvd_api_key', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Fast single sign-on login\"\n\t\t\t\t\t\thelp=\"When only one single sign-on provider is configured and no local user accounts exists the login page will immediately redirect to the single sign-on provider.\"\n\t\t\t\t\t\tchecked={this.state.settings.auth_fast_login}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('auth_fast_login',\n\t\t\t\t\t\t\t\t!this.state.settings.auth_fast_login);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Force fast user single sign-on login\"\n\t\t\t\t\t\thelp=\"When only one single sign-on provider is configured the user login page will immediately redirect to the single sign-on provider. This will occur even if local user accounts exists.\"\n\t\t\t\t\t\tchecked={this.state.settings.auth_force_fast_user_login}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('auth_force_fast_user_login',\n\t\t\t\t\t\t\t\t!this.state.settings.auth_force_fast_user_login);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</PagePanel>\n\t\t\t</PageSplit>\n\t\t\t<PageSave\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tmessage: 'Your changes have been discarded',\n\t\t\t\t\t\tsettings: SettingsStore.settingsM,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/SettingsProvider.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as SettingsTypes from '../types/SettingsTypes';\nimport PageInput from './PageInput';\nimport PageInputButton from './PageInputButton';\nimport PageTextArea from './PageTextArea';\nimport PageSwitch from './PageSwitch';\nimport PageInfo from './PageInfo';\nimport PageSelect from './PageSelect';\nimport Help from './Help';\n\ninterface Props {\n\tprovider: SettingsTypes.ProviderAny;\n\tonChange: (state: SettingsTypes.ProviderAny) => void;\n\tonRemove: () => void;\n}\n\ninterface State {\n\taddRole: string;\n}\n\nconst css = {\n\tlabel: {\n\t\tfontSize: '16px',\n\t\tmargin: '0 0 7px 0',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tmarginBottom: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class SettingsProvider extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\taddRole: '',\n\t\t};\n\t}\n\n\tclone(): SettingsTypes.ProviderAny {\n\t\treturn {\n\t\t\t...this.props.provider,\n\t\t};\n\t}\n\n\tazure(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\n\t\treturn <div>\n\t\t\t<PageSelect\n\t\t\t\tlabel=\"Region\"\n\t\t\t\thelp=\"Azure active directory region\"\n\t\t\t\tvalue={provider.region}\n\t\t\t\tonChange={(val): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.region = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<option value=\"global2\">Global (OAuth v2)</option>\n\t\t\t\t<option value=\"us-gov2\">US Goverment (OAuth v2)</option>\n\t\t\t\t<option value=\"china2\">China (OAuth v2)</option>\n\t\t\t\t<option value=\"global\">Global (OAuth v1)</option>\n\t\t\t\t<option value=\"us-gov\">US Goverment (OAuth v1)</option>\n\t\t\t\t<option value=\"china\">China (OAuth v1)</option>\n\t\t\t</PageSelect>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Directory ID\"\n\t\t\t\thelp=\"Azure active directory ID\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Azure directory ID\"\n\t\t\t\tvalue={provider.tenant}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.tenant = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Application ID\"\n\t\t\t\thelp=\"Azure application ID\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Azure application ID\"\n\t\t\t\tvalue={provider.client_id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.client_id = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Application Secret\"\n\t\t\t\thelp=\"Azure application secret\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Azure application secret\"\n\t\t\t\tvalue={provider.client_secret}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.client_secret = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n\n\tauthzero(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\n\t\treturn <div>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Auth0 Sub-Domain\"\n\t\t\t\thelp=\"Subdomain of Auth0 application. Enter subdomain portion only such as 'pritunl' for pritunl.auth0.com\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Auth0 sub-domain\"\n\t\t\t\tvalue={provider.domain}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.domain = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Client ID\"\n\t\t\t\thelp=\"Auth0 application client ID\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Auth0 client ID\"\n\t\t\t\tvalue={provider.client_id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.client_id = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Client Secret\"\n\t\t\t\thelp=\"Auth0 application client secret\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Auth0 client secret\"\n\t\t\t\tvalue={provider.client_secret}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.client_secret = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n\n\tgoogle(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\n\t\treturn <div>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Domain\"\n\t\t\t\thelp=\"Domain segment of email address to match\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Google domain to match\"\n\t\t\t\tvalue={provider.domain}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.domain = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Google Admin Email\"\n\t\t\t\thelp=\"Optional, the email address of an administrator user in the Google G Suite to delegate API access to. This user will be used to get the groups of Google users. Only needed when providing the Google JSON private key.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Google admin email\"\n\t\t\t\tvalue={provider.google_email}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.google_email = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageTextArea\n\t\t\t\tlabel=\"Google JSON Private Key\"\n\t\t\t\thelp=\"Optional, private key for service account in JSON format. This will copy the Google users groups to Pritunl Cloud. Also requires Google admin email.\"\n\t\t\t\tplaceholder=\"Google JSON private key\"\n\t\t\t\trows={6}\n\t\t\t\tvalue={provider.google_key}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.google_key = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n\n\tonelogin(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\n\t\treturn <div>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Issuer URL\"\n\t\t\t\thelp=\"Single sign-on URL found in OneLogin app settings\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"OneLogin issuer URL\"\n\t\t\t\tvalue={provider.issuer_url}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.issuer_url = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"SAML 2.0 Endpoint (HTTP)\"\n\t\t\t\thelp=\"SAML 2.0 endpoint found in OneLogin app settings\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"OneLogin SAML endpoint\"\n\t\t\t\tvalue={provider.saml_url}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.saml_url = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageTextArea\n\t\t\t\tlabel=\"X.509 Certificate\"\n\t\t\t\thelp=\"X.509 certificate found in OneLogin app settings\"\n\t\t\t\tplaceholder=\"OneLogin X.509 certificate\"\n\t\t\t\trows={6}\n\t\t\t\tvalue={provider.saml_cert}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.saml_cert = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n\n\tokta(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\n\t\treturn <div>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Identity Provider Single Sign-On URL\"\n\t\t\t\thelp=\"Single sign-on URL found in Okta app settings\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Okta single sign-on URL\"\n\t\t\t\tvalue={provider.saml_url}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.saml_url = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Identity Provider Issuer URL\"\n\t\t\t\thelp=\"Issuer URL found in Okta app settings\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Okta issuer URL\"\n\t\t\t\tvalue={provider.issuer_url}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.issuer_url = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageTextArea\n\t\t\t\tlabel=\"X.509 Certificate\"\n\t\t\t\thelp=\"X.509 certificate found in Okta app settings\"\n\t\t\t\tplaceholder=\"Okta X.509 certificate\"\n\t\t\t\trows={6}\n\t\t\t\tvalue={provider.saml_cert}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.saml_cert = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n\n\tjumpcloud(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\n\t\treturn <div>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Identity Provider Single Sign-On URL\"\n\t\t\t\thelp=\"Single sign-on URL found in JumpCloud app settings\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"JumpCloud single sign-on URL\"\n\t\t\t\tvalue={provider.saml_url}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.saml_url = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Identity Provider Issuer URL\"\n\t\t\t\thelp=\"Issuer URL found in JumpCloud app settings\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"JumpCloud issuer URL\"\n\t\t\t\tvalue={provider.issuer_url}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.issuer_url = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageTextArea\n\t\t\t\tlabel=\"X.509 Certificate\"\n\t\t\t\thelp=\"X.509 certificate found in JumpCloud app settings\"\n\t\t\t\tplaceholder=\"JumpCloud X.509 certificate\"\n\t\t\t\trows={6}\n\t\t\t\tvalue={provider.saml_cert}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.saml_cert = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"JumpCloud App ID\"\n\t\t\t\thelp=\"Optional, ID of JumpCloud Pritunl app. This can be found in the URL of the app settings page. Required to verify user is attached to JumpCloud application\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"JumpCloud app ID\"\n\t\t\t\tvalue={provider.jumpcloud_app_id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.jumpcloud_app_id = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"JumpCloud API Key\"\n\t\t\t\thelp=\"JumpCloud API key for user account status verfication\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"JumpCloud client secret\"\n\t\t\t\tvalue={provider.jumpcloud_secret}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.jumpcloud_secret = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n\n\trender(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\t\tlet label = '';\n\t\tlet options: JSX.Element;\n\n\t\tswitch (provider.type) {\n\t\t\tcase 'azure':\n\t\t\t\tlabel = 'Azure';\n\t\t\t\toptions = this.azure();\n\t\t\t\tbreak;\n\t\t\tcase 'authzero':\n\t\t\t\tlabel = 'Auth0';\n\t\t\t\toptions = this.authzero();\n\t\t\t\tbreak;\n\t\t\tcase 'google':\n\t\t\t\tlabel = 'Google';\n\t\t\t\toptions = this.google();\n\t\t\t\tbreak;\n\t\t\tcase 'onelogin':\n\t\t\t\tlabel = 'OneLogin';\n\t\t\t\toptions = this.onelogin();\n\t\t\t\tbreak;\n\t\t\tcase 'okta':\n\t\t\t\tlabel = 'Okta';\n\t\t\t\toptions = this.okta();\n\t\t\t\tbreak;\n\t\t\tcase 'jumpcloud':\n\t\t\t\tlabel = 'JumpCloud';\n\t\t\t\toptions = this.jumpcloud();\n\t\t\t\tbreak;\n\t\t}\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of provider.default_roles) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tlet rls = [\n\t\t\t\t\t\t\t\t...this.props.provider.default_roles,\n\t\t\t\t\t\t\t];\n\n\t\t\t\t\t\t\tlet i = rls.indexOf(role);\n\t\t\t\t\t\t\tif (i === -1) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\trls.splice(i, 1);\n\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.default_roles = rls;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <div className=\"bp5-card\" style={css.card}>\n\t\t\t<h6 style={css.label}>{label}</h6>\n\t\t\t<PageInfo\n\t\t\t\tfields={[\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\tvalue: provider.id || 'None',\n\t\t\t\t\t},\n\t\t\t\t]}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Label\"\n\t\t\t\thelp=\"Provider label that will be shown to users on the login page\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Provider label\"\n\t\t\t\tvalue={provider.label}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.label = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<label className=\"bp5-label\" hidden={!provider.auto_create}>\n\t\t\t\tDefault Roles\n\t\t\t\t<Help\n\t\t\t\t\ttitle=\"Default Roles\"\n\t\t\t\t\tcontent=\"When the user has authenticated for the first time these roles will be given to the user. These roles may also be used to update the users roles depending on the role management option.\"\n\t\t\t\t/>\n\t\t\t\t<div>\n\t\t\t\t\t{roles}\n\t\t\t\t</div>\n\t\t\t</label>\n\t\t\t<PageInputButton\n\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\tlabel=\"Add\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Add default role\"\n\t\t\t\thidden={!provider.auto_create}\n\t\t\t\tvalue={this.state.addRole}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSubmit={(): void => {\n\t\t\t\t\tlet rls = [\n\t\t\t\t\t\t...this.props.provider.default_roles,\n\t\t\t\t\t];\n\n\t\t\t\t\tif (!this.state.addRole) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (rls.indexOf(this.state.addRole) === -1) {\n\t\t\t\t\t\trls.push(this.state.addRole);\n\t\t\t\t\t}\n\n\t\t\t\t\trls.sort();\n\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.default_roles = rls;\n\t\t\t\t\tthis.props.onChange(state);\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\taddRole: '',\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"Create user on authentication\"\n\t\t\t\thelp=\"Create the user on first authentication. If this is disabled all users must be manually created before they are able to authenticate.\"\n\t\t\t\tchecked={provider.auto_create}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.auto_create = !state.auto_create;\n\n\t\t\t\t\tif (!state.auto_create &&\n\t\t\t\t\t\t\tstate.role_management === 'set_on_insert') {\n\t\t\t\t\t\tstate.role_management = 'merge';\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSelect\n\t\t\t\tlabel=\"Role Management\"\n\t\t\t\thelp=\"When the user authenticates for the first time a user will be created and the users roles will be set to the roles configured above. This is referenced as set on insert. It may be desired to update the roles on subsequent authentications. For this the merge mode can be used which will take the users current roles and merge them with the roles configured above using all the roles from both sets. Overwrite mode will replace the users roles on every authentication with the roles configured above. It is important to consider that if a users roles are modified those modifications will be lost when the overwrite mode is used.\"\n\t\t\t\tvalue={provider.role_management}\n\t\t\t\tonChange={(val): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.role_management = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<option\n\t\t\t\t\tvalue=\"set_on_insert\"\n\t\t\t\t\thidden={!provider.auto_create}\n\t\t\t\t>Set on insert</option>\n\t\t\t\t<option value=\"merge\">Merge</option>\n\t\t\t\t<option value=\"overwrite\">Overwrite</option>\n\t\t\t</PageSelect>\n\t\t\t{options}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-intent-danger\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t}}\n\t\t\t>Remove</button>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/SettingsSecondaryProvider.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as SettingsTypes from '../types/SettingsTypes';\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageSwitch from './PageSwitch';\nimport PageInfo from './PageInfo';\n\ninterface Props {\n\tprovider: SettingsTypes.SecondaryProviderAny;\n\tonChange: (state: SettingsTypes.SecondaryProviderAny) => void;\n\tonRemove: () => void;\n}\n\nconst css = {\n\tlabel: {\n\t\tfontSize: '16px',\n\t\tmargin: '0 0 7px 0',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tmarginBottom: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class SettingsSecondaryProvider extends React.Component<Props, {}> {\n\tclone(): SettingsTypes.SecondaryProviderAny {\n\t\treturn {\n\t\t\t...this.props.provider,\n\t\t};\n\t}\n\n\tduo(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\n\t\treturn <div>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Duo API Hostname\"\n\t\t\t\thelp=\"Duo API hostname found in Duo admin console.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Duo API hostname\"\n\t\t\t\tvalue={provider.duo_hostname}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.duo_hostname = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Duo Integration Key\"\n\t\t\t\thelp=\"Duo integration key found in Duo admin console.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Duo integration key\"\n\t\t\t\tvalue={provider.duo_key}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.duo_key = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Duo Secret Key\"\n\t\t\t\thelp=\"Duo secret key found in Duo admin console.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Duo secret key\"\n\t\t\t\tvalue={provider.duo_secret}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.duo_secret = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"Push authentication\"\n\t\t\t\thelp=\"Allow push authentication.\"\n\t\t\t\tchecked={provider.push_factor}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.push_factor = !state.push_factor;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"Phone authentication\"\n\t\t\t\thelp=\"Allow phone authentication.\"\n\t\t\t\tchecked={provider.phone_factor}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.phone_factor = !state.phone_factor;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"Passcode authentication\"\n\t\t\t\thelp=\"Allow passcode authentication.\"\n\t\t\t\tchecked={provider.passcode_factor}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.passcode_factor = !state.passcode_factor;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"SMS authentication\"\n\t\t\t\thelp=\"Allow SMS authentication.\"\n\t\t\t\tchecked={provider.sms_factor}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.sms_factor = !state.sms_factor;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n\n\tonelogin(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\n\t\treturn <div>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"OneLogin API Client ID\"\n\t\t\t\thelp=\"OneLogin API client ID found in OneLogin admin console.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"OneLogin API client ID\"\n\t\t\t\tvalue={provider.one_login_id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.one_login_id = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"OneLogin API Client Secret\"\n\t\t\t\thelp=\"OneLogin API client secret found in OneLogin admin console.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"OneLogin API client secret\"\n\t\t\t\tvalue={provider.one_login_secret}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.one_login_secret = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSelect\n\t\t\t\tlabel=\"OneLogin API Region\"\n\t\t\t\thelp=\"OneLogin region for API requests.\"\n\t\t\t\tvalue={provider.one_login_region}\n\t\t\t\tonChange={(val): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.one_login_region = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<option value=\"us\">United States</option>\n\t\t\t\t<option value=\"eu\">Europe</option>\n\t\t\t</PageSelect>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"Push authentication\"\n\t\t\t\thelp=\"Allow push authentication.\"\n\t\t\t\tchecked={provider.push_factor}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.push_factor = !state.push_factor;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"Passcode authentication\"\n\t\t\t\thelp=\"Allow passcode authentication.\"\n\t\t\t\tchecked={provider.passcode_factor}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.passcode_factor = !state.passcode_factor;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n\n\tokta(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\n\t\treturn <div>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Okta Domain\"\n\t\t\t\thelp=\"Okta domain used to login to Okta such as 'pritunl.okta.com'.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Okta domain\"\n\t\t\t\tvalue={provider.okta_domain}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.okta_domain = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Okta API Token\"\n\t\t\t\thelp=\"Okta API token found in Okta admin console.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Okta API token\"\n\t\t\t\tvalue={provider.okta_token}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.okta_token = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"Push authentication\"\n\t\t\t\thelp=\"Allow push authentication.\"\n\t\t\t\tchecked={provider.push_factor}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.push_factor = !state.push_factor;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageSwitch\n\t\t\t\tlabel=\"Passcode authentication\"\n\t\t\t\thelp=\"Allow passcode authentication.\"\n\t\t\t\tchecked={provider.passcode_factor}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.passcode_factor = !state.passcode_factor;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n\n\trender(): JSX.Element {\n\t\tlet provider = this.props.provider;\n\t\tlet label = '';\n\t\tlet options: JSX.Element;\n\n\t\tswitch (provider.type) {\n\t\t\tcase 'duo':\n\t\t\t\tlabel = 'Duo';\n\t\t\t\toptions = this.duo();\n\t\t\t\tbreak;\n\t\t\tcase 'one_login':\n\t\t\t\tlabel = 'OneLogin';\n\t\t\t\toptions = this.onelogin();\n\t\t\t\tbreak;\n\t\t\tcase 'okta':\n\t\t\t\tlabel = 'Okta';\n\t\t\t\toptions = this.okta();\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn <div className=\"bp5-card\" style={css.card}>\n\t\t\t<h6 style={css.label}>{label}</h6>\n\t\t\t<PageInfo\n\t\t\t\tfields={[\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\tvalue: provider.id || 'None',\n\t\t\t\t\t},\n\t\t\t\t]}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Name\"\n\t\t\t\thelp=\"Two-factor provider name.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Two-factor provider name\"\n\t\t\t\tvalue={provider.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.name = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<PageInput\n\t\t\t\tlabel=\"Label\"\n\t\t\t\thelp=\"Two-factor provider label that will be shown to users on the login page.\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tplaceholder=\"Two-factor provider label\"\n\t\t\t\tvalue={provider.label}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\tstate.label = val;\n\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{options}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-intent-danger\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t}}\n\t\t\t>Remove</button>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Shape.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as ShapeTypes from '../types/ShapeTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport ShapeDetailed from './ShapeDetailed';\nimport ZonesStore from \"../stores/ZonesStore\";\nimport * as PoolTypes from \"../types/PoolTypes\";\nimport DatacentersStore from \"../stores/DatacentersStore\";\n\ninterface Props {\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tpools: PoolTypes.PoolsRo;\n\tshape: ShapeTypes.ShapeRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Shape extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet shape = this.props.shape;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<ShapeDetailed\n\t\t\t\t\tdatacenters={this.props.datacenters}\n\t\t\t\t\tzones={this.props.zones}\n\t\t\t\t\tpools={this.props.pools}\n\t\t\t\t\tshape={this.props.shape}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet datacenter = DatacentersStore.datacenter(this.props.shape.datacenter);\n\t\tlet datacenterName = datacenter ? datacenter.name : null;\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{shape.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\thidden={!datacenterName}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-cloud\"\n\t\t\t\t/>\n\t\t\t\t{datacenterName}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ShapeDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as ShapeTypes from '../types/ShapeTypes';\nimport * as ShapeActions from '../actions/ShapeActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\nimport * as ZoneTypes from \"../types/ZoneTypes\";\nimport * as PoolTypes from \"../types/PoolTypes\";\nimport PageInput from './PageInput';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageInputButton from './PageInputButton';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\nimport PageSwitch from \"./PageSwitch\";\nimport PageNumInput from \"./PageNumInput\";\n\ninterface Props {\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tpools: PoolTypes.PoolsRo;\n\tshape: ShapeTypes.ShapeRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddRole: string;\n\tshape: ShapeTypes.Shape;\n\tdatacenter: string;\n\tzone: string;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\trules: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class ShapeDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tshape: null,\n\t\t\taddRole: '',\n\t\t\tdatacenter: '',\n\t\t\tzone: '',\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet shape: any;\n\n\t\tif (this.state.changed) {\n\t\t\tshape = {\n\t\t\t\t...this.state.shape,\n\t\t\t};\n\t\t} else {\n\t\t\tshape = {\n\t\t\t\t...this.props.shape,\n\t\t\t};\n\t\t}\n\n\t\tshape[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tshape: shape,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tShapeActions.commit(this.state.shape).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tshape: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tShapeActions.remove(this.props.shape.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet shape: ShapeTypes.Shape;\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.state.changed) {\n\t\t\tshape = {\n\t\t\t\t...this.state.shape,\n\t\t\t};\n\t\t} else {\n\t\t\tshape = {\n\t\t\t\t...this.props.shape,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(shape.roles || []),\n\t\t];\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\t\tshape.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tshape: shape,\n\t\t});\n\t}\n\n\tonRemoveRole = (role: string): void => {\n\t\tlet shape: ShapeTypes.Shape;\n\n\t\tif (this.state.changed) {\n\t\t\tshape = {\n\t\t\t\t...this.state.shape,\n\t\t\t};\n\t\t} else {\n\t\t\tshape = {\n\t\t\t\t...this.props.shape,\n\t\t\t};\n\t\t}\n\n\t\tlet roles = [\n\t\t\t...(shape.roles || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\t\tshape.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tshape: shape,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet shape: ShapeTypes.Shape = this.state.shape ||\n\t\t\tthis.props.shape;\n\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters.length) {\n\t\t\thasDatacenters = true;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\tlet hasPools = false;\n\t\tlet poolsSelect: JSX.Element[] = [];\n\t\tif (this.props.pools.length) {\n\t\t\tpoolsSelect.push(<option key=\"null\" value=\"\">Select Pool</option>);\n\n\t\t\tfor (let pool of this.props.pools) {\n\t\t\t\tif (pool.zone !== this.state.zone) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\thasPools = true;\n\t\t\t\tpoolsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={pool.id}\n\t\t\t\t\t\tvalue={pool.id}\n\t\t\t\t\t>{pool.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasPools) {\n\t\t\tpoolsSelect = [<option key=\"null\" value=\"\">No Pools</option>];\n\t\t}\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of (shape.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"shape\" id={this.props.shape.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Shape\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this shape\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[shape.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of shape\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={shape.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Shape comment.\"\n\t\t\t\t\t\tplaceholder=\"Shape comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={shape.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={!!shape.datacenter || this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\thelp=\"Shape datacenter, cannot be changed once set.\"\n\t\t\t\t\t\tvalue={shape.datacenter}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('datacenter', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Disk Type\"\n\t\t\t\t\t\thelp=\"Type of disk. QCOW disk files are stored locally on the node filesystem. LVM disks are partitioned as a logical volume.\"\n\t\t\t\t\t\tvalue={shape.disk_type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('disk_type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option key=\"qcow2\" value=\"qcow2\">QCOW</option>\n\t\t\t\t\t\t<option key=\"lvm\" value=\"lvm\">LVM</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled || !hasPools}\n\t\t\t\t\t\tlabel=\"Disk Pool\"\n\t\t\t\t\t\thelp=\"Disk pool to use for storage.\"\n\t\t\t\t\t\thidden={shape.disk_type !== \"lvm\"}\n\t\t\t\t\t\tvalue={shape.disk_pool}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('disk_pool', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{poolsSelect}\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Flexible\"\n\t\t\t\t\t\thelp=\"Allow process and memory to be customized for each instance.\"\n\t\t\t\t\t\tchecked={shape.flexible}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('flexible', !shape.flexible);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\thelp=\"Block shape from being deleted.\"\n\t\t\t\t\t\tchecked={shape.delete_protection}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('delete_protection', !shape.delete_protection);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.shape.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Node Count',\n\t\t\t\t\t\t\t\tvalue: this.props.shape.node_count || '0',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\tlabel=\"Memory Size\"\n\t\t\t\t\t\thelp=\"Instance memory size in megabytes.\"\n\t\t\t\t\t\tmin={256}\n\t\t\t\t\t\tminorStepSize={512}\n\t\t\t\t\t\tstepSize={1024}\n\t\t\t\t\t\tmajorStepSize={2048}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\tthis.set('memory', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tvalue={shape.memory}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\tlabel=\"Processors\"\n\t\t\t\t\t\thelp=\"Number of instance processors.\"\n\t\t\t\t\t\tmin={1}\n\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\tmajorStepSize={2}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\tthis.set('processors', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tvalue={shape.processors}\n\t\t\t\t\t/>\n\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\tRoles\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\tcontent=\"Roles that will be matched with nodes. Nodes that provide this shape must have a matching role.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.shape && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tshape: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ShapeNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as ShapeTypes from '../types/ShapeTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as ShapeActions from '../actions/ShapeActions';\nimport * as ImageActions from '../actions/ImageActions';\nimport * as InstanceActions from '../actions/InstanceActions';\nimport * as NodeActions from '../actions/NodeActions';\nimport ImagesDatacenterStore from '../stores/ImagesDatacenterStore';\nimport InstancesNodeStore from '../stores/InstancesNodeStore';\nimport NodesZoneStore from '../stores/NodesZoneStore';\nimport PageInput from './PageInput';\nimport PageInputButton from './PageInputButton';\nimport PageCreate from './PageCreate';\nimport PageSelect from './PageSelect';\nimport PageSwitch from \"./PageSwitch\";\nimport PageNumInput from './PageNumInput';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\nimport * as PoolTypes from \"../types/PoolTypes\";\n\ninterface Props {\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tpools: PoolTypes.PoolsRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tshape: ShapeTypes.Shape;\n\tdatacenter: string;\n\tzone: string;\n\taddRole: string;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class ShapeNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tshape: {\n\t\t\t\tname: 'new-shape',\n\t\t\t\tmemory: 1024,\n\t\t\t\tprocessors: 1,\n\t\t\t\tflexible: true,\n\t\t\t},\n\t\t\tdatacenter: '',\n\t\t\tzone: '',\n\t\t\taddRole: '',\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet shape: any = {\n\t\t\t...this.state.shape,\n\t\t};\n\n\t\tshape[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tshape: shape,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet shape: any = {\n\t\t\t...this.state.shape,\n\t\t};\n\n\t\tif (!shape.datacenter && this.props.datacenters.length) {\n\t\t\tshape.datacenter = this.state.datacenter ||\n\t\t\t\tthis.props.datacenters[0].id;\n\t\t}\n\n\t\tShapeActions.create(shape).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Shape created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet shape: ShapeTypes.Shape;\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tshape = {\n\t\t\t...this.state.shape,\n\t\t};\n\n\t\tlet roles = [\n\t\t\t...(shape.roles || []),\n\t\t];\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\t\tshape.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tshape: shape,\n\t\t});\n\t}\n\n\tonRemoveRole = (role: string): void => {\n\t\tlet shape: ShapeTypes.Shape;\n\n\t\tshape = {\n\t\t\t...this.state.shape,\n\t\t};\n\n\t\tlet roles = [\n\t\t\t...(shape.roles || []),\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\t\tshape.roles = roles;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tshape: shape,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet shape = this.state.shape;\n\n\t\tlet defaultDatacenter = '';\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters.length) {\n\t\t\thasDatacenters = true;\n\t\t\tdefaultDatacenter = this.props.datacenters[0].id;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\tlet datacenter = this.state.datacenter || defaultDatacenter;\n\t\tlet hasZones = false;\n\t\tlet zonesSelect: JSX.Element[] = [];\n\t\tif (this.props.zones.length) {\n\t\t\tzonesSelect.push(<option key=\"null\" value=\"\">Select Zone</option>);\n\n\t\t\tfor (let zone of this.props.zones) {\n\t\t\t\tif (!this.state.zone && zone.datacenter !== datacenter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thasZones = true;\n\n\t\t\t\tzonesSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={zone.id}\n\t\t\t\t\t\tvalue={zone.id}\n\t\t\t\t\t>{zone.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasZones) {\n\t\t\tzonesSelect = [<option key=\"null\" value=\"\">No Zones</option>];\n\t\t}\n\n\t\tlet hasPools = false;\n\t\tlet poolsSelect: JSX.Element[] = [];\n\t\tif (this.props.pools.length) {\n\t\t\tpoolsSelect.push(<option key=\"null\" value=\"\">Select Pool</option>);\n\n\t\t\tfor (let pool of this.props.pools) {\n\t\t\t\tif (pool.zone !== this.state.zone) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\thasPools = true;\n\t\t\t\tpoolsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={pool.id}\n\t\t\t\t\t\tvalue={pool.id}\n\t\t\t\t\t>{pool.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasPools) {\n\t\t\tpoolsSelect = [<option key=\"null\" value=\"\">No Pools</option>];\n\t\t}\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of (shape.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of shape\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={shape.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Shape comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Shape comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={shape.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\t\thelp=\"Shape datacenter, cannot be changed once set.\"\n\t\t\t\t\t\t\tvalue={this.state.datacenter}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('datacenter', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Disk Type\"\n\t\t\t\t\t\t\thelp=\"Type of disk. QCOW disk files are stored locally on the node filesystem. LVM disks are partitioned as a logical volume.\"\n\t\t\t\t\t\t\tvalue={shape.disk_type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('disk_type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option key=\"qcow2\" value=\"qcow2\">QCOW</option>\n\t\t\t\t\t\t\t<option key=\"lvm\" value=\"lvm\">LVM</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasPools}\n\t\t\t\t\t\t\tlabel=\"Disk Pool\"\n\t\t\t\t\t\t\thelp=\"Disk pool to use for storage.\"\n\t\t\t\t\t\t\thidden={shape.disk_type !== \"lvm\"}\n\t\t\t\t\t\t\tvalue={shape.disk_pool}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('disk_pool', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{poolsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Flexible\"\n\t\t\t\t\t\t\thelp=\"Allow process and memory to be customized for each instance.\"\n\t\t\t\t\t\t\tchecked={shape.flexible}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('flexible', !shape.flexible);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Delete protection\"\n\t\t\t\t\t\t\thelp=\"Block shape from being deleted.\"\n\t\t\t\t\t\t\tchecked={shape.delete_protection}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('delete_protection', !shape.delete_protection);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"Memory Size\"\n\t\t\t\t\t\t\thelp=\"Instance memory size in megabytes.\"\n\t\t\t\t\t\t\tmin={256}\n\t\t\t\t\t\t\tminorStepSize={512}\n\t\t\t\t\t\t\tstepSize={1024}\n\t\t\t\t\t\t\tmajorStepSize={2048}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('memory', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tvalue={shape.memory}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageNumInput\n\t\t\t\t\t\t\tlabel=\"Processors\"\n\t\t\t\t\t\t\thelp=\"Number of instance processors.\"\n\t\t\t\t\t\t\tmin={1}\n\t\t\t\t\t\t\tminorStepSize={1}\n\t\t\t\t\t\t\tstepSize={1}\n\t\t\t\t\t\t\tmajorStepSize={2}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tselectAllOnFocus={true}\n\t\t\t\t\t\t\tonChange={(val: number): void => {\n\t\t\t\t\t\t\t\tthis.set('processors', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tvalue={shape.processors}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\t\tRoles\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\t\tcontent=\"Roles that will be matched with nodes. Nodes that provide this shape must have a matching role.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.shape}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Shapes.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as ShapeTypes from '../types/ShapeTypes';\nimport ShapesStore from '../stores/ShapesStore';\nimport * as ShapeActions from '../actions/ShapeActions';\nimport Shape from './Shape';\nimport ShapeNew from './ShapeNew';\nimport ShapesFilter from './ShapesFilter';\nimport ShapesPage from './ShapesPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\nimport * as ZoneTypes from \"../types/ZoneTypes\";\nimport * as PoolTypes from \"../types/PoolTypes\";\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as CompletionActions from \"../actions/CompletionActions\";\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tshapes: ShapeTypes.ShapesRo;\n\tfilter: ShapeTypes.Filter;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tzones: ZoneTypes.ZonesRo;\n\tpools: PoolTypes.PoolsRo;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Shapes extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tshapes: ShapesStore.shapes,\n\t\t\tfilter: ShapesStore.filter,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tpools: CompletionStore.pools,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tShapesStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tShapeActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tShapesStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet shapes = ShapesStore.shapes;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tshapes.forEach((shape: ShapeTypes.Shape): void => {\n\t\t\tif (curSelected[shape.id]) {\n\t\t\t\tselected[shape.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[shape.id]) {\n\t\t\t\topened[shape.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tshapes: shapes,\n\t\t\tfilter: ShapesStore.filter,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tzones: CompletionStore.zones,\n\t\t\tpools: CompletionStore.pools,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tShapeActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet shapesDom: JSX.Element[] = [];\n\n\t\tthis.state.shapes.forEach((\n\t\t\t\tshape: ShapeTypes.ShapeRo): void => {\n\t\t\tshapesDom.push(<Shape\n\t\t\t\tkey={shape.id}\n\t\t\t\tshape={shape}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tzones={this.state.zones}\n\t\t\t\tpools={this.state.pools}\n\t\t\t\tselected={!!this.state.selected[shape.id]}\n\t\t\t\topen={!!this.state.opened[shape.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet shapes = this.state.shapes;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < shapes.length; i++) {\n\t\t\t\t\t\t\tlet usr = shapes[i];\n\n\t\t\t\t\t\t\tif (usr.id === shape.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[shapes[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: shape.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[shape.id]) {\n\t\t\t\t\t\tdelete selected[shape.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[shape.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: shape.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[shape.id]) {\n\t\t\t\t\t\tdelete opened[shape.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[shape.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = ShapesStore.shape(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newShapeDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewShapeDom = <ShapeNew\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tzones={this.state.zones}\n\t\t\t\tpools={this.state.pools}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Instance Shapes</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tShapeActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tShapeActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected shapes\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<ShapesFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tShapeActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newShapeDom}\n\t\t\t\t\t{shapesDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!shapesDom.length}\n\t\t\t\ticonClass=\"bp5-icon-zoom-to-fit\"\n\t\t\t\ttitle=\"No shapes\"\n\t\t\t\tdescription=\"Add a new shape to get started.\"\n\t\t\t/>\n\t\t\t<ShapesPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ShapesFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as ShapeTypes from '../types/ShapeTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\n\ninterface Props {\n\tfilter: ShapeTypes.Filter;\n\tonFilter: (filter: ShapeTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class ShapesFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Shape ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ShapesPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport ShapesStore from '../stores/ShapesStore';\nimport * as ShapeActions from '../actions/ShapeActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class ShapesPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: ShapesStore.page,\n\t\t\tpageCount: ShapesStore.pageCount,\n\t\t\tpages: ShapesStore.pages,\n\t\t\tcount: ShapesStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tShapesStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tShapesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: ShapesStore.page,\n\t\t\tpageCount: ShapesStore.pageCount,\n\t\t\tpages: ShapesStore.pages,\n\t\t\tcount: ShapesStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tShapeActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tShapeActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tShapeActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tShapeActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tShapeActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Storage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as StorageTypes from '../types/StorageTypes';\nimport StorageDetailed from './StorageDetailed';\n\ninterface Props {\n\tstorage: StorageTypes.StorageRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Storage extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet storage = this.props.storage;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<StorageDetailed\n\t\t\t\t\tstorage={this.props.storage}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{storage.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-database\"\n\t\t\t\t/>\n\t\t\t\t{storage.endpoint}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/StorageDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as StorageTypes from '../types/StorageTypes';\nimport * as StorageActions from '../actions/StorageActions';\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport PageSelect from './PageSelect';\nimport PageSwitch from './PageSwitch';\nimport ConfirmButton from './ConfirmButton';\nimport * as Alert from \"../Alert\";\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tstorage: StorageTypes.StorageRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tstorage: StorageTypes.Storage;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tcontrolButton: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class StorageDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tstorage: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet storage: any;\n\n\t\tif (this.state.changed) {\n\t\t\tstorage = {\n\t\t\t\t...this.state.storage,\n\t\t\t};\n\t\t} else {\n\t\t\tstorage = {\n\t\t\t\t...this.props.storage,\n\t\t\t};\n\t\t}\n\n\t\tstorage[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tstorage: storage,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tStorageActions.commit(this.state.storage).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tstorage: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonSync = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tStorageActions.commit(this.props.storage).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tAlert.success('Storage sync started');\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tStorageActions.remove(this.props.storage.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet storage: StorageTypes.Storage = this.state.storage ||\n\t\t\tthis.props.storage;\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Storage\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this storage\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[storage.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of storage\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={storage.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Storage comment.\"\n\t\t\t\t\t\tplaceholder=\"Storage comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={storage.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Endpoint\"\n\t\t\t\t\t\thelp=\"Storage endpoint domain and port\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter endpoint\"\n\t\t\t\t\t\tvalue={storage.endpoint}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('endpoint', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Bucket\"\n\t\t\t\t\t\thelp=\"Storage bucket name\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter bucket\"\n\t\t\t\t\t\tvalue={storage.bucket}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('bucket', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.storage.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\thelp=\"Select public for read only storages with virtual machine images. Select private for read-write storages for snapshots.\"\n\t\t\t\t\t\tvalue={storage.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"public\">Public</option>\n\t\t\t\t\t\t<option value=\"private\">Private</option>\n\t\t\t\t\t\t<option value=\"web\">Web</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={storage.type == \"web\"}\n\t\t\t\t\t\tlabel=\"Access Key\"\n\t\t\t\t\t\thelp=\"Storage access key\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter access key\"\n\t\t\t\t\t\tvalue={storage.access_key}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('access_key', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\thidden={storage.type == \"web\"}\n\t\t\t\t\t\tlabel=\"Secret Key\"\n\t\t\t\t\t\thelp=\"Storage secret key\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter secret key\"\n\t\t\t\t\t\tvalue={storage.secret_key}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('secret_key', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"SSL Connection\"\n\t\t\t\t\t\thelp=\"Use secure SSL connection.\"\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tchecked={!storage.insecure}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('insecure', !storage.insecure);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.storage}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tstorage: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t>\n\t\t\t\t<ConfirmButton\n\t\t\t\t\tlabel=\"Sync\"\n\t\t\t\t\tclassName=\"bp5-intent-success bp5-icon-refresh\"\n\t\t\t\t\tprogressClassName=\"bp5-intent-success\"\n\t\t\t\t\tstyle={css.controlButton}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tonConfirm={(): void => {\n\t\t\t\t\t\tthis.onSync();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</PageSave>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/StorageNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as StorageTypes from '../types/StorageTypes';\nimport * as StorageActions from '../actions/StorageActions';\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageCreate from './PageCreate';\nimport PageSelect from './PageSelect';\nimport PageSwitch from './PageSwitch';\nimport ConfirmButton from './ConfirmButton';\nimport * as Alert from \"../Alert\";\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tstorage: StorageTypes.Storage;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tcontrolButton: {\n\t\tmarginRight: '10px',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class StorageDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tstorage: {\n\t\t\t\tname: \"new-storage\",\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet storage: any = {\n\t\t\t...this.state.storage,\n\t\t};\n\n\t\tstorage[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tstorage: storage,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet storage: any = {\n\t\t\t...this.state.storage,\n\t\t};\n\n\t\tStorageActions.create(storage).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Storage created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet storage: StorageTypes.Storage = this.state.storage;\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of storage\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={storage.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Storage comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Storage comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={storage.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Endpoint\"\n\t\t\t\t\t\t\thelp=\"Storage endpoint domain and port\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter endpoint\"\n\t\t\t\t\t\t\tvalue={storage.endpoint}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('endpoint', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Bucket\"\n\t\t\t\t\t\t\thelp=\"Storage bucket name\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter bucket\"\n\t\t\t\t\t\t\tvalue={storage.bucket}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('bucket', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\t\thelp=\"Select public for read only storages with virtual machine images. Select private for read-write storages for snapshots.\"\n\t\t\t\t\t\t\tvalue={storage.type}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"public\">Public</option>\n\t\t\t\t\t\t\t<option value=\"private\">Private</option>\n\t\t\t\t\t\t\t<option value=\"web\">Web</option>\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={storage.type == \"web\"}\n\t\t\t\t\t\t\tlabel=\"Access Key\"\n\t\t\t\t\t\t\thelp=\"Storage access key\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter access key\"\n\t\t\t\t\t\t\tvalue={storage.access_key}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('access_key', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={storage.type == \"web\"}\n\t\t\t\t\t\t\tlabel=\"Secret Key\"\n\t\t\t\t\t\t\thelp=\"Storage secret key\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter secret key\"\n\t\t\t\t\t\t\tvalue={storage.secret_key}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('secret_key', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tlabel=\"SSL Connection\"\n\t\t\t\t\t\t\thelp=\"Use secure SSL connection.\"\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tchecked={!storage.insecure}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('insecure', !storage.insecure);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.storage}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Storages.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as StorageTypes from '../types/StorageTypes';\nimport StoragesStore from '../stores/StoragesStore';\nimport * as StorageActions from '../actions/StorageActions';\nimport Storage from './Storage';\nimport StorageNew from './StorageNew';\nimport StoragesFilter from './StoragesFilter';\nimport StoragesPage from './StoragesPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tstorages: StorageTypes.StoragesRo;\n\tfilter: StorageTypes.Filter;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Storages extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tstorages: StoragesStore.storages,\n\t\t\tfilter: StoragesStore.filter,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tStoragesStore.addChangeListener(this.onChange);\n\t\tStorageActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tStoragesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet storages = StoragesStore.storages;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tstorages.forEach((storage: StorageTypes.Storage): void => {\n\t\t\tif (curSelected[storage.id]) {\n\t\t\t\tselected[storage.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[storage.id]) {\n\t\t\t\topened[storage.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tstorages: storages,\n\t\t\tfilter: StoragesStore.filter,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tStorageActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet storagesDom: JSX.Element[] = [];\n\n\t\tthis.state.storages.forEach((\n\t\t\t\tstorage: StorageTypes.StorageRo): void => {\n\t\t\tstoragesDom.push(<Storage\n\t\t\t\tkey={storage.id}\n\t\t\t\tstorage={storage}\n\t\t\t\tselected={!!this.state.selected[storage.id]}\n\t\t\t\topen={!!this.state.opened[storage.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet storages = this.state.storages;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < storages.length; i++) {\n\t\t\t\t\t\t\tlet usr = storages[i];\n\n\t\t\t\t\t\t\tif (usr.id === storage.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[storages[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: storage.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[storage.id]) {\n\t\t\t\t\t\tdelete selected[storage.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[storage.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: storage.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[storage.id]) {\n\t\t\t\t\t\tdelete opened[storage.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[storage.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = StoragesStore.storage(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newStorageDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewStorageDom = <StorageNew\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Storages</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tStorageActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tStorageActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected storages\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<StoragesFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tStorageActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newStorageDom}\n\t\t\t\t\t{storagesDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!storagesDom.length}\n\t\t\t\ticonClass=\"bp5-icon-database\"\n\t\t\t\ttitle=\"No storages\"\n\t\t\t\tdescription=\"Add a new storage to get started.\"\n\t\t\t/>\n\t\t\t<StoragesPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/StoragesFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as StorageTypes from '../types/StorageTypes';\nimport SearchInput from './SearchInput';\n\ninterface Props {\n\tfilter: StorageTypes.Filter;\n\tonFilter: (filter: StorageTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class StoragesFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Storage ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/StoragesPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport StoragesStore from '../stores/StoragesStore';\nimport * as StorageActions from '../actions/StorageActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class StoragesPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: StoragesStore.page,\n\t\t\tpageCount: StoragesStore.pageCount,\n\t\t\tpages: StoragesStore.pages,\n\t\t\tcount: StoragesStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tStoragesStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tStoragesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: StoragesStore.page,\n\t\t\tpageCount: StoragesStore.pageCount,\n\t\t\tpages: StoragesStore.pages,\n\t\t\tcount: StoragesStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tStorageActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tStorageActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tStorageActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tStorageActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tStorageActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Subscription.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as SubscriptionActions from '../actions/SubscriptionActions';\nimport * as SubscriptionTypes from '../types/SubscriptionTypes';\nimport SubscriptionStore from '../stores/SubscriptionStore';\nimport * as Alert from '../Alert';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport ConfirmButton from './ConfirmButton';\nimport * as Theme from '../Theme';\n\ninterface State {\n\tsubscription: SubscriptionTypes.SubscriptionRo;\n\tupdate: boolean;\n\tmessage: string;\n\tlicense: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\tcard: {\n\t\tpadding: '10px',\n\t\tminWidth: '310px',\n\t\tmaxWidth: '350px',\n\t\twidth: 'calc(100% - 20px)',\n\t\tmargin: '30px auto',\n\t} as React.CSSProperties,\n\tcard2: {\n\t\tpadding: '5px',\n\t\tminWidth: '310px',\n\t\tmaxWidth: '380px',\n\t\twidth: 'calc(100% - 20px)',\n\t\tmargin: '30px auto',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\twidth: '180px',\n\t\tmargin: '20px auto',\n\t\tfontSize: '16px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '2px 0',\n\t} as React.CSSProperties,\n\tmessage: {\n\t\tmargin: '0 0 10px 0',\n\t} as React.CSSProperties,\n\tlicense: {\n\t\twidth: '100%',\n\t\theight: '130px',\n\t\tmargin: '0 0 10px 0',\n\t\tresize: 'none',\n\t\tfontSize: Theme.monospaceSize,\n\t\tfontFamily: Theme.monospaceFont,\n\t\tfontWeight: Theme.monospaceWeight,\n\t} as React.CSSProperties,\n\tbutton: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tbutton2: {\n\t\twidth: '160px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tbutton3: {\n\t\twidth: '195px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmargin: '0 auto',\n\t} as React.CSSProperties,\n\tbuttonsEnd: {\n\t\tmarginBottom: '10px',\n\t} as React.CSSProperties,\n};\n\nexport default class Subscription extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tsubscription: SubscriptionStore.subscription,\n\t\t\tupdate: false,\n\t\t\tmessage: '',\n\t\t\tlicense: '',\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tSubscriptionStore.addChangeListener(this.onChange);\n\t\tif (!this.state.subscription.active) {\n\t\t\tSubscriptionActions.sync(true);\n\t\t}\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tSubscriptionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tsubscription: SubscriptionStore.subscription,\n\t\t});\n\t}\n\n\tupdate(): JSX.Element {\n\t\treturn <div>\n\t\t\t<div className=\"bp5-card bp5-elevation-2\" style={css.card}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-callout bp5-intent-success\"\n\t\t\t\t\tstyle={css.message}\n\t\t\t\t\thidden={!this.state.message}\n\t\t\t\t>\n\t\t\t\t\t{this.state.message}\n\t\t\t\t</div>\n\t\t\t\t<textarea\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.license}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tplaceholder=\"New License Key\"\n\t\t\t\t\tvalue={this.state.license}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tlicense: evt.target.value,\n\t\t\t\t\t\t});\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<div className=\"layout horizontal center-justified\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-danger bp5-icon-cross\"\n\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tupdate: false,\n\t\t\t\t\t\t\t\tlicense: '',\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>Cancel</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-primary bp5-icon-endorsed\"\n\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tdisabled: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tSubscriptionActions.activate(\n\t\t\t\t\t\t\t\tthis.state.license,\n\t\t\t\t\t\t\t).then((): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdisabled: false,\n\t\t\t\t\t\t\t\t\tupdate: false,\n\t\t\t\t\t\t\t\t\tlicense: '',\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}).catch((): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdisabled: false,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>Update License</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n\n\tactivate(): JSX.Element {\n\t\treturn <div>\n\t\t\t<div className=\"bp5-card bp5-elevation-2\" style={css.card}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-callout bp5-intent-success\"\n\t\t\t\t\tstyle={css.message}\n\t\t\t\t\thidden={!this.state.message}\n\t\t\t\t>\n\t\t\t\t\t{this.state.message}\n\t\t\t\t</div>\n\t\t\t\t<textarea\n\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\tstyle={css.license}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tplaceholder=\"License Key\"\n\t\t\t\t\tvalue={this.state.license}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\tlicense: evt.target.value,\n\t\t\t\t\t\t});\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<div className=\"layout horizontal center-justified\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-primary bp5-icon-endorsed\"\n\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tdisabled: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tSubscriptionActions.activate(\n\t\t\t\t\t\t\t\tthis.state.license,\n\t\t\t\t\t\t\t).then((): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdisabled: false,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}).catch((): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdisabled: false,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>Activate License\n\t\t\t\t\t</button>\n\t\t\t\t\t<a\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-credit-card\"\n\t\t\t\t\t\tstyle={css.button2}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\thref=\"https://app.pritunl.com/checkout/cloud\"\n\t\t\t\t\t>Create Account</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n\n\treactivate(): JSX.Element {\n\t\tlet sub = this.state.subscription;\n\t\tlet canceling = sub.cancel_at_period_end || sub.status === 'canceled';\n\t\tlet status = sub.cancel_at_period_end ? 'canceled' : sub.status;\n\t\tlet periodEnd = MiscUtils.formatDateShort(sub.period_end);\n\t\tlet trialEnd = MiscUtils.formatDateShort(sub.trial_end);\n\n\t\tlet balance: string;\n\t\tlet balanceLabel: string;\n\t\tif (sub.balance < 0) {\n\t\t\tbalance = MiscUtils.formatAmount(sub.balance * -1);\n\t\t\tbalanceLabel = 'Credit';\n\t\t} else {\n\t\t\tbalance = MiscUtils.formatAmount(sub.balance);\n\t\t\tbalanceLabel = 'Balance';\n\t\t}\n\n\t\treturn <div>\n\t\t\t<div className=\"bp5-card bp5-elevation-2\" style={css.card2}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-callout bp5-intent-success\"\n\t\t\t\t\tstyle={css.message}\n\t\t\t\t\thidden={!this.state.message}\n\t\t\t\t>\n\t\t\t\t\t{this.state.message}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"layout vertical\" style={css.status}>\n\t\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t\t<div className=\"flex\">Status:</div>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{MiscUtils.capitalize(status)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"layout horizontal\" style={css.item}>\n\t\t\t\t\t\t<div className=\"flex\">Plan:</div>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{MiscUtils.capitalize(sub.plan)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"layout horizontal\" style={css.item}>\n\t\t\t\t\t\t<div className=\"flex\">Amount:</div>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{MiscUtils.formatAmount(sub.amount)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"layout horizontal\" style={css.item}>\n\t\t\t\t\t\t<div className=\"flex\">Quantity:</div>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{sub.quantity}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal\"\n\t\t\t\t\t\tstyle={css.item}\n\t\t\t\t\t\thidden={!sub.balance}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex\">{balanceLabel}:</div>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{balance}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal\"\n\t\t\t\t\t\tstyle={css.item}\n\t\t\t\t\t\thidden={periodEnd === ''}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex\">\n\t\t\t\t\t\t\t{canceling ? 'Ends' : 'Renew'}:\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{periodEnd}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal\"\n\t\t\t\t\t\tstyle={css.item}\n\t\t\t\t\t\thidden={trialEnd === ''}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex\">Trial Ends:</div>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{trialEnd}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"layout horizontal center-justified\">\n\t\t\t\t\t<a\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-cog\"\n\t\t\t\t\t\tstyle={css.button2}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\thref={\"https://app.pritunl.com/subscription/\" +\n\t\t\t\t\t\t\tthis.state.subscription.url_key}\n\t\t\t\t\t>Manage Account</a>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"layout horizontal center-justified\"\n\t\t\t\t\tstyle={css.buttonsEnd}\n\t\t\t\t>\n\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\tstyle={css.button2}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\tlabel=\"Remove License\"\n\t\t\t\t\t\tonConfirm={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tdisabled: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tSubscriptionActions.activate('').then((): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdisabled: false,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}).catch((): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tdisabled: false,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-primary bp5-icon-endorsed\"\n\t\t\t\t\t\tstyle={css.button2}\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tupdate: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>Update License</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>;\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.state.update) {\n\t\t\treturn this.update();\n\t\t} else if (this.state.subscription.status) {\n\t\t\treturn this.reactivate();\n\t\t} else {\n\t\t\treturn this.activate();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Switch.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ntype OnToggle = () => void;\n\ninterface Props {\n\tstyle: React.CSSProperties;\n\tlabel: string;\n\tchecked: boolean;\n\tonToggle: OnToggle;\n}\n\nexport default class Switch extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <label className=\"bp5-control bp5-switch\" style={this.props.style}>\n\t\t\t<input\n\t\t\t\ttype=\"checkbox\"\n\t\t\t\tchecked={this.props.checked}\n\t\t\t\tonChange={this.props.onToggle}\n\t\t\t/>\n\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t{this.props.label}\n\t\t</label>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/SwitchNull.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ntype OnToggle = () => void;\n\ninterface Props {\n\tstyle?: React.CSSProperties;\n\tlabel: string;\n\tchecked: boolean;\n\tonToggle: OnToggle;\n}\n\nexport default class SwitchNull extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet style: React.CSSProperties = {\n\t\t\t...this.props.style,\n\t\t};\n\n\t\tif (this.props.checked === null || this.props.checked === undefined) {\n\t\t\tstyle.opacity = 0.5;\n\t\t}\n\n\t\treturn <label className=\"bp5-control bp5-switch\" style={style}>\n\t\t\t<input\n\t\t\t\ttype=\"checkbox\"\n\t\t\t\tchecked={!!this.props.checked}\n\t\t\t\tonChange={this.props.onToggle}\n\t\t\t/>\n\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t{this.props.label}\n\t\t</label>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/User.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport RouterLink from './RouterLink';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as UserTypes from '../types/UserTypes';\n\ninterface Props {\n\tuser: UserTypes.UserRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t} as React.CSSProperties,\n\tlastActivity: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tnameLink: {\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n};\n\nexport default class User extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet user = this.props.user;\n\t\tlet roles: JSX.Element[] = [];\n\n\t\tfor (let role of (user.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-intent-primary\"\n\t\t\t\t\tstyle={css.tag}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\t\tif (user.disabled) {\n\t\t\tcardStyle.opacity = 0.6;\n\t\t}\n\n\t\tlet userType: string;\n\t\tswitch (user.type) {\n\t\t\tcase 'local':\n\t\t\t\tuserType = 'Local';\n\t\t\t\tbreak;\n\t\t\tcase 'google':\n\t\t\t\tuserType = 'Google';\n\t\t\t\tbreak;\n\t\t\tcase 'onelogin':\n\t\t\t\tuserType = 'OneLogin';\n\t\t\t\tbreak;\n\t\t\tcase 'okta':\n\t\t\t\tuserType = 'Okta';\n\t\t\t\tbreak;\n\t\t\tcase 'authzero':\n\t\t\t\tuserType = 'Auth0';\n\t\t\t\tbreak;\n\t\t\tcase 'azure':\n\t\t\t\tuserType = 'Azure';\n\t\t\t\tbreak;\n\t\t\tcase 'jumpcloud':\n\t\t\t\tuserType = 'JumpCloud';\n\t\t\t\tbreak;\n\t\t\tcase 'api':\n\t\t\t\tuserType = 'API';\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tuserType = user.type;\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label className=\"bp5-control bp5-checkbox\" style={css.select}>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<RouterLink to={'/user/' + user.id} style={css.nameLink}>\n\t\t\t\t\t\t{user.username}\n\t\t\t\t\t</RouterLink>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.type}>\n\t\t\t\t{userType}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.lastActivity}>\n\t\t\t\t{MiscUtils.formatDateShortTime(user.last_active) || 'Inactive'}\n\t\t\t</div>\n\t\t\t<div className=\"flex bp5-cell\" style={css.roles}>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-intent-danger\"\n\t\t\t\t\tstyle={css.tag}\n\t\t\t\t\thidden={!user.administrator}\n\t\t\t\t>\n\t\t\t\t\tadmin\n\t\t\t\t</div>\n\t\t\t\t{roles}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/UserDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as UserActions from '../actions/UserActions';\nimport * as UserTypes from '../types/UserTypes';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport UserStore from '../stores/UserStore';\nimport Sessions from './Sessions';\nimport Devices from './Devices';\nimport Audits from './Audits';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport PagePanel from './PagePanel';\nimport PageSplit from './PageSplit';\nimport PageInfo from './PageInfo';\nimport PageInput from './PageInput';\nimport PageInputButton from './PageInputButton';\nimport PageSwitch from './PageSwitch';\nimport PageSelect from './PageSelect';\nimport PageDateTime from './PageDateTime';\nimport PageSave from './PageSave';\nimport PageNew from './PageNew';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tuserId?: string;\n}\n\ninterface State {\n\tchanged: boolean;\n\tdisabled: boolean;\n\tlocked: boolean;\n\tmessage: string;\n\taddRole: string;\n\tuser: UserTypes.User;\n}\n\nconst css = {\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '10px 0 0 0',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class UserDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tchanged: false,\n\t\t\tdisabled: false,\n\t\t\tlocked: false,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tuser: UserStore.userM,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tUserStore.addChangeListener(this.onChange);\n\t\tUserActions.load(this.props.userId);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tUserStore.removeChangeListener(this.onChange);\n\t\tUserActions.unload();\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tuser: UserStore.userM,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tUserActions.commit(this.state.user).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonNew = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tUserActions.create(this.state.user).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'User has been created',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t\tlocked: true,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tset = (name: string, val: any): void => {\n\t\tlet user: any = {\n\t\t\t...this.state.user,\n\t\t};\n\n\t\tuser[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tuser: user,\n\t\t});\n\t}\n\n\tonAddRole = (): void => {\n\t\tlet roles = [\n\t\t\t...this.state.user.roles,\n\t\t];\n\n\t\tif (!this.state.addRole) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (roles.indexOf(this.state.addRole) === -1) {\n\t\t\troles.push(this.state.addRole);\n\t\t}\n\n\t\troles.sort();\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tuser: {\n\t\t\t\t...this.state.user,\n\t\t\t\troles: roles,\n\t\t\t},\n\t\t});\n\t}\n\n\tonRemoveRole = (role: string): void => {\n\t\tlet roles = [\n\t\t\t...this.state.user.roles,\n\t\t];\n\n\t\tlet i = roles.indexOf(role);\n\t\tif (i === -1) {\n\t\t\treturn;\n\t\t}\n\n\t\troles.splice(i, 1);\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\taddRole: '',\n\t\t\tuser: {\n\t\t\t\t...this.state.user,\n\t\t\t\troles: roles,\n\t\t\t},\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tUserActions.remove([this.props.userId]).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'User has been deleted',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t\tlocked: true,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet userId = this.props.userId;\n\t\tlet user = this.state.user;\n\t\tif (!user) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet roles: JSX.Element[] = [];\n\t\tfor (let role of (user.roles || [])) {\n\t\t\troles.push(\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bp5-tag bp5-tag-removable bp5-intent-primary\"\n\t\t\t\t\tstyle={css.role}\n\t\t\t\t\tkey={role}\n\t\t\t\t>\n\t\t\t\t\t{role}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"bp5-tag-remove\"\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\tonMouseUp={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRole(role);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>,\n\t\t\t);\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>{userId ? 'User Info' : 'New User'}</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete User\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this user\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[user.username]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<PageSplit>\n\t\t\t\t<PagePanel className=\"layout vertical\">\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\tlabel=\"Username\"\n\t\t\t\t\t\thelp=\"Username, if using single sign-on username must match\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter username\"\n\t\t\t\t\t\tvalue={user.username}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('username', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"User comment.\"\n\t\t\t\t\t\tplaceholder=\"User comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={user.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\thidden={user.type !== 'local'}\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\tlabel=\"Password\"\n\t\t\t\t\t\thelp=\"Password, leave blank to keep current password\"\n\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\tplaceholder=\"Change password\"\n\t\t\t\t\t\tvalue={user.password}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('password', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\thidden={user.type !== 'api'}\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\treadOnly={true}\n\t\t\t\t\t\tautoSelect={true}\n\t\t\t\t\t\tlabel=\"Token\"\n\t\t\t\t\t\thelp=\"API token\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Save to generate token\"\n\t\t\t\t\t\tvalue={user.token}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\thidden={user.type !== 'api' || !user.token || !user.secret}\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\treadOnly={true}\n\t\t\t\t\t\tautoSelect={true}\n\t\t\t\t\t\tlabel=\"Secret\"\n\t\t\t\t\t\thelp=\"API secret, will only be shown once\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"\"\n\t\t\t\t\t\tvalue={user.secret}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\thidden={user.type !== 'api' || !user.token || !!user.secret}\n\t\t\t\t\t\tlabel=\"Generate new token and secret\"\n\t\t\t\t\t\thelp=\"Enable to generate a new token and secret on save. Secret can only be shown by generating new credentials.\"\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\tchecked={user.generate_secret}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('generate_secret', !user.generate_secret);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSelect\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\thelp=\"A local user is a user that is created on the Pritunl Cloud database that has a username and password. The other user types can be used to create users for single sign-on services. Generally single sign-on users will be created automatically when the user authenticates for the first time. It can sometimes be desired to manaully create a single sign-on user to provide roles in advanced of the first login.\"\n\t\t\t\t\t\tvalue={user.type}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('type', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"local\">Local</option>\n\t\t\t\t\t\t<option value=\"authzero\">Auth0</option>\n\t\t\t\t\t\t<option value=\"azure\">Azure</option>\n\t\t\t\t\t\t<option value=\"google\">Google</option>\n\t\t\t\t\t\t<option value=\"onelogin\">OneLogin</option>\n\t\t\t\t\t\t<option value=\"okta\">Okta</option>\n\t\t\t\t\t\t<option value=\"jumpcloud\">JumpCloud</option>\n\t\t\t\t\t\t<option value=\"api\">API</option>\n\t\t\t\t\t</PageSelect>\n\t\t\t\t\t<label className=\"bp5-label\">\n\t\t\t\t\t\tRoles\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Roles\"\n\t\t\t\t\t\t\tcontent=\"User roles will be used to match with service roles. A user must have a matching role to access a service.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{roles}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</label>\n\t\t\t\t\t<PageInputButton\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\tbuttonClass=\"bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\tlabel=\"Add\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Add role\"\n\t\t\t\t\t\tvalue={this.state.addRole}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\taddRole: val,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={this.onAddRole}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Administrator\"\n\t\t\t\t\t\thelp=\"Enable to give user administrator access to the management console\"\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\tchecked={user.administrator === 'super'}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tif (this.state.user.administrator === 'super') {\n\t\t\t\t\t\t\t\tthis.set('administrator', '');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.set('administrator', 'super');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tlabel=\"Disabled\"\n\t\t\t\t\t\thelp=\"Disables the user ending all active sessions and prevents new authentications\"\n\t\t\t\t\t\tdisabled={this.state.locked}\n\t\t\t\t\t\tchecked={user.disabled}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('disabled', !this.state.user.disabled);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</PagePanel>\n\t\t\t\t<PagePanel>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: user.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Last Active',\n\t\t\t\t\t\t\t\tvalue: MiscUtils.formatDate(user.last_active) || 'Inactive',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageDateTime\n\t\t\t\t\t\tlabel=\"Active Until\"\n\t\t\t\t\t\thelp=\"Set this to schedule the user to be disabled at the set date and time. This is useful to give a user temporary access to a service.\"\n\t\t\t\t\t\tvalue={user.active_until}\n\t\t\t\t\t\tdisabled={user.disabled || this.state.locked}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('active_until', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</PagePanel>\n\t\t\t</PageSplit>\n\t\t\t{userId ? <PageSave\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled || this.state.locked}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tmessage: 'Your changes have been discarded',\n\t\t\t\t\t\taddRole: '',\n\t\t\t\t\t\tuser: UserStore.userM,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/> : <PageNew\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled || this.state.locked}\n\t\t\t\tonSave={this.onNew}\n\t\t\t/>}\n\t\t\t{this.state.locked ? null : <Sessions userId={userId}/>}\n\t\t\t{this.state.locked ? null : <Devices userId={userId}/>}\n\t\t\t{this.state.locked ? null : <Audits userId={userId}/>}\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Users.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport RouterLink from './RouterLink';\nimport * as UserTypes from '../types/UserTypes';\nimport UsersStore from '../stores/UsersStore';\nimport * as UserActions from '../actions/UserActions';\nimport * as AuditActions from '../actions/AuditActions';\nimport User from './User';\nimport UsersFilter from './UsersFilter';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport UsersPage from './UsersPage';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tusers: UserTypes.UsersRo;\n\tfilter: UserTypes.Filter;\n\tselected: Selected;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\tusers: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\tusersBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Users extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tusers: UsersStore.users,\n\t\t\tfilter: UsersStore.filter,\n\t\t\tselected: {},\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\tfor (let key in this.state.selected) {\n\t\t\tif (this.state.selected[key]) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tUsersStore.addChangeListener(this.onChange);\n\t\tAuditActions.traverse(0);\n\t\tUserActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tUsersStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet users = UsersStore.users;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\n\t\tusers.forEach((user: UserTypes.User): void => {\n\t\t\tif (curSelected[user.id]) {\n\t\t\t\tselected[user.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tusers: users,\n\t\t\tfilter: UsersStore.filter,\n\t\t\tselected: selected,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tUserActions.remove(Object.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet usersDom: JSX.Element[] = [];\n\n\t\tthis.state.users.forEach((user: UserTypes.UserRo): void => {\n\t\t\tusersDom.push(<User\n\t\t\t\tkey={user.id}\n\t\t\t\tuser={user}\n\t\t\t\tselected={!!this.state.selected[user.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet users = this.state.users;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < users.length; i++) {\n\t\t\t\t\t\t\tlet usr = users[i];\n\n\t\t\t\t\t\t\tif (usr.id === user.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[users[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: user.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[user.id]) {\n\t\t\t\t\t\tdelete selected[user.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[user.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: user.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let userId of Object.keys(this.state.selected)) {\n\t\t\tlet user = UsersStore.user(userId);\n\t\t\tif (user) {\n\t\t\t\tselectedNames.push(user.username || userId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(userId);\n\t\t\t}\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Users</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tUserActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tUserActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected users\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<RouterLink\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tto=\"/user\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tNew\n\t\t\t\t\t\t</RouterLink>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<UsersFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tUserActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.usersBox}>\n\t\t\t\t<div style={css.users}>\n\t\t\t\t\t{usersDom}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<UsersPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/UsersFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as UserTypes from '../types/UserTypes';\nimport SearchInput from './SearchInput';\nimport SwitchNull from './SwitchNull';\n\ninterface Props {\n\tfilter: UserTypes.Filter;\n\tonFilter: (filter: UserTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class UsersFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"User ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Username\"\n\t\t\t\tvalue={this.props.filter.username}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.username = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.username;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.role}\n\t\t\t\tplaceholder=\"Role\"\n\t\t\t\tvalue={this.props.filter.role}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.role = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.role;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.type || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.type;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.type = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<option value=\"any\">Any</option>\n\t\t\t\t\t<option value=\"local\">Local</option>\n\t\t\t\t\t<option value=\"azure\">Azure</option>\n\t\t\t\t\t<option value=\"google\">Google</option>\n\t\t\t\t\t<option value=\"onelogin\">OneLogin</option>\n\t\t\t\t\t<option value=\"okta\">Okta</option>\n\t\t\t\t\t<option value=\"api\">API</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<SwitchNull\n\t\t\t\tstyle={css.check}\n\t\t\t\tlabel=\"Administrator\"\n\t\t\t\tchecked={this.props.filter.administrator}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (filter.administrator === undefined) {\n\t\t\t\t\t\tfilter.administrator = true;\n\t\t\t\t\t} else if (filter.administrator === true) {\n\t\t\t\t\t\tfilter.administrator = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.administrator;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SwitchNull\n\t\t\t\tstyle={css.check}\n\t\t\t\tlabel=\"Disabled\"\n\t\t\t\tchecked={this.props.filter.disabled}\n\t\t\t\tonToggle={(): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (filter.disabled === undefined) {\n\t\t\t\t\t\tfilter.disabled = true;\n\t\t\t\t\t} else if (filter.disabled === true) {\n\t\t\t\t\t\tfilter.disabled = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.disabled;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/UsersPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport UsersStore from '../stores/UsersStore';\nimport * as UserActions from '../actions/UserActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class UsersPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: UsersStore.page,\n\t\t\tpageCount: UsersStore.pageCount,\n\t\t\tpages: UsersStore.pages,\n\t\t\tcount: UsersStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tUsersStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tUsersStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: UsersStore.page,\n\t\t\tpageCount: UsersStore.pageCount,\n\t\t\tpages: UsersStore.pages,\n\t\t\tcount: UsersStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tUserActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tUserActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tUserActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tUserActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tUserActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Vpc.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport CompletionStore from '../stores/CompletionStore';\nimport VpcDetailed from './VpcDetailed';\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tvpc: VpcTypes.VpcRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Vpc extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet vpc = this.props.vpc;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<VpcDetailed\n\t\t\t\t\torganizations={this.props.organizations}\n\t\t\t\t\tvpc={this.props.vpc}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet orgName = '';\n\t\tif (vpc.organization) {\n\t\t\tlet org = CompletionStore.organization(vpc.organization);\n\t\t\torgName = org ? org.name : vpc.organization;\n\t\t} else {\n\t\t\torgName = 'Unknown';\n\t\t}\n\n\t\tlet datacenter = CompletionStore.datacenter(vpc.datacenter);\n\t\tlet datacenterName = datacenter ? datacenter.name : vpc.datacenter;\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{vpc.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName={'bp5-icon-standard bp5-text-muted ' + (vpc.organization ?\n\t\t\t\t\t\t'bp5-icon-people' : 'bp5-icon-layers')}\n\t\t\t\t/>\n\t\t\t\t{orgName}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-cloud\"\n\t\t\t\t/>\n\t\t\t\t{datacenterName}\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-layout-auto\"\n\t\t\t\t/>\n\t\t\t\t{vpc.network}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/VpcArp.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as VpcTypes from '../types/VpcTypes';\n\ninterface Props {\n\tdisabled?: boolean;\n\tarp: VpcTypes.Arp;\n\tonChange?: (state: VpcTypes.Arp) => void;\n\tonAdd: (prepend: boolean) => void;\n\tonRemove?: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class VpcArp extends React.Component<Props, {}> {\n\tclone(): VpcTypes.Arp {\n\t\treturn {\n\t\t\t...this.props.arp,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet arp = this.props.arp;\n\n\t\treturn <div>\n\t\t\t<div className=\"bp5-control-group\" style={css.group}>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled} // Constants.user\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"IP Address\"\n\t\t\t\t\t\tvalue={arp.ip || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.ip = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"Mac Address\"\n\t\t\t\t\t\tvalue={arp.mac || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.mac = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\tthis.props.onAdd(evt.shiftKey);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/VpcDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as VpcActions from '../actions/VpcActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as PageInfos from './PageInfo';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport VpcRoute from './VpcRoute';\nimport VpcMap from './VpcMap';\nimport VpcArp from './VpcArp';\nimport VpcSubnet from './VpcSubnet';\nimport PageInput from './PageInput';\nimport PageSwitch from './PageSwitch';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tvpc: VpcTypes.VpcRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddNetworkRole: string;\n\taddVpc: string;\n\tvpc: VpcTypes.Vpc;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tlist: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class VpcDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: null,\n\t\t\taddVpc: null,\n\t\t\tvpc: null,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet vpc: any;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tvpc[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonAddSubnet = (i: number, prepend: boolean): void => {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet subnets = [\n\t\t\t...(vpc.subnets || []),\n\t\t];\n\n\t\tif (subnets.length === 0) {\n\t\t\tsubnets = [{}];\n\t\t}\n\n\t\tsubnets.splice(prepend ? i : i + 1, 0, {} as VpcTypes.Subnet);\n\t\tvpc.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonChangeSubnet(i: number, subnet: VpcTypes.Subnet): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet subnets = [\n\t\t\t...(vpc.subnets || []),\n\t\t];\n\n\t\tif (subnets.length === 0) {\n\t\t\tsubnets = [{}];\n\t\t}\n\n\t\tsubnets[i] = subnet;\n\n\t\tvpc.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonRemoveSubnet(i: number): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet subnets = [\n\t\t\t...(vpc.subnets || []),\n\t\t];\n\n\t\tif (subnets.length !== 0) {\n\t\t\tsubnets.splice(i, 1);\n\t\t}\n\n\t\tif (subnets.length === 0) {\n\t\t\tsubnets = [{}];\n\t\t}\n\n\t\tvpc.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonAddRoute = (i: number, prepend: boolean): void => {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet routes = [\n\t\t\t...(vpc.routes || []),\n\t\t];\n\n\t\troutes.splice(prepend ? i : i + 1, 0, {} as VpcTypes.Route);\n\t\tvpc.routes = routes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonChangeRoute(i: number, route: VpcTypes.Route): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet routes = [\n\t\t\t...vpc.routes,\n\t\t];\n\n\t\troutes[i] = route;\n\n\t\tvpc.routes = routes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonRemoveRoute(i: number): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet routes = [\n\t\t\t...vpc.routes,\n\t\t];\n\n\t\troutes.splice(i, 1);\n\n\t\tvpc.routes = routes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonAddMap = (i: number, prepend: boolean): void => {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet maps = [\n\t\t\t...(vpc.maps || []),\n\t\t];\n\n\t\tif (maps.length === 0) {\n\t\t\tmaps = [{}];\n\t\t}\n\n\t\tmaps.splice(prepend ? i : i + 1, 0, {} as VpcTypes.Map);\n\t\tvpc.maps = maps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonChangeMap(i: number, map: VpcTypes.Map): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet maps = [\n\t\t\t...(vpc.maps || []),\n\t\t];\n\n\t\tif (maps.length === 0) {\n\t\t\tmaps = [{}];\n\t\t}\n\n\t\tmaps[i] = map;\n\n\t\tvpc.maps = maps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonRemoveMap(i: number): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet maps = [\n\t\t\t...(vpc.maps || []),\n\t\t];\n\n\t\tif (maps.length !== 0) {\n\t\t\tmaps.splice(i, 1);\n\t\t}\n\n\t\tif (maps.length === 0) {\n\t\t\tmaps = [{}];\n\t\t}\n\n\t\tvpc.maps = maps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonAddArp = (i: number, prepend: boolean): void => {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet arps = [\n\t\t\t...(vpc.arps || []),\n\t\t];\n\n\t\tif (arps.length === 0) {\n\t\t\tarps = [{}];\n\t\t}\n\n\t\tarps.splice(prepend ? i : i + 1, 0, {} as VpcTypes.Arp);\n\t\tvpc.arps = arps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonChangeArp(i: number, arp: VpcTypes.Arp): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet arps = [\n\t\t\t...(vpc.arps || []),\n\t\t];\n\n\t\tif (arps.length === 0) {\n\t\t\tarps = [{}];\n\t\t}\n\n\t\tarps[i] = arp;\n\n\t\tvpc.arps = arps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonRemoveArp(i: number): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tif (this.state.changed) {\n\t\t\tvpc = {\n\t\t\t\t...this.state.vpc,\n\t\t\t};\n\t\t} else {\n\t\t\tvpc = {\n\t\t\t\t...this.props.vpc,\n\t\t\t};\n\t\t}\n\n\t\tlet arps = [\n\t\t\t...(vpc.arps || []),\n\t\t];\n\n\t\tif (arps.length !== 0) {\n\t\t\tarps.splice(i, 1);\n\t\t}\n\n\t\tif (arps.length === 0) {\n\t\t\tarps = [{}];\n\t\t}\n\n\t\tvpc.arps = arps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tVpcActions.commit(this.state.vpc).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tvpc: null,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tVpcActions.remove(this.props.vpc.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet vpc: VpcTypes.Vpc = this.state.vpc ||\n\t\t\tthis.props.vpc;\n\n\t\tlet datacenter = CompletionStore.datacenter(vpc.datacenter);\n\t\tlet datacenterName = datacenter ? datacenter.name : vpc.datacenter;\n\t\tlet org = CompletionStore.organization(this.props.vpc.organization);\n\n\t\tlet subnets = (vpc.subnets || []);\n\t\tif (subnets.length === 0) {\n\t\t\tsubnets.push({});\n\t\t}\n\n\t\tlet subnetsElem: JSX.Element[] = [];\n\t\tfor (let i = 0; i < subnets.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tsubnetsElem.push(\n\t\t\t\t<VpcSubnet\n\t\t\t\t\tkey={index}\n\t\t\t\t\tsubnet={subnets[index]}\n\t\t\t\t\tonChange={(state: VpcTypes.Subnet): void => {\n\t\t\t\t\t\tthis.onChangeSubnet(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddSubnet(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveSubnet(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet routes: JSX.Element[] = [\n\t\t\t<VpcRoute\n\t\t\t\tdisabled={true}\n\t\t\t\tkey={-1}\n\t\t\t\troute={{\n\t\t\t\t\tdestination: '0.0.0.0/0',\n\t\t\t\t\ttarget: '0.0.0.0',\n\t\t\t\t} as VpcTypes.Route}\n\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\tthis.onAddRoute(-1, prepend);\n\t\t\t\t}}\n\t\t\t/>,\n\t\t];\n\t\tif (vpc.routes) {\n\t\t\tfor (let i = 0; i < (vpc.routes || []).length; i++) {\n\t\t\t\tlet index = i;\n\n\t\t\t\troutes.push(\n\t\t\t\t\t<VpcRoute\n\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\troute={vpc.routes[index]}\n\t\t\t\t\t\tonChange={(state: VpcTypes.Route): void => {\n\t\t\t\t\t\t\tthis.onChangeRoute(index, state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\t\tthis.onAddRoute(index, prepend);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRoute(index);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet maps = (vpc.maps || []);\n\t\tif (maps.length === 0) {\n\t\t\tmaps.push({});\n\t\t}\n\n\t\tlet mapsElem: JSX.Element[] = [];\n\t\tfor (let i = 0; i < maps.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tmapsElem.push(\n\t\t\t\t<VpcMap\n\t\t\t\t\tkey={index}\n\t\t\t\t\tmap={maps[index]}\n\t\t\t\t\tonChange={(state: VpcTypes.Map): void => {\n\t\t\t\t\t\tthis.onChangeMap(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddMap(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveMap(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet arps = (vpc.arps || []);\n\t\tif (arps.length === 0) {\n\t\t\tarps.push({});\n\t\t}\n\n\t\tlet arpsElem: JSX.Element[] = [];\n\t\tfor (let i = 0; i < arps.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tarpsElem.push(\n\t\t\t\t<VpcArp\n\t\t\t\t\tkey={index}\n\t\t\t\t\tarp={arps[index]}\n\t\t\t\t\tonChange={(state: VpcTypes.Arp): void => {\n\t\t\t\t\t\tthis.onChangeArp(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddArp(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveArp(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet fields: PageInfos.Field[] = [\n\t\t\t{\n\t\t\t\tlabel: 'ID',\n\t\t\t\tvalue: this.props.vpc.id || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Datacenter',\n\t\t\t\tvalue: datacenterName,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Organization',\n\t\t\t\tvalue: org ? org.name : this.props.vpc.organization,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'VLAN Number',\n\t\t\t\tvalue: this.props.vpc.vpc_id || 'Unknown',\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Private IPv6 Network',\n\t\t\t\tvalue: this.props.vpc.network6 || 'Unknown',\n\t\t\t\tcopy: true,\n\t\t\t},\n\t\t];\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={4}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"vpc\" id={this.props.vpc.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete VPC\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this VPC\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[vpc.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of vpc\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={vpc.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"VPC comment.\"\n\t\t\t\t\t\tplaceholder=\"VPC comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={vpc.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Network\"\n\t\t\t\t\t\thelp=\"Network address of vpc with cidr.\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter network\"\n\t\t\t\t\t\tvalue={vpc.network}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('network', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\tSubnets\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Subnets\"\n\t\t\t\t\t\t\tcontent=\"Subnets in VPC, can only be added or removed. Once added a subnet network block cannot be modified.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.list}>\n\t\t\t\t\t\t{subnetsElem}\n\t\t\t\t\t</div>\n\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\tNetwork Maps\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Network Maps\"\n\t\t\t\t\t\t\tcontent=\"Map destination network CIDR to new target IP.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.list}>\n\t\t\t\t\t\t{mapsElem}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={fields}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\tlabel=\"ICMP Redirects\"\n\t\t\t\t\t\thelp=\"Enable or disable ICMP redirects for VPC routing table. ICMP redirects will improve the routing path of static routes in the VPC routing table but will be cached by the instance for 5 minutes unless adjusted on the system. If dynamic updates to the VPC routing table are made such as with failover site-to-site systems redirects should be disabled to allow fast failover to the new route. ICMP redirects are not recommended for most configurations.\"\n\t\t\t\t\t\tchecked={vpc.icmp_redirects}\n\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\tthis.set('icmp_redirects', !vpc.icmp_redirects);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\tRoute Table\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Route Table\"\n\t\t\t\t\t\t\tcontent=\"VPC routing table, enter a CIDR network for the desitnation and IP address for taget.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.list}>\n\t\t\t\t\t\t{routes}\n\t\t\t\t\t</div>\n\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\tCustom ARP\n\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\ttitle=\"Custom ARP\"\n\t\t\t\t\t\t\tcontent=\"Custom ARP entries for external resources on VPC VLAN.\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.list}>\n\t\t\t\t\t\t{arpsElem}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.vpc && !this.state.message}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tvpc: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/VpcLinkUri.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\n\ninterface Props {\n\tdisabled?: boolean;\n\tlinkUri: string;\n\tonChange?: (state: string) => void;\n\tonAdd: () => void;\n\tonRemove?: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class VpcUriLink extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\treturn <div>\n\t\t\t<div className=\"bp5-control-group\" style={css.group}>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"Link URI\"\n\t\t\t\t\t\tvalue={this.props.linkUri || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tthis.props.onChange(evt.target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onAdd();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/VpcMap.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as VpcTypes from '../types/VpcTypes';\n\ninterface Props {\n\tdisabled?: boolean;\n\tmap: VpcTypes.Map;\n\tonChange?: (state: VpcTypes.Map) => void;\n\tonAdd: (prepend: boolean) => void;\n\tonRemove?: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class VpcMap extends React.Component<Props, {}> {\n\tclone(): VpcTypes.Map {\n\t\treturn {\n\t\t\t...this.props.map,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet map = this.props.map;\n\n\t\treturn <div>\n\t\t\t<div className=\"bp5-control-group\" style={css.group}>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"Destination\"\n\t\t\t\t\t\tvalue={map.destination || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.destination = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"Target\"\n\t\t\t\t\t\tvalue={map.target || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.target = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\tthis.props.onAdd(evt.shiftKey);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/VpcNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as VpcActions from '../actions/VpcActions';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as Constants from \"../Constants\";\nimport * as PageInfos from './PageInfo';\nimport DatacentersStore from \"../stores/DatacentersStore\";\nimport OrganizationsStore from \"../stores/OrganizationsStore\";\nimport VpcRoute from './VpcRoute';\nimport VpcMap from './VpcMap';\nimport VpcArp from './VpcArp';\nimport VpcSubnet from './VpcSubnet';\nimport PageInput from './PageInput';\nimport PageSwitch from './PageSwitch';\nimport PageSelect from './PageSelect';\nimport PageInfo from './PageInfo';\nimport PageCreate from './PageCreate';\nimport ConfirmButton from './ConfirmButton';\nimport Help from './Help';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\taddNetworkRole: string;\n\taddVpc: string;\n\tvpc: VpcTypes.Vpc;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tstatus: {\n\t\tmargin: '6px 0 0 1px',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n\trole: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\tlist: {\n\t\tmarginBottom: '15px',\n\t} as React.CSSProperties,\n};\n\nexport default class VpcNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddNetworkRole: null,\n\t\t\taddVpc: null,\n\t\t\tvpc: {\n\t\t\t\tname: \"new-vpc\",\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet vpc: any = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tvpc[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonAddSubnet = (i: number, prepend: boolean): void => {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet subnets = [\n\t\t\t...(vpc.subnets || []),\n\t\t];\n\n\t\tif (subnets.length === 0) {\n\t\t\tsubnets = [{}];\n\t\t}\n\n\t\tsubnets.splice(prepend ? i : i + 1, 0, {} as VpcTypes.Subnet);\n\t\tvpc.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonChangeSubnet(i: number, subnet: VpcTypes.Subnet): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet subnets = [\n\t\t\t...(vpc.subnets || []),\n\t\t];\n\n\t\tif (subnets.length === 0) {\n\t\t\tsubnets = [{}];\n\t\t}\n\n\t\tsubnets[i] = subnet;\n\n\t\tvpc.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonRemoveSubnet(i: number): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet subnets = [\n\t\t\t...(vpc.subnets || []),\n\t\t];\n\n\t\tif (subnets.length !== 0) {\n\t\t\tsubnets.splice(i, 1);\n\t\t}\n\n\t\tif (subnets.length === 0) {\n\t\t\tsubnets = [{}];\n\t\t}\n\n\t\tvpc.subnets = subnets;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonAddRoute = (i: number, prepend: boolean): void => {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet routes = [\n\t\t\t...(vpc.routes || []),\n\t\t];\n\n\t\troutes.splice(prepend ? i : i + 1, 0, {} as VpcTypes.Route);\n\t\tvpc.routes = routes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonChangeRoute(i: number, route: VpcTypes.Route): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet routes = [\n\t\t\t...vpc.routes,\n\t\t];\n\n\t\troutes[i] = route;\n\n\t\tvpc.routes = routes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonRemoveRoute(i: number): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet routes = [\n\t\t\t...vpc.routes,\n\t\t];\n\n\t\troutes.splice(i, 1);\n\n\t\tvpc.routes = routes;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonAddMap = (i: number, prepend: boolean): void => {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet maps = [\n\t\t\t...(vpc.maps || []),\n\t\t];\n\n\t\tif (maps.length === 0) {\n\t\t\tmaps = [{}];\n\t\t}\n\n\t\tmaps.splice(prepend ? i : i + 1, 0, {} as VpcTypes.Map);\n\t\tvpc.maps = maps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonChangeMap(i: number, map: VpcTypes.Map): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet maps = [\n\t\t\t...(vpc.maps || []),\n\t\t];\n\n\t\tif (maps.length === 0) {\n\t\t\tmaps = [{}];\n\t\t}\n\n\t\tmaps[i] = map;\n\n\t\tvpc.maps = maps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonRemoveMap(i: number): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet maps = [\n\t\t\t...(vpc.maps || []),\n\t\t];\n\n\t\tif (maps.length !== 0) {\n\t\t\tmaps.splice(i, 1);\n\t\t}\n\n\t\tif (maps.length === 0) {\n\t\t\tmaps = [{}];\n\t\t}\n\n\t\tvpc.maps = maps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonAddArp = (i: number, prepend: boolean): void => {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet arps = [\n\t\t\t...(vpc.arps || []),\n\t\t];\n\n\t\tif (arps.length === 0) {\n\t\t\tarps = [{}];\n\t\t}\n\n\t\tarps.splice(prepend ? i : i + 1, 0, {} as VpcTypes.Arp);\n\t\tvpc.arps = arps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonChangeArp(i: number, arp: VpcTypes.Arp): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet arps = [\n\t\t\t...(vpc.arps || []),\n\t\t];\n\n\t\tif (arps.length === 0) {\n\t\t\tarps = [{}];\n\t\t}\n\n\t\tarps[i] = arp;\n\n\t\tvpc.arps = arps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonRemoveArp(i: number): void {\n\t\tlet vpc: VpcTypes.Vpc;\n\n\t\tvpc = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tlet arps = [\n\t\t\t...(vpc.arps || []),\n\t\t];\n\n\t\tif (arps.length !== 0) {\n\t\t\tarps.splice(i, 1);\n\t\t}\n\n\t\tif (arps.length === 0) {\n\t\t\tarps = [{}];\n\t\t}\n\n\t\tvpc.arps = arps;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tmessage: '',\n\t\t\tvpc: vpc,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet vpc: any = {\n\t\t\t...this.state.vpc,\n\t\t};\n\n\t\tif (this.props.organizations.length && !vpc.organization) {\n\t\t\tvpc.organization = this.props.organizations[0].id;\n\t\t}\n\n\t\tif (this.props.datacenters.length && !vpc.datacenter) {\n\t\t\tvpc.datacenter = this.props.datacenters[0].id;\n\t\t}\n\n\t\tVpcActions.create(vpc).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'VPC created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet vpc: VpcTypes.Vpc = this.state.vpc;\n\n\t\tlet hasOrganizations = false\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\thasOrganizations = true\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasOrganizations) {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Organizations</option>);\n\t\t}\n\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters.length) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Datacenter</option>);\n\n\t\t\thasDatacenters = true;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\tlet subnets = (vpc.subnets || []);\n\t\tif (subnets.length === 0) {\n\t\t\tsubnets.push({});\n\t\t}\n\n\t\tlet subnetsElem: JSX.Element[] = [];\n\t\tfor (let i = 0; i < subnets.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tsubnetsElem.push(\n\t\t\t\t<VpcSubnet\n\t\t\t\t\tkey={index}\n\t\t\t\t\tsubnet={subnets[index]}\n\t\t\t\t\tonChange={(state: VpcTypes.Subnet): void => {\n\t\t\t\t\t\tthis.onChangeSubnet(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddSubnet(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveSubnet(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet routes: JSX.Element[] = [\n\t\t\t<VpcRoute\n\t\t\t\tdisabled={true}\n\t\t\t\tkey={-1}\n\t\t\t\troute={{\n\t\t\t\t\tdestination: '0.0.0.0/0',\n\t\t\t\t\ttarget: '0.0.0.0',\n\t\t\t\t} as VpcTypes.Route}\n\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\tthis.onAddRoute(-1, prepend);\n\t\t\t\t}}\n\t\t\t/>,\n\t\t];\n\t\tif (vpc.routes) {\n\t\t\tfor (let i = 0; i < (vpc.routes || []).length; i++) {\n\t\t\t\tlet index = i;\n\n\t\t\t\troutes.push(\n\t\t\t\t\t<VpcRoute\n\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\troute={vpc.routes[index]}\n\t\t\t\t\t\tonChange={(state: VpcTypes.Route): void => {\n\t\t\t\t\t\t\tthis.onChangeRoute(index, state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\t\tthis.onAddRoute(index, prepend);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\t\tthis.onRemoveRoute(index);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet maps = (vpc.maps || []);\n\t\tif (maps.length === 0) {\n\t\t\tmaps.push({});\n\t\t}\n\n\t\tlet mapsElem: JSX.Element[] = [];\n\t\tfor (let i = 0; i < maps.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tmapsElem.push(\n\t\t\t\t<VpcMap\n\t\t\t\t\tkey={index}\n\t\t\t\t\tmap={maps[index]}\n\t\t\t\t\tonChange={(state: VpcTypes.Map): void => {\n\t\t\t\t\t\tthis.onChangeMap(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddMap(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveMap(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\tlet arps = (vpc.arps || []);\n\t\tif (arps.length === 0) {\n\t\t\tarps.push({});\n\t\t}\n\n\t\tlet arpsElem: JSX.Element[] = [];\n\t\tfor (let i = 0; i < arps.length; i++) {\n\t\t\tlet index = i;\n\n\t\t\tarpsElem.push(\n\t\t\t\t<VpcArp\n\t\t\t\t\tkey={index}\n\t\t\t\t\tarp={arps[index]}\n\t\t\t\t\tonChange={(state: VpcTypes.Arp): void => {\n\t\t\t\t\t\tthis.onChangeArp(index, state);\n\t\t\t\t\t}}\n\t\t\t\t\tonAdd={(prepend: boolean): void => {\n\t\t\t\t\t\tthis.onAddArp(index, prepend);\n\t\t\t\t\t}}\n\t\t\t\t\tonRemove={(): void => {\n\t\t\t\t\t\tthis.onRemoveArp(index);\n\t\t\t\t\t}}\n\t\t\t\t/>,\n\t\t\t);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={4}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of vpc\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={vpc.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"VPC comment.\"\n\t\t\t\t\t\t\tplaceholder=\"VPC comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={vpc.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Network\"\n\t\t\t\t\t\t\thelp=\"Network address of vpc with cidr.\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter network\"\n\t\t\t\t\t\t\tvalue={vpc.network}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('network', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\t\tSubnets\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Subnets\"\n\t\t\t\t\t\t\t\tcontent=\"Subnets in VPC, can only be added or removed. Once added a subnet network block cannot be modified.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<div style={css.list}>\n\t\t\t\t\t\t\t{subnetsElem}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\t\tNetwork Maps\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Network Maps\"\n\t\t\t\t\t\t\t\tcontent=\"Map destination network CIDR to new target IP.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<div style={css.list}>\n\t\t\t\t\t\t\t{mapsElem}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\thidden={Constants.user}\n\t\t\t\t\t\t\tlabel=\"Organization\"\n\t\t\t\t\t\t\thelp=\"Organization for VPC.\"\n\t\t\t\t\t\t\tvalue={vpc.organization}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('organization', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{organizationsSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\t\thelp=\"Datacenter for VPC.\"\n\t\t\t\t\t\t\tvalue={vpc.datacenter}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('datacenter', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t\t<PageSwitch\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tlabel=\"ICMP Redirects\"\n\t\t\t\t\t\t\thelp=\"Enable or disable ICMP redirects for VPC routing table. ICMP redirects will improve the routing path of static routes in the VPC routing table but will be cached by the instance for 5 minutes unless adjusted on the system. If dynamic updates to the VPC routing table are made such as with failover site-to-site systems redirects should be disabled to allow fast failover to the new route. ICMP redirects are not recommended for most configurations.\"\n\t\t\t\t\t\t\tchecked={vpc.icmp_redirects}\n\t\t\t\t\t\t\tonToggle={(): void => {\n\t\t\t\t\t\t\t\tthis.set('icmp_redirects', !vpc.icmp_redirects);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\t\tRoute Table\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Route Table\"\n\t\t\t\t\t\t\t\tcontent=\"VPC routing table, enter a CIDR network for the desitnation and IP address for taget.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<div style={css.list}>\n\t\t\t\t\t\t\t{routes}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<label style={css.itemsLabel}>\n\t\t\t\t\t\t\tCustom ARP\n\t\t\t\t\t\t\t<Help\n\t\t\t\t\t\t\t\ttitle=\"Custom ARP\"\n\t\t\t\t\t\t\t\tcontent=\"Custom ARP entries for external resources on VPC VLAN.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<div style={css.list}>\n\t\t\t\t\t\t\t{arpsElem}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.vpc}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/VpcRoute.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as VpcTypes from '../types/VpcTypes';\n\ninterface Props {\n\tdisabled?: boolean;\n\troute: VpcTypes.Route;\n\tonChange?: (state: VpcTypes.Route) => void;\n\tonAdd: (prepend: boolean) => void;\n\tonRemove?: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class VpcRoute extends React.Component<Props, {}> {\n\tclone(): VpcTypes.Route {\n\t\treturn {\n\t\t\t...this.props.route,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet route = this.props.route;\n\n\t\treturn <div>\n\t\t\t<div className=\"bp5-control-group\" style={css.group}>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"Destination\"\n\t\t\t\t\t\tvalue={route.destination || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.destination = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"Target\"\n\t\t\t\t\t\tvalue={route.target || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.target = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\tthis.props.onAdd(evt.shiftKey);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/VpcSubnet.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as VpcTypes from '../types/VpcTypes';\n\ninterface Props {\n\tdisabled?: boolean;\n\tsubnet: VpcTypes.Subnet;\n\tonChange?: (state: VpcTypes.Subnet) => void;\n\tonAdd: (prepend: boolean) => void;\n\tonRemove?: () => void;\n}\n\nconst css = {\n\tgroup: {\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t\tmarginTop: '5px',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tinputBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n};\n\nexport default class VpcSubnet extends React.Component<Props, {}> {\n\tclone(): VpcTypes.Subnet {\n\t\treturn {\n\t\t\t...this.props.subnet,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tlet subnet = this.props.subnet;\n\n\t\treturn <div>\n\t\t\t<div className=\"bp5-control-group\" style={css.group}>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"Name\"\n\t\t\t\t\t\tvalue={subnet.name || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.name = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.inputBox}>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"bp5-input\"\n\t\t\t\t\t\tstyle={css.input}\n\t\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tautoCapitalize=\"off\"\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\tplaceholder=\"Network\"\n\t\t\t\t\t\tvalue={subnet.network || ''}\n\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\tlet state = this.clone();\n\t\t\t\t\t\t\tstate.network = evt.target.value;\n\t\t\t\t\t\t\tthis.props.onChange(state);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-danger bp5-icon-remove\"\n\t\t\t\t\tdisabled={this.props.disabled}\n\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\tthis.props.onRemove();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-intent-success bp5-icon-add\"\n\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\tthis.props.onAdd(evt.shiftKey);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Vpcs.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport VpcsStore from '../stores/VpcsStore';\nimport OrganizationsStore from '../stores/OrganizationsStore';\nimport * as VpcActions from '../actions/VpcActions';\nimport Vpc from './Vpc';\nimport VpcNew from './VpcNew';\nimport VpcsFilter from './VpcsFilter';\nimport VpcsPage from './VpcsPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as CompletionActions from \"../actions/CompletionActions\";\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tvpcs: VpcTypes.VpcsRo;\n\tfilter: VpcTypes.Filter;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tnetwork: string;\n\tdatacenter: string;\n\torganization: string;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tgroupBox: {\n\t\tmargin: '16px 0 0 0',\n\t\twidth: '100%',\n\t\tmaxWidth: '420px',\n\t} as React.CSSProperties,\n\tgroupBoxUser: {\n\t\tmargin: '16px 0 0 0',\n\t\twidth: '100%',\n\t\tmaxWidth: '310px',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '107px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tselectFirst: {\n\t\twidth: '100%',\n\t\tborderTopLeftRadius: '3px',\n\t\tborderBottomLeftRadius: '3px',\n\t} as React.CSSProperties,\n\tselectInner: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tselectBox: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmargin: '8px 8px 0 0',\n\t} as React.CSSProperties,\n};\n\nexport default class Vpcs extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tvpcs: VpcsStore.vpcs,\n\t\t\tfilter: VpcsStore.filter,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tnetwork: '',\n\t\t\torganization: '',\n\t\t\tdatacenter: '',\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tVpcsStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tOrganizationsStore.addChangeListener(this.onChange);\n\t\tVpcActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tVpcsStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet vpcs = VpcsStore.vpcs;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tvpcs.forEach((vpc: VpcTypes.Vpc): void => {\n\t\t\tif (curSelected[vpc.id]) {\n\t\t\t\tselected[vpc.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[vpc.id]) {\n\t\t\t\topened[vpc.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tvpcs: vpcs,\n\t\t\tfilter: VpcsStore.filter,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\torganizations: CompletionStore.organizations,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tVpcActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet vpcsDom: JSX.Element[] = [];\n\n\t\tlet hasOrganizations = false;\n\t\tlet organizationsSelect: JSX.Element[] = [];\n\t\tif (this.state.organizations.length) {\n\t\t\thasOrganizations = true;\n\t\t\tfor (let organization of this.state.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\torganizationsSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey=\"null\"\n\t\t\t\t\tvalue=\"\"\n\t\t\t\t>No Organizations</option>,\n\t\t\t);\n\t\t}\n\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.state.datacenters.length) {\n\t\t\thasDatacenters = true;\n\t\t\tfor (let datacenter of this.state.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option\n\t\t\t\t\tkey=\"null\"\n\t\t\t\t\tvalue=\"\"\n\t\t\t\t>No Datacenters</option>,\n\t\t\t);\n\t\t}\n\n\t\tthis.state.vpcs.forEach((\n\t\t\t\tvpc: VpcTypes.VpcRo): void => {\n\t\t\tvpcsDom.push(<Vpc\n\t\t\t\tkey={vpc.id}\n\t\t\t\tvpc={vpc}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tselected={!!this.state.selected[vpc.id]}\n\t\t\t\topen={!!this.state.opened[vpc.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet vpcs = this.state.vpcs;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < vpcs.length; i++) {\n\t\t\t\t\t\t\tlet usr = vpcs[i];\n\n\t\t\t\t\t\t\tif (usr.id === vpc.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[vpcs[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: vpc.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[vpc.id]) {\n\t\t\t\t\t\tdelete selected[vpc.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[vpc.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: vpc.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[vpc.id]) {\n\t\t\t\t\t\tdelete opened[vpc.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[vpc.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let vpcId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = VpcsStore.vpc(vpcId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || vpcId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(vpcId);\n\t\t\t}\n\t\t}\n\n\t\tlet newVpcDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewVpcDom = <VpcNew\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>VPCs</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tVpcActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tVpcActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected VPCs\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<VpcsFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tVpcActions.filter(filter);\n\t\t\t\t}}\n\t\t\t\torganizations={this.state.organizations}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newVpcDom}\n\t\t\t\t\t{vpcsDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={4} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!vpcsDom.length}\n\t\t\t\ticonClass=\"bp5-icon-layout-auto\"\n\t\t\t\ttitle=\"No VPCs\"\n\t\t\t\tdescription=\"Add a new vpc to get started.\"\n\t\t\t/>\n\t\t\t<VpcsPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/VpcsFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as VpcTypes from '../types/VpcTypes';\nimport SearchInput from './SearchInput';\nimport * as OrganizationTypes from \"../types/OrganizationTypes\";\nimport * as DatacenterTypes from \"../types/DatacenterTypes\";\n\ninterface Props {\n\tfilter: VpcTypes.Filter;\n\tonFilter: (filter: VpcTypes.Filter) => void;\n\torganizations: OrganizationTypes.OrganizationsRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class VpcsFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet datacentersSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.datacenters && this.props.datacenters.length) {\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet organizationsSelect: JSX.Element[] = [\n\t\t\t<option key=\"key\" value=\"any\">Any</option>,\n\t\t];\n\t\tif (this.props.organizations && this.props.organizations.length) {\n\t\t\tfor (let organization of this.props.organizations) {\n\t\t\t\torganizationsSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\tvalue={organization.id}\n\t\t\t\t\t>{organization.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"VPC ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.role}\n\t\t\t\tplaceholder=\"Network\"\n\t\t\t\tvalue={this.props.filter.network}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.network = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.network;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div className=\"bp5-select\" style={css.type}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.datacenter || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.datacenter;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.datacenter = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{datacentersSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-select\" style={css.type} hidden={Constants.user}>\n\t\t\t\t<select\n\t\t\t\t\tvalue={this.props.filter.organization || 'any'}\n\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlet val = evt.target.value;\n\n\t\t\t\t\t\tif (val === 'any') {\n\t\t\t\t\t\t\tdelete filter.organization;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilter.organization = val;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{organizationsSelect}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/VpcsPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport VpcsStore from '../stores/VpcsStore';\nimport * as VpcActions from '../actions/VpcActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class VpcsPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: VpcsStore.page,\n\t\t\tpageCount: VpcsStore.pageCount,\n\t\t\tpages: VpcsStore.pages,\n\t\t\tcount: VpcsStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tVpcsStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tVpcsStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: VpcsStore.page,\n\t\t\tpageCount: VpcsStore.pageCount,\n\t\t\tpages: VpcsStore.pages,\n\t\t\tcount: VpcsStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tVpcActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tVpcActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tVpcActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tVpcActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tVpcActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Zone.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport ZoneDetailed from './ZoneDetailed';\nimport DatacentersStore from '../stores/DatacentersStore';\n\ninterface Props {\n\tzone: ZoneTypes.ZoneRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\topen: boolean;\n\tonOpen: () => void;\n}\n\nconst css = {\n\tcard: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tcursor: 'pointer',\n\t} as React.CSSProperties,\n\tcardOpen: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '2px 0 0 0',\n\t\tpaddingTop: '3px',\n\t\tminHeight: '18px',\n\t} as React.CSSProperties,\n\tname: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t} as React.CSSProperties,\n\tnameSpan: {\n\t\tmargin: '1px 5px 0 0',\n\t} as React.CSSProperties,\n\titem: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '9px',\n\t\twhiteSpace: 'nowrap',\n\t} as React.CSSProperties,\n\ticon: {\n\t\tmarginRight: '3px',\n\t} as React.CSSProperties,\n\tbars: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '8px',\n\t\twidth: '30px',\n\t} as React.CSSProperties,\n\tbar: {\n\t\theight: '6px',\n\t\tmarginBottom: '1px',\n\t} as React.CSSProperties,\n\tbarLast: {\n\t\theight: '6px',\n\t} as React.CSSProperties,\n\troles: {\n\t\tverticalAlign: 'top',\n\t\tdisplay: 'table-cell',\n\t\tpadding: '0 8px 8px 8px',\n\t} as React.CSSProperties,\n\ttag: {\n\t\tmargin: '8px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n};\n\nexport default class Zone extends React.Component<Props, {}> {\n\trender(): JSX.Element {\n\t\tlet zone = this.props.zone;\n\n\t\tif (this.props.open) {\n\t\t\treturn <div\n\t\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\t\tstyle={css.cardOpen}\n\t\t\t>\n\t\t\t\t<ZoneDetailed\n\t\t\t\t\tzone={this.props.zone}\n\t\t\t\t\tselected={this.props.selected}\n\t\t\t\t\tonSelect={this.props.onSelect}\n\t\t\t\t\tonClose={(): void => {\n\t\t\t\t\t\tthis.props.onOpen();\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>;\n\t\t}\n\n\t\tlet cardStyle = {\n\t\t\t...css.card,\n\t\t};\n\n\t\tlet datacenter = DatacentersStore.datacenter(\n\t\t\tthis.props.zone.datacenter);\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={cardStyle}\n\t\t\tonClick={(evt): void => {\n\t\t\t\tlet target = evt.target as HTMLElement;\n\n\t\t\t\tif (target.className.indexOf('open-ignore') !== -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.props.onOpen();\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"bp5-cell\" style={css.name}>\n\t\t\t\t<div className=\"layout horizontal\">\n\t\t\t\t\t<label\n\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox open-ignore\"\n\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tclassName=\"open-ignore\"\n\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"bp5-control-indicator open-ignore\"/>\n\t\t\t\t\t</label>\n\t\t\t\t\t<div style={css.nameSpan}>\n\t\t\t\t\t\t{zone.name}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"bp5-cell\" style={css.item}>\n\t\t\t\t<span\n\t\t\t\t\tstyle={css.icon}\n\t\t\t\t\tclassName=\"bp5-icon-standard bp5-text-muted bp5-icon-cloud\"\n\t\t\t\t/>\n\t\t\t\t{datacenter?.name || zone.datacenter}\n\t\t\t</div>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ZoneDetailed.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as ZoneActions from '../actions/ZoneActions';\nimport DatacentersStore from '../stores/DatacentersStore';\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageSave from './PageSave';\nimport PageSelect from './PageSelect';\nimport ConfirmButton from './ConfirmButton';\nimport Relations from './Relations';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tzone: ZoneTypes.ZoneRo;\n\tselected: boolean;\n\tonSelect: (shift: boolean) => void;\n\tonClose: () => void;\n}\n\ninterface State {\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tzone: ZoneTypes.Zone;\n\taddCert: string;\n\tforwardedChecked: boolean;\n}\n\nconst css = {\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '48px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tcursor: 'pointer',\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tpadding: '4px',\n\t\theight: '39px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class ZoneDetailed extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\tzone: null,\n\t\t\taddCert: null,\n\t\t\tforwardedChecked: false,\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet zone: any;\n\n\t\tif (this.state.changed) {\n\t\t\tzone = {\n\t\t\t\t...this.state.zone,\n\t\t\t};\n\t\t} else {\n\t\t\tzone = {\n\t\t\t\t...this.props.zone,\n\t\t\t};\n\t\t}\n\n\t\tzone[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tzone: zone,\n\t\t});\n\t}\n\n\tonSave = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tZoneActions.commit(this.state.zone).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Your changes have been saved',\n\t\t\t\tchanged: false,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tif (!this.state.changed) {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tmessage: '',\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tzone: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 3000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tZoneActions.remove(this.props.zone.id).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet zone: ZoneTypes.Zone = this.state.zone ||\n\t\t\tthis.props.zone;\n\n\t\tlet datacenter = DatacentersStore.datacenter(\n\t\t\tthis.props.zone.datacenter);\n\n\t\treturn <td\n\t\t\tclassName=\"bp5-cell\"\n\t\t\tcolSpan={2}\n\t\t\tstyle={css.card}\n\t\t>\n\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"layout horizontal tab-close bp5-card-header\"\n\t\t\t\t\t\tstyle={css.buttons}\n\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\tif (evt.target instanceof HTMLElement &&\n\t\t\t\t\t\t\t\t\tevt.target.className.indexOf('tab-close') !== -1) {\n\t\t\t\t\t\t\t\tthis.props.onClose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"bp5-control bp5-checkbox\"\n\t\t\t\t\t\t\t\tstyle={css.select}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\tchecked={this.props.selected}\n\t\t\t\t\t\t\t\t\tonChange={(evt): void => {\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={(evt): void => {\n\t\t\t\t\t\t\t\t\t\tthis.props.onSelect(evt.shiftKey);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"bp5-control-indicator\"/>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex tab-close\"/>\n\t\t\t\t\t\t<Relations kind=\"zone\" id={this.props.zone.id}/>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tclassName=\"bp5-minimal bp5-intent-danger bp5-icon-trash\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tdialogClassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tdialogLabel=\"Delete Zone\"\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete this zone\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={[zone.name]}\n\t\t\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<PageInput\n\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\thelp=\"Name of zone\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\tvalue={zone.name}\n\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\thelp=\"Zone comment.\"\n\t\t\t\t\t\tplaceholder=\"Zone comment\"\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tvalue={zone.comment}\n\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div style={css.group}>\n\t\t\t\t\t<PageInfo\n\t\t\t\t\t\tfields={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'ID',\n\t\t\t\t\t\t\t\tvalue: this.props.zone.id || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlabel: 'Datacenter',\n\t\t\t\t\t\t\t\tvalue: datacenter ? datacenter.name :\n\t\t\t\t\t\t\t\t\tthis.props.zone.datacenter || 'None',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<PageSave\n\t\t\t\tstyle={css.save}\n\t\t\t\thidden={!this.state.zone}\n\t\t\t\tmessage={this.state.message}\n\t\t\t\tchanged={this.state.changed}\n\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\tlight={true}\n\t\t\t\tonCancel={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\tforwardedChecked: false,\n\t\t\t\t\t\tzone: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonSave={this.onSave}\n\t\t\t/>\n\t\t</td>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ZoneNew.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as ZoneActions from '../actions/ZoneActions';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport DatacentersStore from '../stores/DatacentersStore';\nimport PageInput from './PageInput';\nimport PageInfo from './PageInfo';\nimport PageCreate from './PageCreate';\nimport PageSelect from './PageSelect';\nimport ConfirmButton from './ConfirmButton';\nimport PageTextArea from \"./PageTextArea\";\n\ninterface Props {\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tonClose: () => void;\n}\n\ninterface State {\n\tclosed: boolean;\n\tdisabled: boolean;\n\tchanged: boolean;\n\tmessage: string;\n\tzone: ZoneTypes.Zone;\n\taddCert: string;\n\tforwardedChecked: boolean;\n}\n\nconst css = {\n\trow: {\n\t\tdisplay: 'table-row',\n\t\twidth: '100%',\n\t\tpadding: 0,\n\t\tboxShadow: 'none',\n\t\tposition: 'relative',\n\t} as React.CSSProperties,\n\tcard: {\n\t\tposition: 'relative',\n\t\tpadding: '10px 10px 0 10px',\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tremove: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\titem: {\n\t\tmargin: '9px 5px 0 5px',\n\t\tminHeight: '20px',\n\t} as React.CSSProperties,\n\titemsLabel: {\n\t\tdisplay: 'block',\n\t} as React.CSSProperties,\n\titemsAdd: {\n\t\tmargin: '8px 0 15px 0',\n\t} as React.CSSProperties,\n\tgroup: {\n\t\tflex: 1,\n\t\tminWidth: '280px',\n\t\tmargin: '0 10px',\n\t} as React.CSSProperties,\n\tsave: {\n\t\tpaddingBottom: '10px',\n\t} as React.CSSProperties,\n\tlabel: {\n\t\twidth: '100%',\n\t\tmaxWidth: '280px',\n\t} as React.CSSProperties,\n\tinputGroup: {\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\tprotocol: {\n\t\tflex: '0 1 auto',\n\t} as React.CSSProperties,\n\tport: {\n\t\tflex: '1',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\theight: '30px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tposition: 'absolute',\n\t\ttop: '5px',\n\t\tright: '5px',\n\t} as React.CSSProperties,\n\tselect: {\n\t\tmargin: '7px 0px 0px 6px',\n\t\tpaddingTop: '3px',\n\t} as React.CSSProperties,\n};\n\nexport default class ZoneNew extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tclosed: false,\n\t\t\tdisabled: false,\n\t\t\tchanged: false,\n\t\t\tmessage: '',\n\t\t\taddCert: null,\n\t\t\tforwardedChecked: false,\n\t\t\tzone: {\n\t\t\t\tname: \"new-zone\",\n\t\t\t},\n\t\t};\n\t}\n\n\tset(name: string, val: any): void {\n\t\tlet zone: any = {\n\t\t\t...this.state.zone,\n\t\t};\n\n\t\tzone[name] = val;\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tchanged: true,\n\t\t\tzone: zone,\n\t\t});\n\t}\n\n\tonCreate = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\n\t\tlet zone: any = {\n\t\t\t...this.state.zone,\n\t\t};\n\n\t\tZoneActions.create(zone).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: 'Zone created successfully',\n\t\t\t\tchanged: false,\n\t\t\t});\n\n\t\t\tsetTimeout((): void => {\n\t\t\t\tthis.setState({\n\t\t\t\t\t...this.state,\n\t\t\t\t\tdisabled: false,\n\t\t\t\t\tchanged: true,\n\t\t\t\t});\n\t\t\t}, 2000);\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tmessage: '',\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet zone: ZoneTypes.Zone = this.state.zone;\n\n\t\tlet hasDatacenters = false;\n\t\tlet datacentersSelect: JSX.Element[] = [];\n\t\tif (this.props.datacenters && this.props.datacenters.length) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">Select Datacenter</option>);\n\n\t\t\thasDatacenters = true;\n\t\t\tfor (let datacenter of this.props.datacenters) {\n\t\t\t\tdatacentersSelect.push(\n\t\t\t\t\t<option\n\t\t\t\t\t\tkey={datacenter.id}\n\t\t\t\t\t\tvalue={datacenter.id}\n\t\t\t\t\t>{datacenter.name}</option>,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!hasDatacenters) {\n\t\t\tdatacentersSelect.push(\n\t\t\t\t<option key=\"null\" value=\"\">No Datacenters</option>);\n\t\t}\n\n\t\treturn <div\n\t\t\tclassName=\"bp5-card bp5-row\"\n\t\t\tstyle={css.row}\n\t\t>\n\t\t\t<td\n\t\t\t\tclassName=\"bp5-cell\"\n\t\t\t\tcolSpan={2}\n\t\t\t\tstyle={css.card}\n\t\t\t>\n\t\t\t\t<div className=\"layout horizontal wrap\">\n\t\t\t\t\t<div style={css.group}>\n\t\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<PageInput\n\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\thelp=\"Name of zone\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Enter name\"\n\t\t\t\t\t\t\tvalue={zone.name}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set('name', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageTextArea\n\t\t\t\t\t\t\tlabel=\"Comment\"\n\t\t\t\t\t\t\thelp=\"Zone comment.\"\n\t\t\t\t\t\t\tplaceholder=\"Zone comment\"\n\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\tvalue={zone.comment}\n\t\t\t\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\t\t\t\tthis.set('comment', val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PageSelect\n\t\t\t\t\t\t\tdisabled={this.state.disabled || !hasDatacenters}\n\t\t\t\t\t\t\tlabel=\"Datacenter\"\n\t\t\t\t\t\t\thelp=\"Datacenter for zone.\"\n\t\t\t\t\t\t\tvalue={zone.datacenter}\n\t\t\t\t\t\t\tonChange={(val): void => {\n\t\t\t\t\t\t\t\tthis.set(\"datacenter\", val);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{datacentersSelect}\n\t\t\t\t\t\t</PageSelect>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PageCreate\n\t\t\t\t\tstyle={css.save}\n\t\t\t\t\thidden={!this.state.zone}\n\t\t\t\t\tmessage={this.state.message}\n\t\t\t\t\tchanged={this.state.changed}\n\t\t\t\t\tdisabled={this.state.disabled}\n\t\t\t\t\tclosed={this.state.closed}\n\t\t\t\t\tlight={true}\n\t\t\t\t\tonCancel={this.props.onClose}\n\t\t\t\t\tonCreate={this.onCreate}\n\t\t\t\t/>\n\t\t\t</td>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/Zones.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport ZonesStore from '../stores/ZonesStore';\nimport CompletionStore from \"../stores/CompletionStore\";\nimport * as ZoneActions from '../actions/ZoneActions';\nimport * as CompletionActions from '../actions/CompletionActions';\nimport Zone from './Zone';\nimport ZoneNew from './ZoneNew';\nimport ZonesFilter from './ZonesFilter';\nimport ZonesPage from './ZonesPage';\nimport Page from './Page';\nimport PageHeader from './PageHeader';\nimport NonState from './NonState';\nimport ConfirmButton from './ConfirmButton';\n\ninterface Selected {\n\t[key: string]: boolean;\n}\n\ninterface Opened {\n\t[key: string]: boolean;\n}\n\ninterface State {\n\tzones: ZoneTypes.ZonesRo;\n\tdatacenters: DatacenterTypes.DatacentersRo;\n\tfilter: ZoneTypes.Filter;\n\tselected: Selected;\n\topened: Opened;\n\tnewOpened: boolean;\n\tlastSelected: string;\n\tdisabled: boolean;\n}\n\nconst css = {\n\titems: {\n\t\twidth: '100%',\n\t\tmarginTop: '-5px',\n\t\tdisplay: 'table',\n\t\ttableLayout: 'fixed',\n\t\tborderSpacing: '0 5px',\n\t} as React.CSSProperties,\n\titemsBox: {\n\t\twidth: '100%',\n\t\toverflowY: 'auto',\n\t} as React.CSSProperties,\n\tplaceholder: {\n\t\topacity: 0,\n\t\twidth: '100%',\n\t} as React.CSSProperties,\n\theader: {\n\t\tmarginTop: '-19px',\n\t} as React.CSSProperties,\n\theading: {\n\t\tmargin: '19px 0 0 0',\n\t} as React.CSSProperties,\n\tbutton: {\n\t\tmargin: '8px 0 0 8px',\n\t} as React.CSSProperties,\n\tbuttons: {\n\t\tmarginTop: '8px',\n\t} as React.CSSProperties,\n};\n\nexport default class Zones extends React.Component<{}, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tzones: ZonesStore.zones,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tfilter: ZonesStore.filter,\n\t\t\tselected: {},\n\t\t\topened: {},\n\t\t\tnewOpened: false,\n\t\t\tlastSelected: null,\n\t\t\tdisabled: false,\n\t\t};\n\t}\n\n\tget selected(): boolean {\n\t\treturn !!Object.keys(this.state.selected).length;\n\t}\n\n\tget opened(): boolean {\n\t\treturn !!Object.keys(this.state.opened).length;\n\t}\n\n\tcomponentDidMount(): void {\n\t\tZonesStore.addChangeListener(this.onChange);\n\t\tCompletionStore.addChangeListener(this.onChange);\n\t\tZoneActions.sync();\n\t\tCompletionActions.sync();\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tZonesStore.removeChangeListener(this.onChange);\n\t\tCompletionStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tlet zones = ZonesStore.zones;\n\t\tlet selected: Selected = {};\n\t\tlet curSelected = this.state.selected;\n\t\tlet opened: Opened = {};\n\t\tlet curOpened = this.state.opened;\n\n\t\tzones.forEach((zone: ZoneTypes.Zone): void => {\n\t\t\tif (curSelected[zone.id]) {\n\t\t\t\tselected[zone.id] = true;\n\t\t\t}\n\t\t\tif (curOpened[zone.id]) {\n\t\t\t\topened[zone.id] = true;\n\t\t\t}\n\t\t});\n\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tzones: zones,\n\t\t\tdatacenters: CompletionStore.datacenters,\n\t\t\tfilter: ZonesStore.filter,\n\t\t\tselected: selected,\n\t\t\topened: opened,\n\t\t});\n\t}\n\n\tonDelete = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tdisabled: true,\n\t\t});\n\t\tZoneActions.removeMulti(\n\t\t\t\tObject.keys(this.state.selected)).then((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tselected: {},\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t}).catch((): void => {\n\t\t\tthis.setState({\n\t\t\t\t...this.state,\n\t\t\t\tdisabled: false,\n\t\t\t});\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet zonesDom: JSX.Element[] = [];\n\n\t\tthis.state.zones.forEach((\n\t\t\t\tzone: ZoneTypes.ZoneRo): void => {\n\t\t\tzonesDom.push(<Zone\n\t\t\t\tkey={zone.id}\n\t\t\t\tzone={zone}\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tselected={!!this.state.selected[zone.id]}\n\t\t\t\topen={!!this.state.opened[zone.id]}\n\t\t\t\tonSelect={(shift: boolean): void => {\n\t\t\t\t\tlet selected = {\n\t\t\t\t\t\t...this.state.selected,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (shift) {\n\t\t\t\t\t\tlet zones = this.state.zones;\n\t\t\t\t\t\tlet start: number;\n\t\t\t\t\t\tlet end: number;\n\n\t\t\t\t\t\tfor (let i = 0; i < zones.length; i++) {\n\t\t\t\t\t\t\tlet usr = zones[i];\n\n\t\t\t\t\t\t\tif (usr.id === zone.id) {\n\t\t\t\t\t\t\t\tstart = i;\n\t\t\t\t\t\t\t} else if (usr.id === this.state.lastSelected) {\n\t\t\t\t\t\t\t\tend = i;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (start !== undefined && end !== undefined) {\n\t\t\t\t\t\t\tif (start > end) {\n\t\t\t\t\t\t\t\tend = [start, start = end][0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfor (let i = start; i <= end; i++) {\n\t\t\t\t\t\t\t\tselected[zones[i].id] = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\tlastSelected: zone.id,\n\t\t\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (selected[zone.id]) {\n\t\t\t\t\t\tdelete selected[zone.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselected[zone.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tlastSelected: zone.id,\n\t\t\t\t\t\tselected: selected,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonOpen={(): void => {\n\t\t\t\t\tlet opened = {\n\t\t\t\t\t\t...this.state.opened,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (opened[zone.id]) {\n\t\t\t\t\t\tdelete opened[zone.id];\n\t\t\t\t\t} else {\n\t\t\t\t\t\topened[zone.id] = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\topened: opened,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>);\n\t\t});\n\n\t\tlet filterClass = 'bp5-button bp5-intent-primary bp5-icon-filter ';\n\t\tif (this.state.filter) {\n\t\t\tfilterClass += 'bp5-active';\n\t\t}\n\n\t\tlet selectedNames: string[] = [];\n\t\tfor (let instId of Object.keys(this.state.selected)) {\n\t\t\tlet inst = ZonesStore.zone(instId);\n\t\t\tif (inst) {\n\t\t\t\tselectedNames.push(inst.name || instId);\n\t\t\t} else {\n\t\t\t\tselectedNames.push(instId);\n\t\t\t}\n\t\t}\n\n\t\tlet newZoneDom: JSX.Element;\n\t\tif (this.state.newOpened) {\n\t\t\tnewZoneDom = <ZoneNew\n\t\t\t\tdatacenters={this.state.datacenters}\n\t\t\t\tonClose={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tnewOpened: false,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>;\n\t\t}\n\n\t\treturn <Page>\n\t\t\t<PageHeader>\n\t\t\t\t<div className=\"layout horizontal wrap\" style={css.header}>\n\t\t\t\t\t<h2 style={css.heading}>Zones</h2>\n\t\t\t\t\t<div className=\"flex\"/>\n\t\t\t\t\t<div style={css.buttons}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={filterClass}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tif (this.state.filter === null) {\n\t\t\t\t\t\t\t\t\tZoneActions.filter({});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tZoneActions.filter(null);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tFilters\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-warning bp5-icon-chevron-up\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={!this.opened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\topened: {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tCollapse All\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<ConfirmButton\n\t\t\t\t\t\t\tlabel=\"Delete Selected\"\n\t\t\t\t\t\t\tclassName=\"bp5-intent-danger bp5-icon-delete\"\n\t\t\t\t\t\t\tprogressClassName=\"bp5-intent-danger\"\n\t\t\t\t\t\t\tsafe={true}\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tconfirmMsg=\"Permanently delete the selected zones\"\n\t\t\t\t\t\t\tconfirmInput={true}\n\t\t\t\t\t\t\titems={selectedNames}\n\t\t\t\t\t\t\tdisabled={!this.selected || this.state.disabled}\n\t\t\t\t\t\t\tonConfirm={this.onDelete}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"bp5-button bp5-intent-success bp5-icon-add\"\n\t\t\t\t\t\t\tstyle={css.button}\n\t\t\t\t\t\t\tdisabled={this.state.disabled || this.state.newOpened}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={(): void => {\n\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\t\t\t\tnewOpened: true,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>New</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PageHeader>\n\t\t\t<ZonesFilter\n\t\t\t\tfilter={this.state.filter}\n\t\t\t\tonFilter={(filter): void => {\n\t\t\t\t\tZoneActions.filter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div style={css.itemsBox}>\n\t\t\t\t<div style={css.items}>\n\t\t\t\t\t{newZoneDom}\n\t\t\t\t\t{zonesDom}\n\t\t\t\t\t<tr className=\"bp5-card bp5-row\" style={css.placeholder}>\n\t\t\t\t\t\t<td colSpan={2} style={css.placeholder}/>\n\t\t\t\t\t</tr>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<NonState\n\t\t\t\thidden={!!zonesDom.length}\n\t\t\t\ticonClass=\"bp5-icon-layout-circle\"\n\t\t\t\ttitle=\"No zones\"\n\t\t\t\tdescription=\"Add a new zone to get started.\"\n\t\t\t/>\n\t\t\t<ZonesPage\n\t\t\t\tonPage={(): void => {\n\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t...this.state,\n\t\t\t\t\t\tselected: {},\n\t\t\t\t\t\tlastSelected: null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Page>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ZonesFilter.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport * as Constants from '../Constants';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport SearchInput from './SearchInput';\n\ninterface Props {\n\tfilter: ZoneTypes.Filter;\n\tonFilter: (filter: ZoneTypes.Filter) => void;\n}\n\nconst css = {\n\tfilters: {\n\t\tmargin: '-15px 0 5px 0',\n\t} as React.CSSProperties,\n\tinput: {\n\t\twidth: '200px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tshortInput: {\n\t\twidth: '180px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\trole: {\n\t\twidth: '150px',\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\ttype: {\n\t\tmargin: '5px',\n\t} as React.CSSProperties,\n\tcheck: {\n\t\tmargin: '12px 5px 8px 5px',\n\t} as React.CSSProperties,\n};\n\nexport default class ZonesFilter extends React.Component<Props, {}> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tmenu: false,\n\t\t};\n\t}\n\n\trender(): JSX.Element {\n\t\tif (this.props.filter === null) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\treturn <div className=\"layout horizontal wrap\" style={css.filters}>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Zone ID\"\n\t\t\t\tvalue={this.props.filter.id}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.id = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.id;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.input}\n\t\t\t\tplaceholder=\"Name\"\n\t\t\t\tvalue={this.props.filter.name}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.name = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.name;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<SearchInput\n\t\t\t\tstyle={css.shortInput}\n\t\t\t\tplaceholder=\"Comment\"\n\t\t\t\tvalue={this.props.filter.comment}\n\t\t\t\tonChange={(val: string): void => {\n\t\t\t\t\tlet filter = {\n\t\t\t\t\t\t...this.props.filter,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (val) {\n\t\t\t\t\t\tfilter.comment = val;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete filter.comment;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.props.onFilter(filter);\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/components/ZonesPage.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as React from 'react';\nimport ZonesStore from '../stores/ZonesStore';\nimport * as ZoneActions from '../actions/ZoneActions';\n\ninterface Props {\n\tonPage?: () => void;\n}\n\ninterface State {\n\tpage: number;\n\tpageCount: number;\n\tpages: number;\n\tcount: number;\n}\n\nconst css = {\n\tbutton: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 5px 0 0',\n\t} as React.CSSProperties,\n\tbuttonLast: {\n\t\tuserSelect: 'none',\n\t\tmargin: '0 0 0 0',\n\t} as React.CSSProperties,\n\tlink: {\n\t\tcursor: 'pointer',\n\t\tuserSelect: 'none',\n\t\tmargin: '7px 5px 0 0',\n\t} as React.CSSProperties,\n\tcurrent: {\n\t\topacity: 0.5,\n\t} as React.CSSProperties,\n};\n\nexport default class ZonesPage extends React.Component<Props, State> {\n\tconstructor(props: any, context: any) {\n\t\tsuper(props, context);\n\t\tthis.state = {\n\t\t\tpage: ZonesStore.page,\n\t\t\tpageCount: ZonesStore.pageCount,\n\t\t\tpages: ZonesStore.pages,\n\t\t\tcount: ZonesStore.count,\n\t\t};\n\t}\n\n\tcomponentDidMount(): void {\n\t\tZonesStore.addChangeListener(this.onChange);\n\t}\n\n\tcomponentWillUnmount(): void {\n\t\tZonesStore.removeChangeListener(this.onChange);\n\t}\n\n\tonChange = (): void => {\n\t\tthis.setState({\n\t\t\t...this.state,\n\t\t\tpage: ZonesStore.page,\n\t\t\tpageCount: ZonesStore.pageCount,\n\t\t\tpages: ZonesStore.pages,\n\t\t\tcount: ZonesStore.count,\n\t\t});\n\t}\n\n\trender(): JSX.Element {\n\t\tlet page = this.state.page;\n\t\tlet pages = this.state.pages;\n\n\t\tif (pages <= 1) {\n\t\t\treturn <div/>;\n\t\t}\n\n\t\tlet links: JSX.Element[] = [];\n\t\tlet start = Math.max(0, page - 7);\n\t\tlet end = Math.min(pages, start + 15);\n\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tlinks.push(<span\n\t\t\t\tkey={i}\n\t\t\t\tstyle={page === i ? {\n\t\t\t\t\t...css.link,\n\t\t\t\t\t...css.current,\n\t\t\t\t} : css.link}\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tZoneActions.traverse(i);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{i + 1}\n\t\t\t</span>);\n\t\t}\n\n\t\treturn <div className=\"layout horizontal center-justified\">\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-backward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tZoneActions.traverse(0);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-left\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === 0}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tZoneActions.traverse(Math.max(0, this.state.page - 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{links}\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-right\"\n\t\t\t\tstyle={css.button}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tZoneActions.traverse(Math.min(\n\t\t\t\t\t\tthis.state.pages - 1, this.state.page + 1));\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<button\n\t\t\t\tclassName=\"bp5-button bp5-minimal bp5-icon-chevron-forward\"\n\t\t\t\thidden={pages < 5}\n\t\t\t\tdisabled={page === pages - 1}\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={(): void => {\n\t\t\t\t\tZoneActions.traverse(this.state.pages - 1);\n\t\t\t\t\tif (this.props.onPage) {\n\t\t\t\t\t\tthis.props.onPage();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>;\n\t}\n}\n"
  },
  {
    "path": "www/app/dispatcher/Base.ts",
    "content": "export type DispatchToken = string;\n\nvar _prefix = 'ID_';\n\nexport default class DispatcherBase<TPayload> {\n\t_callbacks: {[key: DispatchToken]: (payload: TPayload) => void};\n\t_isDispatching: boolean;\n\t_isHandled: {[key: DispatchToken]: boolean};\n\t_isPending: {[key: DispatchToken]: boolean};\n\t_lastID: number;\n\t_pendingPayload: TPayload;\n\n\tconstructor() {\n\t\tthis._callbacks = {};\n\t\tthis._isDispatching = false;\n\t\tthis._isHandled = {};\n\t\tthis._isPending = {};\n\t\tthis._lastID = 1;\n\t}\n\n\tregister(callback: (payload: TPayload) => void): DispatchToken {\n\t\tvar id = _prefix + this._lastID++;\n\t\tthis._callbacks[id] = callback;\n\t\treturn id;\n\t}\n\n\tunregister(id: DispatchToken): void {\n\t\tif (!this._callbacks[id]) {\n\t\t\tconsole.error(\n\t\t\t\t'Dispatcher.unregister(...): `%s` does not map ' +\n\t\t\t\t'to a registered callback.',\n\t\t\t\tid\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tdelete this._callbacks[id];\n\t}\n\n\twaitFor(ids: Array<DispatchToken>): void {\n\t\tconsole.error(\n\t\t\tthis._isDispatching,\n\t\t\t'Dispatcher.waitFor(...): Must be invoked while dispatching.',\n\t\t);\n\t\tfor (var ii = 0; ii < ids.length; ii++) {\n\t\t\tvar id = ids[ii];\n\t\t\tif (this._isPending[id]) {\n\t\t\t\tconsole.error(\n\t\t\t\t\tthis._isHandled[id],\n\t\t\t\t\t'Dispatcher.waitFor(...): Circular dependency detected while ' +\n\t\t\t\t\t'waiting for `%s`.',\n\t\t\t\t\tid,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconsole.error(\n\t\t\t\tthis._callbacks[id],\n\t\t\t\t'Dispatcher.waitFor(...): `%s` does not map to a registered callback.',\n\t\t\t\tid,\n\t\t\t);\n\t\t\tthis._invokeCallback(id);\n\t\t}\n\t}\n\n\tdispatch(payload: TPayload): void {\n\t\t// console.error(\n\t\t// \t!this._isDispatching,\n\t\t// \t'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.',\n\t\t// );\n\t\tthis._startDispatching(payload);\n\t\ttry {\n\t\t\tfor (var id in this._callbacks) {\n\t\t\t\tif (this._isPending[id]) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis._invokeCallback(id);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._stopDispatching();\n\t\t}\n\t}\n\n\tisDispatching(): boolean {\n\t\treturn this._isDispatching;\n\t}\n\n\t_invokeCallback(id: DispatchToken): void {\n\t\tthis._isPending[id] = true;\n\t\tthis._callbacks[id](this._pendingPayload);\n\t\tthis._isHandled[id] = true;\n\t}\n\n\t_startDispatching(payload: TPayload): void {\n\t\tfor (var id in this._callbacks) {\n\t\t\tthis._isPending[id] = false;\n\t\t\tthis._isHandled[id] = false;\n\t\t}\n\t\tthis._pendingPayload = payload;\n\t\tthis._isDispatching = true;\n\t}\n\n\t_stopDispatching(): void {\n\t\tdelete this._pendingPayload;\n\t\tthis._isDispatching = false;\n\t}\n}\n"
  },
  {
    "path": "www/app/dispatcher/Dispatcher.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport DispatcherBase from \"./Base\";\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass Dispatcher extends DispatcherBase<GlobalTypes.Dispatch> {}\nexport default new Dispatcher();\n"
  },
  {
    "path": "www/app/dispatcher/EventDispatcher.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport DispatcherBase from \"./Base\";\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass EventDispatcher extends DispatcherBase<GlobalTypes.Dispatch> {}\nexport default new EventDispatcher();\n"
  },
  {
    "path": "www/app/dispatcher/LoadingDispatcher.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport DispatcherBase from \"./Base\";\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass LoadingDispatcher extends DispatcherBase<GlobalTypes.Dispatch> {}\nexport default new LoadingDispatcher();\n"
  },
  {
    "path": "www/app/stores/AlertsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as AlertTypes from '../types/AlertTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass AlertsStore extends EventEmitter {\n\t_alerts: AlertTypes.AlertsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: AlertTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._alerts = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget alerts(): AlertTypes.AlertsRo {\n\t\treturn this._alerts;\n\t}\n\n\tget alertsM(): AlertTypes.Alerts {\n\t\tlet alerts: AlertTypes.Alerts = [];\n\t\tthis._alerts.forEach((alert: AlertTypes.AlertRo): void => {\n\t\t\talerts.push({\n\t\t\t\t...alert,\n\t\t\t});\n\t\t});\n\t\treturn alerts;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): AlertTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\talert(id: string): AlertTypes.AlertRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._alerts[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: AlertTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(alerts: AlertTypes.Alert[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < alerts.length; i++) {\n\t\t\talerts[i] = Object.freeze(alerts[i]);\n\t\t\tthis._map[alerts[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._alerts = Object.freeze(alerts);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: AlertTypes.AlertDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase AlertTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase AlertTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase AlertTypes.SYNC:\n\t\t\t\tthis._sync(action.data.alerts, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new AlertsStore();\n"
  },
  {
    "path": "www/app/stores/AuditsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as AuditTypes from '../types/AuditTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass AuditsStore extends EventEmitter {\n\t_userId: string;\n\t_audits: AuditTypes.AuditsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_count: number;\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._userId = undefined;\n\t\tthis._audits = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._count = undefined;\n\t\tthis.emitChange();\n\t}\n\n\tget userId(): string {\n\t\treturn this._userId;\n\t}\n\n\tget audits(): AuditTypes.AuditsRo {\n\t\treturn this._audits;\n\t}\n\n\tget auditsM(): AuditTypes.Audits {\n\t\tlet audits: AuditTypes.Audits = [];\n\t\tthis._audits.forEach((audit: AuditTypes.Audit): void => {\n\t\t\taudits.push({\n\t\t\t\t...audit,\n\t\t\t});\n\t\t});\n\t\treturn audits;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 10;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_sync(userId: string, audits: AuditTypes.Audit[], count: number): void {\n\t\tthis._userId = userId;\n\n\t\tfor (let i = 0; i < audits.length; i++) {\n\t\t\taudits[i] = Object.freeze(audits[i]);\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._audits = Object.freeze(audits);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: AuditTypes.AuditDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase AuditTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase AuditTypes.SYNC:\n\t\t\t\tthis._sync(action.data.userId, action.data.audits, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new AuditsStore();\n"
  },
  {
    "path": "www/app/stores/AuthoritiesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as AuthorityTypes from '../types/AuthorityTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass AuthoritiesStore extends EventEmitter {\n\t_authorities: AuthorityTypes.AuthoritiesRo = Object.freeze([]);\n\t_authoritiesName: AuthorityTypes.AuthoritiesRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: AuthorityTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_mapName: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._authorities = Object.freeze([]);\n\t\tthis._authoritiesName = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis._mapName = {};\n\t\tthis.emitChange();\n\t}\n\n\tget authorities(): AuthorityTypes.AuthoritiesRo {\n\t\treturn this._authorities;\n\t}\n\n\tget authoritiesM(): AuthorityTypes.Authorities {\n\t\tlet authorities: AuthorityTypes.Authorities = [];\n\t\tthis._authorities.forEach((authority: AuthorityTypes.AuthorityRo): void => {\n\t\t\tauthorities.push({\n\t\t\t\t...authority,\n\t\t\t});\n\t\t});\n\t\treturn authorities;\n\t}\n\n\tget authoritiesName(): AuthorityTypes.AuthoritiesRo {\n\t\treturn this._authoritiesName || [];\n\t}\n\n\tget authoritiesNameM(): AuthorityTypes.Authorities {\n\t\tlet authorities: AuthorityTypes.Authorities = [];\n\t\tthis._authoritiesName.forEach((\n\t\t\tauthority: AuthorityTypes.AuthorityRo): void => {\n\n\t\t\tauthorities.push({\n\t\t\t\t...authority,\n\t\t\t});\n\t\t});\n\t\treturn authorities;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): AuthorityTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tauthority(id: string): AuthorityTypes.AuthorityRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._authorities[i];\n\t}\n\n\tauthorityName(id: string): AuthorityTypes.AuthorityRo {\n\t\tlet i = this._mapName[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._authoritiesName[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: AuthorityTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(authorities: AuthorityTypes.Authority[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < authorities.length; i++) {\n\t\t\tauthorities[i] = Object.freeze(authorities[i]);\n\t\t\tthis._map[authorities[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._authorities = Object.freeze(authorities);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_syncNames(authorities: AuthorityTypes.Authority[]): void {\n\t\tthis._mapName = {};\n\t\tfor (let i = 0; i < authorities.length; i++) {\n\t\t\tauthorities[i] = Object.freeze(authorities[i]);\n\t\t\tthis._mapName[authorities[i].id] = i;\n\t\t}\n\n\t\tthis._authoritiesName = Object.freeze(authorities);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: AuthorityTypes.AuthorityDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase AuthorityTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase AuthorityTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase AuthorityTypes.SYNC:\n\t\t\t\tthis._sync(action.data.authorities, action.data.count);\n\t\t\t\tbreak;\n\n\t\t\tcase AuthorityTypes.SYNC_NAMES:\n\t\t\t\tthis._syncNames(action.data.authorities);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new AuthoritiesStore();\n"
  },
  {
    "path": "www/app/stores/BalancersStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as BalancerTypes from '../types/BalancerTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass BalancersStore extends EventEmitter {\n\t_balancers: BalancerTypes.BalancersRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: BalancerTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._balancers = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget balancers(): BalancerTypes.BalancersRo {\n\t\treturn this._balancers;\n\t}\n\n\tget balancersM(): BalancerTypes.Balancers {\n\t\tlet balancers: BalancerTypes.Balancers = [];\n\t\tthis._balancers.forEach((balancer: BalancerTypes.BalancerRo): void => {\n\t\t\tbalancers.push({\n\t\t\t\t...balancer,\n\t\t\t});\n\t\t});\n\t\treturn balancers;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): BalancerTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tbalancer(id: string): BalancerTypes.BalancerRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._balancers[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: BalancerTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(balancers: BalancerTypes.Balancer[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < balancers.length; i++) {\n\t\t\tbalancers[i] = Object.freeze(balancers[i]);\n\t\t\tthis._map[balancers[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._balancers = Object.freeze(balancers);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: BalancerTypes.BalancerDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase BalancerTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase BalancerTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase BalancerTypes.SYNC:\n\t\t\t\tthis._sync(action.data.balancers, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new BalancersStore();\n"
  },
  {
    "path": "www/app/stores/BlocksStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as BlockTypes from '../types/BlockTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass BlocksStore extends EventEmitter {\n\t_blocks: BlockTypes.BlocksRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: BlockTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._blocks = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget blocks(): BlockTypes.BlocksRo {\n\t\treturn this._blocks;\n\t}\n\n\tget blocksM(): BlockTypes.Blocks {\n\t\tlet blocks: BlockTypes.Blocks = [];\n\t\tthis._blocks.forEach((block: BlockTypes.BlockRo): void => {\n\t\t\tblocks.push({\n\t\t\t\t...block,\n\t\t\t});\n\t\t});\n\t\treturn blocks;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): BlockTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tblock(id: string): BlockTypes.BlockRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._blocks[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: BlockTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(blocks: BlockTypes.Block[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < blocks.length; i++) {\n\t\t\tblocks[i] = Object.freeze(blocks[i]);\n\t\t\tthis._map[blocks[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._blocks = Object.freeze(blocks);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: BlockTypes.BlockDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase BlockTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase BlockTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase BlockTypes.SYNC:\n\t\t\t\tthis._sync(action.data.blocks, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new BlocksStore();\n"
  },
  {
    "path": "www/app/stores/CertificatesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as CertificateTypes from '../types/CertificateTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass CertificatesStore extends EventEmitter {\n\t_certificates: CertificateTypes.CertificatesRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: CertificateTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._certificates = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget certificates(): CertificateTypes.CertificatesRo {\n\t\treturn this._certificates;\n\t}\n\n\tget certificatesM(): CertificateTypes.Certificates {\n\t\tlet certificates: CertificateTypes.Certificates = [];\n\t\tthis._certificates.forEach((certificate: CertificateTypes.CertificateRo): void => {\n\t\t\tcertificates.push({\n\t\t\t\t...certificate,\n\t\t\t});\n\t\t});\n\t\treturn certificates;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): CertificateTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tcertificate(id: string): CertificateTypes.CertificateRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._certificates[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: CertificateTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(certificates: CertificateTypes.Certificate[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < certificates.length; i++) {\n\t\t\tcertificates[i] = Object.freeze(certificates[i]);\n\t\t\tthis._map[certificates[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._certificates = Object.freeze(certificates);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: CertificateTypes.CertificateDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase CertificateTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase CertificateTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase CertificateTypes.SYNC:\n\t\t\t\tthis._sync(action.data.certificates, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new CertificatesStore();\n"
  },
  {
    "path": "www/app/stores/CompletionStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from \"../dispatcher/Dispatcher\"\nimport EventEmitter from \"../EventEmitter\"\nimport * as Router from '../Router';\nimport * as Constants from '../Constants';\nimport * as CompletionTypes from \"../types/CompletionTypes\"\nimport * as OrganizationTypes from \"../types/OrganizationTypes\"\nimport * as AuthorityTypes from \"../types/AuthorityTypes\"\nimport * as PolicyTypes from \"../types/PolicyTypes\"\nimport * as DomainTypes from \"../types/DomainTypes\"\nimport * as BalancerTypes from \"../types/BalancerTypes\"\nimport * as VpcTypes from \"../types/VpcTypes\"\nimport * as DatacenterTypes from \"../types/DatacenterTypes\"\nimport * as BlockTypes from \"../types/BlockTypes\"\nimport * as NodeTypes from \"../types/NodeTypes\"\nimport * as DiskTypes from \"../types/DiskTypes\"\nimport * as PoolTypes from \"../types/PoolTypes\"\nimport * as ZoneTypes from \"../types/ZoneTypes\"\nimport * as ShapeTypes from \"../types/ShapeTypes\"\nimport * as ImageTypes from \"../types/ImageTypes\"\nimport * as StorageTypes from \"../types/StorageTypes\"\nimport * as InstanceTypes from \"../types/InstanceTypes\"\nimport * as FirewallTypes from \"../types/FirewallTypes\"\nimport * as PlanTypes from \"../types/PlanTypes\"\nimport * as CertificateTypes from \"../types/CertificateTypes\"\nimport * as SecretTypes from \"../types/SecretTypes\"\nimport * as PodTypes from \"../types/PodTypes\"\nimport * as GlobalTypes from \"../types/GlobalTypes\"\n\nclass CompletionStore extends EventEmitter {\n\t_userOrg: string;\n\t_data: CompletionTypes.Completion = Object.freeze({})\n\t_map: CompletionTypes.CompletionMap = Object.freeze({})\n\t_filter: CompletionTypes.Filter = null;\n\t_token = Dispatcher.register((this._callback).bind(this))\n\n\t_reset(userOrg: string): void {\n\t\tthis._userOrg = userOrg;\n\t\tthis._data = Object.freeze({})\n\t\tthis._map = Object.freeze({})\n\t\tthis._filter = null\n\t\tthis.emitChange()\n\t}\n\n\tget userOrganization(): string {\n\t\treturn this._userOrg;\n\t}\n\n\tget completion(): CompletionTypes.Completion {\n\t\treturn this._data\n\t}\n\n\tget filter(): CompletionTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget organizations(): OrganizationTypes.OrganizationsRo {\n\t\treturn this._data.organizations || [];\n\t}\n\n\torganization(id: string): OrganizationTypes.OrganizationRo {\n\t\tlet index = this._map?.organizations?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.organizations[index];\n\t}\n\n\tget authorities(): AuthorityTypes.AuthoritiesRo {\n\t\treturn this._data.authorities || [];\n\t}\n\n\tauthority(id: string): AuthorityTypes.AuthorityRo {\n\t\tlet index = this._map?.authorities?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.authorities[index];\n\t}\n\n\tget policies(): PolicyTypes.PoliciesRo {\n\t\treturn this._data.policies || [];\n\t}\n\n\tpolicy(id: string): PolicyTypes.PolicyRo {\n\t\tlet index = this._map?.policies?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.policies[index];\n\t}\n\n\tget domains(): DomainTypes.DomainsRo {\n\t\treturn this._data.domains || [];\n\t}\n\n\tdomain(id: string): DomainTypes.DomainRo {\n\t\tlet index = this._map?.domains?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.domains[index];\n\t}\n\n\tget balancers(): BalancerTypes.BalancersRo {\n\t\treturn this._data.balancers || [];\n\t}\n\n\tbalancer(id: string): BalancerTypes.BalancerRo {\n\t\tlet index = this._map?.balancers?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.balancers[index];\n\t}\n\n\tget vpcs(): VpcTypes.VpcsRo {\n\t\treturn this._data.vpcs || [];\n\t}\n\n\tvpc(id: string): VpcTypes.VpcRo {\n\t\tlet index = this._map?.vpcs?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.vpcs[index];\n\t}\n\n\tget subnets(): VpcTypes.Subnet[] {\n\t\treturn this._data.subnets || [];\n\t}\n\n\tsubnet(id: string): VpcTypes.Subnet {\n\t\tlet index = this._map?.subnets?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.subnets[index];\n\t}\n\n\tget datacenters(): DatacenterTypes.DatacentersRo {\n\t\treturn this._data.datacenters || [];\n\t}\n\n\tdatacenter(id: string): DatacenterTypes.DatacenterRo {\n\t\tlet index = this._map?.datacenters?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.datacenters[index];\n\t}\n\n\tget blocks(): BlockTypes.BlocksRo {\n\t\treturn this._data.blocks || [];\n\t}\n\n\tblock(id: string): BlockTypes.BlockRo {\n\t\tlet index = this._map?.blocks?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.blocks[index];\n\t}\n\n\tget nodes(): NodeTypes.NodesRo {\n\t\treturn this._data.nodes || [];\n\t}\n\n\tnode(id: string): NodeTypes.NodeRo {\n\t\tlet index = this._map?.nodes?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.nodes[index];\n\t}\n\n\tget disks(): DiskTypes.DisksRo {\n\t\treturn this._data.disks || [];\n\t}\n\n\tdisk(id: string): DiskTypes.DiskRo {\n\t\tlet index = this._map?.disks?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.disks[index];\n\t}\n\n\tget pools(): PoolTypes.PoolsRo {\n\t\treturn this._data.pools || [];\n\t}\n\n\tpool(id: string): PoolTypes.PoolRo {\n\t\tlet index = this._map?.pools?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.pools[index];\n\t}\n\n\tget zones(): ZoneTypes.ZonesRo {\n\t\treturn this._data.zones || [];\n\t}\n\n\tzone(id: string): ZoneTypes.ZoneRo {\n\t\tlet index = this._map?.zones?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.zones[index];\n\t}\n\n\tget shapes(): ShapeTypes.ShapesRo {\n\t\treturn this._data.shapes || [];\n\t}\n\n\tshape(id: string): ShapeTypes.ShapeRo {\n\t\tlet index = this._map?.shapes?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.shapes[index];\n\t}\n\n\tget images(): ImageTypes.ImagesRo {\n\t\treturn this._data.images || [];\n\t}\n\n\timage(id: string): ImageTypes.ImageRo {\n\t\tlet index = this._map?.images?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.images[index];\n\t}\n\n\tget storages(): StorageTypes.StoragesRo {\n\t\treturn this._data.storages || [];\n\t}\n\n\tstorage(id: string): StorageTypes.StorageRo {\n\t\tlet index = this._map?.storages?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.storages[index];\n\t}\n\n\tget builds(): CompletionTypes.Build[] {\n\t\treturn this._data.builds || [];\n\t}\n\n\tbuild(id: string): CompletionTypes.Build {\n\t\tlet index = this._map?.builds?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.builds[index];\n\t}\n\n\tget instances(): InstanceTypes.InstancesRo {\n\t\treturn this._data.instances || [];\n\t}\n\n\tinstance(id: string): InstanceTypes.InstanceRo {\n\t\tlet index = this._map?.instances?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.instances[index];\n\t}\n\n\tget firewalls(): FirewallTypes.FirewallsRo {\n\t\treturn this._data.firewalls || [];\n\t}\n\n\tfirewall(id: string): FirewallTypes.FirewallRo {\n\t\tlet index = this._map?.firewalls?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.firewalls[index];\n\t}\n\n\tget plans(): PlanTypes.PlansRo {\n\t\treturn this._data.plans || [];\n\t}\n\n\tplan(id: string): PlanTypes.PlanRo {\n\t\tlet index = this._map?.plans?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.plans[index];\n\t}\n\n\tget certificates(): CertificateTypes.CertificatesRo {\n\t\treturn this._data.certificates || [];\n\t}\n\n\tcertificate(id: string): CertificateTypes.CertificateRo {\n\t\tlet index = this._map?.certificates?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.certificates[index];\n\t}\n\n\tget secrets(): SecretTypes.SecretsRo {\n\t\treturn this._data.secrets || [];\n\t}\n\n\tsecret(id: string): SecretTypes.SecretRo {\n\t\tlet index = this._map?.secrets?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.secrets[index];\n\t}\n\n\tget pods(): PodTypes.PodsRo {\n\t\treturn this._data.pods || [];\n\t}\n\n\tpod(id: string): PodTypes.PodRo {\n\t\tlet index = this._map?.pods?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.pods[index];\n\t}\n\n\tget units(): PodTypes.UnitsRo {\n\t\treturn this._data.units || [];\n\t}\n\n\tunit(id: string): PodTypes.UnitRo {\n\t\tlet index = this._map?.units?.[id];\n\t\tif (index === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._data.units[index];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE)\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback)\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback)\n\t}\n\n\t_filterCallback(filter: CompletionTypes.Filter): void {\n\t\tthis._filter = filter\n\t\tthis.emitChange()\n\t}\n\n\t_sync(completion: CompletionTypes.Completion): void {\n\t\tlet dataMap: {[key: string]: any} = {}\n\t\tlet subnets: VpcTypes.Subnet[] = []\n\n\t\tObject.entries(completion).forEach(([resource, items]) => {\n\t\t\tlet itemsMap: {[key: string]: number} = {}\n\t\t\tif (items) {\n\t\t\t\tif (resource === \"vpc\") {\n\t\t\t\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\t\t\t\titemsMap[items[i].id] = i\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\t\t\t\tsubnets.push(...(items[i].subnets || []) as VpcTypes.Subnet[])\n\t\t\t\t\t\titemsMap[items[i].id] = i\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tdataMap[resource] = itemsMap\n\t\t})\n\n\t\tlet subnetsMap: {[key: string]: any} = {}\n\t\tfor (let i = 0; i < subnets.length; i++) {\n\t\t\tsubnetsMap[subnets[i].id] = i\n\t\t}\n\t\tcompletion.subnets = subnets\n\t\tdataMap[\"subnets\"] = subnetsMap\n\n\t\tthis._data = Object.freeze(completion)\n\t\tthis._map = dataMap as CompletionTypes.CompletionMap\n\n\t\tif (Constants.user && !this._userOrg) {\n\t\t\tthis._userOrg = this._data?.organizations?.[0]?.id\n\t\t}\n\n\t\tthis.emitChange()\n\t}\n\n\t_callback(action: CompletionTypes.CompletionDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset(action.data.organization)\n\t\t\t\tbreak\n\n\t\t\tcase GlobalTypes.RELOAD:\n\t\t\t\tRouter.refresh()\n\t\t\t\tbreak;\n\n\t\t\tcase CompletionTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter)\n\t\t\t\tbreak\n\n\t\t\tcase CompletionTypes.SYNC:\n\t\t\t\tthis._sync(action.data.completion)\n\t\t\t\tbreak\n\t\t}\n\t}\n}\n\nexport default new CompletionStore()\n"
  },
  {
    "path": "www/app/stores/DatacentersStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as DatacenterTypes from '../types/DatacenterTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass DatacentersStore extends EventEmitter {\n\t_datacenters: DatacenterTypes.DatacentersRo = Object.freeze([]);\n\t_datacentersName: DatacenterTypes.DatacentersRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: DatacenterTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_mapName: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._datacenters = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget datacenters(): DatacenterTypes.DatacentersRo {\n\t\treturn this._datacenters;\n\t}\n\n\tget datacentersM(): DatacenterTypes.Datacenters {\n\t\tlet datacenters: DatacenterTypes.Datacenters = [];\n\t\tthis._datacenters.forEach((\n\t\t\tdatacenter: DatacenterTypes.DatacenterRo): void => {\n\n\t\t\tdatacenters.push({\n\t\t\t\t...datacenter,\n\t\t\t});\n\t\t});\n\t\treturn datacenters;\n\t}\n\n\tget datacentersName(): DatacenterTypes.DatacentersRo {\n\t\treturn this._datacentersName || [];\n\t}\n\n\tget datacentersNameM(): DatacenterTypes.Datacenters {\n\t\tlet datacenters: DatacenterTypes.Datacenters = [];\n\t\tthis._datacentersName.forEach((\n\t\t\tdatacenter: DatacenterTypes.DatacenterRo): void => {\n\n\t\t\tdatacenters.push({\n\t\t\t\t...datacenter,\n\t\t\t});\n\t\t});\n\t\treturn datacenters;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): DatacenterTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tdatacenter(id: string): DatacenterTypes.DatacenterRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._datacenters[i];\n\t}\n\n\tdatacenterName(id: string): DatacenterTypes.DatacenterRo {\n\t\tlet i = this._mapName[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._datacentersName[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: DatacenterTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(datacenters: DatacenterTypes.Datacenter[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < datacenters.length; i++) {\n\t\t\tdatacenters[i] = Object.freeze(datacenters[i]);\n\t\t\tthis._map[datacenters[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._datacenters = Object.freeze(datacenters);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_syncNames(datacenters: DatacenterTypes.Datacenter[]): void {\n\t\tthis._mapName = {};\n\t\tfor (let i = 0; i < datacenters.length; i++) {\n\t\t\tdatacenters[i] = Object.freeze(datacenters[i]);\n\t\t\tthis._mapName[datacenters[i].id] = i;\n\t\t}\n\n\t\tthis._datacentersName = Object.freeze(datacenters);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: DatacenterTypes.DatacenterDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase DatacenterTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase DatacenterTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase DatacenterTypes.SYNC:\n\t\t\t\tthis._sync(action.data.datacenters, action.data.count);\n\t\t\t\tbreak;\n\n\t\t\tcase DatacenterTypes.SYNC_NAMES:\n\t\t\t\tthis._syncNames(action.data.datacenters);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new DatacentersStore();\n"
  },
  {
    "path": "www/app/stores/DevicesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as DeviceTypes from '../types/DeviceTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass DevicesStore extends EventEmitter {\n\t_userId: string;\n\t_devices: DeviceTypes.DevicesRo = Object.freeze([]);\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\tget userId(): string {\n\t\treturn this._userId;\n\t}\n\n\tget devices(): DeviceTypes.DevicesRo {\n\t\treturn this._devices;\n\t}\n\n\tget devicesM(): DeviceTypes.Devices {\n\t\tlet devices: DeviceTypes.Devices = [];\n\t\tthis._devices.forEach((device: DeviceTypes.DeviceRo): void => {\n\t\t\tdevices.push({\n\t\t\t\t...device,\n\t\t\t});\n\t\t});\n\t\treturn devices;\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(userId: string, devices: DeviceTypes.Device[]): void {\n\t\tthis._userId = userId;\n\n\t\tfor (let i = 0; i < devices.length; i++) {\n\t\t\tdevices[i] = Object.freeze(devices[i]);\n\t\t}\n\n\t\tthis._devices = Object.freeze(devices);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: DeviceTypes.DeviceDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase DeviceTypes.SYNC:\n\t\t\t\tthis._sync(action.data.userId, action.data.devices);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new DevicesStore();\n"
  },
  {
    "path": "www/app/stores/DisksStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as DiskTypes from '../types/DiskTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass DisksStore extends EventEmitter {\n\t_disks: DiskTypes.DisksRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: DiskTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._disks = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget disks(): DiskTypes.DisksRo {\n\t\treturn this._disks;\n\t}\n\n\tget disksM(): DiskTypes.Disks {\n\t\tlet disks: DiskTypes.Disks = [];\n\t\tthis._disks.forEach((disk: DiskTypes.DiskRo): void => {\n\t\t\tdisks.push({\n\t\t\t\t...disk,\n\t\t\t});\n\t\t});\n\t\treturn disks;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): DiskTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tdisk(id: string): DiskTypes.DiskRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._disks[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: DiskTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(disks: DiskTypes.Disk[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < disks.length; i++) {\n\t\t\tdisks[i] = Object.freeze(disks[i]);\n\t\t\tthis._map[disks[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._disks = Object.freeze(disks);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: DiskTypes.DiskDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase DiskTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase DiskTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase DiskTypes.SYNC:\n\t\t\t\tthis._sync(action.data.disks, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new DisksStore();\n"
  },
  {
    "path": "www/app/stores/DomainsNameStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as DomainTypes from '../types/DomainTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass DomainsNameStore extends EventEmitter {\n\t_domains: DomainTypes.DomainsRo = Object.freeze([]);\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._domains = Object.freeze([]);\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget domains(): DomainTypes.DomainsRo {\n\t\treturn this._domains;\n\t}\n\n\tget domainsM(): DomainTypes.Domains {\n\t\tlet domains: DomainTypes.Domains = [];\n\t\tthis._domains.forEach((\n\t\t\t\tdomain: DomainTypes.DomainRo): void => {\n\t\t\tdomains.push({\n\t\t\t\t...domain,\n\t\t\t});\n\t\t});\n\t\treturn domains;\n\t}\n\n\tdomain(id: string): DomainTypes.DomainRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._domains[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(domains: DomainTypes.Domain[]): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < domains.length; i++) {\n\t\t\tdomains[i] = Object.freeze(domains[i]);\n\t\t\tthis._map[domains[i].id] = i;\n\t\t}\n\n\t\tthis._domains = Object.freeze(domains);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: DomainTypes.DomainDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase DomainTypes.SYNC_NAME:\n\t\t\t\tthis._sync(action.data.domains);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new DomainsNameStore();\n"
  },
  {
    "path": "www/app/stores/DomainsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as DomainTypes from '../types/DomainTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass DomainsStore extends EventEmitter {\n\t_domains: DomainTypes.DomainsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: DomainTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._domains = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget domains(): DomainTypes.DomainsRo {\n\t\treturn this._domains;\n\t}\n\n\tget domainsM(): DomainTypes.Domains {\n\t\tlet domains: DomainTypes.Domains = [];\n\t\tthis._domains.forEach((domain: DomainTypes.DomainRo): void => {\n\t\t\tdomains.push({\n\t\t\t\t...domain,\n\t\t\t});\n\t\t});\n\t\treturn domains;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): DomainTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tdomain(id: string): DomainTypes.DomainRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._domains[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: DomainTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(domains: DomainTypes.Domain[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < domains.length; i++) {\n\t\t\tdomains[i] = Object.freeze(domains[i]);\n\t\t\tthis._map[domains[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._domains = Object.freeze(domains);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: DomainTypes.DomainDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase DomainTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase DomainTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase DomainTypes.SYNC:\n\t\t\t\tthis._sync(action.data.domains, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new DomainsStore();\n"
  },
  {
    "path": "www/app/stores/FirewallsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as FirewallTypes from '../types/FirewallTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass FirewallsStore extends EventEmitter {\n\t_firewalls: FirewallTypes.FirewallsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: FirewallTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._firewalls = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget firewalls(): FirewallTypes.FirewallsRo {\n\t\treturn this._firewalls;\n\t}\n\n\tget firewallsM(): FirewallTypes.Firewalls {\n\t\tlet firewalls: FirewallTypes.Firewalls = [];\n\t\tthis._firewalls.forEach((firewall: FirewallTypes.FirewallRo): void => {\n\t\t\tfirewalls.push({\n\t\t\t\t...firewall,\n\t\t\t});\n\t\t});\n\t\treturn firewalls;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): FirewallTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tfirewall(id: string): FirewallTypes.FirewallRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._firewalls[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: FirewallTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(firewalls: FirewallTypes.Firewall[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < firewalls.length; i++) {\n\t\t\tfirewalls[i] = Object.freeze(firewalls[i]);\n\t\t\tthis._map[firewalls[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._firewalls = Object.freeze(firewalls);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: FirewallTypes.FirewallDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase FirewallTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase FirewallTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase FirewallTypes.SYNC:\n\t\t\t\tthis._sync(action.data.firewalls, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new FirewallsStore();\n"
  },
  {
    "path": "www/app/stores/ImagesDatacenterStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass ImagesDatacenterStore extends EventEmitter {\n\t_images: ImageTypes.ImagesRo = Object.freeze([]);\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._images = Object.freeze([]);\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget images(): ImageTypes.ImagesRo {\n\t\treturn this._images;\n\t}\n\n\tget imagesM(): ImageTypes.Images {\n\t\tlet images: ImageTypes.Images = [];\n\t\tthis._images.forEach((\n\t\t\t\timage: ImageTypes.ImageRo): void => {\n\t\t\timages.push({\n\t\t\t\t...image,\n\t\t\t});\n\t\t});\n\t\treturn images;\n\t}\n\n\timage(id: string): ImageTypes.ImageRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._images[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(images: ImageTypes.Image[]): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < images.length; i++) {\n\t\t\timages[i] = Object.freeze(images[i]);\n\t\t\tthis._map[images[i].id] = i;\n\t\t}\n\n\t\tthis._images = Object.freeze(images);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: ImageTypes.ImageDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase ImageTypes.SYNC_DATACENTER:\n\t\t\t\tthis._sync(action.data.images);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new ImagesDatacenterStore();\n"
  },
  {
    "path": "www/app/stores/ImagesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass ImagesStore extends EventEmitter {\n\t_images: ImageTypes.ImagesRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: ImageTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._images = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget images(): ImageTypes.ImagesRo {\n\t\treturn this._images;\n\t}\n\n\tget imagesM(): ImageTypes.Images {\n\t\tlet images: ImageTypes.Images = [];\n\t\tthis._images.forEach((image: ImageTypes.ImageRo): void => {\n\t\t\timages.push({\n\t\t\t\t...image,\n\t\t\t});\n\t\t});\n\t\treturn images;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): ImageTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\timage(id: string): ImageTypes.ImageRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._images[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: ImageTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(images: ImageTypes.Image[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < images.length; i++) {\n\t\t\timages[i] = Object.freeze(images[i]);\n\t\t\tthis._map[images[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._images = Object.freeze(images);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: ImageTypes.ImageDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase ImageTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase ImageTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase ImageTypes.SYNC:\n\t\t\t\tthis._sync(action.data.images, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new ImagesStore();\n"
  },
  {
    "path": "www/app/stores/InstancesNodeStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass InstancesNodeStore extends EventEmitter {\n\t_instances: InstanceTypes.InstancesNodeRo = new Map<string, InstanceTypes.InstancesRo>(Object.freeze([]));\n\t_map: {[key: string]: [string, number]} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._instances = new Map<string, InstanceTypes.InstancesRo>(\n\t\t\tObject.freeze([]));\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tinstances(scope: string): InstanceTypes.InstancesRo {\n\t\treturn this._instances.get(scope) || [];\n\t}\n\n\tinstance(id: string): InstanceTypes.InstanceRo {\n\t\tlet x = this._map[id];\n\t\tif (x === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._instances.get(x[0])[x[1]];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(scope: string, instances: InstanceTypes.Instance[]): void {\n\n\t\tfor (let i = 0; i < instances.length; i++) {\n\t\t\tinstances[i] = Object.freeze(instances[i]);\n\t\t}\n\t\tthis._instances.set(scope, Object.freeze(instances));\n\n\t\tthis._map = {};\n\t\tfor (let item of this._instances.entries()) {\n\t\t\tlet insts = item[1];\n\n\t\t\tfor (let i = 0; i < insts.length; i++) {\n\t\t\t\tthis._map[insts[i].id] = [item[0], i];\n\t\t\t}\n\t\t}\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: InstanceTypes.InstanceDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase InstanceTypes.SYNC_NODE:\n\t\t\t\tthis._sync(action.data.scope, action.data.instances);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new InstancesNodeStore();\n"
  },
  {
    "path": "www/app/stores/InstancesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as InstanceTypes from '../types/InstanceTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass InstancesStore extends EventEmitter {\n\t_instances: InstanceTypes.InstancesRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: InstanceTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._instances = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget instances(): InstanceTypes.InstancesRo {\n\t\treturn this._instances;\n\t}\n\n\tget instancesM(): InstanceTypes.Instances {\n\t\tlet instances: InstanceTypes.Instances = [];\n\t\tthis._instances.forEach((instance: InstanceTypes.InstanceRo): void => {\n\t\t\tinstances.push({\n\t\t\t\t...instance,\n\t\t\t});\n\t\t});\n\t\treturn instances;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): InstanceTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tinstance(id: string): InstanceTypes.InstanceRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._instances[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: InstanceTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(instances: InstanceTypes.Instance[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < instances.length; i++) {\n\t\t\tinstances[i] = Object.freeze(instances[i]);\n\t\t\tthis._map[instances[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._instances = Object.freeze(instances);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: InstanceTypes.InstanceDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase InstanceTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase InstanceTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase InstanceTypes.SYNC:\n\t\t\t\tthis._sync(action.data.instances, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new InstancesStore();\n"
  },
  {
    "path": "www/app/stores/LoadingStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as LoadingTypes from '../types/LoadingTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass LoadingStore extends EventEmitter {\n\t_loaders: Set<string> = new Set();\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\tget loading(): boolean {\n\t\treturn !!this._loaders.size;\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_add(id: string): void {\n\t\tthis._loaders.add(id);\n\t\tthis.emitChange();\n\t}\n\n\t_done(id: string): void {\n\t\tthis._loaders.delete(id);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: LoadingTypes.LoadingDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase LoadingTypes.ADD:\n\t\t\t\tthis._add(action.data.id);\n\t\t\t\tbreak;\n\n\t\t\tcase LoadingTypes.DONE:\n\t\t\t\tthis._done(action.data.id);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new LoadingStore();\n"
  },
  {
    "path": "www/app/stores/LogsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as LogTypes from '../types/LogTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass LogsStore extends EventEmitter {\n\t_logs: LogTypes.LogsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: LogTypes.Filter = null;\n\t_count: number;\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._logs = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis.emitChange();\n\t}\n\n\tget logs(): LogTypes.LogsRo {\n\t\treturn this._logs;\n\t}\n\n\tget logsM(): LogTypes.Logs {\n\t\tlet logs: LogTypes.Logs = [];\n\t\tthis._logs.forEach((log: LogTypes.LogRo): void => {\n\t\t\tlogs.push({\n\t\t\t\t...log,\n\t\t\t});\n\t\t});\n\t\treturn logs;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 50;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): LogTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: LogTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.level !== this._filter.level\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(logs: LogTypes.Log[], count: number): void {\n\t\tfor (let i = 0; i < logs.length; i++) {\n\t\t\tlogs[i] = Object.freeze(logs[i]);\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._logs = Object.freeze(logs);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: LogTypes.LogDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase LogTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase LogTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase LogTypes.SYNC:\n\t\t\t\tthis._sync(action.data.logs, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new LogsStore();\n"
  },
  {
    "path": "www/app/stores/NodesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass NodesStore extends EventEmitter {\n\t_nodes: NodeTypes.NodesRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: NodeTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._nodes = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget nodes(): NodeTypes.NodesRo {\n\t\treturn this._nodes;\n\t}\n\n\tget nodesM(): NodeTypes.Nodes {\n\t\tlet nodes: NodeTypes.Nodes = [];\n\t\tthis._nodes.forEach((node: NodeTypes.NodeRo): void => {\n\t\t\tnodes.push({\n\t\t\t\t...node,\n\t\t\t});\n\t\t});\n\t\treturn nodes;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): NodeTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tnode(id: string): NodeTypes.NodeRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._nodes[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: NodeTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(nodes: NodeTypes.Node[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < nodes.length; i++) {\n\t\t\tnodes[i] = Object.freeze(nodes[i]);\n\t\t\tthis._map[nodes[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._nodes = Object.freeze(nodes);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: NodeTypes.NodeDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase NodeTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase NodeTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase NodeTypes.SYNC:\n\t\t\t\tthis._sync(action.data.nodes, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new NodesStore();\n"
  },
  {
    "path": "www/app/stores/NodesZoneStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as NodeTypes from '../types/NodeTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass NodesZoneStore extends EventEmitter {\n\t_nodes: NodeTypes.NodesRo = Object.freeze([]);\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._nodes = Object.freeze([]);\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget nodes(): NodeTypes.NodesRo {\n\t\treturn this._nodes;\n\t}\n\n\tget nodesM(): NodeTypes.Nodes {\n\t\tlet nodes: NodeTypes.Nodes = [];\n\t\tthis._nodes.forEach((\n\t\t\t\tnode: NodeTypes.NodeRo): void => {\n\t\t\tnodes.push({\n\t\t\t\t...node,\n\t\t\t});\n\t\t});\n\t\treturn nodes;\n\t}\n\n\tnode(id: string): NodeTypes.NodeRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._nodes[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(nodes: NodeTypes.Node[]): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < nodes.length; i++) {\n\t\t\tnodes[i] = Object.freeze(nodes[i]);\n\t\t\tthis._map[nodes[i].id] = i;\n\t\t}\n\n\t\tthis._nodes = Object.freeze(nodes);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: NodeTypes.NodeDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase NodeTypes.SYNC_ZONE:\n\t\t\t\tthis._sync(action.data.nodes);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new NodesZoneStore();\n"
  },
  {
    "path": "www/app/stores/OrganizationsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as Constants from '../Constants';\nimport * as OrganizationTypes from '../types/OrganizationTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass OrganizationsStore extends EventEmitter {\n\t_organizations: OrganizationTypes.OrganizationsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: OrganizationTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._organizations = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget organizations(): OrganizationTypes.OrganizationsRo {\n\t\treturn this._organizations;\n\t}\n\n\tget organizationsM(): OrganizationTypes.Organizations {\n\t\tlet organizations: OrganizationTypes.Organizations = [];\n\t\tthis._organizations.forEach((organization: OrganizationTypes.OrganizationRo): void => {\n\t\t\torganizations.push({\n\t\t\t\t...organization,\n\t\t\t});\n\t\t});\n\t\treturn organizations;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): OrganizationTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\torganization(id: string): OrganizationTypes.OrganizationRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._organizations[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: OrganizationTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(organizations: OrganizationTypes.Organization[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < organizations.length; i++) {\n\t\t\torganizations[i] = Object.freeze(organizations[i]);\n\t\t\tthis._map[organizations[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._organizations = Object.freeze(organizations);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: OrganizationTypes.OrganizationDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase OrganizationTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase OrganizationTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase OrganizationTypes.SYNC:\n\t\t\t\tthis._sync(action.data.organizations, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new OrganizationsStore();\n"
  },
  {
    "path": "www/app/stores/PlansStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as PlanTypes from '../types/PlanTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass PlansStore extends EventEmitter {\n\t_plans: PlanTypes.PlansRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: PlanTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._plans = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget plans(): PlanTypes.PlansRo {\n\t\treturn this._plans;\n\t}\n\n\tget plansM(): PlanTypes.Plans {\n\t\tlet plans: PlanTypes.Plans = [];\n\t\tthis._plans.forEach((plan: PlanTypes.PlanRo): void => {\n\t\t\tplans.push({\n\t\t\t\t...plan,\n\t\t\t});\n\t\t});\n\t\treturn plans;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): PlanTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tplan(id: string): PlanTypes.PlanRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._plans[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: PlanTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(plans: PlanTypes.Plan[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < plans.length; i++) {\n\t\t\tplans[i] = Object.freeze(plans[i]);\n\t\t\tthis._map[plans[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._plans = Object.freeze(plans);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: PlanTypes.PlanDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase PlanTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase PlanTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase PlanTypes.SYNC:\n\t\t\t\tthis._sync(action.data.plans, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new PlansStore();\n"
  },
  {
    "path": "www/app/stores/PodsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as PodTypes from '../types/PodTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass PodsStore extends EventEmitter {\n\t_pods: PodTypes.PodsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: PodTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_drafts: {[key: string]: PodTypes.Unit[]} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._pods = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis._drafts = {};\n\t\tthis.emitChange();\n\t}\n\n\tget pods(): PodTypes.PodsRo {\n\t\treturn this._pods;\n\t}\n\n\tget podsM(): PodTypes.Pods {\n\t\tlet pods: PodTypes.Pods = [];\n\t\tthis._pods.forEach((pod: PodTypes.PodRo): void => {\n\t\t\tpods.push({\n\t\t\t\t...pod,\n\t\t\t});\n\t\t});\n\t\treturn pods;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): PodTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tpod(id: string): PodTypes.PodRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._pods[i];\n\t}\n\n\tgetDrafts(id: string): PodTypes.Unit[] {\n\t\tlet drafts = this._drafts[id];\n\t\tif (!drafts) {\n\t\t\tdrafts = this.pod(id)?.drafts\n\t\t}\n\t\treturn drafts\n\t}\n\n\tsetDrafts(id: string, drafts: PodTypes.Unit[]) {\n\t\tthis._drafts[id] = drafts;\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\taddChangeListen(callback: () => void): void {\n\t\tthis.once(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: PodTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(pods: PodTypes.Pod[], count: number): void {\n\t\tthis._map = {};\n\t\tthis._drafts = {};\n\t\tfor (let i = 0; i < pods.length; i++) {\n\t\t\tpods[i] = Object.freeze(pods[i]);\n\t\t\tthis._map[pods[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._pods = Object.freeze(pods);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: PodTypes.PodDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase PodTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase PodTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase PodTypes.SYNC:\n\t\t\t\tthis._sync(action.data.pods, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new PodsStore();\n"
  },
  {
    "path": "www/app/stores/PodsUnitStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as PodTypes from '../types/PodTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass PodsUnitStore extends EventEmitter {\n\t_units: {[key: string]: PodTypes.PodUnit} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._units = {};\n\t\tthis.emitChange();\n\t}\n\n\tunit(unitId: string): PodTypes.PodUnit {\n\t\treturn this._units[unitId];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(unit: PodTypes.PodUnit): void {\n\t\tthis._units[unit.id] = Object.freeze(unit);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: PodTypes.PodUnitDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase PodTypes.SYNC_UNIT:\n\t\t\t\tthis._sync(action.data.unit);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new PodsUnitStore();\n"
  },
  {
    "path": "www/app/stores/PoliciesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as PolicyTypes from '../types/PolicyTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass PoliciesStore extends EventEmitter {\n\t_policies: PolicyTypes.PoliciesRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: PolicyTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._policies = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget policies(): PolicyTypes.PoliciesRo {\n\t\treturn this._policies;\n\t}\n\n\tget policiesM(): PolicyTypes.Policies {\n\t\tlet policies: PolicyTypes.Policies = [];\n\t\tthis._policies.forEach((policy: PolicyTypes.PolicyRo): void => {\n\t\t\tpolicies.push({\n\t\t\t\t...policy,\n\t\t\t});\n\t\t});\n\t\treturn policies;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): PolicyTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tpolicy(id: string): PolicyTypes.PolicyRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._policies[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: PolicyTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(policies: PolicyTypes.Policy[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < policies.length; i++) {\n\t\t\tpolicies[i] = Object.freeze(policies[i]);\n\t\t\tthis._map[policies[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._policies = Object.freeze(policies);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: PolicyTypes.PolicyDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase PolicyTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase PolicyTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase PolicyTypes.SYNC:\n\t\t\t\tthis._sync(action.data.policies, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new PoliciesStore();\n"
  },
  {
    "path": "www/app/stores/PoolsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as PoolTypes from '../types/PoolTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass PoolsStore extends EventEmitter {\n\t_pools: PoolTypes.PoolsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: PoolTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._pools = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget pools(): PoolTypes.PoolsRo {\n\t\treturn this._pools;\n\t}\n\n\tget poolsM(): PoolTypes.Pools {\n\t\tlet pools: PoolTypes.Pools = [];\n\t\tthis._pools.forEach((pool: PoolTypes.PoolRo): void => {\n\t\t\tpools.push({\n\t\t\t\t...pool,\n\t\t\t});\n\t\t});\n\t\treturn pools;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): PoolTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tpool(id: string): PoolTypes.PoolRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._pools[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: PoolTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(pools: PoolTypes.Pool[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < pools.length; i++) {\n\t\t\tpools[i] = Object.freeze(pools[i]);\n\t\t\tthis._map[pools[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._pools = Object.freeze(pools);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: PoolTypes.PoolDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase PoolTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase PoolTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase PoolTypes.SYNC:\n\t\t\t\tthis._sync(action.data.pools, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new PoolsStore();\n"
  },
  {
    "path": "www/app/stores/SecretsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as SecretTypes from '../types/SecretTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass SecretsStore extends EventEmitter {\n\t_secrets: SecretTypes.SecretsRo = Object.freeze([]);\n\t_secretsName: SecretTypes.SecretsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: SecretTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_mapName: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._secrets = Object.freeze([]);\n\t\tthis._secretsName = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis._mapName = {};\n\t\tthis.emitChange();\n\t}\n\n\tget secrets(): SecretTypes.SecretsRo {\n\t\treturn this._secrets;\n\t}\n\n\tget secretsM(): SecretTypes.Secrets {\n\t\tlet secrets: SecretTypes.Secrets = [];\n\t\tthis._secrets.forEach((secret: SecretTypes.SecretRo): void => {\n\t\t\tsecrets.push({\n\t\t\t\t...secret,\n\t\t\t});\n\t\t});\n\t\treturn secrets;\n\t}\n\n\tget secretsName(): SecretTypes.SecretsRo {\n\t\treturn this._secretsName || [];\n\t}\n\n\tget secretsNameM(): SecretTypes.Secrets {\n\t\tlet secrets: SecretTypes.Secrets = [];\n\t\tthis._secretsName.forEach((\n\t\t\tsecret: SecretTypes.SecretRo): void => {\n\n\t\t\tsecrets.push({\n\t\t\t\t...secret,\n\t\t\t});\n\t\t});\n\t\treturn secrets;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): SecretTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tsecret(id: string): SecretTypes.SecretRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._secrets[i];\n\t}\n\n\tsecretName(id: string): SecretTypes.SecretRo {\n\t\tlet i = this._mapName[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._secretsName[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: SecretTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(secrets: SecretTypes.Secret[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < secrets.length; i++) {\n\t\t\tsecrets[i] = Object.freeze(secrets[i]);\n\t\t\tthis._map[secrets[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._secrets = Object.freeze(secrets);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_syncNames(secrets: SecretTypes.Secret[]): void {\n\t\tthis._mapName = {};\n\t\tfor (let i = 0; i < secrets.length; i++) {\n\t\t\tsecrets[i] = Object.freeze(secrets[i]);\n\t\t\tthis._mapName[secrets[i].id] = i;\n\t\t}\n\n\t\tthis._secretsName = Object.freeze(secrets);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: SecretTypes.SecretDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase SecretTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase SecretTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase SecretTypes.SYNC:\n\t\t\t\tthis._sync(action.data.secrets, action.data.count);\n\t\t\t\tbreak;\n\n\t\t\tcase SecretTypes.SYNC_NAMES:\n\t\t\t\tthis._syncNames(action.data.secrets);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new SecretsStore();\n"
  },
  {
    "path": "www/app/stores/SessionsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as SessionTypes from '../types/SessionTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass SessionsStore extends EventEmitter {\n\t_userId: string;\n\t_showRemoved: boolean;\n\t_sessions: SessionTypes.SessionsRo = Object.freeze([]);\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._userId = undefined;\n\t\tthis._showRemoved = undefined;\n\t\tthis._sessions = Object.freeze([]);\n\t\tthis.emitChange();\n\t}\n\n\tget userId(): string {\n\t\treturn this._userId;\n\t}\n\n\tget sessions(): SessionTypes.SessionsRo {\n\t\treturn this._sessions;\n\t}\n\n\tget sessionsM(): SessionTypes.Sessions {\n\t\tlet sessions: SessionTypes.Sessions = [];\n\t\tthis._sessions.forEach((session: SessionTypes.SessionRo): void => {\n\t\t\tsessions.push({\n\t\t\t\t...session,\n\t\t\t});\n\t\t});\n\t\treturn sessions;\n\t}\n\n\tget showRemoved(): boolean {\n\t\treturn this._showRemoved;\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(userId: string, sessions: SessionTypes.Session[]): void {\n\t\tthis._userId = userId;\n\n\t\tfor (let i = 0; i < sessions.length; i++) {\n\t\t\tsessions[i] = Object.freeze(sessions[i]);\n\t\t}\n\n\t\tthis._sessions = Object.freeze(sessions);\n\t\tthis.emitChange();\n\t}\n\n\t_setShowRemoved(state: boolean): void {\n\t\tthis._showRemoved = state;\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: SessionTypes.SessionDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase SessionTypes.SYNC:\n\t\t\t\tthis._sync(action.data.userId, action.data.sessions);\n\t\t\t\tbreak;\n\t\t\tcase SessionTypes.SHOW_REMOVED:\n\t\t\t\tthis._setShowRemoved(action.data.showRemoved);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new SessionsStore();\n"
  },
  {
    "path": "www/app/stores/SettingsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as SettingsTypes from '../types/SettingsTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass SettingsStore extends EventEmitter {\n\t_settings: SettingsTypes.SettingsRo;\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._settings = undefined;\n\t\tthis.emitChange();\n\t}\n\n\tget settings(): SettingsTypes.SettingsRo {\n\t\treturn this._settings;\n\t}\n\n\tget settingsM(): SettingsTypes.Settings {\n\t\tif (this._settings) {\n\t\t\treturn {\n\t\t\t\t...this._settings,\n\t\t\t};\n\t\t}\n\t\treturn undefined;\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(settings: SettingsTypes.Settings): void {\n\t\tthis._settings = Object.freeze(settings);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: SettingsTypes.SettingsDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase SettingsTypes.SYNC:\n\t\t\t\tthis._sync(action.data);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new SettingsStore();\n"
  },
  {
    "path": "www/app/stores/ShapesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as ShapeTypes from '../types/ShapeTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass ShapesStore extends EventEmitter {\n\t_shapes: ShapeTypes.ShapesRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: ShapeTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._shapes = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget shapes(): ShapeTypes.ShapesRo {\n\t\treturn this._shapes;\n\t}\n\n\tget shapesM(): ShapeTypes.Shapes {\n\t\tlet shapes: ShapeTypes.Shapes = [];\n\t\tthis._shapes.forEach((shape: ShapeTypes.ShapeRo): void => {\n\t\t\tshapes.push({\n\t\t\t\t...shape,\n\t\t\t});\n\t\t});\n\t\treturn shapes;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): ShapeTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tshape(id: string): ShapeTypes.ShapeRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._shapes[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: ShapeTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(shapes: ShapeTypes.Shape[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < shapes.length; i++) {\n\t\t\tshapes[i] = Object.freeze(shapes[i]);\n\t\t\tthis._map[shapes[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._shapes = Object.freeze(shapes);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: ShapeTypes.ShapeDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase ShapeTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase ShapeTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase ShapeTypes.SYNC:\n\t\t\t\tthis._sync(action.data.shapes, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new ShapesStore();\n"
  },
  {
    "path": "www/app/stores/StoragesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as StorageTypes from '../types/StorageTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass StoragesStore extends EventEmitter {\n\t_storages: StorageTypes.StoragesRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: StorageTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._storages = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget storages(): StorageTypes.StoragesRo {\n\t\treturn this._storages;\n\t}\n\n\tget storagesM(): StorageTypes.Storages {\n\t\tlet storages: StorageTypes.Storages = [];\n\t\tthis._storages.forEach((storage: StorageTypes.StorageRo): void => {\n\t\t\tstorages.push({\n\t\t\t\t...storage,\n\t\t\t});\n\t\t});\n\t\treturn storages;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): StorageTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tstorage(id: string): StorageTypes.StorageRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._storages[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: StorageTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(storages: StorageTypes.Storage[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < storages.length; i++) {\n\t\t\tstorages[i] = Object.freeze(storages[i]);\n\t\t\tthis._map[storages[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._storages = Object.freeze(storages);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: StorageTypes.StorageDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase StorageTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase StorageTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase StorageTypes.SYNC:\n\t\t\t\tthis._sync(action.data.storages, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new StoragesStore();\n"
  },
  {
    "path": "www/app/stores/SubscriptionStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as SubscriptionTypes from '../types/SubscriptionTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass SubscriptionStore extends EventEmitter {\n\t_subscription: SubscriptionTypes.SubscriptionRo;\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\tget subscription(): SubscriptionTypes.SubscriptionRo {\n\t\treturn this._subscription;\n\t}\n\n\tget subscriptionM(): SubscriptionTypes.Subscription {\n\t\tif (this._subscription) {\n\t\t\treturn {\n\t\t\t\t...this._subscription,\n\t\t\t};\n\t\t}\n\t\treturn undefined;\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(subscription: SubscriptionTypes.Subscription): void {\n\t\tthis._subscription = Object.freeze(subscription);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: SubscriptionTypes.SubscriptionDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase SubscriptionTypes.SYNC:\n\t\t\t\tthis._sync(action.data);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new SubscriptionStore();\n"
  },
  {
    "path": "www/app/stores/UserStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as UserTypes from '../types/UserTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass UserStore extends EventEmitter {\n\t_user: UserTypes.UserRo;\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._user = undefined;\n\t\tthis.emitChange();\n\t}\n\n\tget user(): UserTypes.UserRo {\n\t\treturn this._user;\n\t}\n\n\tget userM(): UserTypes.User {\n\t\tif (this._user) {\n\t\t\treturn {\n\t\t\t\t...this._user,\n\t\t\t};\n\t\t}\n\t\treturn undefined;\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_load(user: UserTypes.User): void {\n\t\tthis._user = Object.freeze(user);\n\t\tthis.emitChange();\n\t}\n\n\t_unload(): void {\n\t\tthis._user = null;\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: UserTypes.UserDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase UserTypes.LOAD:\n\t\t\t\tthis._load(action.data.user);\n\t\t\t\tbreak;\n\n\t\t\tcase UserTypes.UNLOAD:\n\t\t\t\tthis._unload();\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new UserStore();\n"
  },
  {
    "path": "www/app/stores/UsersStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as UserTypes from '../types/UserTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass UsersStore extends EventEmitter {\n\t_users: UserTypes.UsersRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: UserTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._users = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis.emitChange();\n\t}\n\n\tget users(): UserTypes.UsersRo {\n\t\treturn this._users;\n\t}\n\n\tget usersM(): UserTypes.Users {\n\t\tlet users: UserTypes.Users = [];\n\t\tthis._users.forEach((user: UserTypes.UserRo): void => {\n\t\t\tusers.push({\n\t\t\t\t...user,\n\t\t\t});\n\t\t});\n\t\treturn users;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 50;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): UserTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tuser(id: string): UserTypes.UserRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._users[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: UserTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.type !== this._filter.type ||\n\t\t\t\t\tfilter.administrator !== this._filter.administrator ||\n\t\t\t\t\tfilter.disabled !== this._filter.disabled ||\n\t\t\t\t\tfilter.role !== this._filter.role\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(users: UserTypes.User[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < users.length; i++) {\n\t\t\tusers[i] = Object.freeze(users[i]);\n\t\t\tthis._map[users[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._users = Object.freeze(users);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: UserTypes.UserDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase UserTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase UserTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase UserTypes.SYNC:\n\t\t\t\tthis._sync(action.data.users, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new UsersStore();\n"
  },
  {
    "path": "www/app/stores/VpcsNameStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass VpcsZoneStore extends EventEmitter {\n\t_vpcs: VpcTypes.VpcsRo = Object.freeze([]);\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._vpcs = Object.freeze([]);\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget vpcs(): VpcTypes.VpcsRo {\n\t\treturn this._vpcs;\n\t}\n\n\tget vpcsM(): VpcTypes.Vpcs {\n\t\tlet vpcs: VpcTypes.Vpcs = [];\n\t\tthis._vpcs.forEach((\n\t\t\t\tvpc: VpcTypes.VpcRo): void => {\n\t\t\tvpcs.push({\n\t\t\t\t...vpc,\n\t\t\t});\n\t\t});\n\t\treturn vpcs;\n\t}\n\n\tvpc(id: string): VpcTypes.VpcRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._vpcs[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_sync(vpcs: VpcTypes.Vpc[]): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < vpcs.length; i++) {\n\t\t\tvpcs[i] = Object.freeze(vpcs[i]);\n\t\t\tthis._map[vpcs[i].id] = i;\n\t\t}\n\n\t\tthis._vpcs = Object.freeze(vpcs);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: VpcTypes.VpcDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase VpcTypes.SYNC_NAMES:\n\t\t\t\tthis._sync(action.data.vpcs);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new VpcsZoneStore();\n"
  },
  {
    "path": "www/app/stores/VpcsStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as VpcTypes from '../types/VpcTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass VpcsStore extends EventEmitter {\n\t_vpcs: VpcTypes.VpcsRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: VpcTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._vpcs = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis.emitChange();\n\t}\n\n\tget vpcs(): VpcTypes.VpcsRo {\n\t\treturn this._vpcs;\n\t}\n\n\tget vpcsM(): VpcTypes.Vpcs {\n\t\tlet vpcs: VpcTypes.Vpcs = [];\n\t\tthis._vpcs.forEach((vpc: VpcTypes.VpcRo): void => {\n\t\t\tvpcs.push({\n\t\t\t\t...vpc,\n\t\t\t});\n\t\t});\n\t\treturn vpcs;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): VpcTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tvpc(id: string): VpcTypes.VpcRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._vpcs[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: VpcTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(vpcs: VpcTypes.Vpc[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < vpcs.length; i++) {\n\t\t\tvpcs[i] = Object.freeze(vpcs[i]);\n\t\t\tthis._map[vpcs[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._vpcs = Object.freeze(vpcs);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: VpcTypes.VpcDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase VpcTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase VpcTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase VpcTypes.SYNC:\n\t\t\t\tthis._sync(action.data.vpcs, action.data.count);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new VpcsStore();\n"
  },
  {
    "path": "www/app/stores/ZonesStore.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport Dispatcher from '../dispatcher/Dispatcher';\nimport EventEmitter from '../EventEmitter';\nimport * as ZoneTypes from '../types/ZoneTypes';\nimport * as GlobalTypes from '../types/GlobalTypes';\n\nclass ZonesStore extends EventEmitter {\n\t_zones: ZoneTypes.ZonesRo = Object.freeze([]);\n\t_zonesName: ZoneTypes.ZonesRo = Object.freeze([]);\n\t_page: number;\n\t_pageCount: number;\n\t_filter: ZoneTypes.Filter = null;\n\t_count: number;\n\t_map: {[key: string]: number} = {};\n\t_mapName: {[key: string]: number} = {};\n\t_token = Dispatcher.register((this._callback).bind(this));\n\n\t_reset(): void {\n\t\tthis._zones = Object.freeze([]);\n\t\tthis._zonesName = Object.freeze([]);\n\t\tthis._page = undefined;\n\t\tthis._pageCount = undefined;\n\t\tthis._filter = null;\n\t\tthis._count = undefined;\n\t\tthis._map = {};\n\t\tthis._mapName = {};\n\t\tthis.emitChange();\n\t}\n\n\tget zones(): ZoneTypes.ZonesRo {\n\t\treturn this._zones;\n\t}\n\n\tget zonesM(): ZoneTypes.Zones {\n\t\tlet zones: ZoneTypes.Zones = [];\n\t\tthis._zones.forEach((zone: ZoneTypes.ZoneRo): void => {\n\t\t\tzones.push({\n\t\t\t\t...zone,\n\t\t\t});\n\t\t});\n\t\treturn zones;\n\t}\n\n\tget zonesName(): ZoneTypes.ZonesRo {\n\t\treturn this._zonesName || [];\n\t}\n\n\tget zonesNameM(): ZoneTypes.Zones {\n\t\tlet zones: ZoneTypes.Zones = [];\n\t\tthis._zonesName.forEach((\n\t\t\tzone: ZoneTypes.ZoneRo): void => {\n\n\t\t\tzones.push({\n\t\t\t\t...zone,\n\t\t\t});\n\t\t});\n\t\treturn zones;\n\t}\n\n\tget page(): number {\n\t\treturn this._page || 0;\n\t}\n\n\tget pageCount(): number {\n\t\treturn this._pageCount || 20;\n\t}\n\n\tget pages(): number {\n\t\treturn Math.ceil(this.count / this.pageCount);\n\t}\n\n\tget filter(): ZoneTypes.Filter {\n\t\treturn this._filter;\n\t}\n\n\tget count(): number {\n\t\treturn this._count || 0;\n\t}\n\n\tzone(id: string): ZoneTypes.ZoneRo {\n\t\tlet i = this._map[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._zones[i];\n\t}\n\n\tzoneName(id: string): ZoneTypes.ZoneRo {\n\t\tlet i = this._mapName[id];\n\t\tif (i === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this._zonesName[i];\n\t}\n\n\temitChange(): void {\n\t\tthis.emitDefer(GlobalTypes.CHANGE);\n\t}\n\n\taddChangeListener(callback: () => void): void {\n\t\tthis.on(GlobalTypes.CHANGE, callback);\n\t}\n\n\tremoveChangeListener(callback: () => void): void {\n\t\tthis.removeListener(GlobalTypes.CHANGE, callback);\n\t}\n\n\t_traverse(page: number): void {\n\t\tthis._page = Math.min(this.pages, page);\n\t}\n\n\t_filterCallback(filter: ZoneTypes.Filter): void {\n\t\tif ((this._filter !== null && filter === null) ||\n\t\t\t(!Object.keys(this._filter || {}).length && filter !== null) || (\n\t\t\t\tfilter && this._filter && (\n\t\t\t\t\tfilter.name !== this._filter.name\n\t\t\t\t))) {\n\t\t\tthis._traverse(0);\n\t\t}\n\t\tthis._filter = filter;\n\t\tthis.emitChange();\n\t}\n\n\t_sync(zones: ZoneTypes.Zone[], count: number): void {\n\t\tthis._map = {};\n\t\tfor (let i = 0; i < zones.length; i++) {\n\t\t\tzones[i] = Object.freeze(zones[i]);\n\t\t\tthis._map[zones[i].id] = i;\n\t\t}\n\n\t\tthis._count = count;\n\t\tthis._zones = Object.freeze(zones);\n\t\tthis._page = Math.min(this.pages, this.page);\n\n\t\tthis.emitChange();\n\t}\n\n\t_syncNames(zones: ZoneTypes.Zone[]): void {\n\t\tthis._mapName = {};\n\t\tfor (let i = 0; i < zones.length; i++) {\n\t\t\tzones[i] = Object.freeze(zones[i]);\n\t\t\tthis._mapName[zones[i].id] = i;\n\t\t}\n\n\t\tthis._zonesName = Object.freeze(zones);\n\t\tthis.emitChange();\n\t}\n\n\t_callback(action: ZoneTypes.ZoneDispatch): void {\n\t\tswitch (action.type) {\n\t\t\tcase GlobalTypes.RESET:\n\t\t\t\tthis._reset();\n\t\t\t\tbreak;\n\n\t\t\tcase ZoneTypes.TRAVERSE:\n\t\t\t\tthis._traverse(action.data.page);\n\t\t\t\tbreak;\n\n\t\t\tcase ZoneTypes.FILTER:\n\t\t\t\tthis._filterCallback(action.data.filter);\n\t\t\t\tbreak;\n\n\t\t\tcase ZoneTypes.SYNC:\n\t\t\t\tthis._sync(action.data.zones, action.data.count);\n\t\t\t\tbreak;\n\n\t\t\tcase ZoneTypes.SYNC_NAMES:\n\t\t\t\tthis._syncNames(action.data.zones);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport default new ZonesStore();\n"
  },
  {
    "path": "www/app/types/AgentTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport interface Agent {\n\toperating_system?: string;\n\tbrowser?: string;\n\tip?: string;\n\tisp?: string;\n\tcontinent?: string;\n\tcontinent_code?: string;\n\tcountry?: string;\n\tcountry_code?: string;\n\tregion?: string;\n\tregion_code?: string;\n\tcity?: string;\n\tlatitude?: number;\n\tlongitude?: number;\n}\n"
  },
  {
    "path": "www/app/types/AlertTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'alert.sync';\nexport const SYNC_NAMES = 'alert.sync_names';\nexport const TRAVERSE = 'alert.traverse';\nexport const FILTER = 'alert.filter';\nexport const CHANGE = 'alert.change';\n\nexport interface Alert {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\torganization?: string;\n\troles?: string[];\n\tresource?: string;\n\tlevel?: number;\n\tfrequency?: number;\n\tignores?: string[];\n\tvalue_int?: number;\n\tvalue_str?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\trole?: string;\n\torganization?: string;\n}\n\nexport type Alerts = Alert[];\n\nexport type AlertRo = Readonly<Alert>;\nexport type AlertsRo = ReadonlyArray<AlertRo>;\n\nexport interface AlertDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\talert?: Alert;\n\t\talerts?: Alerts;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/AuditTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as AgentTypes from './AgentTypes';\n\nexport const SYNC = 'audit.sync';\nexport const TRAVERSE = 'audit.traverse';\nexport const CHANGE = 'audit.change';\n\nexport interface Audit {\n\tid?: string;\n\tuser?: string;\n\ttimestamp?: string;\n\ttype?: string;\n\tfields?: {[key: string]: string};\n\tagent?: AgentTypes.Agent;\n}\n\nexport type Audits = Audit[];\n\nexport type AuditRo = Readonly<Audit>;\nexport type AuditsRo = ReadonlyArray<AuditRo>;\n\nexport interface AuditDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tuserId?: string;\n\t\taudit?: Audit;\n\t\taudits?: Audits;\n\t\tpage?: number;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/AuthorityTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'authority.sync';\nexport const SYNC_NAMES = 'authority.sync_names';\nexport const TRAVERSE = 'authority.traverse';\nexport const FILTER = 'authority.filter';\nexport const CHANGE = 'authority.change';\n\nexport interface Authority {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\ttype?: string;\n\torganization?: string;\n\troles?: string[];\n\tkey?: string;\n\tprincipals?: string[];\n\tcertificate?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\trole?: string;\n\tprincipal?: string;\n\torganization?: string;\n}\n\nexport type Authorities = Authority[];\n\nexport type AuthorityRo = Readonly<Authority>;\nexport type AuthoritiesRo = ReadonlyArray<AuthorityRo>;\n\nexport interface AuthorityDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tauthority?: Authority;\n\t\tauthorities?: Authorities;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/BalancerTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'balancer.sync';\nexport const TRAVERSE = 'balancer.traverse';\nexport const FILTER = 'balancer.filter';\nexport const CHANGE = 'balancer.change';\n\nexport interface Domain {\n\tdomain?: string;\n\thost?: string;\n}\n\nexport interface Backend {\n\tprotocol?: string;\n\thostname?: string;\n\tport?: number;\n}\n\nexport interface State {\n\ttimestamp?: string;\n\trequests?: number;\n\tretries?: number;\n\twebsockets?: number;\n\tonline?: string[];\n\tunknown_high?: string[];\n\tunknown_mid?: string[];\n\tunknown_low?: string[];\n\toffline?: string[];\n}\n\nexport interface Balancer {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\tstate?: boolean;\n\ttype?: string;\n\torganization?: string;\n\tdatacenter?: string;\n\tcertificates?: string[];\n\twebsockets?: boolean;\n\tdomains?: Domain[];\n\tbackends?: Backend[];\n\tcheck_path?: string;\n\tstates?: {[key: string]: State};\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\torganization?: string;\n\tdatacenter?: string;\n\tcomment?: string;\n}\n\nexport type Balancers = Balancer[];\n\nexport type BalancerRo = Readonly<Balancer>;\nexport type BalancersRo = ReadonlyArray<BalancerRo>;\n\nexport interface BalancerDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tbalancer?: Balancer;\n\t\tbalancers?: Balancers;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/BlockTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'block.sync';\nexport const TRAVERSE = 'block.traverse';\nexport const FILTER = 'block.filter';\nexport const CHANGE = 'block.change';\n\nexport interface Block {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\ttype?: string;\n\tvlan?: number;\n\tsubnets?: string[];\n\tsubnets6?: string[];\n\texcludes?: string[];\n\tnetmask?: string;\n\tgateway?: string;\n\tgateway6?: string;\n\tavailable?: number;\n\tcapacity?: number;\n}\n\nexport type Blocks = Block[];\n\nexport type BlockRo = Readonly<Block>;\nexport type BlocksRo = ReadonlyArray<BlockRo>;\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n}\n\nexport interface BlockDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tblock?: Block;\n\t\tblocks?: Blocks;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/CertificateTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'certificate.sync';\nexport const TRAVERSE = 'certificate.traverse';\nexport const FILTER = 'certificate.filter';\nexport const CHANGE = 'certificate.change';\n\nexport interface Info {\n\tsignature_alg?: string;\n\tpublic_key_alg?: string;\n\tissuer?: string;\n\tissued_on?: string;\n\texpires_on?: string;\n\tdns_names?: string[];\n}\n\nexport interface Certificate {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\torganization?: string;\n\ttype?: string;\n\tkey?: string;\n\tcertificate?: string;\n\tinfo?: Info;\n\tacme_type?: string;\n\tacme_auth?: string;\n\tacme_secret?: string;\n\tacme_domains?: string[];\n\trefresh?: boolean;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n}\n\nexport type Certificates = Certificate[];\n\nexport type CertificateRo = Readonly<Certificate>;\nexport type CertificatesRo = ReadonlyArray<CertificateRo>;\n\nexport interface CertificateDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tcertificate?: Certificate;\n\t\tcertificates?: Certificates;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/CompletionTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\n\nimport * as OrganizationTypes from \"./OrganizationTypes\"\nimport * as AuthorityTypes from \"./AuthorityTypes\"\nimport * as PolicyTypes from \"./PolicyTypes\"\nimport * as DomainTypes from \"./DomainTypes\"\nimport * as BalancerTypes from \"./BalancerTypes\"\nimport * as VpcTypes from \"./VpcTypes\"\nimport * as DatacenterTypes from \"./DatacenterTypes\"\nimport * as BlockTypes from \"./BlockTypes\"\nimport * as NodeTypes from \"./NodeTypes\"\nimport * as DiskTypes from \"./DiskTypes\"\nimport * as PoolTypes from \"./PoolTypes\"\nimport * as ZoneTypes from \"./ZoneTypes\"\nimport * as ShapeTypes from \"./ShapeTypes\"\nimport * as ImageTypes from \"./ImageTypes\"\nimport * as StorageTypes from \"./StorageTypes\"\nimport * as InstanceTypes from \"./InstanceTypes\"\nimport * as FirewallTypes from \"./FirewallTypes\"\nimport * as PlanTypes from \"./PlanTypes\"\nimport * as CertificateTypes from \"./CertificateTypes\"\nimport * as SecretTypes from \"./SecretTypes\"\nimport * as PodTypes from \"./PodTypes\"\n\nexport const SYNC = \"completion.sync\"\nexport const FILTER = \"completion.filter\"\nexport const CHANGE = \"completion.change\"\n\nexport interface Completion {\n\torganizations?: OrganizationTypes.Organization[]\n\tauthorities?: AuthorityTypes.Authority[]\n\tpolicies?: PolicyTypes.Policy[]\n\tdomains?: DomainTypes.Domain[]\n\tbalancers?: BalancerTypes.Balancer[]\n\tvpcs?: VpcTypes.Vpc[]\n\tsubnets?: VpcTypes.Subnet[]\n\tdatacenters?: DatacenterTypes.Datacenter[]\n\tblocks?: BlockTypes.Block[]\n\tnodes?: NodeTypes.Node[]\n\tdisks?: DiskTypes.Disk[]\n\tpools?: PoolTypes.Pool[]\n\tzones?: ZoneTypes.Zone[]\n\tshapes?: ShapeTypes.Shape[]\n\timages?: ImageTypes.Image[]\n\tstorages?: StorageTypes.Storage[]\n\tbuilds?: Build[];\n\tinstances?: InstanceTypes.Instance[]\n\tfirewalls?: FirewallTypes.Firewall[]\n\tplans?: PlanTypes.Plan[]\n\tcertificates?: CertificateTypes.Certificate[]\n\tsecrets?: SecretTypes.Secret[]\n\tpods?: PodTypes.Pod[]\n\tunits?: PodTypes.Unit[]\n}\n\nexport interface CompletionMap {\n\torganizations?: {[key: string]: number}\n\tauthorities?: {[key: string]: number}\n\tpolicies?: {[key: string]: number}\n\tdomains?: {[key: string]: number}\n\tbalancers?: {[key: string]: number}\n\tvpcs?: {[key: string]: number}\n\tsubnets?: {[key: string]: number}\n\tdatacenters?: {[key: string]: number}\n\tblocks?: {[key: string]: number}\n\tnodes?: {[key: string]: number}\n\tdisks?: {[key: string]: number}\n\tpools?: {[key: string]: number}\n\tzones?: {[key: string]: number}\n\tshapes?: {[key: string]: number}\n\timages?: {[key: string]: number}\n\tstorages?: {[key: string]: number}\n\tbuilds?: {[key: string]: number}\n\tinstances?: {[key: string]: number}\n\tfirewalls?: {[key: string]: number}\n\tplans?: {[key: string]: number}\n\tcertificates?: {[key: string]: number}\n\tsecrets?: {[key: string]: number}\n\tpods?: {[key: string]: number}\n\tunits?: {[key: string]: number}\n}\n\nexport interface Build {\n\tid?: string\n\tname?: string\n\tpod?: string\n\torganization?: string\n\ttags?: BuildTag[]\n}\n\nexport interface BuildTag {\n\ttag?: string\n\ttimestamp?: string\n}\n\nexport interface Filter {\n}\n\nexport interface CompletionDispatch {\n\ttype: string\n\tdata?: {\n\t\torganization?: string\n\t\tcompletion?: Completion\n\t\tfilter?: Filter\n\t}\n}\n"
  },
  {
    "path": "www/app/types/DatacenterTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'datacenter.sync';\nexport const SYNC_NAMES = 'datacenter.sync_names';\nexport const TRAVERSE = 'datacenter.traverse';\nexport const FILTER = 'datacenter.filter';\nexport const CHANGE = 'datacenter.change';\n\nexport interface Datacenter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\tmatch_organizations?: boolean;\n\torganizations?: string[];\n\tjumbo_mtu?: number;\n\tpublic_storages?: string[];\n\tprivate_storage?: string;\n\tprivate_storage_class?: string;\n\tbackup_storage?: string;\n\tbackup_storage_class?: string;\n\tnetwork_mode?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n}\n\nexport type Datacenters = Datacenter[];\n\nexport type DatacenterRo = Readonly<Datacenter>;\nexport type DatacentersRo = ReadonlyArray<DatacenterRo>;\n\nexport interface DatacenterDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tdatacenter?: Datacenter;\n\t\tdatacenters?: Datacenters;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/DeviceTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'device.sync';\nexport const CHANGE = 'device.change';\n\nexport interface Device {\n\tid?: string;\n\tuser?: string;\n\tname?: string;\n\ttype?: string;\n\tmode?: string;\n\talert_levels?: number[];\n\ttimestamp?: string;\n\tdisabled?: boolean;\n\tactive_until?: string;\n\tnumber?: string;\n\tlast_active?: string;\n\twan_rp_id?: string;\n}\n\nexport type Devices = Device[];\n\nexport type DeviceRo = Readonly<Device>;\nexport type DevicesRo = ReadonlyArray<DeviceRo>;\n\nexport interface DeviceDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tuserId?: string;\n\t\tdevice?: Device;\n\t\tdevices?: Devices;\n\t\tshowRemoved?: boolean;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/DiskTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'disk.sync';\nexport const TRAVERSE = 'disk.traverse';\nexport const FILTER = 'disk.filter';\nexport const CHANGE = 'disk.change';\n\nexport interface Disk {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\ttype?: string;\n\tuuid?: string;\n\tnode?: string;\n\tpool?: string;\n\torganization?: string;\n\tstate?: string;\n\taction?: string;\n\tinstance?: string;\n\tdeployment?: string;\n\tdelete_protection?: boolean;\n\tfile_system?: string;\n\timage?: string;\n\trestore_image?: string;\n\tbacking?: boolean;\n\tbacking_image?: string;\n\tindex?: string;\n\tsize?: number;\n\tlv_size?: number;\n\tnew_size?: number;\n\tbackup?: boolean;\n\tbackups?: Backup[];\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\torganization?: string;\n\tdatacenter?: string;\n\tinstance?: string;\n\tnode?: string;\n}\n\nexport interface Backup {\n\timage?: string;\n\tname?: string;\n}\n\nexport type Disks = Disk[];\n\nexport type DiskRo = Readonly<Disk>;\nexport type DisksRo = ReadonlyArray<DiskRo>;\n\nexport interface DiskDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tdisk?: Disk;\n\t\tdisks?: Disks;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/DomainTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'domain.sync';\nexport const SYNC_NAME = 'domain.sync_name';\nexport const TRAVERSE = 'domain.traverse';\nexport const FILTER = 'domain.filter';\nexport const CHANGE = 'domain.change';\n\nexport interface Domain {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\torganization?: string;\n\ttype?: string;\n\tsecret?: string;\n\troot_domain?: string;\n\trecords?: Record[];\n}\n\nexport interface Record {\n\tid?: string;\n\tdomain?: string;\n\ttimestamp?: string;\n\tsub_domain?: string;\n\ttype?: string;\n\tvalue?: string;\n\toperation?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\torganization?: string;\n}\n\nexport type Domains = Domain[];\n\nexport type DomainRo = Readonly<Domain>;\nexport type DomainsRo = ReadonlyArray<DomainRo>;\n\nexport interface DomainDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tdomain?: Domain;\n\t\tdomains?: Domains;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/FirewallTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'firewall.sync';\nexport const TRAVERSE = 'firewall.traverse';\nexport const FILTER = 'firewall.filter';\nexport const CHANGE = 'firewall.change';\n\nexport interface Rule {\n\tprotocol: string;\n\tport?: string;\n\tsource_ips?: string[];\n}\n\nexport interface Firewall {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\torganization?: string;\n\troles?: string[];\n\tingress?: Rule[];\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\trole?: string;\n\torganization?: string;\n}\n\nexport type Firewalls = Firewall[];\n\nexport type FirewallRo = Readonly<Firewall>;\nexport type FirewallsRo = ReadonlyArray<FirewallRo>;\n\nexport interface FirewallDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tfirewall?: Firewall;\n\t\tfirewalls?: Firewalls;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/GlobalTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const CHANGE = 'change';\nexport const RESET = 'reset';\nexport const RELOAD = 'reload';\n\nexport interface Dispatch {\n\ttype: string;\n\tdata?: any;\n}\n"
  },
  {
    "path": "www/app/types/ImageTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'image.sync';\nexport const SYNC_DATACENTER = 'image.sync_datacenter';\nexport const TRAVERSE = 'image.traverse';\nexport const FILTER = 'image.filter';\nexport const CHANGE = 'image.change';\n\nexport interface Image {\n\tid?: string;\n\tdisk_id?: string;\n\tname?: string;\n\trelease?: string;\n\tbuild?: string;\n\tcomment?: string;\n\torganization?: string;\n\tstorage?: string;\n\tsigned?: boolean;\n\tkey?: string;\n\ttype?: string;\n\tsystem_type?: string;\n\tsystem_kind?: string;\n\tfirmware?: string;\n\tetag?: string;\n\tlast_modified?: string;\n\tstorage_class?: string;\n\ttags?: string[];\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\ttype?: string;\n\torganization?: string;\n}\n\nexport type Images = Image[];\n\nexport type ImageRo = Readonly<Image>;\nexport type ImagesRo = ReadonlyArray<ImageRo>;\n\nexport interface ImageDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\timage?: Image;\n\t\timages?: Images;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/InstanceTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'instance.sync';\nexport const SYNC_NODE = 'instance.sync_node';\nexport const TRAVERSE = 'instance.traverse';\nexport const FILTER = 'instance.filter';\nexport const CHANGE = 'instance.change';\n\nimport * as PageInfos from '../components/PageInfo';\nimport * as MiscUtils from '../utils/MiscUtils';\n\nexport interface Instance {\n\tid?: string;\n\torganization?: string;\n\tzone?: string;\n\tnode?: string;\n\tshape?: string;\n\timage?: string;\n\timage_backing?: boolean;\n\tdisk_type?: string;\n\tdisk_pool?: string;\n\tstatus?: string;\n\tstatus_info?: StatusInfo;\n\tuptime?: string;\n\tstate?: string;\n\taction?: string;\n\ttimestamp?: string;\n\tuefi?: boolean;\n\tsecure_boot?: boolean;\n\ttpm?: boolean;\n\tdhcp_server?: boolean;\n\tcloud_type?: string;\n\tcloud_script?: string;\n\tdelete_protection?: boolean;\n\tskip_source_dest_check?: boolean;\n\tqemu_version?: string;\n\tpublic_ips?: string[];\n\tpublic_ips6?: string[];\n\tprivate_ips?: string[];\n\tprivate_ips6?: string[];\n\tgateway_ips?: string[];\n\tgateway_ips6?: string[];\n\tcloud_private_ips?: string[];\n\tcloud_public_ips?: string[];\n\tcloud_public_ips6?: string[];\n\tnetwork_namespace?: string;\n\thost_ips?: string[];\n\tnode_port_ips?: string[];\n\tnode_ports?: NodePort[];\n\tpublic_mac?: string;\n\tname?: string;\n\tcomment?: string;\n\tinit_disk_size?: number;\n\tmemory?: number;\n\tprocessors?: number;\n\troles?: string[];\n\tisos?: Iso[];\n\tusb_devices?: UsbDevice[];\n\tpci_devices?: PciDevice[];\n\tdrive_devices?: DriveDevice[];\n\tiscsi_devices?: IscsiDevice[];\n\tmounts?: Mount[];\n\troot_enabled?: boolean;\n\troot_passwd?: string;\n\tvnc?: boolean;\n\tvnc_password?: string;\n\tvnc_display?: number;\n\tspice?: boolean;\n\tspice_password?: string;\n\tspice_port?: number;\n\tgui?: boolean;\n\tdeployment?: string;\n\tno_public_address?: boolean;\n\tno_public_address6?: boolean;\n\tno_host_address?: boolean;\n\tvpc?: string;\n\tsubnet?: string;\n\tcloud_subnet?: string;\n\tcount?: number;\n\tguest?: Guest;\n\tinfo?: Info;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\tstate?: string;\n\trole?: string;\n\tnetwork_namespace?: string;\n\torganization?: string;\n\tnode?: string;\n\tzone?: string;\n\tvpc?: string;\n\tsubnet?: string;\n}\n\nexport interface StatusInfo {\n\tdownload_progress: number;\n\tdownload_speed: number;\n}\n\nexport interface Iso {\n\tname?: string;\n}\n\nexport interface UsbDevice {\n\tname?: string;\n\tvendor?: string;\n\tproduct?: string;\n\tbus?: string;\n\taddress?: string;\n}\n\nexport interface PciDevice {\n\tslot?: string;\n\tclass?: string;\n\tname?: string;\n\tdriver?: string;\n}\n\nexport interface IscsiDevice {\n\thost?: string;\n\tport?: number;\n\tiqn?: string;\n\tlun?: string;\n\tusername?: string;\n\tpassword?: string;\n\turi?: string;\n}\n\nexport interface Mount {\n\tname?: string;\n\ttype?: string;\n\tpath?: string;\n\thost_path?: string;\n}\n\nexport interface DriveDevice {\n\tid?: string;\n}\n\nexport interface CloudSubnet {\n\tid?: string;\n\tname?: string;\n}\n\nexport interface NodePort {\n\tnode_port?: string;\n\tprotocol?: string;\n\texternal_port?: number;\n\tinternal_port?: number;\n\tdelete?: boolean;\n}\n\nexport interface Guest {\n\ttimestamp?: string;\n\theartbeat?: string;\n\tmemory?: number;\n\thugepages?: number;\n\tload1?: number;\n\tload5?: number;\n\tload15?: number;\n\tupdates?: Update[];\n}\n\nexport interface Update {\n\tadvisory?: string;\n\tcves?: string[];\n\tseverity?: string;\n\tdescription?: string;\n\tpackages?: string[];\n\tdetails?: Advisory[];\n}\n\nexport interface Advisory {\n\tid?: string;\n\ttimestamp?: string;\n\tstatus?: string;\n\tdescription?: string;\n\tscore?: number;\n\tseverity?: string;\n\tvector?: string;\n\tcomplexity?: string;\n\tprivileges?: string;\n\tinteraction?: string;\n\tscope?: string;\n\tconfidentiality?: string;\n\tintegrity?: string;\n\tavailability?: string;\n}\n\nexport interface Info {\n\tnode?: string;\n\tnode_public_ip?: string;\n\tmtu?: number;\n\tiscsi?: boolean;\n\tfirewall_rules?: Record<string, string>;\n\tauthorities?: string[];\n\tdisks?: string[];\n\tisos?: Iso[];\n\tusb_devices?: UsbDevice[];\n\tpci_devices?: PciDevice[];\n\tdrive_devices?: DriveDevice[];\n\tcloud_subnets?: CloudSubnet[];\n}\n\nexport function FirewallFields(info: Info): PageInfos.Field[] {\n\tif (!info?.firewall_rules || Object.keys(info.firewall_rules).length === 0) {\n\t\treturn [{\n\t\t\tlabel: '-',\n\t\t}];\n\t}\n\n\treturn Object.entries(info.firewall_rules).map(([key, value]) => ({\n\t\tlabel: key,\n\t\tvalue: value,\n\t}));\n}\n\nexport function UpdateFields(instance: Instance): PageInfos.Field[] {\n\tif (!instance?.guest?.updates) {\n\t\treturn [];\n\t}\n\n\tlet fields: PageInfos.Field[] = []\n\tinstance.guest.updates.forEach((update) => {\n\t\tlet link\n\t\tlet advisory = (update.advisory || \"\").replace(/[^a-zA-Z0-9:-]/g, '')\n\t\tif (advisory.startsWith('ALSA') || advisory.startsWith('RLSA') ||\n\t\t\t\tadvisory.startsWith('RHSA')) {\n\t\t\tlink = `https://access.redhat.com/errata/RH${advisory.slice(2)}`\n\t\t} else if (advisory.startsWith('ELSA')) {\n\t\t\tlink = `https://linux.oracle.com/errata/${advisory}.html`\n\t\t} else if (advisory.startsWith('FEDORA')) {\n\t\t\tlink = `https://bodhi.fedoraproject.org/updates/${advisory}`\n\t\t}\n\n\t\tlet className = \"\"\n\t\tswitch (update.severity) {\n\t\t\tcase \"moderate\":\n\t\t\t\tclassName = \"bp5-text-intent-primary\"\n\t\t\t\tbreak\n\t\t\tcase \"important\":\n\t\t\t\tclassName = \"bp5-text-intent-warning\"\n\t\t\t\tbreak\n\t\t\tcase \"critical\":\n\t\t\t\tclassName = \"bp5-text-intent-danger\"\n\t\t\t\tbreak\n\t\t}\n\n\t\tfields.push({\n\t\t\tkey: update.advisory,\n\t\t\tlabel: `${update.advisory} - ${MiscUtils.capitalize(update.severity)}`,\n\t\t\tvalue: update.packages || [],\n\t\t\tvalueClass: className,\n\t\t\tlink: link,\n\t\t})\n\t})\n\n\treturn fields\n}\n\nexport function FilterNodePorts(nodePorts: NodePort[]): NodePort[] {\n\tif (!nodePorts) {\n\t\treturn []\n\t}\n\n\tlet newNodePorts: NodePort[] = []\n\tlet portsSet = new Set<number>()\n\n\tnodePorts.forEach((ndePort) => {\n\t\tif (ndePort.delete) {\n\t\t\treturn\n\t\t}\n\t\tportsSet.add(ndePort.external_port)\n\t\tnewNodePorts.push(ndePort)\n\t})\n\n\tnodePorts.forEach((ndePort) => {\n\t\tif (!ndePort.delete || portsSet.has(ndePort.external_port)) {\n\t\t\treturn\n\t\t}\n\t\tportsSet.add(ndePort.external_port)\n\t\tnewNodePorts.push(ndePort)\n\t})\n\n\treturn newNodePorts\n}\n\nexport type Instances = Instance[];\nexport type InstancesNode = Map<string, Instances>;\n\nexport type InstanceRo = Readonly<Instance>;\nexport type InstancesRo = ReadonlyArray<InstanceRo>;\nexport type InstancesNodeRo = Map<string, InstancesRo>;\n\nexport interface InstanceDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tscope?: string;\n\t\tinstance?: Instance;\n\t\tinstances?: Instances;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/LoadingTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const ADD = 'loading.add';\nexport const DONE = 'loading.done';\n\nexport interface LoadingDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/LogTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'log.sync';\nexport const TRAVERSE = 'log.traverse';\nexport const FILTER = 'log.filter';\nexport const CHANGE = 'log.change';\n\nexport interface Log {\n\tid?: string;\n\tlevel?: string;\n\ttimestamp?: string;\n\tmessage?: string;\n\tstack?: string;\n\tfields?: {[key: string]: any};\n}\n\nexport interface Filter {\n\tlevel?: string;\n\tmessage?: string;\n}\n\nexport type Logs = Log[];\n\nexport type LogRo = Readonly<Log>;\nexport type LogsRo = ReadonlyArray<LogRo>;\n\nexport interface LogDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tlog?: Log;\n\t\tlogs?: Logs;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/NodeTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'node.sync';\nexport const SYNC_ZONE = 'node.sync_zone';\nexport const TRAVERSE = 'node.traverse';\nexport const FILTER = 'node.filter';\nexport const CHANGE = 'node.change';\n\nimport * as PageInfos from '../components/PageInfo';\nimport * as MiscUtils from '../utils/MiscUtils';\nimport * as InstanceTypes from './InstanceTypes';\n\nexport interface Node {\n\tid?: string;\n\ttypes?: string[];\n\tdatacenter?: string;\n\tzone?: string;\n\tname?: string;\n\tcomment?: string;\n\tport?: number;\n\thttp2?: boolean;\n\tno_redirect_server?: boolean;\n\tprotocol?: string;\n\thypervisor?: string;\n\tvga?: string;\n\tvga_render?: string;\n\tavailable_renders?: string[];\n\tgui?: boolean;\n\tgui_user?: string;\n\tgui_mode?: string;\n\ttimestamp?: string;\n\tadmin_domain?: string;\n\tuser_domain?: string;\n\twebauthn_domain?: string;\n\tcertificates?: string[];\n\tnetwork_mode?: string;\n\tnetwork_mode6?: string;\n\texternal_interface?: string;\n\tinternal_interface?: string;\n\texternal_interfaces?: string[];\n\tinternal_interfaces?: string[];\n\texternal_interfaces6?: string[];\n\tavailable_interfaces?: Interface[];\n\tavailable_vpcs?: Vpc[];\n\tcloud_subnets?: string[];\n\tavailable_bridges?: Interface[];\n\tdefault_interface?: string;\n\tblocks?: BlockAttachment[];\n\tblocks6?: BlockAttachment[];\n\tshares?: Share[];\n\tpools?: string[];\n\tavailable_drives?: Drive[];\n\tinstance_drives?: Drive[];\n\tno_host_network?: boolean;\n\tno_node_port_network?: boolean;\n\thost_nat?: boolean;\n\tdefault_no_public_address?: boolean;\n\tdefault_no_public_address6?: boolean;\n\tjumbo_frames?: boolean;\n\tjumbo_frames_internal?: boolean;\n\tiscsi?: boolean;\n\tusb_passthrough?: boolean;\n\tpci_passthrough?: boolean;\n\thugepages?: boolean;\n\thugepages_size?: number;\n\tfirewall?: boolean;\n\troles?: string[];\n\trequests_min?: number;\n\tcpu_units?: number;\n\tmemory_units_used?: number;\n\tmemory_units?: number;\n\thugepages_units_used?: number;\n\thugepages_units?: number;\n\tcpu_units_res?: number;\n\tmemory_units_res?: number;\n\tmemory?: number;\n\thugepages_used?: number;\n\tload1?: number;\n\tload5?: number;\n\tload15?: number;\n\tupdates?: Update[];\n\tpublic_ips?: string[];\n\tpublic_ips6?: string[];\n\tprivate_ips?: Record<string, string>;\n\tforwarded_for_header?: string;\n\tforwarded_proto_header?: string;\n\tsoftware_version?: string;\n\thostname?: string;\n\toracle_user?: string;\n\toracle_tenancy?: string;\n\toracle_public_key?: string;\n}\n\nexport interface Update {\n\tadvisory?: string;\n\tcves?: string[];\n\tseverity?: string;\n\tdescription?: string;\n\tpackages?: string[];\n\tdetails?: InstanceTypes.Advisory[];\n}\n\nexport function GetAllIfaces(node: Node): Interface[] {\n\tconst bridges = node.available_bridges ?? [];\n\tconst interfaces = node.available_interfaces ?? [];\n\n\tconst bridgeNames = new Set(\n\t\tbridges\n\t\t\t.map(bridge => bridge.name)\n\t\t\t.filter(name => name !== undefined)\n\t);\n\n\tconst nonConflictingInterfaces = interfaces.filter(iface => {\n\t\treturn iface.name === undefined || !bridgeNames.has(iface.name);\n\t});\n\n\treturn [\n\t\t...bridges,\n\t\t...nonConflictingInterfaces\n\t];\n}\n\nexport function UpdateFields(node: Node): PageInfos.Field[] {\n\tif (!node?.updates) {\n\t\treturn [];\n\t}\n\n\tlet fields: PageInfos.Field[] = []\n\tnode.updates.forEach((update) => {\n\t\tlet link\n\t\tlet advisory = (update.advisory || \"\").replace(/[^a-zA-Z0-9:-]/g, '')\n\t\tif (advisory.startsWith('ALSA') || advisory.startsWith('RLSA') ||\n\t\t\t\tadvisory.startsWith('RHSA')) {\n\t\t\tlink = `https://access.redhat.com/errata/RH${advisory.slice(2)}`\n\t\t} else if (advisory.startsWith('ELSA')) {\n\t\t\tlink = `https://linux.oracle.com/errata/${advisory}.html`\n\t\t} else if (advisory.startsWith('FEDORA')) {\n\t\t\tlink = `https://bodhi.fedoraproject.org/updates/${advisory}`\n\t\t}\n\n\t\tlet className = \"\"\n\t\tswitch (update.severity) {\n\t\t\tcase \"moderate\":\n\t\t\t\tclassName = \"bp5-text-intent-primary\"\n\t\t\t\tbreak\n\t\t\tcase \"important\":\n\t\t\t\tclassName = \"bp5-text-intent-warning\"\n\t\t\t\tbreak\n\t\t\tcase \"critical\":\n\t\t\t\tclassName = \"bp5-text-intent-danger\"\n\t\t\t\tbreak\n\t\t}\n\n\t\tfields.push({\n\t\t\tkey: update.advisory,\n\t\t\tlabel: `${update.advisory} - ${MiscUtils.capitalize(update.severity)}`,\n\t\t\tvalue: update.packages || [],\n\t\t\tvalueClass: className,\n\t\t\tlink: link,\n\t\t})\n\t})\n\n\treturn fields\n}\n\nexport interface Vpc {\n\tid?: string;\n\tname?: string;\n\tnetwork?: string;\n\tsubnets?: Subnet[];\n}\n\nexport interface Subnet {\n\tid?: string;\n\tvpc_id?: string;\n\tname?: string;\n\tnetwork?: string;\n}\n\nexport interface NodeInit {\n\tprovider?: string;\n\tzone?: string;\n\tfirewall?: boolean;\n\tinternal_interface?: string;\n\texternal_interface?: string;\n\thost_network?: string;\n\tblock_gateway?: string;\n\tblock_netmask?: string;\n\tblock_subnets?: string[];\n}\n\nexport interface Share {\n\ttype?: string;\n\tpath?: string;\n\troles?: string[];\n}\n\nexport interface Drive {\n\tid?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tzone?: string;\n\trole?: string;\n\tadmin?: boolean;\n\tuser?: boolean;\n\thypervisor?: boolean;\n}\n\nexport interface BlockAttachment {\n\tinterface?: string;\n\tblock?: string;\n}\n\nexport interface Interface {\n\tname?: string;\n\taddress?: string;\n}\n\nexport type Nodes = Node[];\n\nexport type NodeRo = Readonly<Node>;\nexport type NodesRo = ReadonlyArray<NodeRo>;\n\nexport interface NodeDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tnode?: Node;\n\t\tnodes?: Nodes;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n\nexport let RenderModes: Set<string> = new Set([\n\t\"virtio_pci\",\n\t\"virtio_vga_gl\",\n\t\"virtio_gl\",\n\t\"virtio_gl_vulkan\",\n\t\"virtio_pci_gl\",\n\t\"virtio_pci_gl_vulkan\",\n]);\n"
  },
  {
    "path": "www/app/types/OrganizationTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'organization.sync';\nexport const CHANGE = 'organization.change';\nexport const TRAVERSE = 'organization.traverse';\nexport const FILTER = 'organization.filter';\nexport const CURRENT = 'organization.current';\n\nexport interface Organization {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\troles?: string[];\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n}\n\nexport type Organizations = Organization[];\n\nexport type OrganizationRo = Readonly<Organization>;\nexport type OrganizationsRo = ReadonlyArray<OrganizationRo>;\n\nexport interface OrganizationDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\torganization?: Organization;\n\t\torganizations?: Organizations;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/PlanTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'plan.sync';\nexport const SYNC_NAME = 'plan.sync_name';\nexport const TRAVERSE = 'plan.traverse';\nexport const FILTER = 'plan.filter';\nexport const CHANGE = 'plan.change';\n\nexport interface Plan {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\torganization?: string;\n\ttype?: string;\n\tstatements?: Statement[];\n}\n\nexport interface Statement {\n\tid?: string;\n\tstatement?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\torganization?: string;\n}\n\nexport type Plans = Plan[];\n\nexport type PlanRo = Readonly<Plan>;\nexport type PlansRo = ReadonlyArray<PlanRo>;\n\nexport interface PlanDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tplan?: Plan;\n\t\tplans?: Plans;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/PodTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'pod.sync';\nexport const SYNC_UNIT = 'pod.sync_unit';\nexport const TRAVERSE = 'pod.traverse';\nexport const FILTER = 'pod.filter';\nexport const CHANGE = 'pod.change';\n\nexport interface Pod {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\torganization?: string;\n\tdelete_protection?: boolean;\n\tunits?: Unit[];\n\tdrafts?: Unit[];\n}\n\nexport interface Unit {\n\tid?: string;\n\tname?: string;\n\tkind?: string;\n\tspec?: string;\n\tspec_index?: number\n\tlast_spec?: string;\n\tdeploy_spec?: string;\n\tdelete?: boolean;\n\tnew?: boolean;\n}\n\nexport interface PodUnit {\n\tid?: string;\n\tkind?: string;\n\tpod?: string;\n\tcommits?: Commit[]\n\tdeployments?: Deployment[];\n}\n\nexport interface Commit {\n\tid?: string\n\tpod?: string\n\tunit?: string\n\tindex?: number\n\ttimestamp?: string\n\tname?: string\n\tkind?: string\n\tcount?: number\n\thash?: string\n\tdata?: string\n}\n\nexport interface CommitData {\n\tunit?: string;\n\tspecs?: Commit[];\n\tcount?: number;\n\tpage?: number;\n\tpage_count?: number;\n}\n\nexport interface Deployment {\n\tid?: string;\n\tpod?: string;\n\tunit?: string;\n\ttimestamp?: string;\n\ttags?: string[];\n\tspec?: string;\n\tkind?: string;\n\tstate?: string;\n\taction?: string;\n\tstatus?: string;\n\tnode?: string;\n\tinstance?: string;\n\tinstance_data?: InstanceData;\n\tdomain_data?: DomainData;\n\tjournals?: Journal[];\n\tzone_name?: string;\n\tnode_name?: string;\n\tspec_offset?: number;\n\tspec_index?: number;\n\tspec_timestamp?: string;\n\tinstance_name?: string;\n\tinstance_roles?: string[];\n\tinstance_memory?: number;\n\tinstance_processors?: number;\n\tinstance_status?: string;\n\tinstance_uptime?: string;\n\tinstance_state?: string;\n\tinstance_guest_status?: string;\n\tinstance_timestamp?: string;\n\tinstance_heartbeat?: string;\n\tinstance_memory_usage?: number;\n\tinstance_hugepages?: number;\n\tinstance_load1?: number;\n\tinstance_load5?: number;\n\tinstance_load15?: number;\n\timage_id?: string;\n\timage_name?: string;\n}\n\nexport interface InstanceData {\n\tpublic_ips?: string[];\n\tpublic_ips6?: string[];\n\tprivate_ips?: string[];\n\tprivate_ips6?: string[];\n\tcloud_private_ips?: string[];\n\tcloud_public_ips?: string[];\n}\n\nexport interface DomainData {\n\trecords?: RecordData[];\n}\n\nexport interface RecordData {\n\tdomain?: string;\n\tvalue?: string;\n}\n\nexport interface Journal {\n\tindex?: number;\n\tkey?: string;\n\ttype?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\trole?: string;\n\torganization?: string;\n}\n\nexport interface Build {\n\tid?: string;\n\tname?: string;\n\tpod?: string;\n\tunit?: string;\n\ttags?: string;\n}\n\nexport type Pods = Pod[];\n\nexport type PodRo = Readonly<Pod>;\nexport type PodsRo = ReadonlyArray<PodRo>;\n\nexport type Units = Unit[];\n\nexport type UnitRo = Readonly<Unit>;\nexport type UnitsRo = ReadonlyArray<UnitRo>;\n\nexport interface PodDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tpod?: Pod;\n\t\tpods?: Pods;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n\nexport interface PodUnitDispatch {\n\ttype: string;\n\tdata?: {\n\t\tunit_id?: string;\n\t\tunit?: PodUnit;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/PolicyTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'policy.sync';\nexport const TRAVERSE = 'policy.traverse';\nexport const FILTER = 'policy.filter';\nexport const CHANGE = 'policy.change';\n\nexport interface Rule {\n\ttype?: string;\n\tdisable?: boolean;\n\tvalues?: string[];\n}\n\nexport interface Policy {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\tdisabled?: boolean;\n\troles?: string[];\n\trules?: {[key: string]: Rule};\n\tadmin_secondary?: string;\n\tuser_secondary?: string;\n\tadmin_device_secondary?: boolean;\n\tuser_device_secondary?: boolean;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n}\n\nexport type Policies = Policy[];\n\nexport type PolicyRo = Readonly<Policy>;\nexport type PoliciesRo = ReadonlyArray<PolicyRo>;\n\nexport interface PolicyDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tpolicy?: Policy;\n\t\tpolicies?: Policies;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/PoolTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'pool.sync';\nexport const TRAVERSE = 'pool.traverse';\nexport const FILTER = 'pool.filter';\nexport const CHANGE = 'pool.change';\n\nexport interface Pool {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\tdelete_protection?: boolean;\n\tzone?: string;\n\ttype?: string;\n\tvg_name?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\tvg_name?: string;\n}\n\nexport type Pools = Pool[];\n\nexport type PoolRo = Readonly<Pool>;\nexport type PoolsRo = ReadonlyArray<PoolRo>;\n\nexport interface PoolDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tpool?: Pool;\n\t\tpools?: Pools;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/RelationTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\n\nexport interface Relation {\n\tid: string;\n\tkind: string;\n\tdata: string;\n}\n"
  },
  {
    "path": "www/app/types/RouterTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport interface Params {\n\t[key: string]: string\n}\n\nexport interface State {\n\tpath: string\n\tspec: string\n\tparams: Params\n\tmatched: boolean\n}\n\nlet curState: State\n\nexport function match(spec: string, path: string): State {\n\tconst specSpl = spec.split('/');\n\tconst testSpl = path.split('/');\n\n\tif (spec === \"/\" && path === \"\") {\n\t\treturn {\n\t\t\tpath: \"/\",\n\t\t\tspec: \"/\",\n\t\t\tparams: {},\n\t\t\tmatched: true,\n\t\t}\n\t}\n\n\tif (specSpl.length !== testSpl.length) {\n\t\treturn {\n\t\t\tpath: \"\",\n\t\t\tspec: \"\",\n\t\t\tparams: {},\n\t\t\tmatched: false,\n\t\t}\n\t}\n\n\tconst params: Params = {};\n\n\tfor (let i = 0; i < specSpl.length; i++) {\n\t\tconst specPart = specSpl[i];\n\t\tconst testPart = testSpl[i];\n\n\t\tif (specPart.startsWith(':')) {\n\t\t\tparams[specPart.substring(1)] = testPart;\n\t\t} else if (specPart !== testPart) {\n\t\t\treturn {\n\t\t\t\tpath: \"\",\n\t\t\t\tspec: \"\",\n\t\t\t\tparams: {},\n\t\t\t\tmatched: false,\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tpath: path,\n\t\tspec: spec,\n\t\tparams: params,\n\t\tmatched: true,\n\t}\n}\n\nexport function getState(): State {\n\treturn curState\n}\n\nexport function setState(data: State) {\n\tcurState = data\n}\n"
  },
  {
    "path": "www/app/types/SecretTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'secret.sync';\nexport const SYNC_NAMES = 'secret.sync_names';\nexport const TRAVERSE = 'secret.traverse';\nexport const FILTER = 'secret.filter';\nexport const CHANGE = 'secret.change';\n\nexport interface Secret {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\torganization?: string;\n\ttype?: string;\n\tkey?: string;\n\tvalue?: string;\n\tdata?: string;\n\tregion?: string;\n\tpublic_key?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n}\n\nexport type Secrets = Secret[];\n\nexport type SecretRo = Readonly<Secret>;\nexport type SecretsRo = ReadonlyArray<SecretRo>;\n\nexport interface SecretDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tsecret?: Secret;\n\t\tsecrets?: Secrets;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/SessionTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as AgentTypes from './AgentTypes';\n\nexport const SYNC = 'session.sync';\nexport const CHANGE = 'session.change';\nexport const SHOW_REMOVED = 'session.show_removed';\n\nexport interface Session {\n\tid?: string;\n\ttype?: string;\n\tuser?: string;\n\ttimestamp?: string;\n\tlast_active?: string;\n\tremoved?: boolean;\n\tagent?: AgentTypes.Agent;\n}\n\nexport type Sessions = Session[];\n\nexport type SessionRo = Readonly<Session>;\nexport type SessionsRo = ReadonlyArray<SessionRo>;\n\nexport interface SessionDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tuserId?: string;\n\t\tsession?: Session;\n\t\tsessions?: Sessions;\n\t\tshowRemoved?: boolean;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/SettingsTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'settings.sync';\nexport const CHANGE = 'settings.change';\n\nexport interface Provider {\n\tid?: string;\n\ttype?: string;\n\tlabel?: string;\n\tdefault_roles?: string[];\n\tauto_create?: boolean;\n\trole_management?: string;\n}\n\nexport interface AzureProvider extends Provider {\n\tregion?: string;\n\ttenant?: string;\n\tclient_id?: string;\n\tclient_secret?: string;\n}\n\nexport interface GoogleProvider extends Provider {\n\tdomain?: string;\n\tgoogle_key?: string;\n\tgoogle_email?: string;\n}\n\nexport interface SamlProvider extends Provider {\n\tissuer_url?: string;\n\tsaml_url?: string;\n\tsaml_cert?: string;\n}\n\nexport interface JumpCloudProvider extends Provider {\n\tissuer_url?: string;\n\tsaml_url?: string;\n\tsaml_cert?: string;\n\tjumpcloud_app_id?: string;\n\tjumpcloud_secret?: string;\n}\n\nexport type ProviderAny = Provider & AzureProvider & GoogleProvider &\n\tSamlProvider & JumpCloudProvider;\nexport type Providers = ProviderAny[];\n\nexport interface SecondaryProvider {\n\tid?: string;\n\ttype?: string;\n\tlabel?: string;\n\tname?: string;\n}\n\nexport interface DuoProvider extends SecondaryProvider {\n\tduo_hostname?: string;\n\tduo_key?: string;\n\tduo_secret?: string;\n\tpush_factor?: boolean;\n\tphone_factor?: boolean;\n\tpasscode_factor?: boolean;\n\tsms_factor?: boolean;\n}\n\nexport interface OneLoginProvider extends SecondaryProvider {\n\tone_login_region?: string;\n\tone_login_id?: string;\n\tone_login_secret?: string;\n\tpush_factor?: boolean;\n\tpasscode_factor?: boolean;\n}\n\nexport interface OktaProvider extends SecondaryProvider {\n\tokta_domain?: string;\n\tokta_token?: string;\n\tpush_factor?: boolean;\n\tpasscode_factor?: boolean;\n}\n\nexport type SecondaryProviderAny = SecondaryProvider & DuoProvider &\n\tOneLoginProvider & OktaProvider;\nexport type SecondaryProviders = SecondaryProviderAny[];\n\nexport interface Settings {\n\tauth_providers: Providers;\n\tauth_secondary_providers: SecondaryProviders;\n\tauth_admin_expire: number;\n\tauth_admin_max_duration: number;\n\tauth_user_expire: number;\n\tauth_user_max_duration: number;\n\tauth_fast_login: boolean;\n\tauth_force_fast_user_login: boolean;\n\ttwilio_account: string;\n\ttwilio_secret: string;\n\tnvd_api_key: string;\n}\n\nexport type SettingsRo = Readonly<Settings>;\n\nexport interface SettingsDispatch {\n\ttype: string;\n\tdata?: Settings;\n}\n"
  },
  {
    "path": "www/app/types/ShapeTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'shape.sync';\nexport const TRAVERSE = 'shape.traverse';\nexport const FILTER = 'shape.filter';\nexport const CHANGE = 'shape.change';\n\nexport interface Shape {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\ttype?: string;\n\tdelete_protection?: boolean;\n\tdatacenter?: string;\n\troles?: string[];\n\tflexible?: boolean;\n\tdisk_type?: string;\n\tdisk_pool?: string;\n\tmemory?: number;\n\tprocessors?: number;\n\tnode_count?: number;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\trole?: string;\n}\n\nexport type Shapes = Shape[];\n\nexport type ShapeRo = Readonly<Shape>;\nexport type ShapesRo = ReadonlyArray<ShapeRo>;\n\nexport interface ShapeDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tshape?: Shape;\n\t\tshapes?: Shapes;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/StorageTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'storage.sync';\nexport const TRAVERSE = 'storage.traverse';\nexport const FILTER = 'storage.filter';\nexport const CHANGE = 'storage.change';\n\nexport interface Storage {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\ttype?: string;\n\tendpoint?: string;\n\tbucket?: string;\n\taccess_key?: string;\n\tsecret_key?: string;\n\tinsecure?: boolean;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n}\n\nexport type Storages = Storage[];\n\nexport type StorageRo = Readonly<Storage>;\nexport type StoragesRo = ReadonlyArray<StorageRo>;\n\nexport interface StorageDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tstorage?: Storage;\n\t\tstorages?: Storages;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/SubscriptionTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'subscription.sync';\nexport const CHANGE = 'subscription.change';\n\nexport interface Subscription {\n\tactive?: boolean;\n\tstatus?: string;\n\tplan?: string;\n\tquantity?: number;\n\tamount?: number;\n\tperiod_end?: string;\n\ttrial_end?: string;\n\tcancel_at_period_end?: boolean;\n\tbalance?: number;\n\turl_key?: string;\n}\n\nexport type SubscriptionRo = Readonly<Subscription>;\n\nexport interface SubscriptionDispatch {\n\ttype: string;\n\tdata?: Subscription;\n}\n"
  },
  {
    "path": "www/app/types/UserTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'user.sync';\nexport const TRAVERSE = 'user.traverse';\nexport const FILTER = 'user.filter';\nexport const LOAD = 'user.load';\nexport const UNLOAD = 'user.unload';\nexport const CHANGE = 'user.change';\n\nexport interface User {\n\tid?: string;\n\ttype?: string;\n\tusername?: string;\n\tpassword?: string;\n\tcomment?: string;\n\ttoken?: string;\n\tsecret?: string;\n\tlast_active?: string;\n\troles?: string[];\n\tadministrator?: string;\n\tgenerate_secret?: boolean;\n\tdisabled?: boolean;\n\tactive_until?: string;\n\tpermissions?: string[];\n}\n\nexport interface Filter {\n\tid?: string;\n\tusername?: string;\n\ttype?: string;\n\tadministrator?: boolean;\n\tdisabled?: boolean;\n\trole?: string;\n}\n\nexport type Users = User[];\n\nexport type UserRo = Readonly<User>;\nexport type UsersRo = ReadonlyArray<UserRo>;\n\nexport interface UserDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tuser?: User;\n\t\tusers?: Users;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/VpcTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'vpc.sync';\nexport const SYNC_NAMES= 'vpc.sync_names';\nexport const TRAVERSE = 'vpc.traverse';\nexport const FILTER = 'vpc.filter';\nexport const CHANGE = 'vpc.change';\n\nexport interface Vpc {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n\tvpc_id?: number;\n\tnetwork?: string;\n\tnetwork6?: string;\n\ticmp_redirects?: boolean;\n\tsubnets?: Subnet[];\n\torganization?: string;\n\tdatacenter?: string;\n\troutes?: Route[];\n\tmaps?: Map[];\n\tarps?: Arp[];\n}\n\nexport interface Subnet {\n\tid?: string;\n\tname?: string;\n\tnetwork?: string;\n}\n\nexport interface Route {\n\tdestination?: string;\n\ttarget?: string;\n}\n\nexport interface Map {\n\tdestination?: string;\n\ttarget?: string;\n}\n\nexport interface Arp {\n\tip?: string;\n\tmac?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tnetwork?: string;\n\torganization?: string;\n\tdatacenter?: string;\n}\n\nexport type Vpcs = Vpc[];\n\nexport type VpcRo = Readonly<Vpc>;\nexport type VpcsRo = ReadonlyArray<VpcRo>;\n\nexport interface VpcDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tvpc?: Vpc;\n\t\tvpcs?: Vpcs;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/types/ZoneTypes.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nexport const SYNC = 'zone.sync';\nexport const SYNC_NAMES = 'zone.sync_names';\nexport const TRAVERSE = 'zone.traverse';\nexport const FILTER = 'zone.filter';\nexport const CHANGE = 'zone.change';\n\nexport interface Zone {\n\tid?: string;\n\tdatacenter?: string;\n\tname?: string;\n\tcomment?: string;\n}\n\nexport interface Filter {\n\tid?: string;\n\tname?: string;\n\tcomment?: string;\n}\n\nexport type Zones = Zone[];\n\nexport type ZoneRo = Readonly<Zone>;\nexport type ZonesRo = ReadonlyArray<ZoneRo>;\n\nexport interface ZoneDispatch {\n\ttype: string;\n\tdata?: {\n\t\tid?: string;\n\t\tzone?: Zone;\n\t\tzones?: Zones;\n\t\tpage?: number;\n\t\tpageCount?: number;\n\t\tfilter?: Filter;\n\t\tcount?: number;\n\t};\n}\n"
  },
  {
    "path": "www/app/utils/AgentUtils.ts",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport * as AgentTypes from '../types/AgentTypes';\n\nexport function formatContinent(agent: AgentTypes.Agent): string {\n\treturn agent.continent && agent.continent_code ?\n\t\tagent.continent + ((agent.continent_code &&\n\t\tagent.continent_code !== 'XX') ? ' (' + agent.continent_code + ')' : '') :\n\t\tagent.continent || agent.continent_code || 'Unknown';\n}\n\nexport function formatLocation(agent: AgentTypes.Agent): string {\n\treturn ((agent.city && agent.city !== 'Unknown') ? agent.city + ', ' : '') +\n\t\t(agent.region || 'Unknown') +\n\t\t((agent.region_code && agent.region_code !== 'XX') ?\n\t\t\t' (' + agent.region_code + ')' : '');\n}\n\nexport function formatCountry(agent: AgentTypes.Agent): string {\n\treturn (agent.country || 'Unknown') +\n\t\t((agent.country_code && agent.country_code !== 'XX') ?\n\t\t\t' (' + agent.country_code + ')' : '');\n}\n\nexport function formatCoordinates(agent: AgentTypes.Agent): string {\n\treturn agent.latitude && agent.longitude ?\n\t\tagent.latitude + ', ' + agent.longitude : 'Unknown';\n}\n"
  },
  {
    "path": "www/app/utils/MiscUtils.tsx",
    "content": "/// <reference path=\"../References.d.ts\"/>\nimport React from \"react\";\nimport * as Blueprint from '@blueprintjs/core';\nimport * as ImageTypes from '../types/ImageTypes';\nimport * as Icons from '@blueprintjs/icons';\n\nexport class SyncInterval {\n\tprivate timer: number | null = null;\n\tprivate cancel: boolean = false;\n\tprivate readonly interval: number;\n\tprivate readonly action: () => Promise<any>;\n\n\tconstructor(action: () => Promise<any>, interval: number) {\n\t\tthis.action = action;\n\t\tthis.interval = interval;\n\t\tthis.start();\n\t}\n\n\tpublic start = async (): Promise<void> => {\n\t\tif (this.timer !== null) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = null;\n\t\t}\n\n\t\tthis.cancel = false;\n\n\t\tconst runSync = async (): Promise<void> => {\n\t\t\tif (this.cancel) return;\n\n\t\t\ttry {\n\t\t\t\tawait this.action();\n\n\t\t\t\tif (!this.cancel) {\n\t\t\t\t\tthis.timer = window.setTimeout(() => {\n\t\t\t\t\t\trunSync();\n\t\t\t\t\t}, this.interval);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Action error:\", error);\n\t\t\t\tif (!this.cancel) {\n\t\t\t\t\tthis.timer = window.setTimeout(() => {\n\t\t\t\t\t\trunSync();\n\t\t\t\t\t}, this.interval);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tthis.timer = window.setTimeout(() => {\n\t\t\trunSync();\n\t\t}, this.interval);\n\t};\n\n\tpublic stop = (): void => {\n\t\tthis.cancel = true;\n\t\tif (this.timer) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = null;\n\t\t}\n\t};\n}\n\nexport function uuid(): string {\n\treturn (+new Date() + Math.floor(Math.random() * 999999)).toString(36);\n}\n\nexport function objectId(): string {\n    const timestamp = Math.floor(Date.now() / 1000).toString(16);\n    const randomBytes = Math.random().toString(16).substring(2, 12);\n    const counter = Math.floor(Math.random() * 0xffffff).toString(16);\n    return (timestamp + randomBytes + counter).padEnd(24, '0');\n}\n\nexport function objectIdNil(objId: string): boolean {\n\treturn !objId || objId == '000000000000000000000000';\n}\n\nexport function zeroPad(num: number, width: number): string {\n\tif (num < Math.pow(10, width)) {\n\t\treturn ('0'.repeat(width - 1) + num).slice(-width);\n\t}\n\treturn num.toString();\n}\n\nexport function capitalize(str: string): string {\n\tif (!str) {\n\t\treturn str;\n\t}\n\treturn str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport function titleCase(str: string): string {\n\tif (!str) {\n\t\treturn str;\n\t}\n\treturn str\n\t\t.toLowerCase()\n\t\t.split(' ')\n\t\t.map(word => word.charAt(0).toUpperCase() + word.slice(1))\n\t\t.join(' ');\n}\n\nexport function formatAmount(amount: number): string {\n\tif (!amount) {\n\t\treturn '-';\n\t}\n\treturn '$' + (amount / 100).toFixed(2);\n}\n\nexport function getTextWidth(text: string) {\n\tconst temp = document.createElement(\"span\");\n\ttemp.style.visibility = \"hidden\";\n\ttemp.style.position = \"absolute\";\n\ttemp.style.whiteSpace = \"nowrap\";\n\ttemp.textContent = text;\n\n\tdocument.body.appendChild(temp);\n\tconst width = temp.offsetWidth;\n\tdocument.body.removeChild(temp);\n\n\treturn width;\n}\n\nexport function formatDate(dateStr: string): string {\n\tif (!dateStr || dateStr === '0001-01-01T00:00:00Z') {\n\t\treturn '';\n\t}\n\n\tlet date = new Date(dateStr);\n\tlet str = '';\n\n\tlet hours = date.getHours();\n\tlet period = 'AM';\n\n\tif (hours > 12) {\n\t\tperiod = 'PM';\n\t\thours -= 12;\n\t} else if (hours === 0) {\n\t\thours = 12;\n\t}\n\n\tlet day;\n\tswitch (date.getDay()) {\n\t\tcase 0:\n\t\t\tday = 'Sun';\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tday = 'Mon';\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tday = 'Tue';\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tday = 'Wed';\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tday = 'Thu';\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\tday = 'Fri';\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tday = 'Sat';\n\t\t\tbreak;\n\t}\n\n\tlet month;\n\tswitch (date.getMonth()) {\n\t\tcase 0:\n\t\t\tmonth = 'Jan';\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tmonth = 'Feb';\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tmonth = 'Mar';\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tmonth = 'Apr';\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tmonth = 'May';\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\tmonth = 'Jun';\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tmonth = 'Jul';\n\t\t\tbreak;\n\t\tcase 7:\n\t\t\tmonth = 'Aug';\n\t\t\tbreak;\n\t\tcase 8:\n\t\t\tmonth = 'Sep';\n\t\t\tbreak;\n\t\tcase 9:\n\t\t\tmonth = 'Oct';\n\t\t\tbreak;\n\t\tcase 10:\n\t\t\tmonth = 'Nov';\n\t\t\tbreak;\n\t\tcase 11:\n\t\t\tmonth = 'Dec';\n\t\t\tbreak;\n\t}\n\n\tstr += day + ' ';\n\tstr += date.getDate() + ' ';\n\tstr += month + ' ';\n\tstr += date.getFullYear() + ', ';\n\tstr += hours + ':';\n\tstr += zeroPad(date.getMinutes(), 2) + ':';\n\tstr += zeroPad(date.getSeconds(), 2) + ' ';\n\tstr += period;\n\n\treturn str;\n}\n\nexport function formatSinceLocal(dateStr: string): string {\n\tif (!dateStr || dateStr === \"0001-01-01T00:00:00Z\") {\n\t\treturn \"\";\n\t}\n\n\tconst now = new Date();\n\tlet date = new Date(dateStr);\n\tdate = new Date(date.getTime());\n\tconst seconds = Math.floor((now.getTime() - date.getTime()) / 1000);\n\n\tif (seconds < 60) {\n\t\treturn `${seconds} secs ago`;\n\t} else if (seconds < 3600) {\n\t\tconst minutes = Math.floor(seconds / 60);\n\t\treturn `${minutes} mins ago`;\n\t} else if (seconds < 86400) {\n\t\tconst hours = Math.floor(seconds / 3600);\n\t\treturn `${hours} hrs ago`;\n\t} else {\n\t\tconst days = Math.floor(seconds / 86400);\n\t\treturn `${days} days ago`;\n\t}\n}\n\nexport function formatDateLocal(dateStr: string): string {\n\tif (!dateStr || dateStr === '0001-01-01T00:00:00Z') {\n\t\treturn '';\n\t}\n\n\tlet date = new Date(dateStr);\n\tdate = new Date(date.getTime());\n\tlet str = '';\n\n\tlet hours = date.getHours();\n\tlet period = 'AM';\n\n\tif (hours > 12) {\n\t\tperiod = 'PM';\n\t\thours -= 12;\n\t} else if (hours === 0) {\n\t\thours = 12;\n\t}\n\n\tlet month;\n\tswitch (date.getMonth()) {\n\t\tcase 0:\n\t\t\tmonth = 'Jan';\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tmonth = 'Feb';\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tmonth = 'Mar';\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tmonth = 'Apr';\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tmonth = 'May';\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\tmonth = 'Jun';\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tmonth = 'Jul';\n\t\t\tbreak;\n\t\tcase 7:\n\t\t\tmonth = 'Aug';\n\t\t\tbreak;\n\t\tcase 8:\n\t\t\tmonth = 'Sep';\n\t\t\tbreak;\n\t\tcase 9:\n\t\t\tmonth = 'Oct';\n\t\t\tbreak;\n\t\tcase 10:\n\t\t\tmonth = 'Nov';\n\t\t\tbreak;\n\t\tcase 11:\n\t\t\tmonth = 'Dec';\n\t\t\tbreak;\n\t}\n\n\tstr += month + ' ';\n\tstr += zeroPad(date.getDate(), 2) + ', ';\n\tstr += date.getFullYear() + ' ';\n\tstr += zeroPad(hours, 2) + ':';\n\tstr += zeroPad(date.getMinutes(), 2) + ':';\n\tstr += zeroPad(date.getSeconds(), 2) + ' ';\n\tstr += period;\n\n\treturn str;\n}\n\nexport function formatDateShort(dateStr: string): string {\n\tif (!dateStr || dateStr === '0001-01-01T00:00:00Z') {\n\t\treturn '';\n\t}\n\n\tlet date = new Date(dateStr);\n\tlet curDate = new Date();\n\n\tlet month;\n\tswitch (date.getMonth()) {\n\t\tcase 0:\n\t\t\tmonth = 'Jan';\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tmonth = 'Feb';\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tmonth = 'Mar';\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tmonth = 'Apr';\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tmonth = 'May';\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\tmonth = 'Jun';\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tmonth = 'Jul';\n\t\t\tbreak;\n\t\tcase 7:\n\t\t\tmonth = 'Aug';\n\t\t\tbreak;\n\t\tcase 8:\n\t\t\tmonth = 'Sep';\n\t\t\tbreak;\n\t\tcase 9:\n\t\t\tmonth = 'Oct';\n\t\t\tbreak;\n\t\tcase 10:\n\t\t\tmonth = 'Nov';\n\t\t\tbreak;\n\t\tcase 11:\n\t\t\tmonth = 'Dec';\n\t\t\tbreak;\n\t}\n\n\tlet str = month + ' ' + date.getDate();\n\n\tif (date.getFullYear() !== curDate.getFullYear()) {\n\t\tstr += ' ' + date.getFullYear();\n\t}\n\n\treturn str;\n}\n\nexport function formatDateShortTime(dateStr: string): string {\n\tif (!dateStr || dateStr === '0001-01-01T00:00:00Z') {\n\t\treturn '';\n\t}\n\n\tlet date = new Date(dateStr);\n\tlet curDate = new Date();\n\n\tlet month;\n\tswitch (date.getMonth()) {\n\t\tcase 0:\n\t\t\tmonth = 'Jan';\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tmonth = 'Feb';\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tmonth = 'Mar';\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tmonth = 'Apr';\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tmonth = 'May';\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\tmonth = 'Jun';\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tmonth = 'Jul';\n\t\t\tbreak;\n\t\tcase 7:\n\t\t\tmonth = 'Aug';\n\t\t\tbreak;\n\t\tcase 8:\n\t\t\tmonth = 'Sep';\n\t\t\tbreak;\n\t\tcase 9:\n\t\t\tmonth = 'Oct';\n\t\t\tbreak;\n\t\tcase 10:\n\t\t\tmonth = 'Nov';\n\t\t\tbreak;\n\t\tcase 11:\n\t\t\tmonth = 'Dec';\n\t\t\tbreak;\n\t}\n\n\tlet str = month + ' ' + date.getDate();\n\n\tif (date.getFullYear() !== curDate.getFullYear()) {\n\t\tstr += ' ' + date.getFullYear();\n\t} else if (date.getMonth() === curDate.getMonth() &&\n\t\t\tdate.getDate() === curDate.getDate()) {\n\t\tlet hours = date.getHours();\n\t\tlet period = 'AM';\n\n\t\tif (hours > 12) {\n\t\t\tperiod = 'PM';\n\t\t\thours -= 12;\n\t\t} else if (hours === 0) {\n\t\t\thours = 12;\n\t\t}\n\n\t\tstr = hours + ':';\n\t\tstr += zeroPad(date.getMinutes(), 2) + ':';\n\t\tstr += zeroPad(date.getSeconds(), 2) + ' ';\n\t\tstr += period;\n\t}\n\n\treturn str;\n}\n\nexport function naturalSort(a: string, b: string): number {\n\tconst regex = /(\\d+)|(\\D+)/g;\n\tconst aParts = a.match(regex) || [];\n\tconst bParts = b.match(regex) || [];\n\n\tfor (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {\n\t\tif (!aParts[i]) return -1;\n\t\tif (!bParts[i]) return 1;\n\n\t\tconst aIsNum = /^\\d+$/.test(aParts[i]);\n\t\tconst bIsNum = /^\\d+$/.test(bParts[i]);\n\n\t\tif (aIsNum && bIsNum) {\n\t\t\tconst diff = parseInt(aParts[i], 10) - parseInt(bParts[i], 10);\n\t\t\tif (diff !== 0) return diff;\n\t\t} else {\n\t\t\tconst diff = aParts[i].localeCompare(bParts[i]);\n\t\t\tif (diff !== 0) return diff;\n\t\t}\n\t}\n\n\treturn 0;\n};\n\nexport function humanReadableSpeedMb(speedMb: number): string {\n  if (!speedMb || speedMb <= 0) {\n    return '';\n  }\n\n  if (speedMb >= 1000) {\n    return `${(speedMb / 1000).toFixed(1)} GB/s`;\n  } else {\n    return `${speedMb.toFixed(1)} MB/s`;\n  }\n}\n\nexport function highlightMatch(input: string, query: string): React.ReactNode {\n\tif (!query) {\n\t\treturn input;\n\t}\n\n\tlet index = input.toLowerCase().indexOf(query.toLowerCase())\n\tif (index === -1) {\n\t\treturn input;\n\t}\n\n\treturn <span>\n\t\t{input.substring(0, index)}\n\t\t<b>{input.substring(index, index + query.length)}</b>\n\t\t{input.substring(index + query.length)}\n\t</span>;\n}\n\nexport function parseImageDate(dateString: string): string {\n  if (!dateString || typeof dateString !== 'string') {\n\t\treturn dateString\n  }\n\n  const cleanedDate = dateString.replace(/\\D/g, '');\n\n  if (cleanedDate.length === 6) {\n    const year = cleanedDate.substring(0, 2);\n    const month = cleanedDate.substring(2, 4);\n\n    const monthNum = parseInt(month, 10);\n    if (monthNum < 1 || monthNum > 12) {\n\t\t\treturn dateString\n    }\n\n    return `${month}/${year}`;\n  } else if (cleanedDate.length === 4) {\n    const year = cleanedDate.substring(0, 2);\n    const month = cleanedDate.substring(2, 4);\n\n    const monthNum = parseInt(month, 10);\n    if (monthNum < 1 || monthNum > 12) {\n\t\t\treturn dateString\n    }\n\n    return `${month}/${year}`;\n  }\n\n\treturn dateString\n}\n"
  },
  {
    "path": "www/build.sh",
    "content": "./node_modules/.bin/tsc\n\n# development\nrm -rf dist-dev/static\nmkdir -p dist-dev/static\ncp styles/global.css dist-dev/static/\ncp styles/blueprint.css dist-dev/static/blueprint3.css\ncp node_modules/normalize.css/normalize.css dist-dev/static/\ncp node_modules/@blueprintjs/core/lib/css/blueprint.css dist-dev/static/blueprint5.css\ncp node_modules/@blueprintjs/datetime2/lib/css/blueprint-datetime2.css dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.eot dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.svg dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.ttf dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.woff dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.woff2 dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.eot dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.svg dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.ttf dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.woff dist-dev/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.woff2 dist-dev/static/\ncp static/RobotoMono-Regular.ttf dist-dev/static/\ncp static/RobotoMono-Medium.ttf dist-dev/static/\nsed -i 's|../../resources/icons/||g' dist-dev/static/blueprint-icons.css\n\n./node_modules/.bin/webpack --config webpack.dev.config\n\ncp index.html dist-dev/index.html\ncp uindex.html dist-dev/uindex.html\ncp login.html dist-dev/login.html\n\n# production\nrm -rf dist/static\nmkdir -p dist/static\ncp styles/global.css dist/static/\ncp styles/blueprint.css dist/static/blueprint3.css\ncp node_modules/normalize.css/normalize.css dist/static/\ncp node_modules/@blueprintjs/core/lib/css/blueprint.css dist/static/blueprint5.css\ncp node_modules/@blueprintjs/datetime2/lib/css/blueprint-datetime2.css dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.eot dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.svg dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.ttf dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.woff dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-16.woff2 dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.eot dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.svg dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.ttf dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.woff dist/static/\ncp node_modules/@blueprintjs/icons/lib/css/blueprint-icons-20.woff2 dist/static/\ncp static/RobotoMono-Regular.ttf dist/static/\ncp static/RobotoMono-Medium.ttf dist/static/\nsed -i 's|../../resources/icons/||g' dist/static/blueprint-icons.css\n\n./node_modules/.bin/webpack\n\ncp index_dist.html dist/index.html\ncp uindex_dist.html dist/uindex.html\ncp login.html dist/login.html\n\nAPP_HASH=`md5sum dist/static/app.js | cut -c1-6`\n\nmv dist/static/app.js dist/static/app.${APP_HASH}.js\nmv dist/static/app.js.map dist/static/app.${APP_HASH}.js.map\n\nsed -i -e \"s|static/app.js|static/app.${APP_HASH}.js|g\" dist/index.html\nsed -i -e \"s|static/app.js|static/app.${APP_HASH}.js|g\" dist/uindex.html\n"
  },
  {
    "path": "www/build_remote.sh",
    "content": "#!/bin/bash\nset -e\n\nrsync --human-readable --archive --xattrs --progress --delete --exclude \"/node_modules/*\" --exclude \"/jspm_packages/*\" --exclude \"app/*.js\" --exclude \"app/*.js.map\" --exclude \"app/**/*.js\" --exclude \"app/**/*.js.map\" /home/cloud/git/pritunl-cloud/www/ $NPM_SERVER:/home/cloud/pritunl-cloud-www/\n\nssh cloud@$NPM_SERVER \"\ncd /home/cloud/pritunl-cloud-www/\nrm -rf node_modules\nnpm install\ncd ./node_modules/@github/webauthn-json/dist/\nln -sf ./esm/* ./\ncd ../../../../\n\"\n\nscp $NPM_SERVER:/home/cloud/pritunl-cloud-www/package.json /home/cloud/git/pritunl-cloud/www/package.json\nscp $NPM_SERVER:/home/cloud/pritunl-cloud-www/package-lock.json /home/cloud/git/pritunl-cloud/www/package-lock.json\nrsync --human-readable --archive --xattrs --progress --delete $NPM_SERVER:/home/cloud/pritunl-cloud-www/node_modules/ /home/cloud/git/pritunl-cloud/www/node_modules/\nrsync --human-readable --archive --xattrs --progress --delete --exclude \"/node_modules/*\" --exclude \"/jspm_packages/*\" --exclude \"app/*.js\" --exclude \"app/*.js.map\" --exclude \"app/**/*.js\" --exclude \"app/**/*.js.map\" /home/cloud/git/pritunl-cloud/www/ $NPM_SERVER:/home/cloud/pritunl-cloud-www/\n\nssh cloud@$NPM_SERVER \"\ncd /home/cloud/pritunl-cloud-www/\nsh build.sh\n\"\n\nrsync --human-readable --archive --xattrs --progress --delete $NPM_SERVER:/home/cloud/pritunl-cloud-www/dist/ /home/cloud/git/pritunl-cloud/www/dist/\nrsync --human-readable --archive --xattrs --progress --delete $NPM_SERVER:/home/cloud/pritunl-cloud-www/dist-dev/ /home/cloud/git/pritunl-cloud/www/dist-dev/\n"
  },
  {
    "path": "www/dist/index.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/normalize.css\"/>\n    <link id=\"blueprint3-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint3.css\" disabled/>\n    <link id=\"blueprint5-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint5.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/global.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-datetime2.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-icons.css\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"toaster\"></div>\n    <script src=\"static/app.e2e283.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/dist/login.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\"\n      content=\"user-scalable=no, width=device-width, initial-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"apple-touch-icon\" href=\"/logo.png\">\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <style>\n      progress,sub,sup{vertical-align:baseline}button,hr,input{overflow:visible}[type=checkbox],[type=radio],legend{box-sizing:border-box;padding:0}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}button,input,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{color:inherit;display:table;max-width:100%;white-space:normal}textarea{overflow:auto}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}\n\n      @charset \"UTF-8\";h1,h2,h3{line-height:.8em}h4,h5,h6{line-height:.9em}.pt-ui-text,body{letter-spacing:0;text-transform:none}a,a:hover{color:#106ba3}.pt-breadcrumbs>li::after,span.pt-icon-large,span.pt-icon-standard,span.pt-icon::before{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-button.pt-loading>:not(.pt-button-spinner),.pt-button.pt-loading[class*=pt-icon-]::before{visibility:hidden}.pt-monospace-text,.pt-ui-text,body,code,pre{text-transform:none}.pt-select::after,.pt-skeleton,.pt-tab-indicator-wrapper,.pt-transition-container.pt-popover-leave .pt-popover-content{pointer-events:none}html{-moz-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-moz-box-sizing:inherit;box-sizing:inherit}@font-face{font-family:Icons16;font-style:normal;font-weight:400;src:url(icons-16.eot?#iefix) format(\"embedded-opentype\"),url(icons-16.woff) format(\"woff\"),url(icons-16.ttf) format(\"truetype\")}@font-face{font-family:Icons20;font-style:normal;font-weight:400;src:url(icons-20.eot?#iefix) format(\"embedded-opentype\"),url(icons-20.woff) format(\"woff\"),url(icons-20.ttf) format(\"truetype\")}hr{margin:20px 0;border:none;border-bottom:1px solid rgba(16,22,26,.15)}code,pre{border-radius:3px}.pt-dark hr{border-color:rgba(255,255,255,.15)}::-moz-selection{background:rgba(125,188,255,.6)}::selection{background:rgba(125,188,255,.6)}body{line-height:1.28581;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,\"Open Sans\",\"Helvetica Neue\",Icons16,sans-serif;font-size:14px;font-weight:400;color:#182026}h1,h2,h3,h4,h5,h6{color:#182026;font-weight:600;margin:0 0 10px;padding:0}small{font-size:12px}.pt-monospace-text{font-family:monospace;font-size:smaller}h1{font-size:40px}h2{font-size:27px}h3{font-size:24px}h4{font-size:20px}h5{font-size:17px}h6{font-size:15px}.pt-ui-text{line-height:1.28581;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,\"Open Sans\",\"Helvetica Neue\",Icons16,sans-serif;font-size:14px;font-weight:400}.pt-ui-text-large{line-height:1.25;font-size:16px}.pt-running-text{line-height:1.5;font-size:16px}.pt-running-text h1,.pt-running-text h2,.pt-running-text h3,.pt-running-text h4,.pt-running-text h5,.pt-running-text h6{margin-top:40px;margin-bottom:20px}blockquote,p{margin:0 0 10px}ol,pre,ul{margin:10px 0}.pt-running-text-small{line-height:1.5;font-size:14px}p{padding:0}a{text-decoration:none}a:hover{cursor:pointer;text-decoration:underline}.pt-dark a code,a .pt-icon,a .pt-icon-large,a .pt-icon-standard,a code{color:inherit}.pt-dark a,.pt-dark a:hover{color:#48aff0}.pt-dark a .pt-icon,.pt-dark a .pt-icon-large,.pt-dark a .pt-icon-standard,.pt-dark a:hover .pt-icon,.pt-dark a:hover .pt-icon-large,.pt-dark a:hover .pt-icon-standard{color:inherit}code,pre{font-family:monospace;font-size:smaller}code{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2);background:rgba(255,255,255,.7);padding:2px 5px;color:#5c7080}.pt-dark code{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#bfccd6}pre{display:block;box-shadow:inset 0 0 0 1px rgba(16,22,26,.15);background:rgba(255,255,255,.7);padding:13px 15px 12px;line-height:1.4;color:#182026;font-size:13px;word-break:break-all;word-wrap:break-word}.pt-dialog-header h5,.pt-menu-header>h6,.pt-menu-item,.pt-tab,.pt-text-overflow-ellipsis,.pt-tree-node-label{text-overflow:ellipsis;word-wrap:normal}pre>code{border-radius:0;box-shadow:none;background:0 0;padding:0;white-space:pre-wrap;color:inherit;font-size:inherit}.pt-dialog-header h5,.pt-editable-text,.pt-menu-header>h6,.pt-menu-item,.pt-popover-target .pt-tooltip,.pt-tab,.pt-tree-node-label{white-space:nowrap}.pt-dark pre{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#f5f8fa}.pt-dark pre>code{box-shadow:none;background:0 0}blockquote{line-height:1.5;font-size:16px;border-left:solid 4px rgba(167,182,194,.5);padding:0 20px}span.pt-icon,span.pt-icon-large,span.pt-icon-standard{line-height:1;font-weight:400;font-style:normal}blockquote p:last-child{margin-bottom:0}.pt-dark blockquote{border-color:rgba(115,134,148,.5)}ol,ul{padding-left:40px}.pt-list li:not(:last-child),.pt-running-text ol li:not(:last-child),.pt-running-text ul li:not(:last-child){padding-bottom:5px}.pt-list li :last-child,.pt-running-text ol li :last-child,.pt-running-text ul li :last-child{margin-bottom:0}.pt-list ol,.pt-list ul,.pt-running-text ol ol,.pt-running-text ol ul,.pt-running-text ul ol,.pt-running-text ul ul{margin-top:5px}.pt-list-unstyled{margin:0;padding:0;list-style:none}.pt-list-unstyled li{padding:0}.pt-text-muted{color:#5c7080}.pt-dark .pt-text-muted{color:#bfccd6}.pt-text-overflow-ellipsis{overflow:hidden;white-space:nowrap}.pt-rtl{text-align:right}.pt-dark,.pt-dark h1,.pt-dark h2,.pt-dark h3,.pt-dark h4,.pt-dark h5,.pt-dark h6{color:#f5f8fa}.pt-icon,.pt-icon-large,.pt-icon-standard{display:inline-block}.pt-icon-large.pt-intent-primary,.pt-icon-standard.pt-intent-primary,.pt-icon.pt-intent-primary{color:#137cbd}.pt-icon-large.pt-intent-success,.pt-icon-standard.pt-intent-success,.pt-icon.pt-intent-success{color:#0f9960}.pt-icon-large.pt-intent-warning,.pt-icon-standard.pt-intent-warning,.pt-icon.pt-intent-warning{color:#d9822b}.pt-icon-large.pt-intent-danger,.pt-icon-standard.pt-intent-danger,.pt-icon.pt-intent-danger{color:#db3737}span.pt-icon-standard{font-family:Icons16,sans-serif;font-size:16px}span.pt-icon-large{font-family:Icons20,sans-serif;font-size:20px}span.pt-icon{font-family:Icons20;font-size:inherit}.pt-icon-blank::before{content:\"\"}.pt-icon-style::before{content:\"\"}.pt-icon-align-left::before{content:\"\"}.pt-icon-align-center::before{content:\"\"}.pt-icon-align-right::before{content:\"\"}.pt-icon-align-justify::before{content:\"\"}.pt-icon-bold::before{content:\"\"}.pt-icon-italic::before{content:\"\"}.pt-icon-underline::before{content:\"⎁\"}.pt-icon-search-around::before{content:\"\"}.pt-icon-graph-remove::before{content:\"\"}.pt-icon-group-objects::before{content:\"\"}.pt-icon-merge-links::before{content:\"\"}.pt-icon-layout::before{content:\"\"}.pt-icon-layout-auto::before{content:\"\"}.pt-icon-layout-circle::before{content:\"\"}.pt-icon-layout-hierarchy::before{content:\"\"}.pt-icon-layout-grid::before{content:\"\"}.pt-icon-layout-group-by::before{content:\"\"}.pt-icon-layout-skew-grid::before{content:\"\"}.pt-icon-geosearch::before{content:\"\"}.pt-icon-heatmap::before{content:\"\"}.pt-icon-drive-time::before{content:\"\"}.pt-icon-select::before{content:\"\"}.pt-icon-predictive-analysis::before{content:\"\"}.pt-icon-layers::before{content:\"\"}.pt-icon-locate::before{content:\"\"}.pt-icon-bookmark::before{content:\"\"}.pt-icon-citation::before{content:\"\"}.pt-icon-tag::before{content:\"\"}.pt-icon-clipboard::before{content:\"\"}.pt-icon-selection::before{content:\"⦿\"}.pt-icon-timeline-events::before{content:\"\"}.pt-icon-timeline-line-chart::before{content:\"\"}.pt-icon-timeline-bar-chart::before{content:\"\"}.pt-icon-applications::before{content:\"\"}.pt-icon-projects::before{content:\"\"}.pt-icon-changes::before{content:\"\"}.pt-icon-notifications::before{content:\"\"}.pt-icon-lock::before{content:\"\"}.pt-icon-unlock::before{content:\"\"}.pt-icon-user::before{content:\"\"}.pt-icon-search-template::before{content:\"\"}.pt-icon-inbox::before{content:\"\"}.pt-icon-more::before{content:\"\"}.pt-icon-help::before{content:\"?\"}.pt-icon-calendar::before{content:\"\"}.pt-icon-media::before{content:\"\"}.pt-icon-link::before{content:\"\"}.pt-icon-share::before{content:\"\"}.pt-icon-download::before{content:\"\"}.pt-icon-document::before{content:\"\"}.pt-icon-properties::before{content:\"\"}.pt-icon-import::before{content:\"\"}.pt-icon-export::before{content:\"\"}.pt-icon-minimize::before{content:\"\"}.pt-icon-maximize::before{content:\"\"}.pt-icon-tick::before{content:\"✓\"}.pt-icon-cross::before{content:\"✗\"}.pt-icon-plus::before{content:\"+\"}.pt-icon-minus::before{content:\"−\"}.pt-icon-arrow-left::before{content:\"←\"}.pt-icon-arrow-right::before{content:\"→\"}.pt-icon-exchange::before{content:\"\"}.pt-icon-comparison::before{content:\"\"}.pt-icon-list::before{content:\"☰\"}.pt-icon-filter::before{content:\"\"}.pt-icon-confirm::before{content:\"\"}.pt-icon-fork::before{content:\"\"}.pt-icon-trash::before{content:\"\"}.pt-icon-person::before{content:\"\"}.pt-icon-people::before{content:\"\"}.pt-icon-add::before{content:\"\"}.pt-icon-remove::before{content:\"\"}.pt-icon-geolocation::before{content:\"\"}.pt-icon-zoom-in::before{content:\"\"}.pt-icon-zoom-out::before{content:\"\"}.pt-icon-refresh::before{content:\"\"}.pt-icon-delete::before{content:\"\"}.pt-icon-cog::before{content:\"\"}.pt-icon-flag::before{content:\"⚑\"}.pt-icon-pin::before{content:\"\"}.pt-icon-warning-sign::before{content:\"\"}.pt-icon-error::before{content:\"\"}.pt-icon-info-sign::before{content:\"ℹ\"}.pt-icon-credit-card::before{content:\"\"}.pt-icon-edit::before{content:\"✎\"}.pt-icon-history::before{content:\"\"}.pt-icon-search::before{content:\"\"}.pt-icon-log-out::before{content:\"\"}.pt-icon-star::before{content:\"★\"}.pt-icon-star-empty::before{content:\"☆\"}.pt-icon-sort-alphabetical::before{content:\"\"}.pt-icon-sort-numerical::before{content:\"\"}.pt-icon-sort::before{content:\"\"}.pt-icon-folder-open::before{content:\"\"}.pt-icon-folder-close::before{content:\"\"}.pt-icon-folder-shared::before{content:\"\"}.pt-icon-caret-up::before{content:\"⌃\"}.pt-icon-caret-right::before{content:\"〉\"}.pt-icon-caret-down::before{content:\"⌄\"}.pt-icon-caret-left::before{content:\"〈\"}.pt-icon-menu-open::before{content:\"\"}.pt-icon-menu-closed::before{content:\"\"}.pt-icon-feed::before{content:\"\"}.pt-icon-two-columns::before{content:\"\"}.pt-icon-one-column::before{content:\"\"}.pt-icon-dot::before{content:\"•\"}.pt-icon-property::before{content:\"\"}.pt-icon-time::before{content:\"⏲\"}.pt-icon-disable::before{content:\"\"}.pt-icon-unpin::before{content:\"\"}.pt-icon-flows::before{content:\"\"}.pt-icon-new-text-box::before{content:\"\"}.pt-icon-new-link::before{content:\"\"}.pt-icon-new-object::before{content:\"\"}.pt-icon-path-search::before{content:\"\"}.pt-icon-automatic-updates::before{content:\"\"}.pt-icon-page-layout::before{content:\"\"}.pt-icon-code::before{content:\"\"}.pt-icon-map::before{content:\"\"}.pt-icon-search-text::before{content:\"\"}.pt-icon-envelope::before{content:\"✉\"}.pt-icon-paperclip::before{content:\"\"}.pt-icon-label::before{content:\"\"}.pt-icon-globe::before{content:\"\"}.pt-icon-home::before{content:\"⌂\"}.pt-icon-th::before{content:\"\"}.pt-icon-th-list::before{content:\"\"}.pt-icon-th-derived::before{content:\"\"}.pt-icon-circle::before{content:\"\"}.pt-icon-draw::before{content:\"\"}.pt-icon-insert::before{content:\"\"}.pt-icon-helper-management::before{content:\"\"}.pt-icon-send-to::before{content:\"\"}.pt-icon-eye-open::before{content:\"\"}.pt-icon-folder-shared-open::before{content:\"\"}.pt-icon-social-media::before{content:\"\"}.pt-icon-arrow-up::before{content:\"↑\"}.pt-icon-arrow-down::before{content:\"↓\"}.pt-icon-arrows-horizontal::before{content:\"↔\"}.pt-icon-arrows-vertical::before{content:\"↕\"}.pt-icon-resolve::before{content:\"\"}.pt-icon-graph::before{content:\"\"}.pt-icon-briefcase::before{content:\"\"}.pt-icon-dollar::before{content:\"$\"}.pt-icon-ninja::before{content:\"\"}.pt-icon-delta::before{content:\"Δ\"}.pt-icon-barcode::before{content:\"\"}.pt-icon-torch::before{content:\"\"}.pt-icon-widget::before{content:\"\"}.pt-icon-unresolve::before{content:\"\"}.pt-icon-offline::before{content:\"\"}.pt-icon-zoom-to-fit::before{content:\"\"}.pt-icon-add-to-artifact::before{content:\"\"}.pt-icon-map-marker::before{content:\"\"}.pt-icon-chart::before{content:\"\"}.pt-icon-control::before{content:\"\"}.pt-icon-multi-select::before{content:\"\"}.pt-icon-direction-left::before{content:\"\"}.pt-icon-direction-right::before{content:\"\"}.pt-icon-database::before{content:\"\"}.pt-icon-pie-chart::before{content:\"\"}.pt-icon-full-circle::before{content:\"\"}.pt-icon-square::before{content:\"\"}.pt-icon-print::before{content:\"⎙\"}.pt-icon-presentation::before{content:\"\"}.pt-icon-ungroup-objects::before{content:\"\"}.pt-icon-chat::before{content:\"\"}.pt-icon-comment::before{content:\"\"}.pt-icon-circle-arrow-right::before{content:\"\"}.pt-icon-circle-arrow-left::before{content:\"\"}.pt-icon-circle-arrow-up::before{content:\"\"}.pt-icon-circle-arrow-down::before{content:\"\"}.pt-icon-upload::before{content:\"\"}.pt-icon-asterisk::before{content:\"*\"}.pt-icon-cloud::before{content:\"☁\"}.pt-icon-cloud-download::before{content:\"\"}.pt-icon-cloud-upload::before{content:\"\"}.pt-icon-repeat::before{content:\"\"}.pt-icon-move::before{content:\"\"}.pt-icon-chevron-left::before{content:\"\"}.pt-icon-chevron-right::before{content:\"\"}.pt-icon-chevron-up::before{content:\"\"}.pt-icon-chevron-down::before{content:\"\"}.pt-icon-random::before{content:\"\"}.pt-icon-fullscreen::before{content:\"\"}.pt-icon-log-in::before{content:\"\"}.pt-icon-heart::before{content:\"♥\"}.pt-icon-office::before{content:\"\"}.pt-icon-duplicate::before{content:\"\"}.pt-icon-ban-circle::before{content:\"\"}.pt-icon-camera::before{content:\"\"}.pt-icon-mobile-video::before{content:\"\"}.pt-icon-video::before{content:\"\"}.pt-icon-film::before{content:\"\"}.pt-icon-settings::before{content:\"\"}.pt-icon-volume-off::before{content:\"\"}.pt-icon-volume-down::before{content:\"\"}.pt-icon-volume-up::before{content:\"\"}.pt-icon-music::before{content:\"\"}.pt-icon-step-backward::before{content:\"\"}.pt-icon-fast-backward::before{content:\"\"}.pt-icon-pause::before{content:\"\"}.pt-icon-stop::before{content:\"\"}.pt-icon-play::before{content:\"\"}.pt-icon-fast-forward::before{content:\"\"}.pt-icon-step-forward::before{content:\"\"}.pt-icon-eject::before{content:\"⏏\"}.pt-icon-record::before{content:\"\"}.pt-icon-desktop::before{content:\"\"}.pt-icon-phone::before{content:\"☎\"}.pt-icon-lightbulb::before{content:\"\"}.pt-icon-glass::before{content:\"\"}.pt-icon-tint::before{content:\"\"}.pt-icon-flash::before{content:\"\"}.pt-icon-font::before{content:\"\"}.pt-icon-header::before{content:\"\"}.pt-icon-saved::before{content:\"\"}.pt-icon-floppy-disk::before{content:\"\"}.pt-icon-book::before{content:\"\"}.pt-icon-hand-right::before{content:\"\"}.pt-icon-hand-up::before{content:\"\"}.pt-icon-hand-down::before{content:\"\"}.pt-icon-hand-left::before{content:\"\"}.pt-icon-thumbs-up::before{content:\"\"}.pt-icon-thumbs-down::before{content:\"\"}.pt-icon-box::before{content:\"\"}.pt-icon-compressed::before{content:\"\"}.pt-icon-shopping-cart::before{content:\"\"}.pt-icon-shop::before{content:\"\"}.pt-icon-layout-linear::before{content:\"\"}.pt-icon-undo::before{content:\"⎌\"}.pt-icon-redo::before{content:\"\"}.pt-icon-code-block::before{content:\"\"}.pt-icon-double-caret-vertical::before{content:\"\"}.pt-icon-double-caret-horizontal::before{content:\"\"}.pt-icon-sort-alphabetical-desc::before{content:\"\"}.pt-icon-sort-numerical-desc::before{content:\"\"}.pt-icon-take-action::before{content:\"\"}.pt-icon-contrast::before{content:\"\"}.pt-icon-eye-off::before{content:\"\"}.pt-icon-timeline-area-chart::before{content:\"\"}.pt-icon-doughnut-chart::before{content:\"\"}.pt-icon-layer::before{content:\"\"}.pt-icon-grid::before{content:\"\"}.pt-icon-polygon-filter::before{content:\"\"}.pt-icon-add-to-folder::before{content:\"\"}.pt-icon-layout-balloon::before{content:\"\"}.pt-icon-layout-sorted-clusters::before{content:\"\"}.pt-icon-sort-asc::before{content:\"\"}.pt-icon-sort-desc::before{content:\"\"}.pt-icon-small-cross::before{content:\"\"}.pt-icon-small-tick::before{content:\"\"}.pt-icon-power::before{content:\"\"}.pt-icon-column-layout::before{content:\"\"}.pt-icon-arrow-top-left::before{content:\"↖\"}.pt-icon-arrow-top-right::before{content:\"↗\"}.pt-icon-arrow-bottom-right::before{content:\"↘\"}.pt-icon-arrow-bottom-left::before{content:\"↙\"}.pt-icon-mugshot::before{content:\"\"}.pt-icon-headset::before{content:\"\"}.pt-icon-text-highlight::before{content:\"\"}.pt-icon-hand::before{content:\"\"}.pt-icon-chevron-backward::before{content:\"\"}.pt-icon-chevron-forward::before{content:\"\"}.pt-icon-rotate-document::before{content:\"\"}.pt-icon-rotate-page::before{content:\"\"}.pt-icon-badge::before{content:\"\"}.pt-icon-grid-view::before{content:\"\"}.pt-icon-function::before{content:\"\"}.pt-icon-waterfall-chart::before{content:\"\"}.pt-icon-stacked-chart::before{content:\"\"}.pt-icon-pulse::before{content:\"\"}.pt-icon-new-person::before{content:\"\"}.pt-icon-exclude-row::before{content:\"\"}.pt-icon-pivot-table::before{content:\"\"}.pt-icon-segmented-control::before{content:\"\"}.pt-icon-highlight::before{content:\"\"}.pt-icon-filter-list::before{content:\"\"}.pt-icon-cut::before{content:\"\"}.pt-icon-annotation::before{content:\"\"}.pt-icon-pivot::before{content:\"\"}.pt-icon-ring::before{content:\"\"}.pt-icon-heat-grid::before{content:\"\"}.pt-icon-gantt-chart::before{content:\"\"}.pt-icon-variable::before{content:\"\"}.pt-icon-manual::before{content:\"\"}.pt-icon-add-row-top::before{content:\"\"}.pt-icon-add-row-bottom::before{content:\"\"}.pt-icon-add-column-left::before{content:\"\"}.pt-icon-add-column-right::before{content:\"\"}.pt-icon-remove-row-top::before{content:\"\"}.pt-icon-remove-row-bottom::before{content:\"\"}.pt-icon-remove-column-left::before{content:\"\"}.pt-icon-remove-column-right::before{content:\"\"}.pt-icon-double-chevron-left::before{content:\"\"}.pt-icon-double-chevron-right::before{content:\"\"}.pt-icon-double-chevron-up::before{content:\"\"}.pt-icon-double-chevron-down::before{content:\"\"}.pt-icon-key-control::before{content:\"\"}.pt-icon-key-command::before{content:\"\"}.pt-icon-key-shift::before{content:\"\"}.pt-icon-key-backspace::before{content:\"\"}.pt-icon-key-delete::before{content:\"\"}.pt-icon-key-escape::before{content:\"\"}.pt-icon-key-enter::before{content:\"\"}.pt-icon-calculator::before{content:\"\"}.pt-icon-horizontal-bar-chart::before{content:\"\"}.pt-icon-small-plus::before{content:\"\"}.pt-icon-small-minus::before{content:\"\"}.pt-icon-step-chart::before{content:\"\"}.pt-icon-euro::before{content:\"€\"}.pt-icon-drag-handle-vertical::before{content:\"\"}.pt-icon-drag-handle-horizontal::before{content:\"\"}.pt-icon-mobile-phone::before{content:\"\"}.pt-icon-sim-card::before{content:\"\"}.pt-icon-trending-up::before{content:\"\"}.pt-icon-trending-down::before{content:\"\"}.pt-icon-curved-range-chart::before{content:\"\"}.pt-icon-vertical-bar-chart-desc::before{content:\"\"}.pt-icon-horizontal-bar-chart-desc::before{content:\"\"}.pt-icon-document-open::before{content:\"\"}.pt-icon-document-share::before{content:\"\"}.pt-icon-horizontal-distribution::before{content:\"\"}.pt-icon-vertical-distribution::before{content:\"\"}.pt-icon-alignment-left::before{content:\"\"}.pt-icon-alignment-vertical-center::before{content:\"\"}.pt-icon-alignment-right::before{content:\"\"}.pt-icon-alignment-top::before{content:\"\"}.pt-icon-alignment-horizontal-center::before{content:\"\"}.pt-icon-alignment-bottom::before{content:\"\"}.pt-icon-git-pull::before{content:\"\"}.pt-icon-git-merge::before{content:\"\"}.pt-icon-git-branch::before{content:\"\"}.pt-icon-git-commit::before{content:\"\"}.pt-icon-git-push::before{content:\"\"}.pt-icon-build::before{content:\"\"}.pt-icon-symbol-circle::before{content:\"\"}.pt-icon-symbol-square::before{content:\"\"}.pt-icon-symbol-diamond::before{content:\"\"}.pt-icon-symbol-cross::before{content:\"\"}.pt-icon-symbol-triangle-up::before{content:\"\"}.pt-icon-symbol-triangle-down::before{content:\"\"}.pt-icon-wrench::before{content:\"\"}.pt-icon-application::before{content:\"\"}.pt-icon-send-to-graph::before{content:\"\"}.pt-icon-send-to-map::before{content:\"\"}.pt-icon-join-table::before{content:\"\"}.pt-icon-derive-column::before{content:\"\"}.pt-icon-image-rotate-left::before{content:\"\"}.pt-icon-image-rotate-right::before{content:\"\"}.pt-icon-known-vehicle::before{content:\"\"}.pt-icon-unknown-vehicle::before{content:\"\"}.pt-icon-scatter-plot::before{content:\"\"}.pt-icon-oil-field::before{content:\"\"}.pt-icon-rig::before{content:\"\"}.pt-icon-map-create::before{content:\"\"}.pt-icon-key-option::before{content:\"\"}.pt-icon-list-detail-view::before{content:\"\"}.pt-icon-swap-vertical::before{content:\"\"}.pt-icon-swap-horizontal::before{content:\"\"}.pt-icon-numbered-list::before{content:\"\"}.pt-icon-new-grid-item::before{content:\"\"}.pt-icon-git-repo::before{content:\"\"}.pt-icon-git-new-branch::before{content:\"\"}.pt-icon-manually-entered-data::before{content:\"\"}.pt-icon-airplane::before{content:\"\"}.pt-icon-merge-columns::before{content:\"\"}.pt-icon-split-columns::before{content:\"\"}.pt-icon-dashboard::before{content:\"\"}.pt-icon-publish-function::before{content:\"\"}.pt-icon-path::before{content:\"\"}.pt-icon-moon::before{content:\"\"}.pt-icon-remove-column::before{content:\"\"}.pt-icon-numerical::before{content:\"\"}.pt-icon-key-tab::before{content:\"\"}.pt-icon-regression-chart::before{content:\"\"}.pt-icon-translate::before{content:\"\"}.pt-icon-eye-on::before{content:\"\"}.pt-icon-vertical-bar-chart-asc::before{content:\"\"}.pt-icon-horizontal-bar-chart-asc::before{content:\"\"}.pt-icon-grouped-bar-chart::before{content:\"\"}.pt-icon-full-stacked-chart::before{content:\"\"}.pt-icon-endorsed::before{content:\"\"}.pt-icon-follower::before{content:\"\"}.pt-icon-following::before{content:\"\"}.pt-icon-menu::before{content:\"\"}.pt-icon-collapse-all::before{content:\"\"}.pt-icon-expand-all::before{content:\"\"}.pt-icon-intersection::before{content:\"\"}.pt-icon-blocked-person::before{content:\"\"}.pt-icon-slash::before{content:\"\"}.pt-icon-percentage::before{content:\"\"}.pt-icon-satellite::before{content:\"\"}.pt-icon-paragraph::before{content:\"\"}.pt-icon-banl-account::before{content:\"\"}.pt-icon-cell-tower::before{content:\"\"}.pt-icon-id-number::before{content:\"\"}.pt-icon-ip-address::before{content:\"\"}:focus{outline:rgba(19,124,189,.5) auto 2px;outline-offset:2px;-moz-outline-radius:6px}.pt-focus-disabled :focus,.pt-focus-disabled :focus~.pt-control-indicator{outline:0!important}.pt-alert{max-width:400px;padding:20px}.pt-alert-body{display:-webkit-flex;display:flex}.pt-alert-body .pt-icon{margin-right:20px;font-size:40px}.pt-alert-footer{display:-webkit-flex;display:flex;-webkit-flex-direction:row-reverse;flex-direction:row-reverse;margin-top:10px}.pt-alert-footer .pt-button{margin-left:10px}.pt-breadcrumbs{display:inline-block;margin:0;cursor:default;padding:0;list-style:none;vertical-align:top;line-height:30px}.pt-breadcrumbs>li{float:left}.pt-breadcrumbs>li::after{line-height:1;font-family:Icons16,sans-serif;font-size:16px;font-weight:400;font-style:normal;padding:0 5px;color:#5c7080;content:\"\"}.pt-breadcrumbs>li:last-child::after{display:none}.pt-breadcrumb,.pt-breadcrumb-current,.pt-breadcrumbs-collapsed{display:inline-block;line-height:19px;font-size:16px}.pt-breadcrumb,.pt-breadcrumbs-collapsed{color:#5c7080}.pt-breadcrumb:hover{text-decoration:none}.pt-breadcrumb.pt-disabled{cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-breadcrumb-current{color:inherit;font-weight:600}.pt-breadcrumb-current .pt-input{vertical-align:baseline;font-size:inherit;font-weight:inherit}.pt-breadcrumbs-collapsed::before,.pt-button[class*=pt-icon-]::before{font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-breadcrumbs-collapsed{margin-right:2px;border:none;border-radius:3px;background:#ced9e0;cursor:pointer;padding:0 5px}.pt-breadcrumbs-collapsed::before{font-family:Icons20,sans-serif;font-size:20px;line-height:19px;content:\"\"}.pt-breadcrumbs-collapsed:hover{background:#bfccd6;text-decoration:none;color:#182026}.pt-dark .pt-breadcrumb,.pt-dark .pt-breadcrumbs-collapsed,.pt-dark .pt-breadcrumbs>li::after{color:#bfccd6}.pt-dark .pt-breadcrumb.pt-disabled{color:rgba(191,204,214,.5)}.pt-dark .pt-breadcrumb-current{color:#f5f8fa}.pt-dark .pt-breadcrumbs-collapsed{background:rgba(16,22,26,.4)}.pt-dark .pt-breadcrumbs-collapsed:hover{background:rgba(16,22,26,.6);color:#f5f8fa}.pt-button{display:inline-block;border:none;border-radius:3px;cursor:pointer;padding:0 10px;vertical-align:middle;font-size:14px;min-width:30px;min-height:30px;line-height:30px}.pt-button.pt-disabled,.pt-button:disabled{cursor:not-allowed}.pt-button.pt-fill{width:100%}.pt-button:not([class*=pt-intent-]){background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);color:#182026}.pt-button:not([class*=pt-intent-]):hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);background-clip:padding-box}.pt-button:not([class*=pt-intent-]).pt-active,.pt-button:not([class*=pt-intent-]):active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-button:not([class*=pt-intent-]).pt-disabled,.pt-button:not([class*=pt-intent-]):disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-button.pt-intent-primary,.pt-button.pt-intent-primary:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button:not([class*=pt-intent-]).pt-disabled.pt-active,.pt-button:not([class*=pt-intent-]):disabled.pt-active{background:rgba(206,217,224,.7)}.pt-button.pt-intent-primary{background:#137cbd;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #137cbd;color:#fff}.pt-button.pt-intent-primary.pt-active,.pt-button.pt-intent-primary:active,.pt-button.pt-intent-primary:hover{color:#fff}.pt-button.pt-intent-primary:hover{background:#106ba3;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #106ba3}.pt-button.pt-intent-primary.pt-active,.pt-button.pt-intent-primary:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#0e5a8a;background-image:none}.pt-button.pt-intent-primary.pt-disabled,.pt-button.pt-intent-primary:disabled{border-color:transparent;box-shadow:none;background-color:rgba(19,124,189,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button.pt-intent-success,.pt-button.pt-intent-success:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button.pt-intent-success{background:#0f9960;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #0f9960;color:#fff}.pt-button.pt-intent-success.pt-active,.pt-button.pt-intent-success:active,.pt-button.pt-intent-success:hover{color:#fff}.pt-button.pt-intent-success:hover{background:#0d8050;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #0d8050}.pt-button.pt-intent-success.pt-active,.pt-button.pt-intent-success:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#0a6640;background-image:none}.pt-button.pt-intent-success.pt-disabled,.pt-button.pt-intent-success:disabled{border-color:transparent;box-shadow:none;background-color:rgba(15,153,96,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button.pt-intent-warning,.pt-button.pt-intent-warning:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button.pt-intent-warning{background:#d9822b;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #d9822b;color:#fff}.pt-button.pt-intent-warning.pt-active,.pt-button.pt-intent-warning:active,.pt-button.pt-intent-warning:hover{color:#fff}.pt-button.pt-intent-warning:hover{background:#bf7326;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #bf7326}.pt-button.pt-intent-warning.pt-active,.pt-button.pt-intent-warning:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#a66321;background-image:none}.pt-button.pt-intent-warning.pt-disabled,.pt-button.pt-intent-warning:disabled{border-color:transparent;box-shadow:none;background-color:rgba(217,130,43,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button.pt-intent-danger,.pt-button.pt-intent-danger:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button.pt-intent-danger{background:#db3737;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #db3737;color:#fff}.pt-button.pt-intent-danger.pt-active,.pt-button.pt-intent-danger:active,.pt-button.pt-intent-danger:hover{color:#fff}.pt-button.pt-intent-danger:hover{background:#c23030;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #c23030}.pt-button.pt-intent-danger.pt-active,.pt-button.pt-intent-danger:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#a82a2a;background-image:none}.pt-button.pt-intent-danger.pt-disabled,.pt-button.pt-intent-danger:disabled{border-color:transparent;box-shadow:none;background-color:rgba(219,55,55,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button[class*=pt-intent-] .pt-button-spinner .pt-spinner-head{stroke:#fff}.pt-button.pt-loading{position:relative}.pt-button.pt-loading .pt-button-spinner{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.pt-button[class*=pt-icon-]::before{line-height:1;font-family:Icons16,sans-serif;font-size:16px;margin-right:7px;color:#5c7080}.pt-button .pt-icon,.pt-button .pt-icon-large,.pt-button .pt-icon-standard{margin-right:7px;color:#5c7080}.pt-button .pt-icon-large.pt-align-right,.pt-button .pt-icon-standard.pt-align-right,.pt-button .pt-icon.pt-align-right{margin-right:0;margin-left:7px}.pt-dark .pt-button:not([class*=pt-intent-]){background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-button:not([class*=pt-intent-]).pt-active,.pt-dark .pt-button:not([class*=pt-intent-]):active,.pt-dark .pt-button:not([class*=pt-intent-]):hover{color:#f5f8fa}.pt-dark .pt-button:not([class*=pt-intent-]):hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-button:not([class*=pt-intent-]).pt-active,.pt-dark .pt-button:not([class*=pt-intent-]):active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-button:not([class*=pt-intent-]).pt-disabled,.pt-dark .pt-button:not([class*=pt-intent-]):disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-button:not([class*=pt-intent-]).pt-disabled.pt-active,.pt-dark .pt-button:not([class*=pt-intent-]):disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-button:not([class*=pt-intent-]) .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-dark .pt-button:not([class*=pt-intent-]) .pt-icon,.pt-dark .pt-button:not([class*=pt-intent-]) .pt-icon-large,.pt-dark .pt-button:not([class*=pt-intent-]) .pt-icon-standard,.pt-dark .pt-button:not([class*=pt-intent-])[class*=pt-icon-]::before{color:#bfccd6}.pt-dark .pt-button[class*=pt-intent-],.pt-dark .pt-button[class*=pt-intent-]:hover{box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-button[class*=pt-intent-].pt-active,.pt-dark .pt-button[class*=pt-intent-]:active{box-shadow:0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2)}.pt-dark .pt-button[class*=pt-intent-].pt-disabled,.pt-dark .pt-button[class*=pt-intent-]:disabled{box-shadow:none;background-image:none;color:rgba(255,255,255,.3)}.pt-dark .pt-button[class*=pt-intent-] .pt-button-spinner .pt-spinner-head{stroke:#8a9ba8}.pt-button.pt-disabled .pt-icon,.pt-button.pt-disabled .pt-icon-large,.pt-button.pt-disabled .pt-icon-standard,.pt-button.pt-disabled::before,.pt-button:disabled .pt-icon,.pt-button:disabled .pt-icon-large,.pt-button:disabled .pt-icon-standard,.pt-button:disabled::before,.pt-button[class*=pt-intent-] .pt-icon,.pt-button[class*=pt-intent-] .pt-icon-large,.pt-button[class*=pt-intent-] .pt-icon-standard,.pt-button[class*=pt-intent-]::before{color:inherit!important}.pt-button.pt-minimal{box-shadow:none;background:0 0}.pt-button.pt-minimal:focus,.pt-button.pt-minimal:hover{box-shadow:none;background:rgba(167,182,194,.3);text-decoration:none;color:#182026}.pt-button.pt-minimal.pt-active,.pt-button.pt-minimal:active{box-shadow:none;background:rgba(115,134,148,.3);color:#182026}.pt-button.pt-minimal.pt-disabled,.pt-button.pt-minimal.pt-disabled:hover,.pt-button.pt-minimal:disabled,.pt-button.pt-minimal:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-dark .pt-button.pt-minimal{box-shadow:none;background:0 0;color:inherit}.pt-dark .pt-button.pt-minimal.pt-active,.pt-dark .pt-button.pt-minimal:active,.pt-dark .pt-button.pt-minimal:hover{box-shadow:none}.pt-dark .pt-button.pt-minimal:hover{background:rgba(138,155,168,.15)}.pt-dark .pt-button.pt-minimal.pt-active,.pt-dark .pt-button.pt-minimal:active{background:rgba(138,155,168,.3);color:#f5f8fa}.pt-dark .pt-button.pt-minimal.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-disabled:hover,.pt-dark .pt-button.pt-minimal:disabled,.pt-dark .pt-button.pt-minimal:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(191,204,214,.5)}.pt-button.pt-minimal.pt-intent-primary{color:#106ba3}.pt-button.pt-minimal.pt-intent-primary.pt-active,.pt-button.pt-minimal.pt-intent-primary:active,.pt-button.pt-minimal.pt-intent-primary:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-primary:focus,.pt-button.pt-minimal.pt-intent-primary:hover{background:rgba(19,124,189,.15);color:#106ba3}.pt-button.pt-minimal.pt-intent-primary.pt-active,.pt-button.pt-minimal.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#106ba3}.pt-button.pt-minimal.pt-intent-primary.pt-disabled,.pt-button.pt-minimal.pt-intent-primary:disabled{background:0 0;color:rgba(16,107,163,.5)}.pt-button.pt-minimal.pt-intent-primary .pt-button-spinner .pt-spinner-head{stroke:#106ba3}.pt-dark .pt-button.pt-minimal.pt-intent-primary{color:#48aff0}.pt-dark .pt-button.pt-minimal.pt-intent-primary:focus,.pt-dark .pt-button.pt-minimal.pt-intent-primary:hover{background:rgba(19,124,189,.2);color:#48aff0}.pt-dark .pt-button.pt-minimal.pt-intent-primary.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#48aff0}.pt-dark .pt-button.pt-minimal.pt-intent-primary.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-primary:disabled{background:0 0;color:rgba(72,175,240,.5)}.pt-button.pt-minimal.pt-intent-success{color:#0d8050}.pt-button.pt-minimal.pt-intent-success.pt-active,.pt-button.pt-minimal.pt-intent-success:active,.pt-button.pt-minimal.pt-intent-success:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-success:focus,.pt-button.pt-minimal.pt-intent-success:hover{background:rgba(15,153,96,.15);color:#0d8050}.pt-button.pt-minimal.pt-intent-success.pt-active,.pt-button.pt-minimal.pt-intent-success:active{background:rgba(15,153,96,.3);color:#0d8050}.pt-button.pt-minimal.pt-intent-success.pt-disabled,.pt-button.pt-minimal.pt-intent-success:disabled{background:0 0;color:rgba(13,128,80,.5)}.pt-button.pt-minimal.pt-intent-success .pt-button-spinner .pt-spinner-head{stroke:#0d8050}.pt-dark .pt-button.pt-minimal.pt-intent-success{color:#3dcc91}.pt-dark .pt-button.pt-minimal.pt-intent-success:focus,.pt-dark .pt-button.pt-minimal.pt-intent-success:hover{background:rgba(15,153,96,.2);color:#3dcc91}.pt-dark .pt-button.pt-minimal.pt-intent-success.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-success:active{background:rgba(15,153,96,.3);color:#3dcc91}.pt-dark .pt-button.pt-minimal.pt-intent-success.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-success:disabled{background:0 0;color:rgba(61,204,145,.5)}.pt-button.pt-minimal.pt-intent-warning{color:#bf7326}.pt-button.pt-minimal.pt-intent-warning.pt-active,.pt-button.pt-minimal.pt-intent-warning:active,.pt-button.pt-minimal.pt-intent-warning:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-warning:focus,.pt-button.pt-minimal.pt-intent-warning:hover{background:rgba(217,130,43,.15);color:#bf7326}.pt-button.pt-minimal.pt-intent-warning.pt-active,.pt-button.pt-minimal.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#bf7326}.pt-button.pt-minimal.pt-intent-warning.pt-disabled,.pt-button.pt-minimal.pt-intent-warning:disabled{background:0 0;color:rgba(191,115,38,.5)}.pt-button.pt-minimal.pt-intent-warning .pt-button-spinner .pt-spinner-head{stroke:#bf7326}.pt-dark .pt-button.pt-minimal.pt-intent-warning{color:#ffb366}.pt-dark .pt-button.pt-minimal.pt-intent-warning:focus,.pt-dark .pt-button.pt-minimal.pt-intent-warning:hover{background:rgba(217,130,43,.2);color:#ffb366}.pt-dark .pt-button.pt-minimal.pt-intent-warning.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#ffb366}.pt-dark .pt-button.pt-minimal.pt-intent-warning.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-warning:disabled{background:0 0;color:rgba(255,179,102,.5)}.pt-button.pt-minimal.pt-intent-danger{color:#c23030}.pt-button.pt-minimal.pt-intent-danger.pt-active,.pt-button.pt-minimal.pt-intent-danger:active,.pt-button.pt-minimal.pt-intent-danger:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-danger:focus,.pt-button.pt-minimal.pt-intent-danger:hover{background:rgba(219,55,55,.15);color:#c23030}.pt-button.pt-minimal.pt-intent-danger.pt-active,.pt-button.pt-minimal.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#c23030}.pt-button.pt-minimal.pt-intent-danger.pt-disabled,.pt-button.pt-minimal.pt-intent-danger:disabled{background:0 0;color:rgba(194,48,48,.5)}.pt-button.pt-minimal.pt-intent-danger .pt-button-spinner .pt-spinner-head{stroke:#c23030}.pt-dark .pt-button.pt-minimal.pt-intent-danger{color:#ff7373}.pt-dark .pt-button.pt-minimal.pt-intent-danger:focus,.pt-dark .pt-button.pt-minimal.pt-intent-danger:hover{background:rgba(219,55,55,.2);color:#ff7373}.pt-dark .pt-button.pt-minimal.pt-intent-danger.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#ff7373}.pt-dark .pt-button.pt-minimal.pt-intent-danger.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-danger:disabled{background:0 0;color:rgba(255,115,115,.5)}.pt-button.pt-large,.pt-large .pt-button{min-width:40px;min-height:40px;line-height:40px;padding:0 15px;font-size:16px}.pt-button.pt-large .pt-icon,.pt-button.pt-large .pt-icon-large,.pt-button.pt-large .pt-icon-standard,.pt-button.pt-large[class*=pt-icon-]::before,.pt-large .pt-button .pt-icon,.pt-large .pt-button .pt-icon-large,.pt-large .pt-button .pt-icon-standard,.pt-large .pt-button[class*=pt-icon-]::before{margin-right:10px}.pt-button.pt-large .pt-icon-large.pt-align-right,.pt-button.pt-large .pt-icon-standard.pt-align-right,.pt-button.pt-large .pt-icon.pt-align-right,.pt-large .pt-button .pt-icon-large.pt-align-right,.pt-large .pt-button .pt-icon-standard.pt-align-right,.pt-large .pt-button .pt-icon.pt-align-right{margin-right:0;margin-left:10px}.pt-button.pt-small,.pt-small .pt-button{min-width:24px;min-height:24px;line-height:24px;padding:0 7px;font-size:12px}.pt-button.pt-small .pt-icon,.pt-button.pt-small .pt-icon-large,.pt-button.pt-small .pt-icon-standard,.pt-button.pt-small[class*=pt-icon-]::before,.pt-small .pt-button .pt-icon,.pt-small .pt-button .pt-icon-large,.pt-small .pt-button .pt-icon-standard,.pt-small .pt-button[class*=pt-icon-]::before{display:none}.pt-button[class*=pt-icon-]:empty{padding:0}.pt-button[class*=pt-icon-]:empty::before{margin-right:0}a.pt-button{text-align:center;text-decoration:none;transition:none}a.pt-button,a.pt-button:active,a.pt-button:hover{color:#182026}a.pt-button.pt-disabled{color:rgba(92,112,128,.5)}.pt-button-group{display:-webkit-inline-flex;display:inline-flex}.pt-button-group .pt-button{-webkit-flex:0 0 auto;flex:0 0 auto;position:relative;z-index:4}.pt-button-group .pt-button:focus{z-index:5}.pt-button-group .pt-button:hover{z-index:6}.pt-button-group .pt-button.pt-active,.pt-button-group .pt-button:active{z-index:7}.pt-button-group .pt-button.pt-disabled,.pt-button-group .pt-button:disabled{z-index:3}.pt-button-group .pt-button[class*=pt-intent-]{z-index:9}.pt-button-group .pt-button[class*=pt-intent-]:focus{z-index:10}.pt-button-group .pt-button[class*=pt-intent-]:hover{z-index:11}.pt-button-group .pt-button[class*=pt-intent-].pt-active,.pt-button-group .pt-button[class*=pt-intent-]:active{z-index:12}.pt-button-group .pt-button[class*=pt-intent-].pt-disabled,.pt-button-group .pt-button[class*=pt-intent-]:disabled{z-index:8}.pt-button-group:not(.pt-minimal)>.pt-button:not(:first-child),.pt-button-group:not(.pt-minimal)>.pt-popover-target:not(:first-child) .pt-button{border-top-left-radius:0;border-bottom-left-radius:0}.pt-button-group:not(.pt-minimal)>.pt-button:not(:last-child),.pt-button-group:not(.pt-minimal)>.pt-popover-target:not(:last-child) .pt-button{margin-right:-1px;border-top-right-radius:0;border-bottom-right-radius:0}.pt-button-group.pt-minimal .pt-button{box-shadow:none;background:0 0;margin-right:11px;border-radius:3px!important;overflow:visible}.pt-button-group.pt-minimal .pt-button:focus,.pt-button-group.pt-minimal .pt-button:hover{box-shadow:none;background:rgba(167,182,194,.3);text-decoration:none;color:#182026}.pt-button-group.pt-minimal .pt-button.pt-active,.pt-button-group.pt-minimal .pt-button:active{box-shadow:none;background:rgba(115,134,148,.3);color:#182026}.pt-button-group.pt-minimal .pt-button.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-disabled:hover,.pt-button-group.pt-minimal .pt-button:disabled,.pt-button-group.pt-minimal .pt-button:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-dark .pt-button-group.pt-minimal .pt-button{box-shadow:none;background:0 0;color:inherit}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button:active,.pt-dark .pt-button-group.pt-minimal .pt-button:hover{box-shadow:none}.pt-dark .pt-button-group.pt-minimal .pt-button:hover{background:rgba(138,155,168,.15)}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button:active{background:rgba(138,155,168,.3);color:#f5f8fa}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-disabled:hover,.pt-dark .pt-button-group.pt-minimal .pt-button:disabled,.pt-dark .pt-button-group.pt-minimal .pt-button:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(191,204,214,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-primary{color:#106ba3}.pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:active,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-primary:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:hover{background:rgba(19,124,189,.15);color:#106ba3}.pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#106ba3}.pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:disabled{background:0 0;color:rgba(16,107,163,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-primary .pt-button-spinner .pt-spinner-head{stroke:#106ba3}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary{color:#48aff0}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:hover{background:rgba(19,124,189,.2);color:#48aff0}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#48aff0}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:disabled{background:0 0;color:rgba(72,175,240,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-success{color:#0d8050}.pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-success:active,.pt-button-group.pt-minimal .pt-button.pt-intent-success:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-success:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-success:hover{background:rgba(15,153,96,.15);color:#0d8050}.pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-success:active{background:rgba(15,153,96,.3);color:#0d8050}.pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-success:disabled{background:0 0;color:rgba(13,128,80,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-success .pt-button-spinner .pt-spinner-head{stroke:#0d8050}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success{color:#3dcc91}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:hover{background:rgba(15,153,96,.2);color:#3dcc91}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:active{background:rgba(15,153,96,.3);color:#3dcc91}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:disabled{background:0 0;color:rgba(61,204,145,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-warning{color:#bf7326}.pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:active,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-warning:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:hover{background:rgba(217,130,43,.15);color:#bf7326}.pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#bf7326}.pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:disabled{background:0 0;color:rgba(191,115,38,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-warning .pt-button-spinner .pt-spinner-head{stroke:#bf7326}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning{color:#ffb366}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:hover{background:rgba(217,130,43,.2);color:#ffb366}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#ffb366}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:disabled{background:0 0;color:rgba(255,179,102,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-danger{color:#c23030}.pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:active,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-danger:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:hover{background:rgba(219,55,55,.15);color:#c23030}.pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#c23030}.pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:disabled{background:0 0;color:rgba(194,48,48,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-danger .pt-button-spinner .pt-spinner-head{stroke:#c23030}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger{color:#ff7373}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:hover{background:rgba(219,55,55,.2);color:#ff7373}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#ff7373}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:disabled{background:0 0;color:rgba(255,115,115,.5)}.pt-button-group.pt-minimal .pt-button:focus{outline-style:solid}.pt-dialog:focus,.pt-editable-input:focus{outline:0}.pt-button-group.pt-minimal .pt-button::after{margin:5px;background:rgba(16,22,26,.15);width:1px;display:inline-block;position:absolute;top:10%;bottom:10%;left:100%;content:\"\"}.pt-dark .pt-button-group.pt-minimal .pt-button::after{background:rgba(255,255,255,.15)}.pt-button-group.pt-minimal>.pt-button:last-child,.pt-button-group.pt-minimal>.pt-popover-target:last-child .pt-button{margin-right:0}.pt-button-group.pt-minimal>.pt-button:last-child::after,.pt-button-group.pt-minimal>.pt-popover-target:last-child .pt-button::after{display:none}.pt-button-group.pt-fill{display:-webkit-flex;display:flex}.pt-button-group .pt-button.pt-fill,.pt-button-group.pt-fill .pt-button:not(.pt-fixed){-webkit-flex:1 1 auto;flex:1 1 auto}.pt-button-group.pt-vertical{-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;align-items:stretch;vertical-align:top}.pt-button-group.pt-vertical .pt-button{margin-right:0!important}.pt-button-group.pt-vertical>.pt-button:first-child,.pt-button-group.pt-vertical>.pt-popover-target:first-child .pt-button{border-radius:3px 3px 0 0}.pt-button-group.pt-vertical>.pt-button:last-child,.pt-button-group.pt-vertical>.pt-popover-target:last-child .pt-button{border-radius:0 0 3px 3px}.pt-button-group.pt-vertical>.pt-button:not(:last-child),.pt-button-group.pt-vertical>.pt-popover-target:not(:last-child) .pt-button{margin-bottom:-1px}.pt-button-group.pt-vertical.pt-minimal .pt-button:not(:last-child){margin-bottom:11px}.pt-button-group.pt-vertical.pt-minimal .pt-button::after{top:100%;right:0;bottom:auto;left:0;width:auto;height:1px}.pt-button-group.pt-align-left .pt-button{text-align:left}.pt-dark .pt-button-group:not(.pt-minimal)>.pt-button:not(:last-child),.pt-dark .pt-button-group:not(.pt-minimal)>.pt-popover-target:not(:last-child) .pt-button{margin-right:1px}.pt-dark .pt-button-group.pt-vertical>.pt-button:not(:last-child),.pt-dark .pt-button-group.pt-vertical>.pt-popover-target:not(:last-child) .pt-button{margin-bottom:1px}.pt-callout{line-height:1.5;font-size:14px;position:relative;border-radius:3px;background-color:rgba(138,155,168,.15);padding:10px 12px 9px}.pt-callout[class*=pt-icon-]::before,.pt-dialog-close-button{font-family:Icons20,sans-serif;font-size:20px;font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-callout[class*=pt-icon-]{padding-left:40px}.pt-callout[class*=pt-icon-]::before{line-height:1;position:absolute;top:10px;left:10px;color:#5c7080}.pt-callout h5{margin-top:0;margin-bottom:5px;line-height:20px}.pt-dark .pt-callout{background-color:rgba(138,155,168,.2)}.pt-dark .pt-callout[class*=pt-icon-]::before{color:#bfccd6}.pt-callout.pt-intent-primary{background-color:rgba(19,124,189,.15)}.pt-callout.pt-intent-primary h5,.pt-callout.pt-intent-primary[class*=pt-icon-]::before{color:#106ba3}.pt-dark .pt-callout.pt-intent-primary{background-color:rgba(19,124,189,.25)}.pt-dark .pt-callout.pt-intent-primary h5,.pt-dark .pt-callout.pt-intent-primary[class*=pt-icon-]::before{color:#48aff0}.pt-callout.pt-intent-success{background-color:rgba(15,153,96,.15)}.pt-callout.pt-intent-success h5,.pt-callout.pt-intent-success[class*=pt-icon-]::before{color:#0d8050}.pt-dark .pt-callout.pt-intent-success{background-color:rgba(15,153,96,.25)}.pt-dark .pt-callout.pt-intent-success h5,.pt-dark .pt-callout.pt-intent-success[class*=pt-icon-]::before{color:#3dcc91}.pt-callout.pt-intent-warning{background-color:rgba(217,130,43,.15)}.pt-callout.pt-intent-warning h5,.pt-callout.pt-intent-warning[class*=pt-icon-]::before{color:#bf7326}.pt-dark .pt-callout.pt-intent-warning{background-color:rgba(217,130,43,.25)}.pt-dark .pt-callout.pt-intent-warning h5,.pt-dark .pt-callout.pt-intent-warning[class*=pt-icon-]::before{color:#ffb366}.pt-callout.pt-intent-danger{background-color:rgba(219,55,55,.15)}.pt-callout.pt-intent-danger h5,.pt-callout.pt-intent-danger[class*=pt-icon-]::before{color:#c23030}.pt-dark .pt-callout.pt-intent-danger{background-color:rgba(219,55,55,.25)}.pt-dark .pt-callout.pt-intent-danger h5,.pt-dark .pt-callout.pt-intent-danger[class*=pt-icon-]::before{color:#ff7373}.pt-running-text .pt-callout{margin:20px 0}.pt-card{border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.15),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0);background-color:#fff;padding:20px;transition:box-shadow .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),box-shadow .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),box-shadow .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9)}.pt-card.pt-dark,.pt-dark .pt-card{box-shadow:0 0 0 1px rgba(16,22,26,.4),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0);background-color:#30404d}.pt-elevation-0{box-shadow:0 0 0 1px rgba(16,22,26,.15),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0)}.pt-dark .pt-elevation-0,.pt-elevation-0.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.4),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0)}.pt-elevation-1{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.2)}.pt-dark .pt-elevation-1,.pt-elevation-1.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-elevation-2{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 1px 1px rgba(16,22,26,.2),0 2px 6px rgba(16,22,26,.2)}.pt-dark .pt-elevation-2,.pt-elevation-2.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.4),0 2px 6px rgba(16,22,26,.4)}.pt-elevation-3{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2)}.pt-dark .pt-elevation-3,.pt-elevation-3.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-elevation-4{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 4px 8px rgba(16,22,26,.2),0 18px 46px 6px rgba(16,22,26,.2)}.pt-dark .pt-elevation-4,.pt-elevation-4.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 4px 8px rgba(16,22,26,.4),0 18px 46px 6px rgba(16,22,26,.4)}.pt-card.pt-interactive:hover{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);cursor:pointer}.pt-card.pt-interactive:hover.pt-dark,.pt-dark .pt-card.pt-interactive:hover{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-card.pt-interactive:active{opacity:.9;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.2);transition-duration:0}.pt-card.pt-interactive:active.pt-dark,.pt-dark .pt-card.pt-interactive:active{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-collapse{height:0;overflow-y:hidden;transition:height .2s cubic-bezier(.4,1,.75,.9)}.pt-collapse .pt-collapse-body{transition:-webkit-transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9)}.pt-context-menu .pt-popover-target{display:block}.pt-context-menu-popover-target{position:fixed}.pt-dialog{opacity:1;-webkit-transform:translateX(50%) scale(1);transform:translateX(50%) scale(1);display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;position:absolute;top:25%;right:50%;z-index:20;margin-bottom:20px;border-radius:6px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 4px 8px rgba(16,22,26,.2),0 18px 46px 6px rgba(16,22,26,.2);background:#ebf1f5;width:500px;padding-bottom:20px}.pt-dialog.pt-overlay-appear,.pt-dialog.pt-overlay-enter{opacity:0;-webkit-transform:translateX(50%) scale(.5);transform:translateX(50%) scale(.5)}.pt-dialog.pt-overlay-appear-active,.pt-dialog.pt-overlay-enter-active{opacity:1;-webkit-transform:translateX(50%) scale(1);transform:translateX(50%) scale(1);transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-dialog.pt-overlay-leave{opacity:1;-webkit-transform:translateX(50%) scale(1);transform:translateX(50%) scale(1)}.pt-dialog.pt-overlay-leave-active{opacity:0;-webkit-transform:translateX(50%) scale(.5);transform:translateX(50%) scale(.5);transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-dark .pt-dialog,.pt-dialog.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 4px 8px rgba(16,22,26,.4),0 18px 46px 6px rgba(16,22,26,.4);background:#293742;color:#f5f8fa}.pt-dialog-header{display:-webkit-flex;display:flex;-webkit-flex:0 0 auto;flex:0 0 auto;-webkit-align-items:center;align-items:center;border-radius:6px 6px 0 0;box-shadow:0 1px 0 rgba(16,22,26,.15);background:#fff;min-height:40px;padding-left:20px}.pt-dialog-header .pt-icon-large{-webkit-flex:0 0 auto;flex:0 0 auto;margin-right:10px;color:#5c7080}.pt-dialog-header h5{overflow:hidden;-webkit-flex:1 1 auto;flex:1 1 auto;margin:0;line-height:inherit}.pt-dialog-header h5:last-child{margin-right:20px}.pt-dark .pt-dialog-header{box-shadow:0 1px 0 rgba(16,22,26,.4);background:#30404d}.pt-dark .pt-dialog-header .pt-icon-large{color:#bfccd6}.pt-dark .pt-dialog-header h5{color:#f5f8fa}.pt-dialog-close-button{line-height:1;color:#5c7080;-webkit-flex:0 0 auto;flex:0 0 auto;border:none;background:0 0;cursor:pointer;padding:10px}.pt-dialog-close-button:hover{color:#182026}.pt-dark .pt-dialog-close-button{color:#bfccd6}.pt-dark .pt-dialog-close-button:hover{color:#f5f8fa}.pt-dialog-body{-webkit-flex:1 1 auto;flex:1 1 auto;margin:20px;line-height:18px}.pt-dialog-footer{-webkit-flex:0 0 auto;flex:0 0 auto;margin:0 20px}.pt-dialog-footer-actions{display:-webkit-flex;display:flex;-webkit-justify-content:flex-end;justify-content:flex-end}.pt-dialog-footer-actions .pt-button{margin-left:10px}.pt-editable-text{display:inline-block;position:relative;cursor:text;max-width:100%;vertical-align:top}.pt-editable-text::before{position:absolute;top:-3px;right:-3px;bottom:-3px;left:-3px;border-radius:3px;content:\"\";transition:background-color .1s cubic-bezier(.4,1,.75,.9),box-shadow .1s cubic-bezier(.4,1,.75,.9)}.pt-editable-text:hover::before{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.15)}.pt-editable-text.pt-editable-editing::before{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2);background-color:#fff}.pt-editable-text.pt-disabled::before{box-shadow:none}.pt-editable-text.pt-intent-primary .pt-editable-content,.pt-editable-text.pt-intent-primary .pt-editable-input{color:#137cbd}.pt-editable-text.pt-intent-primary:hover::before{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(19,124,189,.4)}.pt-editable-text.pt-intent-primary.pt-editable-editing::before{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-editable-text.pt-intent-success .pt-editable-content,.pt-editable-text.pt-intent-success .pt-editable-input{color:#0f9960}.pt-editable-text.pt-intent-success:hover::before{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px rgba(15,153,96,.4)}.pt-editable-text.pt-intent-success.pt-editable-editing::before{box-shadow:0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-editable-text.pt-intent-warning .pt-editable-content,.pt-editable-text.pt-intent-warning .pt-editable-input{color:#d9822b}.pt-editable-text.pt-intent-warning:hover::before{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px rgba(217,130,43,.4)}.pt-editable-text.pt-intent-warning.pt-editable-editing::before{box-shadow:0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-editable-text.pt-intent-danger .pt-editable-content,.pt-editable-text.pt-intent-danger .pt-editable-input{color:#db3737}.pt-editable-text.pt-intent-danger:hover::before{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px rgba(219,55,55,.4)}.pt-editable-text.pt-intent-danger.pt-editable-editing::before{box-shadow:0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-dark .pt-editable-text:hover::before{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(255,255,255,.15)}.pt-dark .pt-editable-text.pt-editable-editing::before{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background-color:rgba(16,22,26,.3)}.pt-dark .pt-editable-text.pt-disabled::before{box-shadow:none}.pt-dark .pt-editable-text.pt-intent-primary .pt-editable-content{color:#48aff0}.pt-dark .pt-editable-text.pt-intent-primary:hover::before{box-shadow:0 0 0 0 rgba(72,175,240,0),0 0 0 0 rgba(72,175,240,0),inset 0 0 0 1px rgba(72,175,240,.4)}.pt-dark .pt-editable-text.pt-intent-primary.pt-editable-editing::before{box-shadow:0 0 0 1px #48aff0,0 0 0 3px rgba(72,175,240,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-editable-text.pt-intent-success .pt-editable-content{color:#3dcc91}.pt-dark .pt-editable-text.pt-intent-success:hover::before{box-shadow:0 0 0 0 rgba(61,204,145,0),0 0 0 0 rgba(61,204,145,0),inset 0 0 0 1px rgba(61,204,145,.4)}.pt-dark .pt-editable-text.pt-intent-success.pt-editable-editing::before{box-shadow:0 0 0 1px #3dcc91,0 0 0 3px rgba(61,204,145,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-editable-text.pt-intent-warning .pt-editable-content{color:#ffb366}.pt-dark .pt-editable-text.pt-intent-warning:hover::before{box-shadow:0 0 0 0 rgba(255,179,102,0),0 0 0 0 rgba(255,179,102,0),inset 0 0 0 1px rgba(255,179,102,.4)}.pt-dark .pt-editable-text.pt-intent-warning.pt-editable-editing::before{box-shadow:0 0 0 1px #ffb366,0 0 0 3px rgba(255,179,102,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-editable-text.pt-intent-danger .pt-editable-content{color:#ff7373}.pt-dark .pt-editable-text.pt-intent-danger:hover::before{box-shadow:0 0 0 0 rgba(255,115,115,0),0 0 0 0 rgba(255,115,115,0),inset 0 0 0 1px rgba(255,115,115,.4)}.pt-dark .pt-editable-text.pt-intent-danger.pt-editable-editing::before{box-shadow:0 0 0 1px #ff7373,0 0 0 3px rgba(255,115,115,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-editable-content,.pt-editable-input{display:inherit;position:relative;min-width:inherit;max-width:inherit;vertical-align:top;text-transform:inherit;letter-spacing:inherit;color:inherit;font:inherit;resize:none}.pt-control .pt-control-indicator,.pt-input-group .pt-icon,.pt-select::after{font-style:normal;-moz-osx-font-smoothing:grayscale}.pt-editable-input{border:none;box-shadow:none;background:0 0;width:100%;padding:0;white-space:pre-wrap}.pt-editable-input::-ms-clear{display:none}.pt-editable-content{overflow:hidden;padding-right:2px;text-overflow:ellipsis;white-space:pre}.pt-editable-editing>.pt-editable-content{position:absolute;left:0;visibility:hidden}.pt-control,.pt-control-group .pt-button,.pt-control-group .pt-input,.pt-control-group .pt-select{position:relative}.pt-editable-placeholder>.pt-editable-content{color:rgba(92,112,128,.5)}.pt-dark .pt-editable-placeholder>.pt-editable-content{color:rgba(191,204,214,.5)}.pt-editable-text.pt-multiline{display:block}.pt-editable-text.pt-multiline .pt-editable-content{overflow:auto;white-space:pre-wrap}.pt-control-group{-webkit-transform:translateZ(0);transform:translateZ(0);display:-webkit-flex;display:flex;-webkit-align-items:flex-start;align-items:flex-start}.pt-control-group>*{-webkit-flex:0 0 auto;flex:0 0 auto}.pt-control-group .pt-input{z-index:2;border-radius:inherit}.pt-control-group .pt-input:focus{z-index:14;border-radius:3px}.pt-control-group .pt-input[class*=pt-intent]{z-index:13}.pt-control-group .pt-input[class*=pt-intent]:focus{z-index:15}.pt-control-group .pt-input.pt-disabled,.pt-control-group .pt-input:disabled,.pt-control-group .pt-input[readonly]{z-index:1}.pt-control-group .pt-input-group[class*=pt-intent] .pt-input{z-index:13}.pt-control-group .pt-input-group[class*=pt-intent] .pt-input:focus{z-index:15}.pt-control-group .pt-button,.pt-control-group .pt-select select{z-index:4;border-radius:inherit}.pt-control-group .pt-button:focus,.pt-control-group .pt-select select:focus{position:relative;z-index:5}.pt-control-group .pt-button:hover,.pt-control-group .pt-select select:hover{z-index:6}.pt-control-group .pt-button:active,.pt-control-group .pt-select select:active{z-index:7}.pt-control-group .pt-button.pt-disabled,.pt-control-group .pt-button:disabled,.pt-control-group .pt-button[readonly],.pt-control-group .pt-select select.pt-disabled,.pt-control-group .pt-select select:disabled,.pt-control-group .pt-select select[readonly]{z-index:3}.pt-control-group .pt-button[class*=pt-intent],.pt-control-group .pt-select select[class*=pt-intent]{z-index:9}.pt-control-group .pt-button[class*=pt-intent]:focus,.pt-control-group .pt-select select[class*=pt-intent]:focus,.pt-navbar{z-index:10}.pt-control-group .pt-button[class*=pt-intent]:hover,.pt-control-group .pt-select select[class*=pt-intent]:hover{z-index:11}.pt-control-group .pt-button[class*=pt-intent]:active,.pt-control-group .pt-select select[class*=pt-intent]:active{z-index:12}.pt-control-group .pt-button[class*=pt-intent].pt-disabled,.pt-control-group .pt-button[class*=pt-intent]:disabled,.pt-control-group .pt-button[class*=pt-intent][readonly],.pt-control-group .pt-select select[class*=pt-intent].pt-disabled,.pt-control-group .pt-select select[class*=pt-intent]:disabled,.pt-control-group .pt-select select[class*=pt-intent][readonly]{z-index:8}.pt-control-group .pt-input-group>.pt-button,.pt-control-group .pt-input-group>.pt-icon,.pt-control-group .pt-input-group>.pt-input-action{z-index:16}.pt-control-group .pt-select::after{z-index:17}.pt-control-group:not(.pt-vertical)>*{margin-right:-1px}.pt-dark .pt-control-group:not(.pt-vertical)>*{margin-right:0}.pt-dark .pt-control-group:not(.pt-vertical)>.pt-button+.pt-button{margin-left:1px}.pt-control-group>:first-child{border-radius:3px 0 0 3px}.pt-control-group>:last-child{margin-right:0;border-radius:0 3px 3px 0}.pt-control-group .pt-input-group .pt-button{border-radius:3px}.pt-control-group.pt-fill>:not(.pt-fixed),.pt-control-group>.pt-fill{-webkit-flex:1 1 auto;flex:1 1 auto}.pt-control-group.pt-vertical{-webkit-flex-direction:column;flex-direction:column}.pt-control-group.pt-vertical>*{margin-top:-1px;width:100%}.pt-control-group.pt-vertical>:first-child{margin-top:0;border-radius:3px 3px 0 0}.pt-control-group.pt-vertical>:last-child{border-radius:0 0 3px 3px}.pt-control{display:block;margin-bottom:10px;cursor:pointer;min-height:20px;padding-left:26px;text-transform:none;line-height:16px}.pt-control.pt-disabled{cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-control.pt-inline{display:inline-block;margin-right:20px}.pt-control input{position:absolute;top:0;left:0;opacity:0;z-index:-1}.pt-control .pt-control-indicator{font-family:Icons16,sans-serif;font-size:16px;font-weight:400;-webkit-font-smoothing:antialiased;background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;position:absolute;top:0;left:0;margin:0;border:none;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);background-clip:padding-box;cursor:pointer;width:16px;height:16px;line-height:16px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pt-control .pt-control-indicator::before{position:relative;content:\"\"}.pt-control input:checked~.pt-control-indicator,.pt-control.pt-checkbox input:indeterminate~.pt-control-indicator{background:#137cbd;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #137cbd;box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2);color:#fff}.pt-control:hover .pt-control-indicator{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5}.pt-control.pt-checkbox .pt-control:hover input:indeterminate~.pt-control-indicator,.pt-control.pt-checkbox:hover input:indeterminate~.pt-control-indicator,.pt-control:hover .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control:hover input:checked~.pt-control-indicator{background:#106ba3;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #106ba3;box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-control input:not(:disabled):active~.pt-control-indicator{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background:#d8e1e8}.pt-control input:not(:disabled):active:checked~.pt-control-indicator,.pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background:#0e5a8a}.pt-control input:focus~.pt-control-indicator{outline:rgba(19,124,189,.5) auto 2px;outline-offset:2px;-moz-outline-radius:6px}.pt-control input:disabled~.pt-control-indicator{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed}.pt-control input:disabled:checked~.pt-control-indicator,.pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator{box-shadow:none;background:rgba(19,124,189,.5)}.pt-control.pt-checkbox .pt-control-indicator{border-radius:3px;font-size:16px}.pt-control.pt-checkbox input:checked~.pt-control-indicator::before,.pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before{content:\"\"}.pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before{content:\"\"}.pt-control.pt-radio .pt-control-indicator{border-radius:50%;font-size:6px}.pt-control.pt-checkbox .pt-control.pt-radio input:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio input:checked~.pt-control-indicator::before{display:inline-block;position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border-radius:50%;background:#fff;width:1em;height:1em;content:\"\"}.pt-control.pt-checkbox .pt-control.pt-radio input:disabled:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio input:checked:disabled~.pt-control-indicator::before{box-shadow:none}.pt-control.pt-radio input:focus~.pt-control-indicator{-moz-outline-radius:16px}.pt-control.pt-switch{padding-left:38px}.pt-control.pt-switch .pt-control-indicator{border:none;border-radius:28px;box-shadow:none;background:rgba(167,182,194,.5);width:28px;height:16px;transition:background .1s cubic-bezier(.4,1,.75,.9)}.pt-control.pt-switch .pt-control-indicator::before{display:block;top:2px;left:2px;border-radius:16px;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2);background:#fff;background-clip:padding-box;width:12px;height:12px;content:\"\";transition:left .1s cubic-bezier(.4,1,.75,.9)}.pt-file-upload-input,.pt-input{vertical-align:middle;transition:box-shadow .1s cubic-bezier(.4,1,.75,.9);-moz-appearance:none;outline:0}.pt-control.pt-checkbox .pt-control.pt-switch input:indeterminate~.pt-control-indicator,.pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control.pt-switch input:checked~.pt-control-indicator{box-shadow:none;background-color:#137cbd;width:28px;height:16px}.pt-control.pt-checkbox .pt-control.pt-switch input:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch input:checked~.pt-control-indicator::before{left:14px;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2)}.pt-control.pt-switch:hover .pt-control-indicator{background-color:rgba(115,134,148,.5)}.pt-control.pt-checkbox .pt-control.pt-switch:hover input:indeterminate~.pt-control-indicator,.pt-control.pt-switch:hover .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control.pt-switch:hover input:checked~.pt-control-indicator{background-color:#106ba3}.pt-control.pt-switch input:not(:disabled):active~.pt-control-indicator{box-shadow:none;background-color:rgba(92,112,128,.5)}.pt-control.pt-checkbox .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator{background-color:#0e5a8a}.pt-control.pt-checkbox .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator::before{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2)}.pt-control.pt-switch input:disabled~.pt-control-indicator{background-color:rgba(206,217,224,.5)}.pt-control.pt-switch input:disabled~.pt-control-indicator::before{box-shadow:none;background-color:rgba(255,255,255,.8)}.pt-control.pt-checkbox .pt-control.pt-switch input:disabled:indeterminate~.pt-control-indicator,.pt-control.pt-switch .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator,.pt-control.pt-switch input:disabled:checked~.pt-control-indicator{background-color:rgba(19,124,189,.5)}.pt-control.pt-align-right{padding-right:26px;padding-left:0}.pt-control.pt-align-right .pt-control-indicator{right:0;left:auto}.pt-control.pt-large{padding-left:30px;line-height:20px;font-size:16px}.pt-control.pt-large .pt-control-indicator{width:20px;height:20px;line-height:20px;font-family:Icons20;font-size:20px}.pt-input-group .pt-icon,.pt-select::after{font-family:Icons16,sans-serif;font-weight:400;-webkit-font-smoothing:antialiased}.pt-control.pt-large.pt-checkbox .pt-control.pt-checkbox:hover input:indeterminate~.pt-control-indicator::before,.pt-control.pt-large.pt-checkbox input:checked~.pt-control-indicator::before,.pt-control.pt-large.pt-checkbox input:indeterminate~.pt-control-indicator::before{top:1px}.pt-control.pt-large.pt-radio .pt-control-indicator{font-size:8px}.pt-control.pt-large.pt-switch{padding-left:42px}.pt-control.pt-large.pt-switch .pt-control-indicator{width:32px;height:20px}.pt-control.pt-large.pt-switch .pt-control-indicator::before{width:16px;height:16px}.pt-control.pt-checkbox .pt-control.pt-large.pt-switch input:indeterminate~.pt-control-indicator,.pt-control.pt-large.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control.pt-large.pt-switch input:checked~.pt-control-indicator{width:32px;height:20px}.pt-control.pt-checkbox .pt-control.pt-large.pt-switch input:indeterminate~.pt-control-indicator::before,.pt-control.pt-large.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-control.pt-large.pt-switch input:checked~.pt-control-indicator::before{left:14px}.pt-dark .pt-control{color:#f5f8fa}.pt-dark .pt-control.pt-disabled{color:rgba(92,112,128,.5)}.pt-dark .pt-control .pt-control-indicator{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control input:checked~.pt-control-indicator,.pt-dark .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator{box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control:hover .pt-control-indicator{background:#30404d;background:linear-gradient(to bottom,rgba(16,22,26,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d}.pt-dark .pt-control input:not(:disabled):active~.pt-control-indicator{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background:#202b33}.pt-dark .pt-control input:not(:disabled):active:checked~.pt-control-indicator,.pt-dark .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator{box-shadow:0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#0e5a8a}.pt-dark .pt-control input:disabled~.pt-control-indicator{box-shadow:none;background:rgba(57,75,89,.5);cursor:not-allowed}.pt-dark .pt-control input:disabled:checked~.pt-control-indicator,.pt-dark .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator{box-shadow:none;background:rgba(14,90,138,.5)}.pt-dark .pt-control.pt-checkbox input:checked:disabled~.pt-control-indicator::before,.pt-dark .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator::before{color:rgba(191,204,214,.5)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-radio input:disabled:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-radio .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-radio input:checked:disabled~.pt-control-indicator::before{background:rgba(191,204,214,.5)}.pt-dark .pt-control.pt-switch .pt-control-indicator{box-shadow:none;background:rgba(16,22,26,.5)}.pt-dark .pt-control.pt-switch .pt-control-indicator::before{box-shadow:0 0 0 1px rgba(16,22,26,.4);background:#30404d}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch input:checked~.pt-control-indicator{box-shadow:none;background-color:#137cbd}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch input:checked~.pt-control-indicator::before{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control.pt-switch:hover .pt-control-indicator{background:rgba(16,22,26,.7)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch:hover input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch:hover .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch:hover input:checked~.pt-control-indicator{background:#2b95d6}.pt-dark .pt-control.pt-switch input:not(:disabled):active~.pt-control-indicator{box-shadow:none;background:rgba(16,22,26,.9)}.pt-dark .pt-control.pt-switch input:not(:disabled):active~.pt-control-indicator::before{box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator{background:#48aff0}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator::before{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control.pt-switch input:disabled~.pt-control-indicator{background:rgba(57,75,89,.5)}.pt-dark .pt-control.pt-switch input:disabled~.pt-control-indicator::before{box-shadow:none;background:rgba(16,22,26,.4)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:disabled:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch input:disabled:checked~.pt-control-indicator{background:rgba(14,90,138,.5)}.pt-file-upload{display:inline-block;position:relative;cursor:pointer;height:30px}.pt-file-upload-input,.pt-file-upload-input::after{position:absolute;height:30px;line-height:30px;right:0;top:0}.pt-file-upload input{opacity:0;margin:0;min-width:200px}.pt-file-upload input.pt-disabled+.pt-file-upload-input,.pt-file-upload input:disabled+.pt-file-upload-input{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5);resize:none}.pt-file-upload input.pt-disabled+.pt-file-upload-input::after,.pt-file-upload input:disabled+.pt-file-upload-input::after{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-file-upload input.pt-disabled+.pt-file-upload-input::after.pt-active,.pt-file-upload input:disabled+.pt-file-upload-input::after.pt-active{background:rgba(206,217,224,.7)}.pt-dark .pt-file-upload input.pt-disabled+.pt-file-upload-input,.pt-dark .pt-file-upload input:disabled+.pt-file-upload-input{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload input.pt-disabled+.pt-file-upload-input::after,.pt-dark .pt-file-upload input:disabled+.pt-file-upload-input::after{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload input.pt-disabled+.pt-file-upload-input::after.pt-active,.pt-dark .pt-file-upload input:disabled+.pt-file-upload-input::after.pt-active{background:rgba(57,75,89,.7)}.pt-file-upload.pt-fill{width:100%}.pt-file-upload-input{border:none;border-radius:3px 0 0 3px;box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2);background:#fff;padding:0 10px;font-size:14px;font-weight:400;-webkit-appearance:none;appearance:none;left:0;margin-right:70px;color:#5c7080;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pt-menu-item,.pt-skeleton{-moz-user-select:none;-ms-user-select:none}.pt-file-upload-input::-webkit-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input::-moz-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input:-ms-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input::placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-file-upload-input.pt-round,.pt-file-upload-input[type=search]{border-radius:30px;-moz-box-sizing:border-box;box-sizing:border-box;padding-left:10px}.pt-file-upload-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.15)}.pt-file-upload-input.pt-disabled,.pt-file-upload-input:disabled{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5);resize:none}.pt-file-upload-input::after,.pt-file-upload-input::after:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1)}.pt-file-upload-input::after{background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;color:#182026;left:100%;margin-left:-1px;border-radius:0 3px 3px 0;width:70px;padding:0 10px;text-align:center;content:\"Browse\"}.pt-file-upload-input::after:hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;background-clip:padding-box}.pt-file-upload-input::after.pt-active,.pt-file-upload-input::after:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-file-upload-input::after.pt-disabled,.pt-file-upload-input::after:disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-file-upload-input::after.pt-disabled.pt-active,.pt-file-upload-input::after:disabled.pt-active{background:rgba(206,217,224,.7)}.pt-file-upload-input:hover::after{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);background-clip:padding-box}.pt-file-upload-input:active::after{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-large .pt-file-upload-input{margin-right:85px;height:40px;line-height:40px;font-size:16px}.pt-large .pt-file-upload-input::after{width:85px;height:40px;padding:0 15px;line-height:40px;font-size:16px}.pt-dark .pt-file-upload-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#bfccd6}.pt-dark .pt-file-upload-input::-webkit-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::-moz-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input:-ms-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input.pt-disabled,.pt-dark .pt-file-upload-input:disabled{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::after{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-file-upload-input::after.pt-active,.pt-dark .pt-file-upload-input::after:active,.pt-dark .pt-file-upload-input::after:hover{color:#f5f8fa}.pt-dark .pt-file-upload-input::after:hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input::after.pt-active,.pt-dark .pt-file-upload-input::after:active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-file-upload-input::after.pt-disabled,.pt-dark .pt-file-upload-input::after:disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::after.pt-disabled.pt-active,.pt-dark .pt-file-upload-input::after:disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-file-upload-input::after .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-dark .pt-file-upload-input:hover::after{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input:active::after{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-form-group{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;margin:0 0 15px}.pt-form-group label.pt-label{margin-bottom:5px}.pt-form-group .pt-form-helper-text{margin-top:5px;color:#5c7080;font-size:12px}.pt-form-group.pt-intent-primary .pt-form-helper-text{color:#106ba3}.pt-form-group.pt-intent-success .pt-form-helper-text{color:#0d8050}.pt-form-group.pt-intent-warning .pt-form-helper-text{color:#bf7326}.pt-form-group.pt-intent-danger .pt-form-helper-text{color:#c23030}.pt-form-group.pt-inline{-webkit-flex-direction:row;flex-direction:row}.pt-form-group.pt-inline.pt-large label.pt-label{margin:0 10px 0 0;line-height:40px}.pt-form-group.pt-inline label.pt-label{margin:0 10px 0 0;line-height:30px}.pt-form-group.pt-disabled .pt-form-helper-text,.pt-form-group.pt-disabled .pt-label,.pt-form-group.pt-disabled .pt-text-muted{color:rgba(92,112,128,.5)!important}.pt-dark .pt-form-group.pt-intent-primary .pt-form-helper-text{color:#48aff0}.pt-dark .pt-form-group.pt-intent-success .pt-form-helper-text{color:#3dcc91}.pt-dark .pt-form-group.pt-intent-warning .pt-form-helper-text{color:#ffb366}.pt-dark .pt-form-group.pt-intent-danger .pt-form-helper-text{color:#ff7373}.pt-dark .pt-form-group .pt-form-helper-text{color:#bfccd6}.pt-dark .pt-form-group.pt-disabled .pt-form-helper-text,.pt-dark .pt-form-group.pt-disabled .pt-label,.pt-dark .pt-form-group.pt-disabled .pt-text-muted{color:rgba(191,204,214,.5)!important}.pt-input-group{display:block;position:relative}.pt-input-group .pt-input{position:relative;width:100%}.pt-input-group .pt-input:not(:first-child){padding-left:30px}.pt-input-group .pt-input:not(:last-child){padding-right:30px}.pt-input-group .pt-input-action,.pt-input-group>.pt-button,.pt-input-group>.pt-icon{position:absolute;top:0}.pt-input-group .pt-input-action:first-child,.pt-input-group>.pt-button:first-child,.pt-input-group>.pt-icon:first-child{left:0}.pt-input-group .pt-input-action:last-child,.pt-input-group>.pt-button:last-child,.pt-input-group>.pt-icon:last-child{right:0}.pt-input-group .pt-button{min-width:24px;min-height:24px;line-height:24px;margin:3px;padding-top:0;padding-bottom:0}.pt-input-group .pt-icon{font-size:16px;z-index:1;margin:0 7px;line-height:30px;color:#5c7080}.pt-input-group .pt-spinner{margin:3px}.pt-input-group .pt-tag{margin:5px}.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus),.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus){color:#5c7080}.pt-dark .pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus),.pt-dark .pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus){color:#bfccd6}.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-standard,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-standard{color:#5c7080}.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled .pt-icon-standard,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled .pt-icon-standard{color:rgba(92,112,128,.5)!important}.pt-input-group.pt-disabled{cursor:not-allowed}.pt-input-group.pt-disabled .pt-icon{color:rgba(92,112,128,.5)}.pt-input-group.pt-large .pt-button{min-width:30px;min-height:30px;margin:5px;line-height:0}.pt-input-group.pt-large .pt-icon{margin:0 12px;line-height:40px}.pt-input-group.pt-large .pt-input{height:40px;line-height:40px;font-size:16px}.pt-input-group.pt-large .pt-input.pt-round,.pt-input-group.pt-large .pt-input[type=search]{padding:0 15px}.pt-input-group.pt-large .pt-input:not(:first-child){padding-left:40px}.pt-input-group.pt-large .pt-input:not(:last-child){padding-right:40px}.pt-input-group.pt-large .pt-spinner{margin:8px}.pt-input-group.pt-round .pt-button,.pt-input-group.pt-round .pt-input,.pt-input-group.pt-round .pt-tag{border-radius:30px}.pt-dark .pt-input-group .pt-icon{color:#bfccd6}.pt-dark .pt-input-group.pt-disabled .pt-icon{color:rgba(191,204,214,.5)}.pt-input-group.pt-intent-primary .pt-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px #137cbd,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-primary .pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-primary .pt-input[readonly]{box-shadow:inset 0 0 0 1px #137cbd}.pt-input-group.pt-intent-primary .pt-input.pt-disabled,.pt-input-group.pt-intent-primary .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-primary .pt-icon{color:#106ba3}.pt-dark .pt-input-group.pt-intent-primary .pt-icon{color:#48aff0}.pt-input-group.pt-intent-success .pt-input{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px #0f9960,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-success .pt-input:focus{box-shadow:0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-success .pt-input[readonly]{box-shadow:inset 0 0 0 1px #0f9960}.pt-input-group.pt-intent-success .pt-input.pt-disabled,.pt-input-group.pt-intent-success .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-success .pt-icon{color:#0d8050}.pt-dark .pt-input-group.pt-intent-success .pt-icon{color:#3dcc91}.pt-input-group.pt-intent-warning .pt-input{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px #d9822b,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-warning .pt-input:focus{box-shadow:0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-warning .pt-input[readonly]{box-shadow:inset 0 0 0 1px #d9822b}.pt-input-group.pt-intent-warning .pt-input.pt-disabled,.pt-input-group.pt-intent-warning .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-warning .pt-icon{color:#bf7326}.pt-dark .pt-input-group.pt-intent-warning .pt-icon{color:#ffb366}.pt-input-group.pt-intent-danger .pt-input{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px #db3737,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-danger .pt-input:focus{box-shadow:0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-danger .pt-input[readonly]{box-shadow:inset 0 0 0 1px #db3737}.pt-input-group.pt-intent-danger .pt-input.pt-disabled,.pt-input-group.pt-intent-danger .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-danger .pt-icon{color:#c23030}.pt-dark .pt-input-group.pt-intent-danger .pt-icon{color:#ff7373}.pt-input{border:none;border-radius:3px;box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2);background:#fff;height:30px;padding:0 10px;line-height:30px;color:#182026;font-size:14px;font-weight:400;-webkit-appearance:none;appearance:none}.pt-input::-webkit-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input::-moz-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input:-ms-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input::placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-round,.pt-input[type=search]{border-radius:30px;-moz-box-sizing:border-box;box-sizing:border-box;padding-left:10px}.pt-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.15)}.pt-input.pt-disabled,.pt-input:disabled{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5);resize:none}.pt-input.pt-large{height:40px;line-height:40px;font-size:16px}.pt-input.pt-large.pt-round,.pt-input.pt-large[type=search]{padding:0 15px}.pt-input.pt-fill{width:100%}.pt-dark .pt-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#f5f8fa}.pt-dark .pt-input::-webkit-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input::-moz-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input:-ms-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input::placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-disabled,.pt-dark .pt-input:disabled{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-input.pt-intent-primary{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px #137cbd,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-primary:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-primary[readonly]{box-shadow:inset 0 0 0 1px #137cbd}.pt-input.pt-intent-primary.pt-disabled,.pt-input.pt-intent-primary:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-primary{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px #137cbd,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-primary:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-primary[readonly]{box-shadow:inset 0 0 0 1px #137cbd}.pt-dark .pt-input.pt-intent-primary.pt-disabled,.pt-dark .pt-input.pt-intent-primary:disabled{box-shadow:none}.pt-input.pt-intent-success{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px #0f9960,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-success:focus{box-shadow:0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-success[readonly]{box-shadow:inset 0 0 0 1px #0f9960}.pt-input.pt-intent-success.pt-disabled,.pt-input.pt-intent-success:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-success{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px #0f9960,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-success:focus{box-shadow:0 0 0 1px #0f9960,0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-success[readonly]{box-shadow:inset 0 0 0 1px #0f9960}.pt-dark .pt-input.pt-intent-success.pt-disabled,.pt-dark .pt-input.pt-intent-success:disabled{box-shadow:none}.pt-input.pt-intent-warning{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px #d9822b,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-warning:focus{box-shadow:0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-warning[readonly]{box-shadow:inset 0 0 0 1px #d9822b}.pt-input.pt-intent-warning.pt-disabled,.pt-input.pt-intent-warning:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-warning{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px #d9822b,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-warning:focus{box-shadow:0 0 0 1px #d9822b,0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-warning[readonly]{box-shadow:inset 0 0 0 1px #d9822b}.pt-dark .pt-input.pt-intent-warning.pt-disabled,.pt-dark .pt-input.pt-intent-warning:disabled{box-shadow:none}.pt-input.pt-intent-danger{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px #db3737,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-danger:focus{box-shadow:0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-danger[readonly]{box-shadow:inset 0 0 0 1px #db3737}.pt-input.pt-intent-danger.pt-disabled,.pt-input.pt-intent-danger:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-danger{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px #db3737,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-danger:focus{box-shadow:0 0 0 1px #db3737,0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-danger[readonly]{box-shadow:inset 0 0 0 1px #db3737}.pt-dark .pt-input.pt-intent-danger.pt-disabled,.pt-dark .pt-input.pt-intent-danger:disabled{box-shadow:none}.pt-input::-ms-clear{display:none}textarea.pt-input{max-width:100%;height:auto;padding:10px;line-height:1.28581}textarea.pt-input.pt-large{line-height:1.28581;font-size:16px}.pt-dark textarea.pt-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#f5f8fa}.pt-dark textarea.pt-input::-webkit-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input::-moz-placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input:-ms-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input::placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark textarea.pt-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark textarea.pt-input.pt-disabled,.pt-dark textarea.pt-input:disabled{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-select select,.pt-select select:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1)}label.pt-label{display:block;margin:0 0 15px}label.pt-label .pt-input,label.pt-label .pt-select{display:block;margin-top:5px;text-transform:none}label.pt-label .pt-select select{width:100%;vertical-align:top;font-weight:400}label.pt-label.pt-inline{line-height:30px}label.pt-label.pt-inline .pt-input,label.pt-label.pt-inline .pt-input-group,label.pt-label.pt-inline .pt-select{display:inline-block;margin:0 0 0 5px;vertical-align:top}label.pt-label.pt-inline .pt-input-group .pt-input{margin-left:0}label.pt-label.pt-inline.pt-large{line-height:40px}label.pt-label.pt-disabled,label.pt-label.pt-disabled .pt-text-muted{color:rgba(92,112,128,.5)}.pt-dark label.pt-label{color:#f5f8fa}.pt-dark label.pt-label.pt-disabled,.pt-dark label.pt-label.pt-disabled .pt-text-muted{color:rgba(191,204,214,.5)}.pt-select{display:inline-block;position:relative;vertical-align:middle;letter-spacing:normal}.pt-select select::-ms-expand{display:none}.pt-key,.pt-select select{display:inline-block;vertical-align:middle}.pt-select.pt-large::after{top:0;right:12px;line-height:40px}.pt-select.pt-fill,.pt-select.pt-fill select{width:100%}.pt-dark .pt-select option{background-color:#30404d;color:#f5f8fa}.pt-dark .pt-select::after{color:#bfccd6}.pt-select select{border:none;cursor:pointer;font-size:14px;background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;color:#182026;-webkit-appearance:none;-moz-appearance:none;border-radius:3px;height:30px;padding:0 25px 0 10px}.pt-select select:hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;background-clip:padding-box}.pt-select select.pt-active,.pt-select select:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-select select.pt-disabled,.pt-select select:disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-select select.pt-disabled.pt-active,.pt-select select:disabled.pt-active{background:rgba(206,217,224,.7)}.pt-select.pt-minimal select{box-shadow:none;background:0 0}.pt-select.pt-minimal select:focus,.pt-select.pt-minimal select:hover{box-shadow:none;background:rgba(167,182,194,.3);text-decoration:none;color:#182026}.pt-select.pt-minimal select.pt-active,.pt-select.pt-minimal select:active{box-shadow:none;background:rgba(115,134,148,.3);color:#182026}.pt-select.pt-minimal select.pt-disabled,.pt-select.pt-minimal select.pt-disabled:hover,.pt-select.pt-minimal select:disabled,.pt-select.pt-minimal select:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-dark .pt-select.pt-minimal select,.pt-select.pt-minimal .pt-dark select{box-shadow:none;background:0 0;color:inherit}.pt-dark .pt-select.pt-minimal select.pt-active,.pt-dark .pt-select.pt-minimal select:active,.pt-dark .pt-select.pt-minimal select:hover,.pt-select.pt-minimal .pt-dark select.pt-active,.pt-select.pt-minimal .pt-dark select:active,.pt-select.pt-minimal .pt-dark select:hover{box-shadow:none}.pt-dark .pt-select.pt-minimal select:hover,.pt-select.pt-minimal .pt-dark select:hover{background:rgba(138,155,168,.15)}.pt-dark .pt-select.pt-minimal select.pt-active,.pt-dark .pt-select.pt-minimal select:active,.pt-select.pt-minimal .pt-dark select.pt-active,.pt-select.pt-minimal .pt-dark select:active{background:rgba(138,155,168,.3);color:#f5f8fa}.pt-dark .pt-select.pt-minimal select.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-disabled:hover,.pt-dark .pt-select.pt-minimal select:disabled,.pt-dark .pt-select.pt-minimal select:disabled:hover,.pt-select.pt-minimal .pt-dark select.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-disabled:hover,.pt-select.pt-minimal .pt-dark select:disabled,.pt-select.pt-minimal .pt-dark select:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(191,204,214,.5)}.pt-select.pt-minimal select.pt-intent-primary{color:#106ba3}.pt-select.pt-minimal select.pt-intent-primary.pt-active,.pt-select.pt-minimal select.pt-intent-primary:active,.pt-select.pt-minimal select.pt-intent-primary:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-primary:focus,.pt-select.pt-minimal select.pt-intent-primary:hover{background:rgba(19,124,189,.15);color:#106ba3}.pt-select.pt-minimal select.pt-intent-primary.pt-active,.pt-select.pt-minimal select.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#106ba3}.pt-select.pt-minimal select.pt-intent-primary.pt-disabled,.pt-select.pt-minimal select.pt-intent-primary:disabled{background:0 0;color:rgba(16,107,163,.5)}.pt-select.pt-minimal select.pt-intent-primary .pt-button-spinner .pt-spinner-head{stroke:#106ba3}.pt-dark .pt-select.pt-minimal select.pt-intent-primary,.pt-select.pt-minimal .pt-dark select.pt-intent-primary{color:#48aff0}.pt-dark .pt-select.pt-minimal select.pt-intent-primary:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-primary:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:hover{background:rgba(19,124,189,.2);color:#48aff0}.pt-dark .pt-select.pt-minimal select.pt-intent-primary.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-primary:active,.pt-select.pt-minimal .pt-dark select.pt-intent-primary.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#48aff0}.pt-dark .pt-select.pt-minimal select.pt-intent-primary.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-primary:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-primary.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:disabled{background:0 0;color:rgba(72,175,240,.5)}.pt-select.pt-minimal select.pt-intent-success{color:#0d8050}.pt-select.pt-minimal select.pt-intent-success.pt-active,.pt-select.pt-minimal select.pt-intent-success:active,.pt-select.pt-minimal select.pt-intent-success:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-success:focus,.pt-select.pt-minimal select.pt-intent-success:hover{background:rgba(15,153,96,.15);color:#0d8050}.pt-select.pt-minimal select.pt-intent-success.pt-active,.pt-select.pt-minimal select.pt-intent-success:active{background:rgba(15,153,96,.3);color:#0d8050}.pt-select.pt-minimal select.pt-intent-success.pt-disabled,.pt-select.pt-minimal select.pt-intent-success:disabled{background:0 0;color:rgba(13,128,80,.5)}.pt-select.pt-minimal select.pt-intent-success .pt-button-spinner .pt-spinner-head{stroke:#0d8050}.pt-dark .pt-select.pt-minimal select.pt-intent-success,.pt-select.pt-minimal .pt-dark select.pt-intent-success{color:#3dcc91}.pt-dark .pt-select.pt-minimal select.pt-intent-success:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-success:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-success:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-success:hover{background:rgba(15,153,96,.2);color:#3dcc91}.pt-dark .pt-select.pt-minimal select.pt-intent-success.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-success:active,.pt-select.pt-minimal .pt-dark select.pt-intent-success.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-success:active{background:rgba(15,153,96,.3);color:#3dcc91}.pt-dark .pt-select.pt-minimal select.pt-intent-success.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-success:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-success.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-success:disabled{background:0 0;color:rgba(61,204,145,.5)}.pt-select.pt-minimal select.pt-intent-warning{color:#bf7326}.pt-select.pt-minimal select.pt-intent-warning.pt-active,.pt-select.pt-minimal select.pt-intent-warning:active,.pt-select.pt-minimal select.pt-intent-warning:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-warning:focus,.pt-select.pt-minimal select.pt-intent-warning:hover{background:rgba(217,130,43,.15);color:#bf7326}.pt-select.pt-minimal select.pt-intent-warning.pt-active,.pt-select.pt-minimal select.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#bf7326}.pt-select.pt-minimal select.pt-intent-warning.pt-disabled,.pt-select.pt-minimal select.pt-intent-warning:disabled{background:0 0;color:rgba(191,115,38,.5)}.pt-select.pt-minimal select.pt-intent-warning .pt-button-spinner .pt-spinner-head{stroke:#bf7326}.pt-dark .pt-select.pt-minimal select.pt-intent-warning,.pt-select.pt-minimal .pt-dark select.pt-intent-warning{color:#ffb366}.pt-dark .pt-select.pt-minimal select.pt-intent-warning:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-warning:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:hover{background:rgba(217,130,43,.2);color:#ffb366}.pt-dark .pt-select.pt-minimal select.pt-intent-warning.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-warning:active,.pt-select.pt-minimal .pt-dark select.pt-intent-warning.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#ffb366}.pt-dark .pt-select.pt-minimal select.pt-intent-warning.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-warning:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-warning.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:disabled{background:0 0;color:rgba(255,179,102,.5)}.pt-select.pt-minimal select.pt-intent-danger{color:#c23030}.pt-select.pt-minimal select.pt-intent-danger.pt-active,.pt-select.pt-minimal select.pt-intent-danger:active,.pt-select.pt-minimal select.pt-intent-danger:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-danger:focus,.pt-select.pt-minimal select.pt-intent-danger:hover{background:rgba(219,55,55,.15);color:#c23030}.pt-select.pt-minimal select.pt-intent-danger.pt-active,.pt-select.pt-minimal select.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#c23030}.pt-select.pt-minimal select.pt-intent-danger.pt-disabled,.pt-select.pt-minimal select.pt-intent-danger:disabled{background:0 0;color:rgba(194,48,48,.5)}.pt-select.pt-minimal select.pt-intent-danger .pt-button-spinner .pt-spinner-head{stroke:#c23030}.pt-dark .pt-select.pt-minimal select.pt-intent-danger,.pt-select.pt-minimal .pt-dark select.pt-intent-danger{color:#ff7373}.pt-dark .pt-select.pt-minimal select.pt-intent-danger:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-danger:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:hover{background:rgba(219,55,55,.2);color:#ff7373}.pt-dark .pt-select.pt-minimal select.pt-intent-danger.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-danger:active,.pt-select.pt-minimal .pt-dark select.pt-intent-danger.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#ff7373}.pt-dark .pt-select.pt-minimal select.pt-intent-danger.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-danger:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-danger.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:disabled{background:0 0;color:rgba(255,115,115,.5)}.pt-select.pt-large select{height:40px;padding-right:35px;font-size:16px}.pt-dark .pt-select select{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-select select.pt-active,.pt-dark .pt-select select:active,.pt-dark .pt-select select:hover{color:#f5f8fa}.pt-dark .pt-select select:hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-select select.pt-active,.pt-dark .pt-select select:active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-select select.pt-disabled,.pt-dark .pt-select select:disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-select select.pt-disabled.pt-active,.pt-dark .pt-select select:disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-select select .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-select select:disabled{box-shadow:none;background-color:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-select::after{font-size:16px;position:absolute;top:0;right:7px;line-height:30px;color:#5c7080;content:\"⌄\"}.pt-disabled.pt-select::after{color:rgba(92,112,128,.5)}.pt-numeric-input .pt-button-group.pt-vertical>.pt-button:first-child{min-height:16px;border-radius:0 3px 0 0}.pt-numeric-input .pt-button-group.pt-vertical>.pt-button:last-child{min-height:15px;border-radius:0 0 3px}.pt-numeric-input .pt-button-group.pt-vertical>.pt-button[class*=pt-icon-]::before{display:block;height:14px;overflow:hidden;line-height:14px}.pt-numeric-input .pt-button-group.pt-vertical:first-child>.pt-button:first-child{border-radius:3px 0 0}.pt-numeric-input .pt-button-group.pt-vertical:first-child>.pt-button:last-child{border-radius:0 0 0 3px}.pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:first-child{min-height:21px}.pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:last-child{min-height:20px}.pt-dark .pt-numeric-input .pt-button-group.pt-vertical>.pt-button{line-height:14px}.pt-dark .pt-numeric-input .pt-button-group.pt-vertical>.pt-button:first-child{min-height:15px}.pt-dark .pt-numeric-input .pt-button-group.pt-vertical>.pt-button:last-child{min-height:14px}.pt-dark .pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:first-child{min-height:20px}.pt-dark .pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:last-child{min-height:19px}form{display:block}.pt-key{border-radius:2px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 1px 1px rgba(16,22,26,.2),0 2px 6px rgba(16,22,26,.2);background:#fff;min-width:25px;height:25px;padding:3px 5px;text-align:center;line-height:20px;color:#5c7080;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,\"Open Sans\",\"Helvetica Neue\",Icons16,sans-serif;font-size:12px}.pt-menu-item::before,.pt-submenu>.pt-popover-target>.pt-menu-item::after{font-family:Icons16,sans-serif;font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-key.pt-modifier-key{padding:3px 8px 3px 6px}.pt-key.pt-modifier-key .pt-icon-standard{margin-right:5px}.pt-dark .pt-key{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.4),0 2px 6px rgba(16,22,26,.4),inset 0 1px 0 rgba(255,255,255,.05);background:#394b59;color:#bfccd6}.pt-key-combo .pt-key:not(:last-child){margin-right:5px}.pt-hotkey-dialog{top:40px;padding-bottom:0}.pt-hotkey-dialog .pt-dialog-body{margin:0;padding:0}.pt-hotkey-dialog .pt-key-combo{-webkit-flex:1 0 auto;flex:1 0 auto;width:160px;text-align:right}.pt-hotkey-column{margin:auto;max-height:80vh;overflow-y:auto;padding:30px}.pt-menu-item,body.pt-overlay-open{overflow:hidden}.pt-hotkey-column .pt-hotkey-group{margin-bottom:30px}.pt-hotkey-column .pt-hotkey-group:not(:first-child){margin-top:50px}.pt-hotkey{display:-webkit-flex;display:flex;-webkit-align-items:baseline;align-items:baseline;-webkit-justify-content:space-between;justify-content:space-between;margin-right:0;margin-left:0}.pt-hotkey:not(:last-child){margin-bottom:10px}.pt-submenu>.pt-popover-target{display:inherit}.pt-submenu>.pt-popover-target>.pt-menu-item{padding-right:26px}.pt-submenu>.pt-popover-target>.pt-menu-item::after{line-height:1;font-size:16px;position:absolute;right:5px;content:\"〉\"}.pt-large .pt-submenu>.pt-popover-target>.pt-menu-item::after{line-height:20px}.pt-submenu .pt-popover{position:relative;top:-5px;left:5px}.pt-navbar.pt-fixed-top,.pt-overlay-backdrop{top:0;right:0;left:0;position:fixed}.pt-submenu .pt-popover.pt-align-left{right:5px;left:auto}.pt-menu{margin:0;border-radius:3px;background:#fff;min-width:180px;padding:5px;list-style:none;text-align:left;color:#182026}.pt-dark .pt-menu-item,.pt-menu-item{color:inherit}.pt-menu-divider{display:block;margin:5px;border-top:1px solid rgba(16,22,26,.15)}.pt-menu-item{display:block;border-radius:2px;padding:7px;line-height:16px;-webkit-user-select:none;user-select:none}.pt-menu-item:hover,.pt-submenu>.pt-popover-open>.pt-menu-item{background-color:rgba(167,182,194,.3);cursor:pointer}.pt-dark .pt-menu-item:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-menu-item{background-color:rgba(138,155,168,.15);color:inherit}.pt-dark .pt-menu-item.pt-disabled{background-color:inherit}.pt-menu-item.pt-intent-primary,.pt-menu-item.pt-intent-primary .pt-menu-item-label,.pt-menu-item.pt-intent-primary::after,.pt-menu-item.pt-intent-primary::before{color:#106ba3}.pt-menu-item.pt-intent-primary.pt-active,.pt-menu-item.pt-intent-primary:hover,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item{background-color:#137cbd}.pt-menu-item.pt-intent-primary:active{background-color:#106ba3}.pt-menu-item.pt-intent-primary.pt-active,.pt-menu-item.pt-intent-primary.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-primary.pt-active::after,.pt-menu-item.pt-intent-primary.pt-active::before,.pt-menu-item.pt-intent-primary:active,.pt-menu-item.pt-intent-primary:active .pt-menu-item-label,.pt-menu-item.pt-intent-primary:active::after,.pt-menu-item.pt-intent-primary:active::before,.pt-menu-item.pt-intent-primary:hover,.pt-menu-item.pt-intent-primary:hover .pt-menu-item-label,.pt-menu-item.pt-intent-primary:hover::after,.pt-menu-item.pt-intent-primary:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::before{color:#fff}.pt-menu-item.pt-intent-success,.pt-menu-item.pt-intent-success .pt-menu-item-label,.pt-menu-item.pt-intent-success::after,.pt-menu-item.pt-intent-success::before{color:#0d8050}.pt-menu-item.pt-intent-success.pt-active,.pt-menu-item.pt-intent-success:hover,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item{background-color:#0f9960}.pt-menu-item.pt-intent-success:active{background-color:#0d8050}.pt-menu-item.pt-intent-success.pt-active,.pt-menu-item.pt-intent-success.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-success.pt-active::after,.pt-menu-item.pt-intent-success.pt-active::before,.pt-menu-item.pt-intent-success:active,.pt-menu-item.pt-intent-success:active .pt-menu-item-label,.pt-menu-item.pt-intent-success:active::after,.pt-menu-item.pt-intent-success:active::before,.pt-menu-item.pt-intent-success:hover,.pt-menu-item.pt-intent-success:hover .pt-menu-item-label,.pt-menu-item.pt-intent-success:hover::after,.pt-menu-item.pt-intent-success:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::before{color:#fff}.pt-menu-item.pt-intent-warning,.pt-menu-item.pt-intent-warning .pt-menu-item-label,.pt-menu-item.pt-intent-warning::after,.pt-menu-item.pt-intent-warning::before{color:#bf7326}.pt-menu-item.pt-intent-warning.pt-active,.pt-menu-item.pt-intent-warning:hover,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item{background-color:#d9822b}.pt-menu-item.pt-intent-warning:active{background-color:#bf7326}.pt-menu-item.pt-intent-warning.pt-active,.pt-menu-item.pt-intent-warning.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-warning.pt-active::after,.pt-menu-item.pt-intent-warning.pt-active::before,.pt-menu-item.pt-intent-warning:active,.pt-menu-item.pt-intent-warning:active .pt-menu-item-label,.pt-menu-item.pt-intent-warning:active::after,.pt-menu-item.pt-intent-warning:active::before,.pt-menu-item.pt-intent-warning:hover,.pt-menu-item.pt-intent-warning:hover .pt-menu-item-label,.pt-menu-item.pt-intent-warning:hover::after,.pt-menu-item.pt-intent-warning:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::before{color:#fff}.pt-menu-item.pt-intent-danger,.pt-menu-item.pt-intent-danger .pt-menu-item-label,.pt-menu-item.pt-intent-danger::after,.pt-menu-item.pt-intent-danger::before{color:#c23030}.pt-menu-item.pt-intent-danger.pt-active,.pt-menu-item.pt-intent-danger:hover,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item{background-color:#db3737}.pt-menu-item.pt-intent-danger:active{background-color:#c23030}.pt-menu-item.pt-intent-danger.pt-active,.pt-menu-item.pt-intent-danger.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-danger.pt-active::after,.pt-menu-item.pt-intent-danger.pt-active::before,.pt-menu-item.pt-intent-danger:active,.pt-menu-item.pt-intent-danger:active .pt-menu-item-label,.pt-menu-item.pt-intent-danger:active::after,.pt-menu-item.pt-intent-danger:active::before,.pt-menu-item.pt-intent-danger:hover,.pt-menu-item.pt-intent-danger:hover .pt-menu-item-label,.pt-menu-item.pt-intent-danger:hover::after,.pt-menu-item.pt-intent-danger:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::before{color:#fff}.pt-menu-item::before{line-height:1;font-size:16px;float:left;margin-right:7px}.pt-menu-item .pt-menu-item-label,.pt-menu-item::after,.pt-menu-item::before{color:#5c7080}.pt-menu-item:hover,.pt-submenu>.pt-popover-open>.pt-menu-item{color:inherit}.pt-menu-item.pt-disabled,.pt-menu-item.pt-disabled .pt-menu-item-label,.pt-menu-item.pt-disabled::after,.pt-menu-item.pt-disabled::before{color:rgba(92,112,128,.5)!important}.pt-menu-item.pt-active,.pt-menu-item:active{background-color:rgba(115,134,148,.3)}.pt-menu-item.pt-disabled{outline:0!important;background-color:inherit!important;cursor:not-allowed!important}.pt-overlay-backdrop:focus,.pt-slider,.pt-transition-container:focus{outline:0}.pt-large .pt-menu-item{padding:10px 7px;line-height:20px;font-size:16px}.pt-large .pt-menu-item::before{line-height:1;font-family:Icons20,sans-serif;font-size:20px;font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;margin-right:10px}.pt-submenu>.pt-popover-open>a.pt-menu-item,a.pt-menu-item,a.pt-menu-item:hover{text-decoration:none}button.pt-menu-item{border:none;background:0 0;width:100%;text-align:left}.pt-menu-item-label{float:right;margin-left:7px}.pt-menu-header{display:block;margin:5px;border-top:1px solid rgba(16,22,26,.15);cursor:default;padding-left:2px}.pt-menu-header:first-of-type{border-top:none}.pt-menu-header>h6{color:#182026;font-weight:600;overflow:hidden;margin:0;padding:10px 7px 0 1px;line-height:17px}.pt-menu-header:first-of-type>h6{padding-top:0}.pt-large .pt-menu-header>h6{padding-top:15px;padding-bottom:5px;font-size:18px}.pt-large .pt-menu-header:first-of-type>h6{padding-top:0}.pt-dark .pt-menu{background:#30404d;color:#f5f8fa}.pt-dark .pt-menu-item.pt-intent-primary,.pt-dark .pt-menu-item.pt-intent-primary .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary::after,.pt-dark .pt-menu-item.pt-intent-primary::before{color:#48aff0}.pt-dark .pt-menu-item.pt-intent-primary.pt-active,.pt-dark .pt-menu-item.pt-intent-primary:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item{background-color:#137cbd}.pt-dark .pt-menu-item.pt-intent-primary:active{background-color:#106ba3}.pt-dark .pt-menu-item.pt-intent-primary.pt-active,.pt-dark .pt-menu-item.pt-intent-primary.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary.pt-active::after,.pt-dark .pt-menu-item.pt-intent-primary.pt-active::before,.pt-dark .pt-menu-item.pt-intent-primary:active,.pt-dark .pt-menu-item.pt-intent-primary:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary:active::after,.pt-dark .pt-menu-item.pt-intent-primary:active::before,.pt-dark .pt-menu-item.pt-intent-primary:hover,.pt-dark .pt-menu-item.pt-intent-primary:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary:hover::after,.pt-dark .pt-menu-item.pt-intent-primary:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-intent-success,.pt-dark .pt-menu-item.pt-intent-success .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success::after,.pt-dark .pt-menu-item.pt-intent-success::before{color:#3dcc91}.pt-dark .pt-menu-item.pt-intent-success.pt-active,.pt-dark .pt-menu-item.pt-intent-success:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item{background-color:#0f9960}.pt-dark .pt-menu-item.pt-intent-success:active{background-color:#0d8050}.pt-dark .pt-menu-item.pt-intent-success.pt-active,.pt-dark .pt-menu-item.pt-intent-success.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success.pt-active::after,.pt-dark .pt-menu-item.pt-intent-success.pt-active::before,.pt-dark .pt-menu-item.pt-intent-success:active,.pt-dark .pt-menu-item.pt-intent-success:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success:active::after,.pt-dark .pt-menu-item.pt-intent-success:active::before,.pt-dark .pt-menu-item.pt-intent-success:hover,.pt-dark .pt-menu-item.pt-intent-success:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success:hover::after,.pt-dark .pt-menu-item.pt-intent-success:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-intent-warning,.pt-dark .pt-menu-item.pt-intent-warning .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning::after,.pt-dark .pt-menu-item.pt-intent-warning::before{color:#ffb366}.pt-dark .pt-menu-item.pt-intent-warning.pt-active,.pt-dark .pt-menu-item.pt-intent-warning:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item{background-color:#d9822b}.pt-dark .pt-menu-item.pt-intent-warning:active{background-color:#bf7326}.pt-dark .pt-menu-item.pt-intent-warning.pt-active,.pt-dark .pt-menu-item.pt-intent-warning.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning.pt-active::after,.pt-dark .pt-menu-item.pt-intent-warning.pt-active::before,.pt-dark .pt-menu-item.pt-intent-warning:active,.pt-dark .pt-menu-item.pt-intent-warning:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning:active::after,.pt-dark .pt-menu-item.pt-intent-warning:active::before,.pt-dark .pt-menu-item.pt-intent-warning:hover,.pt-dark .pt-menu-item.pt-intent-warning:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning:hover::after,.pt-dark .pt-menu-item.pt-intent-warning:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-intent-danger,.pt-dark .pt-menu-item.pt-intent-danger .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger::after,.pt-dark .pt-menu-item.pt-intent-danger::before{color:#ff7373}.pt-dark .pt-menu-item.pt-intent-danger.pt-active,.pt-dark .pt-menu-item.pt-intent-danger:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item{background-color:#db3737}.pt-dark .pt-menu-item.pt-intent-danger:active{background-color:#c23030}.pt-dark .pt-menu-item.pt-intent-danger.pt-active,.pt-dark .pt-menu-item.pt-intent-danger.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger.pt-active::after,.pt-dark .pt-menu-item.pt-intent-danger.pt-active::before,.pt-dark .pt-menu-item.pt-intent-danger:active,.pt-dark .pt-menu-item.pt-intent-danger:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger:active::after,.pt-dark .pt-menu-item.pt-intent-danger:active::before,.pt-dark .pt-menu-item.pt-intent-danger:hover,.pt-dark .pt-menu-item.pt-intent-danger:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger:hover::after,.pt-dark .pt-menu-item.pt-intent-danger:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item .pt-menu-item-label,.pt-dark .pt-menu-item::after,.pt-dark .pt-menu-item::before{color:#bfccd6}.pt-dark .pt-menu-item:hover::after,.pt-dark .pt-menu-item:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-active,.pt-dark .pt-menu-item:active{background-color:rgba(138,155,168,.3)}.pt-dark .pt-menu-item.pt-disabled,.pt-dark .pt-menu-item.pt-disabled .pt-menu-item-label,.pt-dark .pt-menu-item.pt-disabled::after,.pt-dark .pt-menu-item.pt-disabled::before{color:rgba(191,204,214,.5)!important}.pt-dark .pt-menu-divider,.pt-dark .pt-menu-header{border-color:rgba(255,255,255,.15)}.pt-dark .pt-menu-header>h6{color:#f5f8fa}.pt-label .pt-menu{margin-top:5px}.pt-navbar{position:relative;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.2);background-color:#fff;height:50px;padding:0 15px}.pt-dark .pt-navbar,.pt-navbar.pt-dark{background-color:#394b59}.pt-navbar.pt-dark{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-navbar{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-navbar .pt-logo{margin-right:15px;width:20px}.pt-navbar-heading{margin-right:15px;font-size:16px}.pt-navbar-group{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;height:50px}.pt-navbar-group.pt-align-left{float:left}.pt-navbar-group.pt-align-right{float:right}.pt-navbar-divider{margin:0 10px;border-left:1px solid rgba(16,22,26,.15);height:20px}.pt-dark .pt-navbar-divider{border-left-color:rgba(255,255,255,.15)}.pt-non-ideal-state{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;margin:0 auto;width:100%;max-width:400px;height:100%}.pt-non-ideal-state>:not(:last-child){margin-bottom:20px}.pt-non-ideal-state-icon .pt-icon{color:rgba(92,112,128,.5);font-size:60px}.pt-dark .pt-non-ideal-state-icon .pt-icon{color:rgba(191,204,214,.5)}.pt-non-ideal-state-description{text-align:center}.pt-overlay-backdrop{bottom:0;opacity:1;z-index:20;background-color:rgba(16,22,26,.7);overflow:auto}.pt-overlay-backdrop.pt-overlay-appear,.pt-overlay-backdrop.pt-overlay-enter{opacity:0}.pt-overlay-backdrop.pt-overlay-appear-active,.pt-overlay-backdrop.pt-overlay-enter-active{opacity:1;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-overlay-backdrop.pt-overlay-leave{opacity:1}.pt-overlay-backdrop.pt-overlay-leave-active{opacity:0;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-overlay-content{position:fixed}.pt-overlay-inline .pt-overlay-backdrop,.pt-overlay-inline .pt-overlay-content{position:absolute}.pt-overlay-scroll-container.pt-overlay-open{position:fixed;top:0;right:0;bottom:0;left:0;z-index:20;overflow:auto}.pt-overlay-scroll-container.pt-overlay-open .pt-overlay-content{position:absolute}.pt-popover{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);-webkit-transform:scale(1);transform:scale(1);display:inline-block;z-index:20;border-radius:3px}.pt-popover .pt-popover-arrow{position:absolute;width:30px;height:30px}.pt-popover .pt-popover-arrow::before{margin:5px;width:20px;height:20px;box-shadow:1px 1px 6px rgba(16,22,26,.2)}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-popover{margin-top:-17px;margin-bottom:17px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-popover>.pt-popover-arrow{bottom:-11px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover{margin-left:17px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow{left:-11px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(0);transform:rotate(0)}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-popover{margin-top:17px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-popover>.pt-popover-arrow{top:-11px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-popover{margin-right:17px;margin-left:-17px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-popover>.pt-popover-arrow{right:-11px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.pt-tether-element-attached-middle>.pt-popover>.pt-popover-arrow{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.pt-tether-element-attached-center>.pt-popover>.pt-popover-arrow{right:50%;-webkit-transform:translateX(50%);transform:translateX(50%)}.pt-tether-element-attached-top.pt-tether-target-attached-top>.pt-popover>.pt-popover-arrow{top:-.39px}.pt-tether-element-attached-right.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow{right:-.39px}.pt-tether-element-attached-left.pt-tether-target-attached-left>.pt-popover>.pt-popover-arrow{left:-.39px}.pt-tether-element-attached-bottom.pt-tether-target-attached-bottom>.pt-popover>.pt-popover-arrow{bottom:-.39px}.pt-tether-element-attached-top.pt-tether-element-attached-left>.pt-popover{-webkit-transform-origin:top left;transform-origin:top left}.pt-tether-element-attached-top.pt-tether-element-attached-center>.pt-popover{-webkit-transform-origin:top center;transform-origin:top center}.pt-tether-element-attached-top.pt-tether-element-attached-right>.pt-popover{-webkit-transform-origin:top right;transform-origin:top right}.pt-tether-element-attached-middle.pt-tether-element-attached-left>.pt-popover{-webkit-transform-origin:center left;transform-origin:center left}.pt-tether-element-attached-middle.pt-tether-element-attached-center>.pt-popover{-webkit-transform-origin:center center;transform-origin:center center}.pt-tether-element-attached-middle.pt-tether-element-attached-right>.pt-popover{-webkit-transform-origin:center right;transform-origin:center right}.pt-tether-element-attached-bottom.pt-tether-element-attached-left>.pt-popover{-webkit-transform-origin:bottom left;transform-origin:bottom left}.pt-tether-element-attached-bottom.pt-tether-element-attached-center>.pt-popover{-webkit-transform-origin:bottom center;transform-origin:bottom center}.pt-tether-element-attached-bottom.pt-tether-element-attached-right>.pt-popover{-webkit-transform-origin:bottom right;transform-origin:bottom right}.pt-popover .pt-popover-content{background:#fff;color:inherit;position:relative;border-radius:3px}.pt-popover .pt-popover-arrow-border{fill:#10161a;fill-opacity:.1}.pt-popover .pt-popover-arrow-fill{fill:#fff}.pt-popover-appear>.pt-popover,.pt-popover-enter>.pt-popover{-webkit-transform:scale(.3);transform:scale(.3)}.pt-popover-appear-active>.pt-popover,.pt-popover-enter-active>.pt-popover{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-popover-leave>.pt-popover{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-leave-active>.pt-popover{-webkit-transform:scale(.3);transform:scale(.3);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-popover.pt-popover-content-sizing .pt-popover-content{max-width:350px;padding:20px}.pt-popover-target .pt-popover.pt-popover-content-sizing{width:350px}.pt-popover.pt-minimal{margin:0!important}.pt-popover.pt-minimal .pt-popover-arrow{display:none}.pt-popover-appear>.pt-popover.pt-minimal.pt-popover,.pt-popover-enter>.pt-popover.pt-minimal.pt-popover,.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-appear-active>.pt-popover.pt-minimal.pt-popover,.pt-popover-enter-active>.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-popover-leave>.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-leave-active>.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-dark .pt-popover,.pt-popover.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-dark .pt-popover .pt-popover-content,.pt-popover.pt-dark .pt-popover-content{background:#30404d;color:inherit}.pt-dark .pt-popover .pt-popover-arrow::before,.pt-popover.pt-dark .pt-popover-arrow::before{box-shadow:1px 1px 6px rgba(16,22,26,.4)}.pt-dark .pt-popover .pt-popover-arrow-border,.pt-popover.pt-dark .pt-popover-arrow-border{fill:#10161a;fill-opacity:.2}.pt-dark .pt-popover .pt-popover-arrow-fill,.pt-popover.pt-dark .pt-popover-arrow-fill{fill:#30404d}.pt-popover-arrow::before{display:block;position:absolute;-webkit-transform:rotate(45deg);transform:rotate(45deg);border-radius:2px;content:\"\"}.pt-tether-pinned .pt-popover-arrow{display:none}.pt-popover-target,.pt-popover-target>.pt-popover-target{display:inline-block}.pt-popover-backdrop{background:rgba(255,255,255,0)}.pt-transition-container{opacity:1;z-index:20}.pt-transition-container.pt-popover-appear,.pt-transition-container.pt-popover-enter{opacity:0}.pt-transition-container.pt-popover-appear-active,.pt-transition-container.pt-popover-enter-active{opacity:1;transition-property:opacity;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-transition-container.pt-popover-leave{opacity:1}.pt-transition-container.pt-popover-leave-active{opacity:0;transition-property:opacity;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-popover-target{position:relative;vertical-align:top}.pt-popover-target .pt-transition-container,.pt-portal{position:absolute}.pt-popover-target .pt-transition-container.pt-tether-element-attached-bottom.pt-tether-target-attached-top{bottom:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-left.pt-tether-target-attached-right{left:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-top.pt-tether-target-attached-bottom{top:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-right.pt-tether-target-attached-left{right:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-top.pt-tether-target-attached-top{top:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-right.pt-tether-target-attached-right{right:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-bottom.pt-tether-target-attached-bottom{bottom:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-left.pt-tether-target-attached-left{left:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-middle{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.pt-popover-target .pt-transition-container.pt-tether-element-attached-center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pt-button-group.pt-vertical .pt-popover-target{display:block}.pt-button-group:not(.pt-vertical) .pt-popover-target,.pt-button-group:not(.pt-vertical) .pt-tether-target{float:left}.pt-portal{top:0;right:0;left:0}@-webkit-keyframes linear-progress-bar-stripes{from{background-position:0 0}to{background-position:30px 0}}@keyframes linear-progress-bar-stripes{from{background-position:0 0}to{background-position:30px 0}}.pt-progress-bar{display:block;position:relative;border-radius:40px;background:rgba(92,112,128,.2);width:100%;height:8px;overflow:hidden}.pt-progress-bar .pt-progress-meter{display:inline-block;position:absolute;border-radius:40px;background:linear-gradient(-45deg,rgba(255,255,255,.2) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.2) 50%,rgba(255,255,255,.2) 75%,transparent 75%);background-color:rgba(92,112,128,.8);background-size:30px 30px;width:100%;height:100%;transition:width .2s cubic-bezier(.4,1,.75,.9)}.pt-progress-bar:not(.pt-no-animation):not(.pt-no-stripes) .pt-progress-meter{-webkit-animation:linear-progress-bar-stripes .3s linear infinite reverse;animation:linear-progress-bar-stripes .3s linear infinite reverse}.pt-progress-bar.pt-no-stripes .pt-progress-meter{background-image:none}.pt-dark .pt-progress-bar,.pt-progress-bar.pt-dark{background:rgba(16,22,26,.5)}.pt-dark .pt-progress-bar .pt-progress-meter,.pt-progress-bar.pt-dark .pt-progress-meter{background-color:#8a9ba8}.pt-progress-bar.pt-intent-primary .pt-progress-meter{background-color:#137cbd}.pt-progress-bar.pt-intent-success .pt-progress-meter{background-color:#0f9960}.pt-progress-bar.pt-intent-warning .pt-progress-meter{background-color:#d9822b}.pt-progress-bar.pt-intent-danger .pt-progress-meter{background-color:#db3737}@-webkit-keyframes glow{0%,100%{border-color:rgba(167,182,194,.2);background-color:rgba(167,182,194,.2)}50%{border-color:rgba(92,112,128,.2);background-color:rgba(92,112,128,.2)}}@keyframes glow{0%,100%{border-color:rgba(167,182,194,.2);background-color:rgba(167,182,194,.2)}50%{border-color:rgba(92,112,128,.2);background-color:rgba(92,112,128,.2)}}.pt-skeleton{border-color:rgba(167,182,194,.2)!important;border-radius:2px;box-shadow:none!important;background:rgba(167,182,194,.2)!important;background-clip:padding-box!important;cursor:default;color:transparent!important;-webkit-animation:2s linear infinite glow;animation:2s linear infinite glow;-webkit-user-select:none;user-select:none}.pt-slider,.pt-tree-node-label{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.pt-slider{position:relative;cursor:default;width:100%;min-width:150px;height:40px;user-select:none}.pt-slider:hover{cursor:pointer}.pt-slider:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pt-slider.pt-disabled{opacity:.5;cursor:not-allowed}.pt-slider.pt-slider-unlabeled{height:16px}.pt-slider-progress,.pt-slider-track{position:absolute;top:5px;right:0;left:0;border-radius:3px;background:rgba(92,112,128,.2);height:6px}.pt-dark .pt-slider-progress,.pt-dark .pt-slider-track{background:rgba(16,22,26,.5)}.pt-dark .pt-slider-progress,.pt-slider-progress{background:#137cbd}.pt-slider-handle{background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;color:#182026;position:absolute;top:0;left:0;border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2);cursor:pointer;width:16px;height:16px}.pt-slider-handle.pt-active,.pt-slider-handle:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-slider-handle.pt-disabled,.pt-slider-handle:disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-slider-handle.pt-disabled.pt-active,.pt-slider-handle:disabled.pt-active{background:rgba(206,217,224,.7)}.pt-slider-handle:focus{z-index:1}.pt-slider-handle:hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;background-clip:padding-box;z-index:2;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2);cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.pt-slider-handle.pt-active{background-color:#d8e1e8;background-image:none;box-shadow:0 0 0 1px rgba(16,22,26,.2),inset 0 1px 1px rgba(16,22,26,.1);cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pt-disabled .pt-slider-handle{box-shadow:none;background:#bfccd6;pointer-events:none}.pt-dark .pt-slider-handle{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-slider-handle.pt-active,.pt-dark .pt-slider-handle:active,.pt-dark .pt-slider-handle:hover{color:#f5f8fa}.pt-dark .pt-slider-handle:hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-slider-handle.pt-active,.pt-dark .pt-slider-handle:active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-slider-handle.pt-disabled,.pt-dark .pt-slider-handle:disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-slider-handle.pt-disabled.pt-active,.pt-dark .pt-slider-handle:disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-slider-handle .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-dark .pt-slider-handle,.pt-dark .pt-slider-handle:hover{background-color:#394b59}.pt-dark .pt-slider-handle.pt-active{background-color:#293742}.pt-dark .pt-disabled .pt-slider-handle{border-color:#5c7080;box-shadow:none;background:#5c7080}.pt-slider-handle .pt-slider-label{-webkit-transform:translate(-50%,20px);transform:translate(-50%,20px);margin-left:8px;border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);background:#394b59;color:#f5f8fa}.pt-dark .pt-slider-handle .pt-slider-label{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4);background:#e1e8ed;color:#394b59}.pt-disabled .pt-slider-handle .pt-slider-label{box-shadow:none}.pt-slider-label{display:inline-block;padding:2px 5px;vertical-align:top;line-height:1;font-size:12px;position:absolute;-webkit-transform:translate(-50%,20px);transform:translate(-50%,20px)}.pt-range-slider .pt-slider-handle{width:8px}.pt-range-slider .pt-slider-handle:first-of-type{border-top-right-radius:0;border-bottom-right-radius:0}.pt-range-slider .pt-slider-handle:last-of-type{margin-left:8px;border-top-left-radius:0;border-bottom-left-radius:0}.pt-range-slider .pt-slider-handle:last-of-type .pt-slider-label{margin-left:0}.pt-range-slider .pt-slider-progress{border-radius:0}@-webkit-keyframes pt-spinner-animation{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes pt-spinner-animation{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.pt-spinner:not(.pt-svg-spinner){width:50px;display:inline-block}.pt-spinner.pt-svg-spinner,.pt-spinner:not(.pt-svg-spinner) .pt-spinner-svg-container{-webkit-animation-duration:.4s;animation-duration:.4s}.pt-spinner .pt-svg-spinner-transform-group{-webkit-transform:scale(.5);transform:scale(.5)}.pt-spinner path{stroke-width:5;fill-opacity:0}.pt-spinner .pt-spinner-head{transition:stroke-dashoffset .2s cubic-bezier(.4,1,.75,.9);stroke:rgba(92,112,128,.8);stroke-linecap:round}.pt-spinner .pt-spinner-track{stroke:rgba(92,112,128,.2)}.pt-spinner.pt-small:not(.pt-svg-spinner){width:24px}.pt-spinner.pt-small.pt-svg-spinner,.pt-spinner.pt-small:not(.pt-svg-spinner) .pt-spinner-svg-container{-webkit-animation-duration:.4s;animation-duration:.4s}.pt-spinner.pt-small .pt-svg-spinner-transform-group{-webkit-transform:scale(.24);transform:scale(.24)}.pt-spinner.pt-small path{stroke-width:12}.pt-spinner.pt-large:not(.pt-svg-spinner){width:100px}.pt-spinner.pt-large.pt-svg-spinner,.pt-spinner.pt-large:not(.pt-svg-spinner) .pt-spinner-svg-container{-webkit-animation-duration:450ms;animation-duration:450ms}.pt-spinner.pt-large .pt-svg-spinner-transform-group{-webkit-transform:scale(1);transform:scale(1)}.pt-spinner.pt-large path{stroke-width:3}.pt-spinner:not(.pt-svg-spinner) .pt-spinner-svg-container{position:relative;width:100%;height:0;padding:0 0 100%;-webkit-animation:pt-spinner-animation .4s linear infinite;animation:pt-spinner-animation .4s linear infinite}.pt-spinner:not(.pt-svg-spinner).pt-no-spin .pt-spinner-svg-container{-webkit-animation:none;animation:none}.pt-spinner:not(.pt-svg-spinner) svg{position:absolute;top:0;left:0;width:100%;height:100%}.pt-svg-spinner{-webkit-transform-origin:center;transform-origin:center;-webkit-animation:pt-spinner-animation .4s linear infinite;animation:pt-spinner-animation .4s linear infinite}.pt-svg-spinner.pt-no-spin{-webkit-animation:none;animation:none}.pt-svg-spinner-transform-group{-webkit-transform-origin:center;transform-origin:center}.pt-dark .pt-spinner .pt-spinner-head{stroke:#8a9ba8}.pt-dark .pt-spinner .pt-spinner-track{stroke:rgba(16,22,26,.5)}.pt-spinner.pt-intent-primary .pt-spinner-head{stroke:#137cbd}.pt-spinner.pt-intent-success .pt-spinner-head{stroke:#0f9960}.pt-spinner.pt-intent-warning .pt-spinner-head{stroke:#d9822b}.pt-spinner.pt-intent-danger .pt-spinner-head{stroke:#db3737}table.pt-table{border-spacing:0;font-size:14px}table.pt-table td,table.pt-table th{padding:11px;vertical-align:top;text-align:left}table.pt-table th{color:#182026;font-weight:600}table.pt-table td{color:#182026}table.pt-table tbody tr:first-child td{box-shadow:inset 0 1px 0 0 rgba(16,22,26,.15)}table.pt-table.pt-condensed td,table.pt-table.pt-condensed th{padding-top:6px;padding-bottom:6px}table.pt-table.pt-striped tbody tr:nth-child(odd) td{background:rgba(191,204,214,.2)}table.pt-table.pt-bordered th:not(:first-child){box-shadow:inset 1px 0 0 0 rgba(16,22,26,.15)}table.pt-table.pt-bordered tbody tr td{box-shadow:inset 0 1px 0 0 rgba(16,22,26,.15)}table.pt-table.pt-bordered tbody tr td:not(:first-child){box-shadow:inset 1px 1px 0 0 rgba(16,22,26,.15)}table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td{box-shadow:none}table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td:not(:first-child){box-shadow:inset 1px 0 0 0 rgba(16,22,26,.15)}table.pt-table.pt-interactive tbody tr:hover td{background-color:rgba(191,204,214,.4);cursor:pointer}.pt-dark table.pt-table td,.pt-dark table.pt-table th{color:#f5f8fa}.pt-dark table.pt-table tbody tr:first-child td{box-shadow:inset 0 1px 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-striped tbody tr:nth-child(odd) td{background:rgba(92,112,128,.15)}.pt-dark table.pt-table.pt-bordered th:not(:first-child){box-shadow:inset 1px 0 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered tbody tr td{box-shadow:inset 0 1px 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered tbody tr td:not(:first-child){box-shadow:inset 1px 1px 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td{box-shadow:inset 1px 0 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td:first-child{box-shadow:none}.pt-dark table.pt-table.pt-interactive tbody tr:hover td{background-color:rgba(92,112,128,.3);cursor:pointer}.pt-tabs.pt-vertical{display:-webkit-flex;display:flex}.pt-tabs.pt-vertical>.pt-tab-list{-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:flex-start;align-items:flex-start}.pt-tabs.pt-vertical>.pt-tab-list .pt-tab{width:100%;padding:0 10px}.pt-tabs.pt-vertical>.pt-tab-list .pt-tab-indicator-wrapper .pt-tab-indicator{top:0;right:0;bottom:0;left:0;border-radius:3px;background-color:rgba(19,124,189,.2);height:auto}.pt-tabs.pt-vertical>.pt-tab-panel{margin-top:0;padding-left:20px}.pt-tab-list{display:-webkit-flex;display:flex;-webkit-flex:0 0 auto;flex:0 0 auto;-webkit-align-items:flex-end;align-items:flex-end;position:relative;margin:0;border:none;padding:0;list-style:none}.pt-tab-list>:not(:last-child){margin-right:20px}.pt-tab{overflow:hidden;-webkit-flex:0 0 auto;flex:0 0 auto;position:relative;cursor:pointer;max-width:100%;vertical-align:top;line-height:30px;color:#182026;font-size:14px}.pt-tab-indicator-wrapper~.pt-tab{box-shadow:none!important}.pt-tab[aria-disabled=true]{cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-tab[aria-selected=true]{border-radius:0;box-shadow:inset 0 -3px 0 #106ba3}.pt-tab:not([aria-disabled=true]):hover,.pt-tab[aria-selected=true]{color:#106ba3}.pt-tab:focus{-moz-outline-radius:0}.pt-large .pt-tab{line-height:40px;font-size:16px}.pt-tab-panel{margin-top:20px}.pt-tab-panel[aria-hidden=true]{display:none}.pt-tab-indicator-wrapper{position:absolute;top:0;left:0;-webkit-transform:translateX(0),translateY(0);transform:translateX(0),translateY(0);transition:height,width,-webkit-transform;transition:height,transform,width;transition:height,transform,width,-webkit-transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,1,.75,.9)}.pt-tab-indicator-wrapper .pt-tab-indicator{position:absolute;right:0;bottom:0;left:0;background-color:#106ba3;height:3px}.pt-tab-indicator-wrapper.pt-no-animation{transition:none}.pt-dark .pt-tab{color:#f5f8fa}.pt-dark .pt-tab[aria-disabled=true]{color:rgba(191,204,214,.5)}.pt-dark .pt-tab[aria-selected=true]{box-shadow:inset 0 -3px 0 #48aff0}.pt-dark .pt-tab:not([aria-disabled=true]):hover,.pt-dark .pt-tab[aria-selected=true]{color:#48aff0}.pt-dark .pt-tab-indicator{background-color:#48aff0}.pt-flex-expander{-webkit-flex:1 1;flex:1 1}.pt-tag{display:inline-block;position:relative;border:none;border-radius:3px;box-shadow:none;background-color:#5c7080;min-width:20px;padding:2px 6px;line-height:16px;color:#f5f8fa;font-size:12px}.pt-tag.pt-round{border-radius:10px}.pt-dark .pt-tag{background-color:#bfccd6;color:#182026}.pt-tag.pt-tag-removable{padding-right:20px}.pt-large .pt-tag,.pt-tag.pt-large{min-width:30px;padding:5px 10px;line-height:20px;font-size:14px}.pt-large .pt-tag-remove,.pt-tag-remove{line-height:1;font-weight:400;font-style:normal}.pt-large .pt-tag.pt-round,.pt-tag.pt-large.pt-round{border-radius:15px}.pt-large .pt-tag.pt-tag-removable,.pt-tag.pt-large.pt-tag-removable{padding-right:30px}.pt-tag.pt-intent-primary{background:#137cbd;color:#fff}.pt-tag.pt-intent-success{background:#0f9960;color:#fff}.pt-tag.pt-intent-warning{background:#d9822b;color:#fff}.pt-tag.pt-intent-danger{background:#db3737;color:#fff}.pt-tag.pt-minimal:not([class*=pt-intent-]){background-color:rgba(138,155,168,.2);color:#182026}.pt-dark .pt-tag.pt-minimal:not([class*=pt-intent-]){color:#f5f8fa}.pt-tag.pt-minimal.pt-intent-primary{background-color:rgba(19,124,189,.15);color:#106ba3}.pt-dark .pt-tag.pt-minimal.pt-intent-primary{background-color:rgba(19,124,189,.25);color:#48aff0}.pt-tag.pt-minimal.pt-intent-success{background-color:rgba(15,153,96,.15);color:#0d8050}.pt-dark .pt-tag.pt-minimal.pt-intent-success{background-color:rgba(15,153,96,.25);color:#3dcc91}.pt-tag.pt-minimal.pt-intent-warning{background-color:rgba(217,130,43,.15);color:#bf7326}.pt-dark .pt-tag.pt-minimal.pt-intent-warning{background-color:rgba(217,130,43,.25);color:#ffb366}.pt-tag.pt-minimal.pt-intent-danger{background-color:rgba(219,55,55,.15);color:#c23030}.pt-dark .pt-tag.pt-minimal.pt-intent-danger{background-color:rgba(219,55,55,.25);color:#ff7373}.pt-tag-remove{font-family:Icons16,sans-serif;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;position:absolute;top:0;right:0;opacity:.5;border:none;background:0 0;cursor:pointer;padding:2px;color:inherit}.pt-tag-remove:hover{opacity:.8;background:0 0;text-decoration:none}.pt-tag-remove:active{opacity:1}.pt-tag-remove::before{content:\"\"}.pt-large .pt-tag-remove{font-family:Icons20,sans-serif;font-size:20px;padding:5px}.pt-toast{display:-webkit-flex;display:flex;-webkit-align-items:flex-start;align-items:flex-start;position:relative!important;margin:15px 0 0;border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);background-color:#fff;min-width:300px;max-width:500px;pointer-events:all}.pt-toast.pt-toast-appear,.pt-toast.pt-toast-enter{-webkit-transform:translateY(-40px);transform:translateY(-40px)}.pt-toast.pt-toast-appear-active,.pt-toast.pt-toast-enter-active{-webkit-transform:translateY(0);transform:translateY(0);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-toast.pt-toast-appear~.pt-toast,.pt-toast.pt-toast-enter~.pt-toast{-webkit-transform:translateY(-40px);transform:translateY(-40px)}.pt-toast.pt-toast-appear-active~.pt-toast,.pt-toast.pt-toast-enter-active~.pt-toast{-webkit-transform:translateY(0);transform:translateY(0);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-toast.pt-toast-leave{opacity:1;-webkit-filter:blur(0);filter:blur(0)}.pt-toast.pt-toast-leave-active{opacity:0;-webkit-filter:blur(10px);filter:blur(10px);transition-property:opacity,-webkit-filter;transition-property:opacity,filter;transition-property:opacity,filter,-webkit-filter;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-popover-appear-active>.pt-tooltip,.pt-popover-enter-active>.pt-tooltip,.pt-popover-leave-active>.pt-tooltip,.pt-toast.pt-toast-leave-active~.pt-toast{transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9)}.pt-toast.pt-toast-leave~.pt-toast{-webkit-transform:translateY(0);transform:translateY(0)}.pt-toast.pt-toast-leave-active~.pt-toast{-webkit-transform:translateY(-40px);transform:translateY(-40px);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-delay:50ms}.pt-toast .pt-button-group{-webkit-flex:0 0 auto;flex:0 0 auto;padding:5px 5px 5px 0}.pt-toast>.pt-icon-standard{padding:12px 0 12px 12px;color:#5c7080}.pt-dark .pt-toast,.pt-toast.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4);background-color:#394b59}.pt-dark .pt-toast>.pt-icon-standard,.pt-toast.pt-dark>.pt-icon-standard{color:#bfccd6}.pt-toast[class*=pt-intent-] a{color:rgba(255,255,255,.7)}.pt-toast[class*=pt-intent-] a:hover,.pt-toast[class*=pt-intent-]>.pt-icon-standard{color:#fff}.pt-toast[class*=pt-intent-] .pt-button,.pt-toast[class*=pt-intent-] .pt-button::before,.pt-toast[class*=pt-intent-] .pt-button:active{color:rgba(255,255,255,.7)!important}.pt-toast[class*=pt-intent-] .pt-button:focus{outline-color:rgba(255,255,255,.5)}.pt-toast[class*=pt-intent-] .pt-button:hover{background-color:rgba(255,255,255,.15)!important;color:#fff!important}.pt-toast[class*=pt-intent-] .pt-button:active{background-color:rgba(255,255,255,.3)!important;color:#fff!important}.pt-toast[class*=pt-intent-] .pt-button::after{background:rgba(255,255,255,.3)!important}.pt-toast.pt-intent-primary{background-color:#137cbd;color:#fff}.pt-toast.pt-intent-success{background-color:#0f9960;color:#fff}.pt-toast.pt-intent-warning{background-color:#d9822b;color:#fff}.pt-toast.pt-intent-danger{background-color:#db3737;color:#fff}.pt-toast-message{-webkit-flex:1 1 auto;flex:1 1 auto;padding:11px}.pt-toast-container{position:fixed;right:0;left:0;z-index:40;padding:0 15px 15px;pointer-events:none}.pt-toast-container>span{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:center;align-items:center}.pt-toast-container.pt-toast-container-top{top:0}.pt-toast-container.pt-toast-container-bottom{bottom:0}.pt-toast-container.pt-toast-container-bottom>span{-webkit-flex-direction:column-reverse;flex-direction:column-reverse}.pt-toast-container.pt-toast-container-left>span{-webkit-align-items:flex-start;align-items:flex-start}.pt-toast-container.pt-toast-container-right>span{-webkit-align-items:flex-end;align-items:flex-end}.pt-toast-container-bottom .pt-toast.pt-toast-enter:not(.pt-toast-enter-active),.pt-toast-container-bottom .pt-toast.pt-toast-enter:not(.pt-toast-enter-active)~.pt-toast,.pt-toast-container-bottom .pt-toast.pt-toast-leave-active~.pt-toast{-webkit-transform:translateY(55px);transform:translateY(55px)}.pt-tooltip{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);-webkit-transform:scale(1);transform:scale(1)}.pt-tooltip .pt-popover-arrow{position:absolute;width:22px;height:22px}.pt-tooltip .pt-popover-arrow::before{margin:4px;width:14px;height:14px;box-shadow:1px 1px 6px rgba(16,22,26,.2)}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-tooltip{margin-top:-11px;margin-bottom:11px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-tooltip>.pt-popover-arrow{bottom:-8px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-tooltip{margin-left:11px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-tooltip>.pt-popover-arrow{left:-8px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(0);transform:rotate(0)}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-tooltip{margin-top:11px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-tooltip>.pt-popover-arrow{top:-8px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-tooltip{margin-right:11px;margin-left:-11px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-tooltip>.pt-popover-arrow{right:-8px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.pt-tether-element-attached-middle>.pt-tooltip>.pt-popover-arrow{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.pt-tether-element-attached-center>.pt-tooltip>.pt-popover-arrow{right:50%;-webkit-transform:translateX(50%);transform:translateX(50%)}.pt-tether-element-attached-top.pt-tether-target-attached-top>.pt-tooltip>.pt-popover-arrow{top:-.22px}.pt-tether-element-attached-right.pt-tether-target-attached-right>.pt-tooltip>.pt-popover-arrow{right:-.22px}.pt-tether-element-attached-left.pt-tether-target-attached-left>.pt-tooltip>.pt-popover-arrow{left:-.22px}.pt-tether-element-attached-bottom.pt-tether-target-attached-bottom>.pt-tooltip>.pt-popover-arrow{bottom:-.22px}.pt-tether-element-attached-top.pt-tether-element-attached-left>.pt-tooltip{-webkit-transform-origin:top left;transform-origin:top left}.pt-tether-element-attached-top.pt-tether-element-attached-center>.pt-tooltip{-webkit-transform-origin:top center;transform-origin:top center}.pt-tether-element-attached-top.pt-tether-element-attached-right>.pt-tooltip{-webkit-transform-origin:top right;transform-origin:top right}.pt-tether-element-attached-middle.pt-tether-element-attached-left>.pt-tooltip{-webkit-transform-origin:center left;transform-origin:center left}.pt-tether-element-attached-middle.pt-tether-element-attached-center>.pt-tooltip{-webkit-transform-origin:center center;transform-origin:center center}.pt-tether-element-attached-middle.pt-tether-element-attached-right>.pt-tooltip{-webkit-transform-origin:center right;transform-origin:center right}.pt-tether-element-attached-bottom.pt-tether-element-attached-left>.pt-tooltip{-webkit-transform-origin:bottom left;transform-origin:bottom left}.pt-tether-element-attached-bottom.pt-tether-element-attached-center>.pt-tooltip{-webkit-transform-origin:bottom center;transform-origin:bottom center}.pt-tether-element-attached-bottom.pt-tether-element-attached-right>.pt-tooltip{-webkit-transform-origin:bottom right;transform-origin:bottom right}.pt-tooltip .pt-popover-content{background:#394b59;color:#f5f8fa;padding:10px 12px}.pt-tree-node-content-0,.pt-tree-root{padding-left:0}.pt-tooltip .pt-popover-arrow-border{fill:#10161a;fill-opacity:.1}.pt-tooltip .pt-popover-arrow-fill{fill:#394b59}.pt-popover-appear>.pt-tooltip,.pt-popover-enter>.pt-tooltip{-webkit-transform:scale(.8);transform:scale(.8)}.pt-popover-appear-active>.pt-tooltip,.pt-popover-enter-active>.pt-tooltip{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-delay:0}.pt-popover-leave>.pt-tooltip{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-leave-active>.pt-tooltip{-webkit-transform:scale(.8);transform:scale(.8);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-delay:0}.pt-dark .pt-tooltip,.pt-tooltip.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-dark .pt-tooltip .pt-popover-content,.pt-tooltip.pt-dark .pt-popover-content{background:#e1e8ed;color:#394b59}.pt-dark .pt-tooltip .pt-popover-arrow::before,.pt-tooltip.pt-dark .pt-popover-arrow::before{box-shadow:1px 1px 6px rgba(16,22,26,.4)}.pt-dark .pt-tooltip .pt-popover-arrow-border,.pt-tooltip.pt-dark .pt-popover-arrow-border{fill:#10161a;fill-opacity:.2}.pt-dark .pt-tooltip .pt-popover-arrow-fill,.pt-tooltip.pt-dark .pt-popover-arrow-fill{fill:#e1e8ed}.pt-tooltip.pt-intent-primary .pt-popover-content{background:#137cbd;color:#fff}.pt-tooltip.pt-intent-primary .pt-popover-arrow-fill{fill:#137cbd}.pt-tooltip.pt-intent-success .pt-popover-content{background:#0f9960;color:#fff}.pt-tooltip.pt-intent-success .pt-popover-arrow-fill{fill:#0f9960}.pt-tooltip.pt-intent-warning .pt-popover-content{background:#d9822b;color:#fff}.pt-tooltip.pt-intent-warning .pt-popover-arrow-fill{fill:#d9822b}.pt-tooltip.pt-intent-danger .pt-popover-content{background:#db3737;color:#fff}.pt-tooltip.pt-intent-danger .pt-popover-arrow-fill{fill:#db3737}.pt-tooltip-indicator{border-bottom:dotted 1px;cursor:help}.pt-tree-node-list{margin:0;padding-left:0;list-style:none}.pt-tree-root{position:relative;background-color:transparent;cursor:default}.pt-tree-node-content-1{padding-left:23px}.pt-tree-node-content-2{padding-left:46px}.pt-tree-node-content-3{padding-left:69px}.pt-tree-node-content-4{padding-left:92px}.pt-tree-node-content-5{padding-left:115px}.pt-tree-node-content-6{padding-left:138px}.pt-tree-node-content-7{padding-left:161px}.pt-tree-node-content-8{padding-left:184px}.pt-tree-node-content-9{padding-left:207px}.pt-tree-node-content-10{padding-left:230px}.pt-tree-node-content-11{padding-left:253px}.pt-tree-node-content-12{padding-left:276px}.pt-tree-node-content-13{padding-left:299px}.pt-tree-node-content-14{padding-left:322px}.pt-tree-node-content-15{padding-left:345px}.pt-tree-node-content-16{padding-left:368px}.pt-tree-node-content-17{padding-left:391px}.pt-tree-node-content-18{padding-left:414px}.pt-tree-node-content-19{padding-left:437px}.pt-tree-node-content-20{padding-left:460px}.pt-tree-node-content{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;width:100%;height:30px;padding-right:5px}.pt-tree-node-content:hover{background-color:rgba(191,204,214,.4)}.pt-tree-node-caret,.pt-tree-node-caret-none{position:relative;min-width:30px;line-height:30px!important}.pt-tree-node-caret{color:#5c7080;cursor:pointer;text-align:center}.pt-tree-node-caret:hover{color:#182026}.pt-dark .pt-tree-node-caret{color:#bfccd6}.pt-dark .pt-tree-node-caret:hover{color:#f5f8fa}.pt-tree-node-caret::before{display:inline-block;content:\"\";transition:-webkit-transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9)}.pt-tree-node-caret.pt-tree-node-caret-open::before{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.pt-tree-node-icon{position:relative;margin-right:7px;color:#5c7080}.pt-tree-node-label{overflow:hidden;-webkit-flex:1 1 auto;flex:1 1 auto;position:relative;user-select:none}.pt-tree-node-label span{display:inline}.pt-tree-node-secondary-label{padding:0 5px;line-height:30px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content{background-color:#137cbd}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content,.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-icon,.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-icon-large,.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-icon-standard{color:#fff}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-tree-node-caret::before{color:rgba(255,255,255,.7)}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-tree-node-caret:hover::before{color:#fff}.pt-dark .pt-tree-node-content:hover{background-color:rgba(92,112,128,.3)}.pt-dark .pt-tree-node-icon{color:#bfccd6}.pt-dark .pt-tree-node.pt-tree-node-selected>.pt-tree-node-content{background-color:#137cbd}\n\n      body {\n        background-color: #f5f8fa;\n        width: 100%;\n        height: 100%;\n      }\n\n      body.pt-dark {\n        background-color: #293742;\n      }\n\n      form {\n        margin: 0;\n      }\n\n      #logo-light,\n      #logo-dark {\n        height: 34px;\n      }\n\n      #logo-dark {\n        display: none;\n      }\n\n      .pt-card.box {\n        padding: 0;\n      }\n\n      .pt-card.box .box-header {\n        background: rgba(0, 0, 0, 0.07);\n        padding: 10px;\n      }\n\n      .pt-dark .pt-card.box .box-header {\n        background: rgba(0, 0, 0, 0.16);\n        padding: 10px;\n      }\n\n      .pt-card.box .box-body {\n        padding: 40px 50px 50px 50px;\n      }\n\n      .pt-dark #logo-light {\n        display: none;\n      }\n      .pt-dark #logo-dark {\n        display: block;\n      }\n\n      .pt-card {\n        padding: 50px;\n        min-width: 260px;\n        max-width: 300px;\n        margin: 0 auto;\n        position: relative;\n        top: 50%;\n        transform: translateY(-50%);\n      }\n\n      .pt-card .pt-button {\n        width: 100%;\n        margin-bottom: 7px;\n      }\n\n      .pt-input {\n        width: 100%;\n      }\n\n      .device-info {\n        margin-bottom: 16px;\n      }\n\n      .pt-card .pt-button.auth-cancel,\n      #auth-buttons .pt-button:last-child,\n      #auth-secondary .pt-button:last-child {\n        margin-bottom: 0;\n      }\n\n      #alert,\n      #secondary-alert,\n      #device-alert {\n        display: none;\n        margin-bottom: 15px;\n        transition: background-color ease-in-out 0.1s,\n          background-color ease-in-out 0.1s;\n        word-break: break-word;\n      }\n\n      #alert.flash,\n      #secondary-alert.flash,\n      #device-alert.flash {\n        background-color: rgba(255, 255, 255, 0.50);\n      }\n\n      .auth-button {\n        padding: 15px 10px;\n      }\n\n      #auth-local {\n        display: none;\n      }\n\n      #auth-secondary {\n        display: none;\n      }\n\n      #auth-device {\n        display: none;\n      }\n\n      #secondary-passcode {\n        margin-bottom: 7px;\n      }\n    </style>\n  </head>\n  <body class=\"pt-dark pt-focus-disabled\">\n    <div class=\"box pt-card pt-elevation-2\">\n      <div class=\"box-header\">\n        <svg id=\"logo-light\" role=\"img\" viewBox=\"0 0 123.159 21.35\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#2e4153\">\n          <path d=\"M80.048 16.606q-1.126 0-2.067-.348-.92-.347-1.596-.982-.675-.655-1.043-1.534-.348-.9-.348-2.006 0-1.104.348-1.984.368-.9 1.023-1.555.675-.655 1.596-1.003.92-.348 2.046-.348.675 0 1.37.246.717.225 1.29.614.573.368.818.839.143.266.143.388 0 .123-.102.246-.123.143-.286.225-.144.082-.266.082-.246 0-.471-.287-.205-.286-.491-.511t-.634-.389q-.328-.164-.676-.246-.347-.102-.695-.102-1.146 0-2.005.47-.86.471-1.33 1.33-.471.84-.471 1.985 0 1.126.47 1.985.471.84 1.31 1.31.86.47 1.985.47.47 0 .941-.122.47-.123.9-.348.43-.246.737-.553.164-.143.266-.225.123-.102.286-.102.123 0 .225.102.123.082.246.246.082.082.102.204.02.103-.02.225-.02.123-.123.225-.43.43-1.043.758-.594.327-1.249.511-.634.184-1.186.184m8.675 0q-.573 0-1.043-.123-.45-.122-.798-.368-.348-.245-.594-.614-.225-.368-.347-.86-.103-.49-.103-1.084V2.917q0-.245.041-.368.061-.143.184-.204.143-.062.348-.062.225 0 .348.062.143.04.205.164.06.122.06.347v10.742q0 .45.103.798.103.328.307.573.225.225.532.348.328.102.737.102.245 0 .368.062.143.04.205.184.061.123.061.348t-.061.368q-.062.123-.205.164-.123.061-.348.061m7.12.082q-1.022 0-1.923-.389-.9-.388-1.596-1.064-.675-.675-1.064-1.575-.388-.9-.388-1.924 0-1.043.368-1.923.389-.9 1.064-1.575.696-.696 1.575-1.064.9-.39 1.924-.39 1.37 0 2.476.656 1.104.634 1.76 1.76.654 1.104.654 2.516 0 1.064-.389 1.964-.368.9-1.043 1.576-.655.675-1.535 1.064-.88.368-1.882.368m-.04-1.105q1.043 0 1.882-.511.86-.532 1.35-1.412.512-.9.512-1.985 0-1.105-.49-1.964-.492-.86-1.351-1.33-.84-.491-1.924-.491-1.064 0-1.923.511-.839.491-1.35 1.35-.492.86-.492 1.945 0 1.104.492 1.984.511.88 1.37 1.392t1.924.511m10.906 1.044q-.88 0-1.617-.287-.737-.307-1.289-.86-.552-.572-.86-1.37-.306-.818-.306-1.862V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.348-.062.225 0 .347.062.144.061.185.204.061.123.061.348v4.808q0 .983.327 1.74.328.736.983 1.145.654.41 1.636.41 1.003 0 1.72-.41.715-.409 1.104-1.145.389-.757.389-1.74V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.368-.062.205 0 .328.062.122.061.163.204.062.123.062.348v8.594q0 .184-.062.307-.04.122-.163.163-.123.062-.348.062-.144 0-.246-.041-.102-.02-.164-.082-.06-.061-.102-.164-.02-.102-.02-.245v-1.207q-.266.347-.594.675-.327.307-.736.573-.39.245-.88.388-.491.144-1.085.144m11.478-.02q-.941 0-1.78-.349-.818-.347-1.432-1.002t-.962-1.555q-.348-.921-.348-2.026 0-1.084.348-1.964.348-.9.962-1.535.614-.655 1.432-1.002.839-.348 1.78-.348 1.003 0 1.842.348.839.347 1.452 1.002.614.655.942 1.555.347.9.347 2.005t-.347 2.006q-.328.88-.942 1.534-.613.635-1.452.982-.84.348-1.842.348m.184-1.085q1.023 0 1.821-.47.798-.492 1.269-1.33.47-.86.47-1.986 0-1.145-.47-2.005t-1.29-1.33q-.797-.49-1.8-.49-1.002 0-1.82.49-.82.47-1.29 1.33t-.47 2.005q0 1.126.47 1.985.47.84 1.29 1.33.818.47 1.82.47m4.215 1.084q-.327 0-.47-.143-.123-.143-.123-.47V2.958q0-.246.04-.369.062-.143.185-.204.143-.062.348-.062.225 0 .348.062.122.061.184.204.061.123.061.348v13.034q0 .225-.061.368-.041.143-.164.205-.123.061-.348.061\" />\n          <path d=\"M68.888 21.35q-.24 0-.408-.072-.144-.072-.216-.24-.048-.144-.048-.408V.696q0-.264.048-.408.072-.168.216-.216.168-.072.432-.072t.408.072.192.24q.072.144.072.432v19.91q0 .264-.072.408-.048.168-.216.216-.144.072-.408.072\" />\n          <path d=\"M5.606 16.685q-1.002 0-1.739-.41-.736-.409-1.207-1.104-.47-.717-.696-1.617t-.204-1.903q.02-.982.245-1.903.246-.92.716-1.636.47-.717 1.187-1.126.716-.43 1.698-.43.9 0 1.74.43.859.43 1.534 1.166.675.717 1.064 1.637.389.921.389 1.903 0 1.023-.389 1.944-.389.9-1.064 1.596-.655.675-1.514 1.064-.839.389-1.76.389m-3.785 4.583q-.9 0-1.289-.266t-.47-.675Q0 19.917 0 19.467V8.357q0-.45.061-.86.082-.409.45-.675.39-.286 1.29-.286.716 0 1.186.205.47.184.676.593v12.113q.04.45-.062.86-.082.429-.47.695-.39.266-1.31.266m3.294-8.062q.41 0 .737-.204.348-.205.552-.532.205-.348.205-.737 0-.41-.205-.736-.184-.348-.532-.532-.327-.205-.736-.205-.41 0-.757.205-.328.204-.532.532-.184.327-.184.736 0 .389.204.737.205.327.532.532.328.204.716.204m7.878 3.724q-.92 0-1.31-.266-.388-.286-.47-.716-.062-.45-.062-.9v-6.67q0-.471.082-.88.082-.43.47-.696.39-.266 1.31-.266.9 0 1.269.246.389.245.47.552.082.286.082.45l-.327-.123.164-.184q.163-.184.47-.43.328-.245.757-.43.45-.183 1.003-.183.184 0 .45.04.286.021.593.103.328.082.594.245.286.164.45.43.184.266.184.655 0 1.023-.45 1.74-.43.715-1.146.715-.389 0-.552-.04-.164-.062-.246-.103-.061-.061-.164-.102-.081-.062-.327-.062-.307 0-.573.082-.245.062-.45.205-.184.143-.286.348-.103.184-.103.409v3.99q0 .45-.082.88-.081.43-.47.695-.389.266-1.33.266m8.941 0q-.92 0-1.31-.266-.388-.286-.47-.716-.061-.43-.061-.88v-6.67q0-.45.082-.86.081-.43.47-.695.389-.287 1.31-.287.92 0 1.289.287.389.266.47.696.082.43.082.9v6.65q0 .45-.082.88-.081.43-.47.695-.368.266-1.31.266m0-11.376q-.92 0-1.31-.266-.388-.287-.47-.716-.061-.43-.061-.88 0-.47.082-.88.081-.43.47-.696t1.31-.266 1.289.287q.389.266.47.696.082.43.082.88t-.082.88-.47.695q-.368.266-1.31.266m8.758 11.376q-1.085 0-1.883-.245-.798-.246-1.33-.757-.511-.512-.777-1.29-.246-.777-.246-1.82v-7.94q0-.47.082-.859.082-.409.47-.654.39-.266 1.31-.266.9 0 1.29.286.388.266.47.696t.082.88v7.672q0 .226.02.39.041.142.102.224.062.082.164.123t.266.041q.532 0 .941.082.43.082.655.43.246.347.246 1.166 0 .92-.287 1.31-.286.388-.716.45-.43.081-.86.081M26.026 6.66h2.394l2.824-.103q.45 0 .86.082.43.082.695.47.287.39.287 1.33 0 .86-.287 1.249-.266.389-.696.491-.409.102-.88.102l-2.598-.061h-2.68q-.696-.04-.962-.45t-.266-1.35q0-.88.327-1.31.328-.45.982-.45m12.523 10.23q-.982 0-1.842-.388-.839-.39-1.473-1.085-.634-.716-.982-1.657t-.348-2.046V8.378q0-.471.082-.88.082-.43.47-.696.39-.286 1.31-.286.941 0 1.31.286.388.266.47.696t.082.9v3.315q0 .47.164.798.184.327.511.511.348.164.798.164t.778-.184.511-.512.184-.777V8.357q0-.47.082-.88.082-.43.47-.695.39-.266 1.31-.266.942 0 1.31.286.389.266.47.696.082.43.082.88v6.731q0 .45-.082.86-.081.409-.47.675t-1.31.266q-.675 0-1.064-.164-.368-.143-.532-.368-.163-.246-.204-.47-.02-.246-.02-.41l.347.102-.164.205q-.163.184-.49.45-.308.246-.758.45-.43.184-1.002.184m8.818.02q-.92 0-1.31-.265-.388-.287-.47-.716-.061-.43-.061-.88V8.316q0-.45.082-.86.082-.408.47-.654.39-.266 1.31-.266.839 0 1.207.225.389.205.491.512.103.307.082.573 0 .245.041.306l-.164.123q-.02-.307.225-.614.266-.307.676-.552.43-.266.9-.41.49-.163.92-.163 1.024 0 1.801.389.778.389 1.29 1.105.531.695.797 1.637.266.94.266 2.046v3.355q0 .45-.082.88-.081.43-.47.696-.369.266-1.31.266t-1.33-.266q-.388-.287-.47-.716-.082-.43-.082-.88v-3.335q0-.47-.184-.798-.164-.328-.491-.491-.307-.185-.778-.185-.45 0-.798.185-.327.184-.511.511-.185.328-.185.778v3.355q0 .45-.081.88-.082.43-.471.696-.368.266-1.31.266m14.078.021q-.942 0-1.658-.082-.695-.081-1.186-.327-.491-.245-.798-.696-.307-.47-.45-1.207-.144-.757-.144-1.882V3.67q0-.47.082-.88.082-.429.47-.695.39-.286 1.31-.286.921 0 1.29.286.388.266.47.696t.082.9V13q.02.164.061.246.061.082.164.103h.327q.266 0 .553.081.307.062.511.43.225.348.225 1.228 0 .92-.225 1.31-.204.388-.511.45-.307.081-.573.081\" />\n        </svg>\n        <svg id=\"logo-dark\" role=\"img\" viewBox=\"0 0 123.159 21.35\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\">\n          <path d=\"M80.048 16.606q-1.126 0-2.067-.348-.92-.347-1.596-.982-.675-.655-1.043-1.534-.348-.9-.348-2.006 0-1.104.348-1.984.368-.9 1.023-1.555.675-.655 1.596-1.003.92-.348 2.046-.348.675 0 1.37.246.717.225 1.29.614.573.368.818.839.143.266.143.388 0 .123-.102.246-.123.143-.286.225-.144.082-.266.082-.246 0-.471-.287-.205-.286-.491-.511t-.634-.389q-.328-.164-.676-.246-.347-.102-.695-.102-1.146 0-2.005.47-.86.471-1.33 1.33-.471.84-.471 1.985 0 1.126.47 1.985.471.84 1.31 1.31.86.47 1.985.47.47 0 .941-.122.47-.123.9-.348.43-.246.737-.553.164-.143.266-.225.123-.102.286-.102.123 0 .225.102.123.082.246.246.082.082.102.204.02.103-.02.225-.02.123-.123.225-.43.43-1.043.758-.594.327-1.249.511-.634.184-1.186.184m8.675 0q-.573 0-1.043-.123-.45-.122-.798-.368-.348-.245-.594-.614-.225-.368-.347-.86-.103-.49-.103-1.084V2.917q0-.245.041-.368.061-.143.184-.204.143-.062.348-.062.225 0 .348.062.143.04.205.164.06.122.06.347v10.742q0 .45.103.798.103.328.307.573.225.225.532.348.328.102.737.102.245 0 .368.062.143.04.205.184.061.123.061.348t-.061.368q-.062.123-.205.164-.123.061-.348.061m7.12.082q-1.022 0-1.923-.389-.9-.388-1.596-1.064-.675-.675-1.064-1.575-.388-.9-.388-1.924 0-1.043.368-1.923.389-.9 1.064-1.575.696-.696 1.575-1.064.9-.39 1.924-.39 1.37 0 2.476.656 1.104.634 1.76 1.76.654 1.104.654 2.516 0 1.064-.389 1.964-.368.9-1.043 1.576-.655.675-1.535 1.064-.88.368-1.882.368m-.04-1.105q1.043 0 1.882-.511.86-.532 1.35-1.412.512-.9.512-1.985 0-1.105-.49-1.964-.492-.86-1.351-1.33-.84-.491-1.924-.491-1.064 0-1.923.511-.839.491-1.35 1.35-.492.86-.492 1.945 0 1.104.492 1.984.511.88 1.37 1.392t1.924.511m10.906 1.044q-.88 0-1.617-.287-.737-.307-1.289-.86-.552-.572-.86-1.37-.306-.818-.306-1.862V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.348-.062.225 0 .347.062.144.061.185.204.061.123.061.348v4.808q0 .983.327 1.74.328.736.983 1.145.654.41 1.636.41 1.003 0 1.72-.41.715-.409 1.104-1.145.389-.757.389-1.74V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.368-.062.205 0 .328.062.122.061.163.204.062.123.062.348v8.594q0 .184-.062.307-.04.122-.163.163-.123.062-.348.062-.144 0-.246-.041-.102-.02-.164-.082-.06-.061-.102-.164-.02-.102-.02-.245v-1.207q-.266.347-.594.675-.327.307-.736.573-.39.245-.88.388-.491.144-1.085.144m11.478-.02q-.941 0-1.78-.349-.818-.347-1.432-1.002t-.962-1.555q-.348-.921-.348-2.026 0-1.084.348-1.964.348-.9.962-1.535.614-.655 1.432-1.002.839-.348 1.78-.348 1.003 0 1.842.348.839.347 1.452 1.002.614.655.942 1.555.347.9.347 2.005t-.347 2.006q-.328.88-.942 1.534-.613.635-1.452.982-.84.348-1.842.348m.184-1.085q1.023 0 1.821-.47.798-.492 1.269-1.33.47-.86.47-1.986 0-1.145-.47-2.005t-1.29-1.33q-.797-.49-1.8-.49-1.002 0-1.82.49-.82.47-1.29 1.33t-.47 2.005q0 1.126.47 1.985.47.84 1.29 1.33.818.47 1.82.47m4.215 1.084q-.327 0-.47-.143-.123-.143-.123-.47V2.958q0-.246.04-.369.062-.143.185-.204.143-.062.348-.062.225 0 .348.062.122.061.184.204.061.123.061.348v13.034q0 .225-.061.368-.041.143-.164.205-.123.061-.348.061\" />\n          <path d=\"M68.888 21.35q-.24 0-.408-.072-.144-.072-.216-.24-.048-.144-.048-.408V.696q0-.264.048-.408.072-.168.216-.216.168-.072.432-.072t.408.072.192.24q.072.144.072.432v19.91q0 .264-.072.408-.048.168-.216.216-.144.072-.408.072\" />\n          <path d=\"M5.606 16.685q-1.002 0-1.739-.41-.736-.409-1.207-1.104-.47-.717-.696-1.617t-.204-1.903q.02-.982.245-1.903.246-.92.716-1.636.47-.717 1.187-1.126.716-.43 1.698-.43.9 0 1.74.43.859.43 1.534 1.166.675.717 1.064 1.637.389.921.389 1.903 0 1.023-.389 1.944-.389.9-1.064 1.596-.655.675-1.514 1.064-.839.389-1.76.389m-3.785 4.583q-.9 0-1.289-.266t-.47-.675Q0 19.917 0 19.467V8.357q0-.45.061-.86.082-.409.45-.675.39-.286 1.29-.286.716 0 1.186.205.47.184.676.593v12.113q.04.45-.062.86-.082.429-.47.695-.39.266-1.31.266m3.294-8.062q.41 0 .737-.204.348-.205.552-.532.205-.348.205-.737 0-.41-.205-.736-.184-.348-.532-.532-.327-.205-.736-.205-.41 0-.757.205-.328.204-.532.532-.184.327-.184.736 0 .389.204.737.205.327.532.532.328.204.716.204m7.878 3.724q-.92 0-1.31-.266-.388-.286-.47-.716-.062-.45-.062-.9v-6.67q0-.471.082-.88.082-.43.47-.696.39-.266 1.31-.266.9 0 1.269.246.389.245.47.552.082.286.082.45l-.327-.123.164-.184q.163-.184.47-.43.328-.245.757-.43.45-.183 1.003-.183.184 0 .45.04.286.021.593.103.328.082.594.245.286.164.45.43.184.266.184.655 0 1.023-.45 1.74-.43.715-1.146.715-.389 0-.552-.04-.164-.062-.246-.103-.061-.061-.164-.102-.081-.062-.327-.062-.307 0-.573.082-.245.062-.45.205-.184.143-.286.348-.103.184-.103.409v3.99q0 .45-.082.88-.081.43-.47.695-.389.266-1.33.266m8.941 0q-.92 0-1.31-.266-.388-.286-.47-.716-.061-.43-.061-.88v-6.67q0-.45.082-.86.081-.43.47-.695.389-.287 1.31-.287.92 0 1.289.287.389.266.47.696.082.43.082.9v6.65q0 .45-.082.88-.081.43-.47.695-.368.266-1.31.266m0-11.376q-.92 0-1.31-.266-.388-.287-.47-.716-.061-.43-.061-.88 0-.47.082-.88.081-.43.47-.696t1.31-.266 1.289.287q.389.266.47.696.082.43.082.88t-.082.88-.47.695q-.368.266-1.31.266m8.758 11.376q-1.085 0-1.883-.245-.798-.246-1.33-.757-.511-.512-.777-1.29-.246-.777-.246-1.82v-7.94q0-.47.082-.859.082-.409.47-.654.39-.266 1.31-.266.9 0 1.29.286.388.266.47.696t.082.88v7.672q0 .226.02.39.041.142.102.224.062.082.164.123t.266.041q.532 0 .941.082.43.082.655.43.246.347.246 1.166 0 .92-.287 1.31-.286.388-.716.45-.43.081-.86.081M26.026 6.66h2.394l2.824-.103q.45 0 .86.082.43.082.695.47.287.39.287 1.33 0 .86-.287 1.249-.266.389-.696.491-.409.102-.88.102l-2.598-.061h-2.68q-.696-.04-.962-.45t-.266-1.35q0-.88.327-1.31.328-.45.982-.45m12.523 10.23q-.982 0-1.842-.388-.839-.39-1.473-1.085-.634-.716-.982-1.657t-.348-2.046V8.378q0-.471.082-.88.082-.43.47-.696.39-.286 1.31-.286.941 0 1.31.286.388.266.47.696t.082.9v3.315q0 .47.164.798.184.327.511.511.348.164.798.164t.778-.184.511-.512.184-.777V8.357q0-.47.082-.88.082-.43.47-.695.39-.266 1.31-.266.942 0 1.31.286.389.266.47.696.082.43.082.88v6.731q0 .45-.082.86-.081.409-.47.675t-1.31.266q-.675 0-1.064-.164-.368-.143-.532-.368-.163-.246-.204-.47-.02-.246-.02-.41l.347.102-.164.205q-.163.184-.49.45-.308.246-.758.45-.43.184-1.002.184m8.818.02q-.92 0-1.31-.265-.388-.287-.47-.716-.061-.43-.061-.88V8.316q0-.45.082-.86.082-.408.47-.654.39-.266 1.31-.266.839 0 1.207.225.389.205.491.512.103.307.082.573 0 .245.041.306l-.164.123q-.02-.307.225-.614.266-.307.676-.552.43-.266.9-.41.49-.163.92-.163 1.024 0 1.801.389.778.389 1.29 1.105.531.695.797 1.637.266.94.266 2.046v3.355q0 .45-.082.88-.081.43-.47.696-.369.266-1.31.266t-1.33-.266q-.388-.287-.47-.716-.082-.43-.082-.88v-3.335q0-.47-.184-.798-.164-.328-.491-.491-.307-.185-.778-.185-.45 0-.798.185-.327.184-.511.511-.185.328-.185.778v3.355q0 .45-.081.88-.082.43-.471.696-.368.266-1.31.266m14.078.021q-.942 0-1.658-.082-.695-.081-1.186-.327-.491-.245-.798-.696-.307-.47-.45-1.207-.144-.757-.144-1.882V3.67q0-.47.082-.88.082-.429.47-.695.39-.286 1.31-.286.921 0 1.29.286.388.266.47.696t.082.9V13q.02.164.061.246.061.082.164.103h.327q.266 0 .553.081.307.062.511.43.225.348.225 1.228 0 .92-.225 1.31-.204.388-.511.45-.307.081-.573.081\" />\n        </svg>\n      </div>\n      <div class=\"box-body\">\n        <div id=\"auth-buttons\">\n        </div>\n        <div id=\"auth-secondary\">\n          <h5 id=\"secondary-label\"></h5>\n          <div id=\"secondary-alert\" class=\"pt-callout\"></div>\n          <button id=\"secondary-push\" class=\"pt-button auth-button\">Push</button>\n          <button id=\"secondary-phone\" class=\"pt-button auth-button\">Call Me</button>\n          <button id=\"secondary-sms\" class=\"pt-button auth-button\">Text Me</button>\n          <input id=\"secondary-passcode\" class=\"pt-input\"\n            autocapitalize=\"off\" spellcheck=\"false\" autocomplete=\"off\"\n            type=\"text\" placeholder=\"Passcode\"/>\n          <button id=\"secondary-submit\" class=\"pt-button auth-button\">Submit</button>\n          <button id=\"secondary-cancel\" class=\"pt-button auth-button\">Cancel</button>\n        </div>\n        <div id=\"auth-device\">\n          <h5 id=\"device-label\"></h5>\n          <div id=\"device-alert\" class=\"pt-callout\"></div>\n          <form id=\"device-form\">\n            <label class=\"pt-label\">\n              <input id=\"device-name\" class=\"pt-input\" autocomplete=\"off\"\n                type=\"text\" placeholder=\"Device name\" autofocus/>\n            </label>\n            <input id=\"device-submit\" class=\"pt-button\" disabled\n              type=\"submit\" value=\"Register Device\"/>\n          </form>\n        </div>\n        <div id=\"auth-local\">\n          <div id=\"alert\" class=\"pt-callout\"></div>\n          <form id=\"login-form\">\n            <label class=\"pt-label\">\n              <input id=\"username\" class=\"pt-input\"\n                autocapitalize=\"off\" spellcheck=\"false\" autocomplete=\"off\"\n                type=\"text\" placeholder=\"Username\" autofocus/>\n            </label>\n            <label class=\"pt-label\">\n              <input id=\"password\" class=\"pt-input\" autocomplete=\"off\"\n                type=\"password\" placeholder=\"Password\"/>\n            </label>\n            <input id=\"submit\" class=\"pt-button\" type=\"submit\" value=\"Login\"/>\n          </form>\n          <button class=\"pt-button auth-cancel\">Cancel</button>\n        </div>\n      </div>\n    </div>\n    <script>\n      var __defProp = Object.defineProperty;\n      var __export = (target, all) => {\n        for (var name in all)\n          __defProp(target, name, { get: all[name], enumerable: true });\n      };\n      var __async = (__this, __arguments, generator) => {\n        return new Promise((resolve, reject) => {\n          var fulfilled = (value) => {\n            try {\n              step(generator.next(value));\n            } catch (e) {\n              reject(e);\n            }\n          };\n          var rejected = (value) => {\n            try {\n              step(generator.throw(value));\n            } catch (e) {\n              reject(e);\n            }\n          };\n          var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);\n          step((generator = generator.apply(__this, __arguments)).next());\n        });\n      };\n\n      // src/webauthn-json/index.ts\n      var webauthn_json_exports = {};\n      __export(webauthn_json_exports, {\n        create: () => create,\n        get: () => get,\n        schema: () => schema,\n        supported: () => supported\n      });\n\n      // src/webauthn-json/base64url.ts\n      function base64urlToBuffer(baseurl64String) {\n        const padding = \"==\".slice(0, (4 - baseurl64String.length % 4) % 4);\n        const base64String = baseurl64String.replace(/-/g, \"+\").replace(/_/g, \"/\") + padding;\n        const str = atob(base64String);\n        const buffer = new ArrayBuffer(str.length);\n        const byteView = new Uint8Array(buffer);\n        for (let i = 0; i < str.length; i++) {\n          byteView[i] = str.charCodeAt(i);\n        }\n        return buffer;\n      }\n      function bufferToBase64url(buffer) {\n        const byteView = new Uint8Array(buffer);\n        let str = \"\";\n        for (const charCode of byteView) {\n          str += String.fromCharCode(charCode);\n        }\n        const base64String = btoa(str);\n        const base64urlString = base64String.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=/g, \"\");\n        return base64urlString;\n      }\n\n      // src/webauthn-json/convert.ts\n      var copyValue = \"copy\";\n      var convertValue = \"convert\";\n      function convert(conversionFn, schema2, input) {\n        if (schema2 === copyValue) {\n          return input;\n        }\n        if (schema2 === convertValue) {\n          return conversionFn(input);\n        }\n        if (schema2 instanceof Array) {\n          return input.map((v) => convert(conversionFn, schema2[0], v));\n        }\n        if (schema2 instanceof Object) {\n          const output = {};\n          for (const [key, schemaField] of Object.entries(schema2)) {\n            if (schemaField.deriveFn) {\n              const v = schemaField.deriveFn(input);\n              if (v !== void 0) {\n                input[key] = v;\n              }\n            }\n            if (!(key in input)) {\n              if (schemaField.required) {\n                throw new Error(`Missing key: ${key}`);\n              }\n              continue;\n            }\n            if (input[key] == null) {\n              output[key] = null;\n              continue;\n            }\n            output[key] = convert(conversionFn, schemaField.schema, input[key]);\n          }\n          return output;\n        }\n      }\n      function derived(schema2, deriveFn) {\n        return {\n          required: true,\n          schema: schema2,\n          deriveFn\n        };\n      }\n      function required(schema2) {\n        return {\n          required: true,\n          schema: schema2\n        };\n      }\n      function optional(schema2) {\n        return {\n          required: false,\n          schema: schema2\n        };\n      }\n\n      // src/webauthn-json/basic/schema.ts\n      var publicKeyCredentialDescriptorSchema = {\n        type: required(copyValue),\n        id: required(convertValue),\n        transports: optional(copyValue)\n      };\n      var simplifiedExtensionsSchema = {\n        appid: optional(copyValue),\n        appidExclude: optional(copyValue),\n        credProps: optional(copyValue)\n      };\n      var simplifiedClientExtensionResultsSchema = {\n        appid: optional(copyValue),\n        appidExclude: optional(copyValue),\n        credProps: optional(copyValue)\n      };\n      var credentialCreationOptions = {\n        publicKey: required({\n          rp: required(copyValue),\n          user: required({\n            id: required(convertValue),\n            name: required(copyValue),\n            displayName: required(copyValue)\n          }),\n          challenge: required(convertValue),\n          pubKeyCredParams: required(copyValue),\n          timeout: optional(copyValue),\n          excludeCredentials: optional([publicKeyCredentialDescriptorSchema]),\n          authenticatorSelection: optional(copyValue),\n          attestation: optional(copyValue),\n          extensions: optional(simplifiedExtensionsSchema)\n        }),\n        signal: optional(copyValue)\n      };\n      var publicKeyCredentialWithAttestation = {\n        type: required(copyValue),\n        id: required(copyValue),\n        rawId: required(convertValue),\n        response: required({\n          clientDataJSON: required(convertValue),\n          attestationObject: required(convertValue),\n          transports: derived(copyValue, (response) => {\n            var _a;\n            return ((_a = response.getTransports) == null ? void 0 : _a.call(response)) || [];\n          })\n        }),\n        clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults())\n      };\n      var credentialRequestOptions = {\n        mediation: optional(copyValue),\n        publicKey: required({\n          challenge: required(convertValue),\n          timeout: optional(copyValue),\n          rpId: optional(copyValue),\n          allowCredentials: optional([publicKeyCredentialDescriptorSchema]),\n          userVerification: optional(copyValue),\n          extensions: optional(simplifiedExtensionsSchema)\n        }),\n        signal: optional(copyValue)\n      };\n      var publicKeyCredentialWithAssertion = {\n        type: required(copyValue),\n        id: required(copyValue),\n        rawId: required(convertValue),\n        response: required({\n          clientDataJSON: required(convertValue),\n          authenticatorData: required(convertValue),\n          signature: required(convertValue),\n          userHandle: required(convertValue)\n        }),\n        clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults())\n      };\n      var schema = {\n        credentialCreationOptions,\n        publicKeyCredentialWithAttestation,\n        credentialRequestOptions,\n        publicKeyCredentialWithAssertion\n      };\n\n      // src/webauthn-json/basic/api.ts\n      function createRequestFromJSON(requestJSON) {\n        return convert(base64urlToBuffer, credentialCreationOptions, requestJSON);\n      }\n      function createResponseToJSON(credential) {\n        return convert(bufferToBase64url, publicKeyCredentialWithAttestation, credential);\n      }\n      function create(requestJSON) {\n        return __async(this, null, function* () {\n          const credential = yield navigator.credentials.create(createRequestFromJSON(requestJSON));\n          return createResponseToJSON(credential);\n        });\n      }\n      function getRequestFromJSON(requestJSON) {\n        return convert(base64urlToBuffer, credentialRequestOptions, requestJSON);\n      }\n      function getResponseToJSON(credential) {\n        return convert(bufferToBase64url, publicKeyCredentialWithAssertion, credential);\n      }\n      function get(requestJSON) {\n        return __async(this, null, function* () {\n          const credential = yield navigator.credentials.get(getRequestFromJSON(requestJSON));\n          return getResponseToJSON(credential);\n        });\n      }\n\n      // src/webauthn-json/basic/supported.ts\n      function supported() {\n        return !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get && window.PublicKeyCredential);\n      }\n\n      // src/webauthn-json/browser-global.ts\n      globalThis.webauthnJSON = webauthn_json_exports;\n      //# sourceMappingURL=webauthn-json.browser-global.js.map\n    </script>\n    <script type=\"text/javascript\">\n      var i;\n      var state;\n      var authButtons = document.getElementById('auth-buttons');\n      var authLocal = document.getElementById('auth-local');\n      var alertElm = document.getElementById('alert');\n      var usernameElm = document.getElementById('username');\n      var passwordElm = document.getElementById('password');\n      var submitElm = document.getElementById('submit');\n\n      var secondaryToken = null;\n      var secondaryLabl = '';\n      var secondaryFactors = {\n        push: false,\n        phone: false,\n        sms: false,\n        passcode: false,\n        device: false,\n        device_register: false\n      };\n      var secondaryAuth = document.getElementById('auth-secondary');\n      var secondaryLabel = document.getElementById('secondary-label');\n      var secondaryAlert = document.getElementById('secondary-alert');\n      var secondaryPush = document.getElementById('secondary-push');\n      var secondaryPhone = document.getElementById('secondary-phone');\n      var secondarySms = document.getElementById('secondary-sms');\n      var secondaryPasscode = document.getElementById('secondary-passcode');\n      var secondarySubmit = document.getElementById('secondary-submit');\n      var secondaryCancel = document.getElementById('secondary-cancel');\n\n      var queryVals = window.location.search.substring(1).split('&');\n      for (i = 0; i < queryVals.length; i++) {\n        var queryKeyVal = queryVals[i].split('=');\n        if (queryKeyVal[0] === 'secondary') {\n          secondaryToken = queryKeyVal[1];\n        } else if (queryKeyVal[0] === 'label') {\n          secondaryLabl = decodeURIComponent(queryKeyVal[1]);\n        } else if (queryKeyVal[0] === 'factors') {\n          if (queryKeyVal[1].indexOf('push') !== -1) {\n            secondaryFactors.push = true;\n          }\n          if (queryKeyVal[1].indexOf('phone') !== -1) {\n            secondaryFactors.phone = true;\n          }\n          if (queryKeyVal[1].indexOf('sms') !== -1) {\n            secondaryFactors.sms = true;\n          }\n          if (queryKeyVal[1].indexOf('passcode') !== -1) {\n            secondaryFactors.passcode = true;\n          }\n          if (queryKeyVal[1].indexOf('device') !== -1) {\n            secondaryFactors.device = true;\n          }\n          if (queryKeyVal[1].indexOf('device_register') !== -1) {\n            secondaryFactors.device_register = true;\n          }\n        }\n      }\n\n      var deviceAuthElm = document.getElementById('auth-device');\n      var deviceLabelElm = document.getElementById('device-label');\n      var deviceFormElm = document.getElementById('device-form');\n      var deviceNameElm = document.getElementById('device-name');\n      var deviceSubmitElm = document.getElementById('device-submit');\n      var deviceAlertElm = document.getElementById('device-alert');\n\n      var deviceRegisterPost = function(cred) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              setDeviceAlert();\n              window.location = '/';\n            } else if (xmlhttp.status === 202) {\n              setDeviceAlert();\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setDeviceAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/webauthn/register' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify(cred));\n      }\n\n      var deviceRegister = function() {\n        setDeviceAlert('WebAuthn device registration required', 'warning');\n\n        deviceNameElm.onkeyup = function(evt) {\n          deviceSubmitElm.disabled = !evt.target.value;\n        };\n\n        deviceFormElm.onsubmit = function(evt) {\n          if (evt) {\n            evt.preventDefault ? evt.preventDefault() :\n              evt.returnValue = false;\n          }\n\n          deviceSubmitElm.disabled = true;\n          deviceNameElm.disabled = true;\n\n          var xmlhttp = new XMLHttpRequest();\n\n          xmlhttp.onreadystatechange = function() {\n            if (xmlhttp.readyState === 4) {\n              if (xmlhttp.status === 200) {\n                setDeviceAlert(\n                  'Insert your security key and tap the button', 'info');\n\n                var resp = JSON.parse(xmlhttp.responseText);\n                create(resp).then(function (cred) {\n                  cred.name = deviceNameElm.value;\n                  cred.token = secondaryToken;\n                  deviceRegisterPost(cred);\n                }).catch(function (err) {\n                  setDeviceAlert(err, 'danger');\n                });\n              } else {\n                var errorMsg;\n\n                try {\n                  errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n                } catch(error) {\n                  errorMsg = 'Server error occurred';\n                }\n\n                setDeviceAlert(errorMsg, 'danger');\n              }\n            }\n          };\n\n          xmlhttp.open(\n            'GET',\n            '/auth/webauthn/register?token=' + secondaryToken,\n            true\n          );\n          xmlhttp.send();\n        }\n      };\n\n      var deviceRespond = function(cred) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              window.location = '/';\n            } else if (xmlhttp.status === 201) {\n              setSecondaryAlert();\n\n              var data = JSON.parse(xmlhttp.response);\n              secondaryToken = data['token'];\n              secondaryLabl = data['label'];\n              secondaryFactors.push = data['push'];\n              secondaryFactors.phone = data['phone'];\n              secondaryFactors.sms = data['sms'];\n              secondaryFactors.passcode = data['passcode'];\n              secondaryFactors.device = data['device'];\n              secondaryFactors.device_register = data['device_register'];\n\n              secondaryLabel.innerText = secondaryLabl;\n              deviceLabelElm.innerText = secondaryLabl;\n              secondaryPush.style.display = secondaryFactors.push ?\n                'block' : 'none';\n              secondaryPhone.style.display = secondaryFactors.phone ?\n                'block' : 'none';\n              secondarySms.style.display = secondaryFactors.sms ?\n                'block' : 'none';\n              secondaryPasscode.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              secondarySubmit.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              authLocal.style.display = 'none';\n\n              if (secondaryFactors.device_register) {\n                deviceAuthElm.style.display = 'block';\n                deviceRegister();\n                return;\n              }\n\n              secondaryAuth.style.display = 'block';\n              if (secondaryFactors.device) {\n                deviceAuth();\n              }\n            } else if (xmlhttp.status === 202) {\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setSecondaryAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/webauthn/respond' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify(cred));\n      };\n\n      var deviceAuth = function() {\n        setSecondaryAlert(\n          'Insert your security key and tap the button.', 'info');\n\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              var options = JSON.parse(xmlhttp.responseText);\n\n              get(options).then(function(cred) {\n                cred.token = secondaryToken;\n                deviceRespond(cred);\n              }).catch(function(err) {\n                var errorMsg = 'Webauthn error ' + err;\n                setSecondaryAlert(errorMsg, 'danger');\n              });\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setSecondaryAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'GET',\n          '/auth/webauthn/request?token=' + secondaryToken,\n          true\n        );\n        xmlhttp.send();\n      };\n\n      var loadState = function(redirect) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              state = JSON.parse(xmlhttp.responseText);\n\n              var buttons = '';\n\n              var provider;\n              for (i = 0; i < state.providers.length; i++) {\n                provider = state.providers[i];\n\n                if (provider.id === 'demo') {\n                  usernameElm.value = 'demo';\n                  passwordElm.value = 'demo';\n                  continue;\n                }\n\n                if (provider.type === 'local') {\n                  buttons += '<button id=\"auth-local-btn\" ' +\n                    'class=\"pt-button auth-button\">Username/Password</button>';\n                  continue;\n                }\n\n                buttons += '<button id=\"' + provider.id + '\" ' +\n                  'class=\"pt-button auth-button auth-' + provider.type + '\"' +\n                  '>' + provider.label + '</button>';\n              }\n\n              authButtons.innerHTML = buttons;\n\n              bindState();\n\n              // if (redirect && state.providers.length === 1) {\n              //   provider = state.providers[0];\n              //\n              //   if (provider.type === 'local') {\n              //     onAuthLocal();\n              //   } else {\n              //     onAuthProvider(provider.id);\n              //   }\n              // }\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open('GET', '/auth/state', true);\n        xmlhttp.send();\n\n        return false;\n      };\n\n      var onAuthLocal = function () {\n        authButtons.style.display = 'none';\n        authLocal.style.display = 'block';\n      };\n\n      var onAuthProvider = function (providerId) {\n        var location = '/auth/request';\n\n        if (window.location.search !== '') {\n          location += window.location.search + '&';\n        } else {\n          location += '?';\n        }\n\n        location += 'id=' + providerId;\n\n        window.location = location;\n      };\n\n      var bindState = function() {\n        for (i = 0; i < state.providers.length; i++) {\n          if (state.providers[i].type === 'local') {\n            document.getElementById('auth-local-btn').onclick = onAuthLocal;\n            continue;\n          }\n\n          (function(provider) {\n            document.getElementById(provider.id).onclick = function() {\n              onAuthProvider(provider.id);\n            };\n          })(state.providers[i]);\n        }\n      };\n\n      var flashAlert = function() {\n        var alertClass = alertElm.className;\n        var alertClassFlash = alertClass + ' flash';\n\n        alertElm.className = alertClassFlash;\n        setTimeout(function() {\n          alertElm.className = alertClass;\n          setTimeout(function() {\n            alertElm.className = alertClassFlash;\n            setTimeout(function() {\n              alertElm.className = alertClass;\n            }.bind(this), 150);\n          }.bind(this), 150);\n        }.bind(this), 150);\n      };\n\n      var setAlert = function(alert, intent) {\n        intent = intent || 'default';\n\n        if (alert) {\n          alertElm.className = 'pt-callout pt-intent-' + intent;\n          alertElm.innerHTML = alert;\n          if (alertElm.style.display === 'block') {\n            flashAlert(alertElm);\n          } else {\n            alertElm.style.display = 'block';\n          }\n        } else {\n          alertElm.style.display = 'none';\n        }\n      };\n\n      document.getElementById('login-form').onsubmit = function(evt) {\n        if (evt) {\n          evt.preventDefault ? evt.preventDefault() : evt.returnValue = false;\n        }\n\n        submitElm.disabled = true;\n        for (i = 0; i < authCancels.length; i++) {\n          authCancels[i].disabled = true;\n        }\n\n        var username = usernameElm.value;\n        var password = passwordElm.value;\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            setTimeout(function() {\n              submitElm.disabled = false;\n              for (i = 0; i < authCancels.length; i++) {\n                authCancels[i].disabled = false;\n              }\n            }, 500);\n\n            if (xmlhttp.status === 200) {\n              setAlert();\n              window.location = '/';\n            } else if (xmlhttp.status === 201) {\n              setAlert();\n\n              var data = JSON.parse(xmlhttp.response);\n              secondaryToken = data['token'];\n              secondaryLabl = data['label'];\n              secondaryFactors.push = data['push'];\n              secondaryFactors.phone = data['phone'];\n              secondaryFactors.sms = data['sms'];\n              secondaryFactors.passcode = data['passcode'];\n              secondaryFactors.device = data['device'];\n              secondaryFactors.device_register = data['device_register'];\n\n              secondaryLabel.innerText = secondaryLabl;\n              deviceLabelElm.innerText = secondaryLabl;\n              secondaryPush.style.display = secondaryFactors.push ?\n                'block' : 'none';\n              secondaryPhone.style.display = secondaryFactors.phone ?\n                'block' : 'none';\n              secondarySms.style.display = secondaryFactors.sms ?\n                'block' : 'none';\n              secondaryPasscode.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              secondarySubmit.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              authLocal.style.display = 'none';\n\n              if (secondaryFactors.device_register) {\n                deviceAuthElm.style.display = 'block';\n                deviceRegister();\n              } else {\n                secondaryAuth.style.display = 'block';\n                if (secondaryFactors.device) {\n                  deviceAuth();\n                }\n              }\n            } else if (xmlhttp.status === 202) {\n              setAlert();\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/session' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify({\n          'username': username,\n          'password': password\n        }));\n\n        return false;\n      };\n\n      var flashSecondaryAlert = function() {\n        var alertClass = secondaryAlert.className;\n        var alertClassFlash = alertClass + ' flash';\n\n        secondaryAlert.className = alertClassFlash;\n        setTimeout(function() {\n          secondaryAlert.className = alertClass;\n          setTimeout(function() {\n            secondaryAlert.className = alertClassFlash;\n            setTimeout(function() {\n              secondaryAlert.className = alertClass;\n            }.bind(this), 150);\n          }.bind(this), 150);\n        }.bind(this), 150);\n      };\n\n      var setSecondaryAlert = function(alert, intent) {\n        intent = intent || 'default';\n\n        if (alert) {\n          secondaryAlert.className = 'pt-callout pt-intent-' + intent;\n          secondaryAlert.innerHTML = alert;\n          if (secondaryAlert.style.display === 'block') {\n            flashSecondaryAlert(secondaryAlert);\n          } else {\n            secondaryAlert.style.display = 'block';\n          }\n        } else {\n          secondaryAlert.style.display = 'none';\n        }\n      };\n\n      var flashDeviceAlert = function() {\n        var alertClass = deviceAlertElm.className;\n        var alertClassFlash = alertClass + ' flash';\n\n        deviceAlertElm.className = alertClassFlash;\n        setTimeout(function() {\n          deviceAlertElm.className = alertClass;\n          setTimeout(function() {\n            deviceAlertElm.className = alertClassFlash;\n            setTimeout(function() {\n              deviceAlertElm.className = alertClass;\n            }.bind(this), 150);\n          }.bind(this), 150);\n        }.bind(this), 150);\n      };\n\n      var setDeviceAlert = function(alert, intent) {\n        intent = intent || 'default';\n\n        if (alert) {\n          deviceAlertElm.className = 'pt-callout pt-intent-' + intent;\n          deviceAlertElm.innerHTML = alert;\n          if (deviceAlertElm.style.display === 'block') {\n            flashDeviceAlert(deviceAlertElm);\n          } else {\n            deviceAlertElm.style.display = 'block';\n          }\n        } else {\n          deviceAlertElm.style.display = 'none';\n        }\n      };\n\n      var secondaryChallenge = function(factor, passcode, callback) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              setSecondaryAlert();\n              callback(true);\n              window.location = '/';\n            } else if (xmlhttp.status === 201) {\n              setSecondaryAlert();\n              callback(true);\n\n              var data = JSON.parse(xmlhttp.response);\n              secondaryToken = data['token'];\n              secondaryLabl = data['label'];\n              secondaryFactors.push = data['push'];\n              secondaryFactors.phone = data['phone'];\n              secondaryFactors.sms = data['sms'];\n              secondaryFactors.passcode = data['passcode'];\n              secondaryFactors.device = data['device'];\n              secondaryFactors.device_register = data['device_register'];\n\n              deviceLabelElm.innerText = secondaryLabl;\n\n              secondaryAuth.style.display = 'none';\n              deviceAuthElm.style.display = 'block';\n\n              deviceRegister();\n            } else if (xmlhttp.status === 202) {\n              setSecondaryAlert();\n              callback(true);\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else if (xmlhttp.status === 206 && factor === 'sms') {\n              setSecondaryAlert('Text message sent', 'info');\n              callback(true);\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setSecondaryAlert(errorMsg, 'danger');\n              callback(xmlhttp.status === 401);\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/secondary' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify({\n          'token': secondaryToken,\n          'factor': factor,\n          'passcode': passcode\n        }));\n      };\n\n      secondaryPush.onclick = function() {\n        secondaryPush.disabled = true;\n        secondaryChallenge('push', null, function(success) {\n          if (!success) {\n            secondaryPush.disabled = false;\n          }\n        });\n      };\n      secondaryPhone.onclick = function() {\n        secondaryPhone.disabled = true;\n        secondaryChallenge('phone', null, function(success) {\n          if (!success) {\n            secondaryPhone.disabled = false;\n          }\n        });\n      };\n      secondarySms.onclick = function() {\n        secondarySms.disabled = true;\n        secondaryChallenge('sms', null, function(success) {\n          if (!success) {\n            secondarySms.disabled = false;\n          }\n        });\n      };\n      var secondarySub = function() {\n        var val = secondaryPasscode.value;\n        if (!val) {\n          return;\n        }\n\n        secondaryPasscode.disabled = true;\n        secondarySubmit.disabled = true;\n        secondaryChallenge('passcode', val, function() {\n          secondaryPasscode.disabled = false;\n          secondarySubmit.disabled = false;\n        });\n      };\n      secondaryPasscode.onkeypress = function(evt) {\n        if (evt.keyCode === 13) {\n          secondarySub();\n        }\n      };\n      secondarySubmit.onclick = secondarySub;\n      secondaryCancel.onclick = function() {\n        var query = [];\n\n        var queryVals = window.location.search.substring(1).split('&');\n        for (i = 0; i < queryVals.length; i++) {\n          var queryKeyVal = queryVals[i].split('=');\n          if (queryKeyVal[0] === 'secondary' ||\n              queryKeyVal[0] === 'label' ||\n              queryKeyVal[0] === 'factors') {\n            continue;\n          }\n          query.push(queryVals[i]);\n        }\n\n        var location = '/login';\n        if (query.length) {\n          location += '?' + query.join('&');\n        }\n        window.location = location;\n      };\n\n      var authCancels = document.querySelectorAll('.auth-cancel');\n      for (i = 0; i < authCancels.length; i++) {\n        authCancels[i].onclick = function() {\n          authLocal.style.display = 'none';\n          authButtons.style.display = 'block';\n        };\n      }\n\n      if (secondaryToken) {\n        primary = false;\n        secondaryLabel.innerText = secondaryLabl;\n        deviceLabelElm.innerText = secondaryLabl;\n        secondaryPush.style.display = secondaryFactors.push ?\n          'block' : 'none';\n        secondaryPhone.style.display = secondaryFactors.phone ?\n          'block' : 'none';\n        secondarySms.style.display = secondaryFactors.sms ?\n          'block' : 'none';\n        secondaryPasscode.style.display = secondaryFactors.passcode ?\n          'block' : 'none';\n        secondarySubmit.style.display = secondaryFactors.passcode ?\n          'block' : 'none';\n        authButtons.style.display = 'none';\n\n        if (secondaryFactors.device_register) {\n          deviceAuthElm.style.display = 'block';\n          deviceRegister();\n        } else {\n          secondaryAuth.style.display = 'block';\n          if (secondaryFactors.device) {\n            deviceAuth();\n          }\n        }\n\n        loadState(false);\n      } else {\n        loadState(true);\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/dist/static/blueprint-datetime2.css",
    "content": ".rdp{\n  --rdp-cell-size:40px;\n  --rdp-caption-font-size:18px;\n  --rdp-accent-color:#0000ff;\n  --rdp-background-color:#e7edff;\n  --rdp-accent-color-dark:#3003e1;\n  --rdp-background-color-dark:#180270;\n  --rdp-outline:2px solid var(--rdp-accent-color);\n  --rdp-outline-selected:3px solid var(--rdp-accent-color);\n  --rdp-selected-color:#fff;\n  margin:1em;\n}\n.rdp-vhidden{\n  box-sizing:border-box;\n  padding:0;\n  margin:0;\n  background:transparent;\n  border:0;\n  -moz-appearance:none;\n  -webkit-appearance:none;\n  appearance:none;\n  position:absolute !important;\n  top:0;\n  width:1px !important;\n  height:1px !important;\n  padding:0 !important;\n  overflow:hidden !important;\n  clip:rect(1px, 1px, 1px, 1px) !important;\n  border:0 !important;\n}\n.rdp-button_reset{\n  appearance:none;\n  position:relative;\n  margin:0;\n  padding:0;\n  cursor:default;\n  color:inherit;\n  background:none;\n  font:inherit;\n  -moz-appearance:none;\n  -webkit-appearance:none;\n}\n\n.rdp-button_reset:focus-visible{\n  outline:none;\n}\n\n.rdp-button{\n  border:2px solid transparent;\n}\n\n.rdp-button[disabled]:not(.rdp-day_selected){\n  opacity:0.25;\n}\n\n.rdp-button:not([disabled]){\n  cursor:pointer;\n}\n\n.rdp-button:focus-visible:not([disabled]){\n  color:inherit;\n  background-color:var(--rdp-background-color);\n  border:var(--rdp-outline);\n}\n\n.rdp-button:hover:not([disabled]):not(.rdp-day_selected){\n  background-color:var(--rdp-background-color);\n}\n\n.rdp-months{\n  display:flex;\n}\n\n.rdp-month{\n  margin:0 1em;\n}\n\n.rdp-month:first-child{\n  margin-left:0;\n}\n\n.rdp-month:last-child{\n  margin-right:0;\n}\n\n.rdp-table{\n  margin:0;\n  max-width:calc(var(--rdp-cell-size) * 7);\n  border-collapse:collapse;\n}\n\n.rdp-with_weeknumber .rdp-table{\n  max-width:calc(var(--rdp-cell-size) * 8);\n  border-collapse:collapse;\n}\n\n.rdp-caption{\n  display:flex;\n  align-items:center;\n  justify-content:space-between;\n  padding:0;\n  text-align:left;\n}\n\n.rdp-multiple_months .rdp-caption{\n  position:relative;\n  display:block;\n  text-align:center;\n}\n\n.rdp-caption_dropdowns{\n  position:relative;\n  display:inline-flex;\n}\n\n.rdp-caption_label{\n  position:relative;\n  z-index:1;\n  display:inline-flex;\n  align-items:center;\n  margin:0;\n  padding:0 0.25em;\n  white-space:nowrap;\n  color:currentColor;\n  border:0;\n  border:2px solid transparent;\n  font-family:inherit;\n  font-size:var(--rdp-caption-font-size);\n  font-weight:bold;\n}\n\n.rdp-nav{\n  white-space:nowrap;\n}\n\n.rdp-multiple_months .rdp-caption_start .rdp-nav{\n  position:absolute;\n  top:50%;\n  left:0;\n  transform:translateY(-50%);\n}\n\n.rdp-multiple_months .rdp-caption_end .rdp-nav{\n  position:absolute;\n  top:50%;\n  right:0;\n  transform:translateY(-50%);\n}\n\n.rdp-nav_button{\n  display:inline-flex;\n  align-items:center;\n  justify-content:center;\n  width:var(--rdp-cell-size);\n  height:var(--rdp-cell-size);\n  padding:0.25em;\n  border-radius:100%;\n}\n.rdp-dropdown_year,\n.rdp-dropdown_month{\n  position:relative;\n  display:inline-flex;\n  align-items:center;\n}\n\n.rdp-dropdown{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  position:absolute;\n  z-index:2;\n  top:0;\n  bottom:0;\n  left:0;\n  width:100%;\n  margin:0;\n  padding:0;\n  cursor:inherit;\n  opacity:0;\n  border:none;\n  background-color:transparent;\n  font-family:inherit;\n  font-size:inherit;\n  line-height:inherit;\n}\n\n.rdp-dropdown[disabled]{\n  opacity:unset;\n  color:unset;\n}\n\n.rdp-dropdown:focus-visible:not([disabled]) + .rdp-caption_label{\n  background-color:var(--rdp-background-color);\n  border:var(--rdp-outline);\n  border-radius:6px;\n}\n\n.rdp-dropdown_icon{\n  margin:0 0 0 5px;\n}\n\n.rdp-head{\n  border:0;\n}\n\n.rdp-head_row,\n.rdp-row{\n  height:100%;\n}\n\n.rdp-head_cell{\n  vertical-align:middle;\n  font-size:0.75em;\n  font-weight:700;\n  text-align:center;\n  height:100%;\n  height:var(--rdp-cell-size);\n  padding:0;\n  text-transform:uppercase;\n}\n\n.rdp-tbody{\n  border:0;\n}\n\n.rdp-tfoot{\n  margin:0.5em;\n}\n\n.rdp-cell{\n  width:var(--rdp-cell-size);\n  height:100%;\n  height:var(--rdp-cell-size);\n  padding:0;\n  text-align:center;\n}\n\n.rdp-weeknumber{\n  font-size:0.75em;\n}\n\n.rdp-weeknumber,\n.rdp-day{\n  display:flex;\n  overflow:hidden;\n  align-items:center;\n  justify-content:center;\n  box-sizing:border-box;\n  width:var(--rdp-cell-size);\n  max-width:var(--rdp-cell-size);\n  height:var(--rdp-cell-size);\n  margin:0;\n  border:2px solid transparent;\n  border-radius:100%;\n}\n\n.rdp-day_today:not(.rdp-day_outside){\n  font-weight:bold;\n}\n\n.rdp-day_selected,\n.rdp-day_selected:focus-visible,\n.rdp-day_selected:hover{\n  color:var(--rdp-selected-color);\n  opacity:1;\n  background-color:var(--rdp-accent-color);\n}\n\n.rdp-day_outside{\n  opacity:0.5;\n}\n\n.rdp-day_selected:focus-visible{\n  outline:var(--rdp-outline);\n  outline-offset:2px;\n  z-index:1;\n}\n\n.rdp:not([dir=rtl]) .rdp-day_range_start:not(.rdp-day_range_end){\n  border-top-right-radius:0;\n  border-bottom-right-radius:0;\n}\n\n.rdp:not([dir=rtl]) .rdp-day_range_end:not(.rdp-day_range_start){\n  border-top-left-radius:0;\n  border-bottom-left-radius:0;\n}\n\n.rdp[dir=rtl] .rdp-day_range_start:not(.rdp-day_range_end){\n  border-top-left-radius:0;\n  border-bottom-left-radius:0;\n}\n\n.rdp[dir=rtl] .rdp-day_range_end:not(.rdp-day_range_start){\n  border-top-right-radius:0;\n  border-bottom-right-radius:0;\n}\n\n.rdp-day_range_end.rdp-day_range_start{\n  border-radius:100%;\n}\n\n.rdp-day_range_middle{\n  border-radius:0;\n}\n\n\n.bp5-datepicker-content .rdp{\n  --rdp-cell-size:30px;\n  --rdp-accent-color:#2d72d2;\n  --rdp-background-color:#ffffff;\n  --rdp-accent-color-dark:#215db0;\n  --rdp-background-color-dark:#2f343c;\n  --rdp-outline:2px solid var(--rdp-accent-color);\n  --rdp-outline-selected:2px solid rgba(0, 0, 0, 75%);\n  margin:0;\n  min-width:auto;\n}\n.bp5-datepicker .rdp{\n  display:inline-block;\n  min-width:210px;\n  position:relative;\n  vertical-align:top;\n}\n.bp5-datepicker .rdp:focus{\n  outline:none;\n}\n.bp5-datepicker .rdp-month{\n  display:flex;\n  flex-direction:column;\n  margin:0 5px;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-datepicker .rdp-month + .bp5-datepicker .rdp-month{\n  margin-left:10px;\n}\n.bp5-datepicker .rdp-caption{\n  border-bottom:solid 1px rgba(17, 20, 24, 0.15);\n  padding-bottom:5px;\n}\n.bp5-datepicker .rdp-table{\n  align-self:center;\n}\n.bp5-datepicker .rdp-head_cell{\n  font-size:inherit;\n  font-weight:600;\n  padding-top:5px;\n  text-decoration:none;\n  text-transform:none;\n}\n.bp5-datepicker .rdp-weeknumber{\n  color:#5f6b7c;\n}\n.bp5-datepicker .rdp-day{\n  border-radius:2px;\n}\n.bp5-datepicker .rdp-day.rdp-day_outside{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-datepicker .rdp-day.rdp-day_today{\n  font-weight:400;\n}\n.bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):hover, .bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):focus{\n  background:rgba(143, 153, 168, 0.15);\n  color:#1c2127;\n}\n.bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-datepicker .rdp-day.rdp-day_disabled{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-datepicker .rdp-day.rdp-day_selected{\n  background-color:#2d72d2;\n  border-radius:2px;\n  color:#ffffff;\n}\n.bp5-datepicker .rdp-day.rdp-day_selected:hover{\n  background-color:#215db0;\n  color:#ffffff;\n}\n.bp5-datepicker .rdp-day.rdp-day_selected:active{\n  background-color:#184a90;\n}\n.bp5-datepicker.bp5-datepicker-highlight-current-day .rdp-day.rdp-day_today{\n  border:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-datepicker.bp5-datepicker-reverse-month-and-year .rdp-caption_dropdowns{\n  flex-direction:row-reverse;\n}\n\n.bp5-datepicker-content{\n  align-items:center;\n  display:flex;\n  flex-direction:column;\n  gap:5px;\n}\n.bp5-datepicker-content > .bp5-divider{\n  margin:0;\n  width:calc(100% - 10px);\n}\n\n.bp5-datepicker-month-select select,\n.bp5-datepicker-year-select select{\n  font-weight:600;\n  padding-left:5px;\n  padding-right:16px;\n}\n.bp5-datepicker-month-select select + .bp5-icon,\n.bp5-datepicker-year-select select + .bp5-icon{\n  right:2px;\n}\n\n.bp5-datepicker-footer{\n  display:flex;\n  justify-content:space-between;\n  width:100%;\n}\n\n.bp5-dark .bp5-datepicker{\n  background:#2f343c;\n}\n.bp5-dark .bp5-datepicker .rdp-week-number{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_outside{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):hover, .bp5-dark .bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):focus{\n  background:rgba(143, 153, 168, 0.15);\n  color:#ffffff;\n}\n.bp5-dark .bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_selected{\n  background-color:#2d72d2;\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_selected:hover{\n  background-color:#215db0;\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_selected:active{\n  background-color:#184a90;\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_disabled{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-datepicker.bp5-datepicker-highlight-current-day .rdp-day.rdp-day_today{\n  border:1px solid rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-datepicker .bp5-datepicker-footer{\n  border-top-color:rgba(17, 20, 24, 0.4);\n}\n\n.bp5-datepicker-timepicker-wrapper{\n  align-items:center;\n  display:flex;\n  flex-direction:column;\n}\n.bp5-datepicker-timepicker-wrapper .bp5-timepicker-arrow-row:empty + .bp5-timepicker-input-row{\n  margin:5px 0;\n}\n.bp5-datepicker-caption.rdp-caption{\n  display:flex;\n  flex-direction:row;\n  justify-content:space-between;\n  margin:0;\n}\n.bp5-datepicker-caption.rdp-caption > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-datepicker-caption.rdp-caption > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-datepicker-caption.rdp-caption .bp5-html-select select{\n  font-weight:600;\n  padding-left:5px;\n  padding-right:16px;\n}\n.bp5-datepicker-caption.rdp-caption .bp5-html-select select + .bp5-icon{\n  right:2px;\n}\n.bp5-datepicker-caption.rdp-caption + .bp5-divider{\n  margin:0;\n}\n.bp5-datepicker-caption.rdp-caption .bp5-datepicker-nav-button-hidden{\n  visibility:hidden;\n}\n\n.bp5-datepicker-month-select{\n  flex-shrink:1;\n}\n\n.bp5-datepicker-year-select{\n  flex-shrink:1;\n  min-width:60px;\n}\n\n.bp5-datepicker-caption-measure{\n  font-weight:600;\n  padding-left:5px;\n}\n.bp5-daterangepicker{\n}\n.bp5-daterangepicker.bp5-daterangepicker-contiguous .rdp{\n  min-width:220px;\n}\n.bp5-daterangepicker.bp5-daterangepicker-single-month .rdp{\n  min-width:210px;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption{\n  display:flex;\n  flex-direction:row;\n  justify-content:space-between;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption_start .rdp-caption{\n  flex-direction:row-reverse;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption_start .rdp-caption::before{\n  content:\"\";\n  height:30px;\n  width:30px;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption_end .rdp-caption{\n  flex-direction:row;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption_end .rdp-caption::before{\n  content:\"\";\n  height:30px;\n  width:30px;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-nav{\n  left:initial;\n  position:initial;\n  top:initial;\n  transform:none;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_outside{\n  visibility:hidden;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_hovered:not(.rdp-day_selected){\n  border-radius:0;\n  color:#215db0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_hovered:not(.rdp-day_selected):not(.rdp-day_range_start, .rdp-day_range_middle, .rdp-day_range_end){\n  background-color:rgba(45, 114, 210, 0.1);\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_range_middle{\n  background-color:rgba(45, 114, 210, 0.1);\n  border-radius:0;\n  color:#215db0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_range_middle:hover{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#215db0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_range_start:not(.rdp-day_range_end, .rdp-day_hovered_end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_range_end:not(.rdp-day_range_start, .rdp_day_hovered_start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_hovered_start:not(.rdp-day_hovered_end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_hovered_end:not(.rdp-day_hovered_start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-daterangepicker.bp5-datepicker-highlight-current-day .rdp-day.rdp-day_today{\n  border:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-daterangepicker.bp5-daterangepicker-reverse-month-and-year.bp5-daterangepicker-contiguous.rdp-caption_dropdowns{\n  flex-direction:row-reverse;\n}\n\n.bp5-daterangepicker-timepickers.bp5-daterangepicker-timepickers-stacked{\n  align-items:center;\n  flex-direction:column;\n}\n\n.bp5-dark .bp5-daterangepicker.bp5-datepicker .rdp-day_hovered{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-daterangepicker.bp5-datepicker .rdp-day_hovered:not(.rdp-day_selected, .rdp-day_range_start, .rdp-day_range_middle, .rdp-day_range_end){\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-dark .bp5-daterangepicker.bp5-datepicker .rdp-day_range_middle{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-daterangepicker.bp5-datepicker .rdp-day_range_middle:hover{\n  background-color:rgba(45, 114, 210, 0.4);\n}\n.bp5-dark .bp5-daterangepicker.bp5-datepicker-highlight-current-day .rdp-day.rdp-day_today{\n  border:1px solid rgba(255, 255, 255, 0.2);\n}"
  },
  {
    "path": "www/dist/static/blueprint-icons.css",
    "content": "@font-face{\n  font-family:\"blueprint-icons-16\";\n  src:url(\"./blueprint-icons-16.ttf?18de873f8edfd138f084e5f339eab193\") format(\"truetype\"), url(\"./blueprint-icons-16.eot?18de873f8edfd138f084e5f339eab193#iefix\") format(\"embedded-opentype\"), url(\"./blueprint-icons-16.woff2?18de873f8edfd138f084e5f339eab193\") format(\"woff2\"), url(\"./blueprint-icons-16.woff?18de873f8edfd138f084e5f339eab193\") format(\"woff\"), url(\"./blueprint-icons-16.svg?18de873f8edfd138f084e5f339eab193#blueprint-icons-16\") format(\"svg\");\n}\n@font-face{\n  font-family:\"blueprint-icons-20\";\n  src:url(\"./blueprint-icons-20.ttf?dce239fc78c479d44f652d039433588a\") format(\"truetype\"), url(\"./blueprint-icons-20.eot?dce239fc78c479d44f652d039433588a#iefix\") format(\"embedded-opentype\"), url(\"./blueprint-icons-20.woff2?dce239fc78c479d44f652d039433588a\") format(\"woff2\"), url(\"./blueprint-icons-20.woff?dce239fc78c479d44f652d039433588a\") format(\"woff\"), url(\"./blueprint-icons-20.svg?dce239fc78c479d44f652d039433588a#blueprint-icons-20\") format(\"svg\");\n}"
  },
  {
    "path": "www/dist/static/blueprint3.css",
    "content": "@charset \"UTF-8\";\nhtml{\n  -webkit-box-sizing:border-box;\n          box-sizing:border-box; }\n\n*,\n*::before,\n*::after{\n  -webkit-box-sizing:inherit;\n          box-sizing:inherit; }\n\nbody {\n  background-color: #f5f8fa !important;\n}\nbody.bp5-dark {\n  background-color: #293742 !important;\n}\n\nbody{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none;\n  color:#182026;\n  background-color: #f5f8fa;\n  font-family:-apple-system, \"BlinkMacSystemFont\", \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Open Sans\", \"Helvetica Neue\", \"blueprint-icons-16\", sans-serif; }\n\np{\n  margin-bottom:10px;\n  margin-top:0; }\n\nsmall{\n  font-size:12px; }\n\nstrong{\n  font-weight:600; }\n\n::-moz-selection{\n  background:rgba(125, 188, 255, 0.6); }\n\n::selection{\n  background:rgba(125, 188, 255, 0.6); }\n.bp5-heading{\n  color:#182026;\n  font-weight:600;\n  margin:0 0 10px;\n  padding:0; }\n  .bp5-dark .bp5-heading{\n    color:#f5f8fa; }\n\nh1.bp5-heading, .bp5-running-text h1{\n  font-size:36px;\n  line-height:40px; }\n\nh2.bp5-heading, .bp5-running-text h2{\n  font-size:28px;\n  line-height:32px; }\n\nh3.bp5-heading, .bp5-running-text h3{\n  font-size:22px;\n  line-height:25px; }\n\nh4.bp5-heading, .bp5-running-text h4{\n  font-size:18px;\n  line-height:21px; }\n\nh5.bp5-heading, .bp5-running-text h5{\n  font-size:16px;\n  line-height:19px; }\n\nh6.bp5-heading, .bp5-running-text h6{\n  font-size:14px;\n  line-height:16px; }\n.bp5-ui-text{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none; }\n\n.bp5-monospace-text{\n  font-family:monospace;\n  text-transform:none; }\n\n.bp5-text-muted{\n  color:#5c7080; }\n  .bp5-dark .bp5-text-muted{\n    color:#a7b6c2; }\n\n.bp5-text-disabled{\n  color:rgba(92, 112, 128, 0.6); }\n  .bp5-dark .bp5-text-disabled{\n    color:rgba(167, 182, 194, 0.6); }\n\n.bp5-text-overflow-ellipsis{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal; }\n.bp5-running-text{\n  font-size:14px;\n  line-height:1.5; }\n  .bp5-running-text h1{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h1{\n      color:#f5f8fa; }\n  .bp5-running-text h2{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h2{\n      color:#f5f8fa; }\n  .bp5-running-text h3{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h3{\n      color:#f5f8fa; }\n  .bp5-running-text h4{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h4{\n      color:#f5f8fa; }\n  .bp5-running-text h5{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h5{\n      color:#f5f8fa; }\n  .bp5-running-text h6{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h6{\n      color:#f5f8fa; }\n  .bp5-running-text hr{\n    border:none;\n    border-bottom:1px solid rgba(16, 22, 26, 0.15);\n    margin:20px 0; }\n    .bp5-dark .bp5-running-text hr{\n      border-color:rgba(255, 255, 255, 0.15); }\n  .bp5-running-text p{\n    margin:0 0 10px;\n    padding:0; }\n\n.bp5-text-large{\n  font-size:16px; }\n\n.bp5-text-small{\n  font-size:12px; }\na{\n  color:#106ba3;\n  text-decoration:none; }\n  a:hover{\n    color:#106ba3;\n    cursor:pointer;\n    text-decoration:underline; }\n  a .bp5-icon, a .bp5-icon-standard, a .bp5-icon-large{\n    color:inherit; }\n  a code,\n  .bp5-dark a code{\n    color:inherit; }\n  .bp5-dark a,\n  .bp5-dark a:hover{\n    color:#48aff0; }\n    .bp5-dark a .bp5-icon, .bp5-dark a .bp5-icon-standard, .bp5-dark a .bp5-icon-large,\n    .bp5-dark a:hover .bp5-icon,\n    .bp5-dark a:hover .bp5-icon-standard,\n    .bp5-dark a:hover .bp5-icon-large{\n      color:inherit; }\n.bp5-running-text code, .bp5-code{\n  font-family:monospace;\n  text-transform:none;\n  background:rgba(255, 255, 255, 0.7);\n  border-radius:3px;\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2);\n  color:#5c7080;\n  font-size:smaller;\n  padding:2px 5px; }\n  .bp5-dark .bp5-running-text code, .bp5-running-text .bp5-dark code, .bp5-dark .bp5-code{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#a7b6c2; }\n  .bp5-running-text a > code, a > .bp5-code{\n    color:#137cbd; }\n    .bp5-dark .bp5-running-text a > code, .bp5-running-text .bp5-dark a > code, .bp5-dark a > .bp5-code{\n      color:inherit; }\n\n.bp5-running-text pre, .bp5-code-block{\n  font-family:monospace;\n  text-transform:none;\n  background:rgba(255, 255, 255, 0.7);\n  border-radius:3px;\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n  color:#182026;\n  display:block;\n  font-size:13px;\n  line-height:1.4;\n  margin:10px 0;\n  padding:13px 15px 12px;\n  word-break:break-all;\n  word-wrap:break-word; }\n  .bp5-dark .bp5-running-text pre, .bp5-running-text .bp5-dark pre, .bp5-dark .bp5-code-block{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n  .bp5-running-text pre > code, .bp5-code-block > code{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:inherit;\n    font-size:inherit;\n    padding:0; }\n\n.bp5-running-text kbd, .bp5-key{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n  color:#5c7080;\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  font-family:inherit;\n  font-size:12px;\n  height:24px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  line-height:24px;\n  min-width:24px;\n  padding:3px 6px;\n  vertical-align:middle; }\n  .bp5-running-text kbd .bp5-icon, .bp5-key .bp5-icon, .bp5-running-text kbd .bp5-icon-standard, .bp5-key .bp5-icon-standard, .bp5-running-text kbd .bp5-icon-large, .bp5-key .bp5-icon-large{\n    margin-right:5px; }\n  .bp5-dark .bp5-running-text kbd, .bp5-running-text .bp5-dark kbd, .bp5-dark .bp5-key{\n    background:#394b59;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#a7b6c2; }\n.bp5-running-text blockquote, .bp5-blockquote{\n  border-left:solid 4px rgba(167, 182, 194, 0.5);\n  margin:0 0 10px;\n  padding:0 20px; }\n  .bp5-dark .bp5-running-text blockquote, .bp5-running-text .bp5-dark blockquote, .bp5-dark .bp5-blockquote{\n    border-color:rgba(115, 134, 148, 0.5); }\n.bp5-running-text ul,\n.bp5-running-text ol, .bp5-list{\n  margin:10px 0;\n  padding-left:30px; }\n  .bp5-running-text ul li:not(:last-child), .bp5-running-text ol li:not(:last-child), .bp5-list li:not(:last-child){\n    margin-bottom:5px; }\n  .bp5-running-text ul ol, .bp5-running-text ol ol, .bp5-list ol,\n  .bp5-running-text ul ul,\n  .bp5-running-text ol ul,\n  .bp5-list ul{\n    margin-top:5px; }\n\n.bp5-list-unstyled{\n  list-style:none;\n  margin:0;\n  padding:0; }\n  .bp5-list-unstyled li{\n    padding:0; }\n.bp5-rtl{\n  text-align:right; }\n\n.bp5-dark{\n  color:#f5f8fa; }\n\n:focus{\n  outline:rgba(19, 124, 189, 0.6) auto 2px;\n  outline-offset:2px;\n  -moz-outline-radius:6px; }\n\n.bp5-focus-disabled :focus{\n  outline:none !important; }\n  .bp5-focus-disabled :focus ~ .bp5-control-indicator{\n    outline:none !important; }\n.bp5-alert{\n  max-width:400px;\n  padding:20px; }\n\n.bp5-alert-body{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex; }\n  .bp5-alert-body .bp5-icon{\n    font-size:40px;\n    margin-right:20px;\n    margin-top:0; }\n\n.bp5-alert-contents{\n  word-break:break-word; }\n\n.bp5-alert-footer{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:reverse;\n      -ms-flex-direction:row-reverse;\n          flex-direction:row-reverse;\n  margin-top:10px; }\n  .bp5-alert-footer .bp5-button{\n    margin-left:10px; }\n.bp5-breadcrumbs{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  cursor:default;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-wrap:wrap;\n      flex-wrap:wrap;\n  height:30px;\n  list-style:none;\n  margin:0;\n  padding:0; }\n  .bp5-breadcrumbs > li{\n    -webkit-box-align:center;\n        -ms-flex-align:center;\n            align-items:center;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex; }\n    .bp5-breadcrumbs > li::after{\n      background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 00-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='%235C7080'/%3e%3c/svg%3e\");\n      content:\"\";\n      display:block;\n      height:16px;\n      margin:0 5px;\n      width:16px; }\n    .bp5-breadcrumbs > li:last-of-type::after{\n      display:none; }\n\n.bp5-breadcrumb,\n.bp5-breadcrumb-current,\n.bp5-breadcrumbs-collapsed{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  font-size:16px; }\n\n.bp5-breadcrumb,\n.bp5-breadcrumbs-collapsed{\n  color:#5c7080; }\n\n.bp5-breadcrumb:hover{\n  text-decoration:none; }\n\n.bp5-breadcrumb.bp5-disabled{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-breadcrumb .bp5-icon{\n  margin-right:5px; }\n\n.bp5-breadcrumb-current{\n  color:inherit;\n  font-weight:600; }\n  .bp5-breadcrumb-current .bp5-input{\n    font-size:inherit;\n    font-weight:inherit;\n    vertical-align:baseline; }\n\n.bp5-breadcrumbs-collapsed{\n  background:#ced9e0;\n  border:none;\n  border-radius:3px;\n  cursor:pointer;\n  margin-right:2px;\n  padding:1px 5px;\n  vertical-align:text-bottom; }\n  .bp5-breadcrumbs-collapsed::before{\n    background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cg fill='%235C7080'%3e%3ccircle cx='2' cy='8.03' r='2'/%3e%3ccircle cx='14' cy='8.03' r='2'/%3e%3ccircle cx='8' cy='8.03' r='2'/%3e%3c/g%3e%3c/svg%3e\") center no-repeat;\n    content:\"\";\n    display:block;\n    height:16px;\n    width:16px; }\n  .bp5-breadcrumbs-collapsed:hover{\n    background:#bfccd6;\n    color:#182026;\n    text-decoration:none; }\n\n.bp5-dark .bp5-breadcrumb,\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  color:#a7b6c2; }\n\n.bp5-dark .bp5-breadcrumbs > li::after{\n  color:#a7b6c2; }\n\n.bp5-dark .bp5-breadcrumb.bp5-disabled{\n  color:rgba(167, 182, 194, 0.6); }\n\n.bp5-dark .bp5-breadcrumb-current{\n  color:#f5f8fa; }\n\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  background:rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-breadcrumbs-collapsed:hover{\n    background:rgba(16, 22, 26, 0.6);\n    color:#f5f8fa; }\n.bp5-button{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  border:none;\n  border-radius:3px;\n  cursor:pointer;\n  font-size:14px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  min-height:30px;\n  min-width:30px; }\n  .bp5-button > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-button > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-button::before,\n  .bp5-button > *{\n    margin-right:7px; }\n  .bp5-button:empty::before,\n  .bp5-button > :last-child{\n    margin-right:0; }\n  .bp5-button:empty{\n    padding:0 !important; }\n  .bp5-button:disabled, .bp5-button.bp5-disabled{\n    cursor:not-allowed; }\n  .bp5-button.bp5-fill{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    width:100%; }\n  .bp5-button.bp5-align-right,\n  .bp5-align-right .bp5-button{\n    text-align:right; }\n  .bp5-button.bp5-align-left,\n  .bp5-align-left .bp5-button{\n    text-align:left; }\n  .bp5-button:not([class*=\"bp5-intent-\"]){\n    background-color:#f5f8fa;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    color:#182026; }\n    .bp5-button:not([class*=\"bp5-intent-\"]):hover{\n      background-clip:padding-box;\n      background-color:#ebf1f5;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n    .bp5-button:not([class*=\"bp5-intent-\"]):active, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-active{\n      background-color:#d8e1e8;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button:not([class*=\"bp5-intent-\"]):disabled, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled{\n      background-color:rgba(206, 217, 224, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed;\n      outline:none; }\n      .bp5-button:not([class*=\"bp5-intent-\"]):disabled.bp5-active, .bp5-button:not([class*=\"bp5-intent-\"]):disabled.bp5-active:hover, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled.bp5-active, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled.bp5-active:hover{\n        background:rgba(206, 217, 224, 0.7); }\n  .bp5-button.bp5-intent-primary{\n    background-color:#137cbd;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-primary:hover, .bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-primary:hover{\n      background-color:#106ba3;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n      background-color:#0e5a8a;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-primary:disabled, .bp5-button.bp5-intent-primary.bp5-disabled{\n      background-color:rgba(19, 124, 189, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button.bp5-intent-success{\n    background-color:#0f9960;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-success:hover, .bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-success:hover{\n      background-color:#0d8050;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n      background-color:#0a6640;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-success:disabled, .bp5-button.bp5-intent-success.bp5-disabled{\n      background-color:rgba(15, 153, 96, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button.bp5-intent-warning{\n    background-color:#d9822b;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-warning:hover, .bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-warning:hover{\n      background-color:#bf7326;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n      background-color:#a66321;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-warning:disabled, .bp5-button.bp5-intent-warning.bp5-disabled{\n      background-color:rgba(217, 130, 43, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button.bp5-intent-danger{\n    background-color:#db3737;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-danger:hover, .bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-danger:hover{\n      background-color:#c23030;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n      background-color:#a82a2a;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-danger:disabled, .bp5-button.bp5-intent-danger.bp5-disabled{\n      background-color:rgba(219, 55, 55, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button[class*=\"bp5-intent-\"] .bp5-button-spinner .bp5-spinner-head{\n    stroke:#ffffff; }\n  .bp5-button.bp5-large,\n  .bp5-large .bp5-button{\n    min-height:40px;\n    min-width:40px;\n    font-size:16px;\n    padding:5px 15px; }\n    .bp5-button.bp5-large::before,\n    .bp5-button.bp5-large > *,\n    .bp5-large .bp5-button::before,\n    .bp5-large .bp5-button > *{\n      margin-right:10px; }\n    .bp5-button.bp5-large:empty::before,\n    .bp5-button.bp5-large > :last-child,\n    .bp5-large .bp5-button:empty::before,\n    .bp5-large .bp5-button > :last-child{\n      margin-right:0; }\n  .bp5-button.bp5-small,\n  .bp5-small .bp5-button{\n    min-height:24px;\n    min-width:24px;\n    padding:0 7px; }\n  .bp5-button.bp5-loading{\n    position:relative; }\n    .bp5-button.bp5-loading[class*=\"bp5-icon-\"]::before{\n      visibility:hidden; }\n    .bp5-button.bp5-loading .bp5-button-spinner{\n      margin:0;\n      position:absolute; }\n    .bp5-button.bp5-loading > :not(.bp5-button-spinner){\n      visibility:hidden; }\n  .bp5-button[class*=\"bp5-icon-\"]::before{\n    font-family:\"blueprint-icons-16\", sans-serif;\n    font-size:16px;\n    font-style:normal;\n    font-weight:400;\n    line-height:1;\n    -moz-osx-font-smoothing:grayscale;\n    -webkit-font-smoothing:antialiased;\n    color:#5c7080; }\n  .bp5-button .bp5-icon, .bp5-button .bp5-icon-standard, .bp5-button .bp5-icon-large{\n    color:#5c7080; }\n    .bp5-button .bp5-icon.bp5-align-right, .bp5-button .bp5-icon-standard.bp5-align-right, .bp5-button .bp5-icon-large.bp5-align-right{\n      margin-left:7px; }\n  .bp5-button .bp5-icon:first-child:last-child,\n  .bp5-button .bp5-spinner + .bp5-icon:last-child{\n    margin:0 -7px; }\n  .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]){\n    background-color:#394b59;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):hover, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):active, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-active{\n      color:#f5f8fa; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):hover{\n      background-color:#30404d;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):active, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-active{\n      background-color:#202b33;\n      background-image:none;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):disabled, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled{\n      background-color:rgba(57, 75, 89, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n      .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):disabled.bp5-active, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled.bp5-active{\n        background:rgba(57, 75, 89, 0.7); }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-button-spinner .bp5-spinner-head{\n      background:rgba(16, 22, 26, 0.5);\n      stroke:#8a9ba8; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"])[class*=\"bp5-icon-\"]::before{\n      color:#a7b6c2; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-icon:not([class*=\"bp5-intent-\"]), .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-icon-standard:not([class*=\"bp5-intent-\"]), .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-icon-large:not([class*=\"bp5-intent-\"]){\n      color:#a7b6c2; }\n  .bp5-dark .bp5-button[class*=\"bp5-intent-\"]{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"]:hover{\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"]:active, .bp5-dark .bp5-button[class*=\"bp5-intent-\"].bp5-active{\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"]:disabled, .bp5-dark .bp5-button[class*=\"bp5-intent-\"].bp5-disabled{\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.3); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"] .bp5-button-spinner .bp5-spinner-head{\n      stroke:#8a9ba8; }\n  .bp5-button:disabled::before,\n  .bp5-button:disabled .bp5-icon, .bp5-button:disabled .bp5-icon-standard, .bp5-button:disabled .bp5-icon-large, .bp5-button.bp5-disabled::before,\n  .bp5-button.bp5-disabled .bp5-icon, .bp5-button.bp5-disabled .bp5-icon-standard, .bp5-button.bp5-disabled .bp5-icon-large, .bp5-button[class*=\"bp5-intent-\"]::before,\n  .bp5-button[class*=\"bp5-intent-\"] .bp5-icon, .bp5-button[class*=\"bp5-intent-\"] .bp5-icon-standard, .bp5-button[class*=\"bp5-intent-\"] .bp5-icon-large{\n    color:inherit !important; }\n  .bp5-button.bp5-minimal{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n    .bp5-button.bp5-minimal:hover{\n      background:rgba(167, 182, 194, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026;\n      text-decoration:none; }\n    .bp5-button.bp5-minimal:active, .bp5-button.bp5-minimal.bp5-active{\n      background:rgba(115, 134, 148, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026; }\n    .bp5-button.bp5-minimal:disabled, .bp5-button.bp5-minimal:disabled:hover, .bp5-button.bp5-minimal.bp5-disabled, .bp5-button.bp5-minimal.bp5-disabled:hover{\n      background:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed; }\n      .bp5-button.bp5-minimal:disabled.bp5-active, .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n        background:rgba(115, 134, 148, 0.3); }\n    .bp5-dark .bp5-button.bp5-minimal{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:inherit; }\n      .bp5-dark .bp5-button.bp5-minimal:hover, .bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n      .bp5-dark .bp5-button.bp5-minimal:hover{\n        background:rgba(138, 155, 168, 0.15); }\n      .bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n        background:rgba(138, 155, 168, 0.3);\n        color:#f5f8fa; }\n      .bp5-dark .bp5-button.bp5-minimal:disabled, .bp5-dark .bp5-button.bp5-minimal:disabled:hover, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover{\n        background:none;\n        color:rgba(167, 182, 194, 0.6);\n        cursor:not-allowed; }\n        .bp5-dark .bp5-button.bp5-minimal:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n          background:rgba(138, 155, 168, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-primary{\n      color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:hover, .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.15);\n        color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(16, 107, 163, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n        stroke:#106ba3; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary{\n        color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:hover{\n          background:rgba(19, 124, 189, 0.2);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n          background:rgba(19, 124, 189, 0.3);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n          background:none;\n          color:rgba(72, 175, 240, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n            background:rgba(19, 124, 189, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-success{\n      color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:hover, .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.15);\n        color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(13, 128, 80, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n        stroke:#0d8050; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success{\n        color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:hover{\n          background:rgba(15, 153, 96, 0.2);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n          background:rgba(15, 153, 96, 0.3);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n          background:none;\n          color:rgba(61, 204, 145, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n            background:rgba(15, 153, 96, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-warning{\n      color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:hover, .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.15);\n        color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(191, 115, 38, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n        stroke:#bf7326; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning{\n        color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:hover{\n          background:rgba(217, 130, 43, 0.2);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n          background:rgba(217, 130, 43, 0.3);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n          background:none;\n          color:rgba(255, 179, 102, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n            background:rgba(217, 130, 43, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-danger{\n      color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:hover, .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.15);\n        color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(194, 48, 48, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n        stroke:#c23030; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger{\n        color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:hover{\n          background:rgba(219, 55, 55, 0.2);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n          background:rgba(219, 55, 55, 0.3);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n          background:none;\n          color:rgba(255, 115, 115, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n            background:rgba(219, 55, 55, 0.3); }\n  .bp5-button.bp5-outlined{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    border:1px solid rgba(24, 32, 38, 0.2);\n    -webkit-box-sizing:border-box;\n            box-sizing:border-box; }\n    .bp5-button.bp5-outlined:hover{\n      background:rgba(167, 182, 194, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026;\n      text-decoration:none; }\n    .bp5-button.bp5-outlined:active, .bp5-button.bp5-outlined.bp5-active{\n      background:rgba(115, 134, 148, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026; }\n    .bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined.bp5-disabled:hover{\n      background:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed; }\n      .bp5-button.bp5-outlined:disabled.bp5-active, .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n        background:rgba(115, 134, 148, 0.3); }\n    .bp5-dark .bp5-button.bp5-outlined{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:inherit; }\n      .bp5-dark .bp5-button.bp5-outlined:hover, .bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n      .bp5-dark .bp5-button.bp5-outlined:hover{\n        background:rgba(138, 155, 168, 0.15); }\n      .bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n        background:rgba(138, 155, 168, 0.3);\n        color:#f5f8fa; }\n      .bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n        background:none;\n        color:rgba(167, 182, 194, 0.6);\n        cursor:not-allowed; }\n        .bp5-dark .bp5-button.bp5-outlined:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n          background:rgba(138, 155, 168, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-primary{\n      color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:hover, .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.15);\n        color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(16, 107, 163, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n        stroke:#106ba3; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n        color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:hover{\n          background:rgba(19, 124, 189, 0.2);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n          background:rgba(19, 124, 189, 0.3);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n          background:none;\n          color:rgba(72, 175, 240, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n            background:rgba(19, 124, 189, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-success{\n      color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:hover, .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.15);\n        color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(13, 128, 80, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n        stroke:#0d8050; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n        color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:hover{\n          background:rgba(15, 153, 96, 0.2);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n          background:rgba(15, 153, 96, 0.3);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n          background:none;\n          color:rgba(61, 204, 145, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n            background:rgba(15, 153, 96, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-warning{\n      color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:hover, .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.15);\n        color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(191, 115, 38, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n        stroke:#bf7326; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n        color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:hover{\n          background:rgba(217, 130, 43, 0.2);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n          background:rgba(217, 130, 43, 0.3);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n          background:none;\n          color:rgba(255, 179, 102, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n            background:rgba(217, 130, 43, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-danger{\n      color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:hover, .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.15);\n        color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(194, 48, 48, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n        stroke:#c23030; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n        color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:hover{\n          background:rgba(219, 55, 55, 0.2);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n          background:rgba(219, 55, 55, 0.3);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n          background:none;\n          color:rgba(255, 115, 115, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n            background:rgba(219, 55, 55, 0.3); }\n    .bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled:hover{\n      border-color:rgba(92, 112, 128, 0.1); }\n    .bp5-dark .bp5-button.bp5-outlined{\n      border-color:rgba(255, 255, 255, 0.4); }\n      .bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n        border-color:rgba(255, 255, 255, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-primary{\n      border-color:rgba(16, 107, 163, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n        border-color:rgba(16, 107, 163, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n        border-color:rgba(72, 175, 240, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n          border-color:rgba(72, 175, 240, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-success{\n      border-color:rgba(13, 128, 80, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n        border-color:rgba(13, 128, 80, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n        border-color:rgba(61, 204, 145, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n          border-color:rgba(61, 204, 145, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-warning{\n      border-color:rgba(191, 115, 38, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n        border-color:rgba(191, 115, 38, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n        border-color:rgba(255, 179, 102, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n          border-color:rgba(255, 179, 102, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-danger{\n      border-color:rgba(194, 48, 48, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n        border-color:rgba(194, 48, 48, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n        border-color:rgba(255, 115, 115, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n          border-color:rgba(255, 115, 115, 0.2); }\n\na.bp5-button{\n  text-align:center;\n  text-decoration:none;\n  -webkit-transition:none;\n  transition:none; }\n  a.bp5-button, a.bp5-button:hover, a.bp5-button:active{\n    color:#182026; }\n  a.bp5-button.bp5-disabled{\n    color:rgba(92, 112, 128, 0.6); }\n\n.bp5-button-text{\n  -webkit-box-flex:0;\n      -ms-flex:0 1 auto;\n          flex:0 1 auto; }\n\n.bp5-button.bp5-align-left .bp5-button-text, .bp5-button.bp5-align-right .bp5-button-text,\n.bp5-button-group.bp5-align-left .bp5-button-text,\n.bp5-button-group.bp5-align-right .bp5-button-text{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto; }\n.bp5-button-group{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex; }\n  .bp5-button-group .bp5-button{\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    position:relative;\n    z-index:4; }\n    .bp5-button-group .bp5-button:focus{\n      z-index:5; }\n    .bp5-button-group .bp5-button:hover{\n      z-index:6; }\n    .bp5-button-group .bp5-button:active, .bp5-button-group .bp5-button.bp5-active{\n      z-index:7; }\n    .bp5-button-group .bp5-button:disabled, .bp5-button-group .bp5-button.bp5-disabled{\n      z-index:3; }\n    .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]{\n      z-index:9; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:focus{\n        z-index:10; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:hover{\n        z-index:11; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:active, .bp5-button-group .bp5-button[class*=\"bp5-intent-\"].bp5-active{\n        z-index:12; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:disabled, .bp5-button-group .bp5-button[class*=\"bp5-intent-\"].bp5-disabled{\n        z-index:8; }\n  .bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:first-child) .bp5-button,\n  .bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:first-child){\n    border-bottom-left-radius:0;\n    border-top-left-radius:0; }\n  .bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n  .bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:last-child){\n    border-bottom-right-radius:0;\n    border-top-right-radius:0;\n    margin-right:-1px; }\n  .bp5-button-group.bp5-minimal .bp5-button{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n    .bp5-button-group.bp5-minimal .bp5-button:hover{\n      background:rgba(167, 182, 194, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026;\n      text-decoration:none; }\n    .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-active{\n      background:rgba(115, 134, 148, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026; }\n    .bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover{\n      background:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed; }\n      .bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active{\n        background:rgba(115, 134, 148, 0.3); }\n    .bp5-dark .bp5-button-group.bp5-minimal .bp5-button{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:inherit; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover{\n        background:rgba(138, 155, 168, 0.15); }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active{\n        background:rgba(138, 155, 168, 0.3);\n        color:#f5f8fa; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover{\n        background:none;\n        color:rgba(167, 182, 194, 0.6);\n        cursor:not-allowed; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active{\n          background:rgba(138, 155, 168, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary{\n      color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.15);\n        color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(16, 107, 163, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n        stroke:#106ba3; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary{\n        color:#48aff0; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover{\n          background:rgba(19, 124, 189, 0.2);\n          color:#48aff0; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active{\n          background:rgba(19, 124, 189, 0.3);\n          color:#48aff0; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled{\n          background:none;\n          color:rgba(72, 175, 240, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n            background:rgba(19, 124, 189, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success{\n      color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.15);\n        color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(13, 128, 80, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n        stroke:#0d8050; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success{\n        color:#3dcc91; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover{\n          background:rgba(15, 153, 96, 0.2);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active{\n          background:rgba(15, 153, 96, 0.3);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled{\n          background:none;\n          color:rgba(61, 204, 145, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n            background:rgba(15, 153, 96, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning{\n      color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.15);\n        color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(191, 115, 38, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n        stroke:#bf7326; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning{\n        color:#ffb366; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover{\n          background:rgba(217, 130, 43, 0.2);\n          color:#ffb366; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active{\n          background:rgba(217, 130, 43, 0.3);\n          color:#ffb366; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled{\n          background:none;\n          color:rgba(255, 179, 102, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n            background:rgba(217, 130, 43, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger{\n      color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.15);\n        color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(194, 48, 48, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n        stroke:#c23030; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger{\n        color:#ff7373; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover{\n          background:rgba(219, 55, 55, 0.2);\n          color:#ff7373; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active{\n          background:rgba(219, 55, 55, 0.3);\n          color:#ff7373; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled{\n          background:none;\n          color:rgba(255, 115, 115, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n            background:rgba(219, 55, 55, 0.3); }\n  .bp5-button-group .bp5-popover-wrapper,\n  .bp5-button-group .bp5-popover-target{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-button-group.bp5-fill{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    width:100%; }\n  .bp5-button-group .bp5-button.bp5-fill,\n  .bp5-button-group.bp5-fill .bp5-button:not(.bp5-fixed){\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-button-group.bp5-vertical{\n    -webkit-box-align:stretch;\n        -ms-flex-align:stretch;\n            align-items:stretch;\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:column;\n            flex-direction:column;\n    vertical-align:top; }\n    .bp5-button-group.bp5-vertical.bp5-fill{\n      height:100%;\n      width:unset; }\n    .bp5-button-group.bp5-vertical .bp5-button{\n      margin-right:0 !important;\n      width:100%; }\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:first-child .bp5-button,\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:first-child{\n      border-radius:3px 3px 0 0; }\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:last-child .bp5-button,\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:last-child{\n      border-radius:0 0 3px 3px; }\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:not(:last-child){\n      margin-bottom:-1px; }\n  .bp5-button-group.bp5-align-left .bp5-button{\n    text-align:left; }\n  .bp5-dark .bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n  .bp5-dark .bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:last-child){\n    margin-right:1px; }\n  .bp5-dark .bp5-button-group.bp5-vertical > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n  .bp5-dark .bp5-button-group.bp5-vertical > .bp5-button:not(:last-child){\n    margin-bottom:1px; }\n.bp5-callout{\n  font-size:14px;\n  line-height:1.5;\n  background-color:rgba(138, 155, 168, 0.15);\n  border-radius:3px;\n  padding:10px 12px 9px;\n  position:relative;\n  width:100%; }\n  .bp5-callout[class*=\"bp5-icon-\"]{\n    padding-left:40px; }\n    .bp5-callout[class*=\"bp5-icon-\"]::before{\n      font-family:\"blueprint-icons-20\", sans-serif;\n      font-size:20px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1;\n      -moz-osx-font-smoothing:grayscale;\n      -webkit-font-smoothing:antialiased;\n      color:#5c7080;\n      left:10px;\n      position:absolute;\n      top:10px; }\n  .bp5-callout.bp5-callout-icon{\n    padding-left:40px; }\n    .bp5-callout.bp5-callout-icon > .bp5-icon:first-child{\n      color:#5c7080;\n      left:10px;\n      position:absolute;\n      top:10px; }\n  .bp5-callout .bp5-heading{\n    line-height:20px;\n    margin-bottom:5px;\n    margin-top:0; }\n    .bp5-callout .bp5-heading:last-child{\n      margin-bottom:0; }\n  .bp5-dark .bp5-callout{\n    background-color:rgba(138, 155, 168, 0.2); }\n    .bp5-dark .bp5-callout[class*=\"bp5-icon-\"]::before{\n      color:#a7b6c2; }\n  .bp5-callout.bp5-intent-primary{\n    background-color:rgba(19, 124, 189, 0.15); }\n    .bp5-callout.bp5-intent-primary[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-primary .bp5-heading{\n      color:#106ba3; }\n    .bp5-dark .bp5-callout.bp5-intent-primary{\n      background-color:rgba(19, 124, 189, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-primary[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-primary .bp5-heading{\n        color:#48aff0; }\n  .bp5-callout.bp5-intent-success{\n    background-color:rgba(15, 153, 96, 0.15); }\n    .bp5-callout.bp5-intent-success[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-success .bp5-heading{\n      color:#0d8050; }\n    .bp5-dark .bp5-callout.bp5-intent-success{\n      background-color:rgba(15, 153, 96, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-success[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-success .bp5-heading{\n        color:#3dcc91; }\n  .bp5-callout.bp5-intent-warning{\n    background-color:rgba(217, 130, 43, 0.15); }\n    .bp5-callout.bp5-intent-warning[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-warning .bp5-heading{\n      color:#bf7326; }\n    .bp5-dark .bp5-callout.bp5-intent-warning{\n      background-color:rgba(217, 130, 43, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-warning[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-warning .bp5-heading{\n        color:#ffb366; }\n  .bp5-callout.bp5-intent-danger{\n    background-color:rgba(219, 55, 55, 0.15); }\n    .bp5-callout.bp5-intent-danger[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-danger .bp5-heading{\n      color:#c23030; }\n    .bp5-dark .bp5-callout.bp5-intent-danger{\n      background-color:rgba(219, 55, 55, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-danger[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-danger .bp5-heading{\n        color:#ff7373; }\n  .bp5-running-text .bp5-callout{\n    margin:20px 0; }\n.bp5-card{\n  background-color:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n  padding:20px;\n  -webkit-transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-card.bp5-dark,\n  .bp5-dark .bp5-card{\n    background-color:#30404d;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); }\n\n.bp5-elevation-0{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); }\n  .bp5-elevation-0.bp5-dark,\n  .bp5-dark .bp5-elevation-0{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); }\n\n.bp5-elevation-1{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-1.bp5-dark,\n  .bp5-dark .bp5-elevation-1{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-elevation-2{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 1px 1px rgba(16, 22, 26, 0.2), 0 2px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 1px 1px rgba(16, 22, 26, 0.2), 0 2px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-2.bp5-dark,\n  .bp5-dark .bp5-elevation-2{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.4), 0 2px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.4), 0 2px 6px rgba(16, 22, 26, 0.4); }\n\n.bp5-elevation-3{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-3.bp5-dark,\n  .bp5-dark .bp5-elevation-3{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n\n.bp5-elevation-4{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-4.bp5-dark,\n  .bp5-dark .bp5-elevation-4{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4); }\n\n.bp5-card.bp5-interactive:hover{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  cursor:pointer; }\n  .bp5-card.bp5-interactive:hover.bp5-dark,\n  .bp5-dark .bp5-card.bp5-interactive:hover{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n\n.bp5-card.bp5-interactive:active{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n  opacity:0.9;\n  -webkit-transition-duration:0;\n          transition-duration:0; }\n  .bp5-card.bp5-interactive:active.bp5-dark,\n  .bp5-dark .bp5-card.bp5-interactive:active{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-collapse{\n  height:0;\n  overflow-y:hidden;\n  -webkit-transition:height 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:height 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-collapse .bp5-collapse-body{\n    -webkit-transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-collapse .bp5-collapse-body[aria-hidden=\"true\"]{\n      display:none; }\n\n.bp5-context-menu .bp5-popover-target{\n  display:block; }\n\n.bp5-context-menu-popover-target{\n  position:fixed; }\n.bp5-divider{\n  border-bottom:1px solid rgba(16, 22, 26, 0.15);\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  margin:5px; }\n  .bp5-dark .bp5-divider{\n    border-color:rgba(16, 22, 26, 0.4); }\n.bp5-dialog-container{\n  opacity:1;\n  -webkit-transform:scale(1);\n          transform:scale(1);\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  min-height:100%;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  width:100%; }\n  .bp5-dialog-container.bp5-overlay-enter > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear > .bp5-dialog{\n    opacity:0;\n    -webkit-transform:scale(0.5);\n            transform:scale(0.5); }\n  .bp5-dialog-container.bp5-overlay-enter-active > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear-active > .bp5-dialog{\n    opacity:1;\n    -webkit-transform:scale(1);\n            transform:scale(1);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:opacity, -webkit-transform;\n    transition-property:opacity, -webkit-transform;\n    transition-property:opacity, transform;\n    transition-property:opacity, transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-dialog-container.bp5-overlay-exit > .bp5-dialog{\n    opacity:1;\n    -webkit-transform:scale(1);\n            transform:scale(1); }\n  .bp5-dialog-container.bp5-overlay-exit-active > .bp5-dialog{\n    opacity:0;\n    -webkit-transform:scale(0.5);\n            transform:scale(0.5);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:opacity, -webkit-transform;\n    transition-property:opacity, -webkit-transform;\n    transition-property:opacity, transform;\n    transition-property:opacity, transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n\n.bp5-dialog{\n  background:#ebf1f5;\n  border-radius:6px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin:30px 0;\n  padding-bottom:20px;\n  pointer-events:all;\n  -webkit-user-select:text;\n     -moz-user-select:text;\n      -ms-user-select:text;\n          user-select:text;\n  width:500px; }\n  .bp5-dialog:focus{\n    outline:0; }\n  .bp5-dialog.bp5-dark,\n  .bp5-dark .bp5-dialog{\n    background:#293742;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n\n.bp5-dialog-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background:#ffffff;\n  border-radius:6px 6px 0 0;\n  -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  min-height:40px;\n  padding-left:20px;\n  padding-right:5px;\n  z-index:0; }\n  .bp5-dialog-header .bp5-icon-large,\n  .bp5-dialog-header .bp5-icon{\n    color:#5c7080;\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    margin-right:10px; }\n  .bp5-dialog-header .bp5-heading{\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    line-height:inherit;\n    margin:0; }\n    .bp5-dialog-header .bp5-heading:last-child{\n      margin-right:20px; }\n  .bp5-dark .bp5-dialog-header{\n    background:#30404d;\n    -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.4);\n            box-shadow:0 1px 0 rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-dialog-header .bp5-icon-large,\n    .bp5-dark .bp5-dialog-header .bp5-icon{\n      color:#a7b6c2; }\n\n.bp5-dialog-body{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  line-height:18px;\n  margin:20px; }\n\n.bp5-dialog-footer{\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  margin:0 20px; }\n\n.bp5-dialog-footer-actions{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:end;\n      -ms-flex-pack:end;\n          justify-content:flex-end; }\n  .bp5-dialog-footer-actions .bp5-button{\n    margin-left:10px; }\n.bp5-multistep-dialog-panels{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex; }\n  .bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:first-child{\n    border-radius:6px 0 0 0; }\n  .bp5-multistep-dialog-panels:first-child .bp5-multistep-dialog-right-panel{\n    border-top-right-radius:6px; }\n\n.bp5-multistep-dialog-left-panel{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:1;\n      -ms-flex:1;\n          flex:1;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column; }\n  .bp5-dark .bp5-multistep-dialog-left-panel{\n    background:#202b33; }\n\n.bp5-multistep-dialog-right-panel{\n  background-color:#f5f8fa;\n  border-left:1px solid rgba(16, 22, 26, 0.15);\n  border-radius:0 0 6px 0;\n  -webkit-box-flex:3;\n      -ms-flex:3;\n          flex:3;\n  min-width:0; }\n  .bp5-dark .bp5-multistep-dialog-right-panel{\n    background-color:#293742;\n    border-left:1px solid rgba(16, 22, 26, 0.4); }\n\n.bp5-multistep-dialog-footer{\n  background-color:#ffffff;\n  border-radius:0 0 6px 0;\n  border-top:1px solid rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:justify;\n      -ms-flex-pack:justify;\n          justify-content:space-between;\n  padding:10px; }\n  .bp5-dark .bp5-multistep-dialog-footer{\n    background:#30404d;\n    border-top:1px solid rgba(16, 22, 26, 0.4); }\n  .bp5-multistep-dialog-footer .bp5-dialog-footer-actions{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1; }\n\n.bp5-dialog-step-container{\n  background-color:#f5f8fa;\n  border-bottom:1px solid rgba(16, 22, 26, 0.15); }\n  .bp5-dark .bp5-dialog-step-container{\n    background:#293742;\n    border-bottom:1px solid rgba(16, 22, 26, 0.4); }\n  .bp5-dialog-step-container.bp5-dialog-step-viewed{\n    background-color:#ffffff; }\n    .bp5-dark .bp5-dialog-step-container.bp5-dialog-step-viewed{\n      background:#30404d; }\n\n.bp5-dialog-step{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background-color:#f5f8fa;\n  border-radius:6px;\n  cursor:not-allowed;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  margin:4px;\n  padding:6px 14px; }\n  .bp5-dark .bp5-dialog-step{\n    background:#293742; }\n  .bp5-dialog-step-viewed .bp5-dialog-step{\n    background-color:#ffffff;\n    cursor:pointer; }\n    .bp5-dark .bp5-dialog-step-viewed .bp5-dialog-step{\n      background:#30404d; }\n  .bp5-dialog-step:hover{\n    background-color:#f5f8fa; }\n    .bp5-dark .bp5-dialog-step:hover{\n      background:#293742; }\n\n.bp5-dialog-step-icon{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background-color:rgba(92, 112, 128, 0.6);\n  border-radius:50%;\n  color:#ffffff;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:25px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  width:25px; }\n  .bp5-dark .bp5-dialog-step-icon{\n    background-color:rgba(167, 182, 194, 0.6); }\n  .bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-icon{\n    background-color:#2b95d6; }\n  .bp5-dialog-step-viewed .bp5-dialog-step-icon{\n    background-color:#8a9ba8; }\n\n.bp5-dialog-step-title{\n  color:rgba(92, 112, 128, 0.6);\n  -webkit-box-flex:1;\n      -ms-flex:1;\n          flex:1;\n  padding-left:10px; }\n  .bp5-dark .bp5-dialog-step-title{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-title{\n    color:#2b95d6; }\n  .bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n    color:#182026; }\n    .bp5-dark .bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n      color:#f5f8fa; }\n.bp5-drawer{\n  background:#ffffff;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin:0;\n  padding:0; }\n  .bp5-drawer:focus{\n    outline:0; }\n  .bp5-drawer.bp5-position-top{\n    height:50%;\n    left:0;\n    right:0;\n    top:0; }\n    .bp5-drawer.bp5-position-top.bp5-overlay-enter, .bp5-drawer.bp5-position-top.bp5-overlay-appear{\n      -webkit-transform:translateY(-100%);\n              transform:translateY(-100%); }\n    .bp5-drawer.bp5-position-top.bp5-overlay-enter-active, .bp5-drawer.bp5-position-top.bp5-overlay-appear-active{\n      -webkit-transform:translateY(0);\n              transform:translateY(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-top.bp5-overlay-exit{\n      -webkit-transform:translateY(0);\n              transform:translateY(0); }\n    .bp5-drawer.bp5-position-top.bp5-overlay-exit-active{\n      -webkit-transform:translateY(-100%);\n              transform:translateY(-100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-position-bottom{\n    bottom:0;\n    height:50%;\n    left:0;\n    right:0; }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-enter, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%); }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-enter-active, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear-active{\n      -webkit-transform:translateY(0);\n              transform:translateY(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-exit{\n      -webkit-transform:translateY(0);\n              transform:translateY(0); }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-exit-active{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-position-left{\n    bottom:0;\n    left:0;\n    top:0;\n    width:50%; }\n    .bp5-drawer.bp5-position-left.bp5-overlay-enter, .bp5-drawer.bp5-position-left.bp5-overlay-appear{\n      -webkit-transform:translateX(-100%);\n              transform:translateX(-100%); }\n    .bp5-drawer.bp5-position-left.bp5-overlay-enter-active, .bp5-drawer.bp5-position-left.bp5-overlay-appear-active{\n      -webkit-transform:translateX(0);\n              transform:translateX(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-left.bp5-overlay-exit{\n      -webkit-transform:translateX(0);\n              transform:translateX(0); }\n    .bp5-drawer.bp5-position-left.bp5-overlay-exit-active{\n      -webkit-transform:translateX(-100%);\n              transform:translateX(-100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-position-right{\n    bottom:0;\n    right:0;\n    top:0;\n    width:50%; }\n    .bp5-drawer.bp5-position-right.bp5-overlay-enter, .bp5-drawer.bp5-position-right.bp5-overlay-appear{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%); }\n    .bp5-drawer.bp5-position-right.bp5-overlay-enter-active, .bp5-drawer.bp5-position-right.bp5-overlay-appear-active{\n      -webkit-transform:translateX(0);\n              transform:translateX(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-right.bp5-overlay-exit{\n      -webkit-transform:translateX(0);\n              transform:translateX(0); }\n    .bp5-drawer.bp5-position-right.bp5-overlay-exit-active{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n  .bp5-position-right):not(.bp5-vertical){\n    bottom:0;\n    right:0;\n    top:0;\n    width:50%; }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-enter, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-appear{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-enter-active, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-appear-active{\n      -webkit-transform:translateX(0);\n              transform:translateX(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-exit{\n      -webkit-transform:translateX(0);\n              transform:translateX(0); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-exit-active{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n  .bp5-position-right).bp5-vertical{\n    bottom:0;\n    height:50%;\n    left:0;\n    right:0; }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-enter, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-appear{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-enter-active, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-appear-active{\n      -webkit-transform:translateY(0);\n              transform:translateY(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-exit{\n      -webkit-transform:translateY(0);\n              transform:translateY(0); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-exit-active{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-dark,\n  .bp5-dark .bp5-drawer{\n    background:#30404d;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n\n.bp5-drawer-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  border-radius:0;\n  -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  min-height:40px;\n  padding:5px;\n  padding-left:20px;\n  position:relative; }\n  .bp5-drawer-header .bp5-icon-large,\n  .bp5-drawer-header .bp5-icon{\n    color:#5c7080;\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    margin-right:10px; }\n  .bp5-drawer-header .bp5-heading{\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    line-height:inherit;\n    margin:0; }\n    .bp5-drawer-header .bp5-heading:last-child{\n      margin-right:20px; }\n  .bp5-dark .bp5-drawer-header{\n    -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.4);\n            box-shadow:0 1px 0 rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-drawer-header .bp5-icon-large,\n    .bp5-dark .bp5-drawer-header .bp5-icon{\n      color:#a7b6c2; }\n\n.bp5-drawer-body{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  line-height:18px;\n  overflow:auto; }\n\n.bp5-drawer-footer{\n  -webkit-box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.15);\n          box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.15);\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  padding:10px 20px;\n  position:relative; }\n  .bp5-dark .bp5-drawer-footer{\n    -webkit-box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.4); }\n.bp5-editable-text{\n  cursor:text;\n  display:inline-block;\n  max-width:100%;\n  position:relative;\n  vertical-align:top;\n  white-space:nowrap; }\n  .bp5-editable-text::before{\n    bottom:-3px;\n    left:-3px;\n    position:absolute;\n    right:-3px;\n    top:-3px;\n    border-radius:3px;\n    content:\"\";\n    -webkit-transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-editable-text:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15); }\n  .bp5-editable-text.bp5-editable-text-editing::before{\n    background-color:#ffffff;\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-disabled::before{\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-primary .bp5-editable-text-content{\n    color:#137cbd; }\n  .bp5-editable-text.bp5-intent-primary:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(19, 124, 189, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(19, 124, 189, 0.4); }\n  .bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-intent-success .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-success .bp5-editable-text-content{\n    color:#0f9960; }\n  .bp5-editable-text.bp5-intent-success:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px rgba(15, 153, 96, 0.4);\n            box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px rgba(15, 153, 96, 0.4); }\n  .bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-warning .bp5-editable-text-content{\n    color:#d9822b; }\n  .bp5-editable-text.bp5-intent-warning:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px rgba(217, 130, 43, 0.4);\n            box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px rgba(217, 130, 43, 0.4); }\n  .bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-danger .bp5-editable-text-content{\n    color:#db3737; }\n  .bp5-editable-text.bp5-intent-danger:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px rgba(219, 55, 55, 0.4);\n            box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px rgba(219, 55, 55, 0.4); }\n  .bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-editable-text:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.15);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.15); }\n  .bp5-dark .bp5-editable-text.bp5-editable-text-editing::before{\n    background-color:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-disabled::before{\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-content{\n    color:#48aff0; }\n  .bp5-dark .bp5-editable-text.bp5-intent-primary:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(72, 175, 240, 0), 0 0 0 0 rgba(72, 175, 240, 0), inset 0 0 0 1px rgba(72, 175, 240, 0.4);\n            box-shadow:0 0 0 0 rgba(72, 175, 240, 0), 0 0 0 0 rgba(72, 175, 240, 0), inset 0 0 0 1px rgba(72, 175, 240, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #48aff0, 0 0 0 3px rgba(72, 175, 240, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #48aff0, 0 0 0 3px rgba(72, 175, 240, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-content{\n    color:#3dcc91; }\n  .bp5-dark .bp5-editable-text.bp5-intent-success:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(61, 204, 145, 0), 0 0 0 0 rgba(61, 204, 145, 0), inset 0 0 0 1px rgba(61, 204, 145, 0.4);\n            box-shadow:0 0 0 0 rgba(61, 204, 145, 0), 0 0 0 0 rgba(61, 204, 145, 0), inset 0 0 0 1px rgba(61, 204, 145, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #3dcc91, 0 0 0 3px rgba(61, 204, 145, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #3dcc91, 0 0 0 3px rgba(61, 204, 145, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-content{\n    color:#ffb366; }\n  .bp5-dark .bp5-editable-text.bp5-intent-warning:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(255, 179, 102, 0), 0 0 0 0 rgba(255, 179, 102, 0), inset 0 0 0 1px rgba(255, 179, 102, 0.4);\n            box-shadow:0 0 0 0 rgba(255, 179, 102, 0), 0 0 0 0 rgba(255, 179, 102, 0), inset 0 0 0 1px rgba(255, 179, 102, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #ffb366, 0 0 0 3px rgba(255, 179, 102, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #ffb366, 0 0 0 3px rgba(255, 179, 102, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-content{\n    color:#ff7373; }\n  .bp5-dark .bp5-editable-text.bp5-intent-danger:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(255, 115, 115, 0), 0 0 0 0 rgba(255, 115, 115, 0), inset 0 0 0 1px rgba(255, 115, 115, 0.4);\n            box-shadow:0 0 0 0 rgba(255, 115, 115, 0), 0 0 0 0 rgba(255, 115, 115, 0), inset 0 0 0 1px rgba(255, 115, 115, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #ff7373, 0 0 0 3px rgba(255, 115, 115, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #ff7373, 0 0 0 3px rgba(255, 115, 115, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-editable-text-input,\n.bp5-editable-text-content{\n  color:inherit;\n  display:inherit;\n  font:inherit;\n  letter-spacing:inherit;\n  max-width:inherit;\n  min-width:inherit;\n  position:relative;\n  resize:none;\n  text-transform:inherit;\n  vertical-align:top; }\n\n.bp5-editable-text-input{\n  background:none;\n  border:none;\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  padding:0;\n  white-space:pre-wrap;\n  width:100%; }\n  .bp5-editable-text-input::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input:focus{\n    outline:none; }\n  .bp5-editable-text-input::-ms-clear{\n    display:none; }\n\n.bp5-editable-text-content{\n  overflow:hidden;\n  padding-right:2px;\n  text-overflow:ellipsis;\n  white-space:pre; }\n  .bp5-editable-text-editing > .bp5-editable-text-content{\n    left:0;\n    position:absolute;\n    visibility:hidden; }\n  .bp5-editable-text-placeholder > .bp5-editable-text-content{\n    color:rgba(92, 112, 128, 0.6); }\n    .bp5-dark .bp5-editable-text-placeholder > .bp5-editable-text-content{\n      color:rgba(167, 182, 194, 0.6); }\n\n.bp5-editable-text.bp5-multiline{\n  display:block; }\n  .bp5-editable-text.bp5-multiline .bp5-editable-text-content{\n    overflow:auto;\n    white-space:pre-wrap;\n    word-wrap:break-word; }\n.bp5-divider{\n  border-bottom:1px solid rgba(16, 22, 26, 0.15);\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  margin:5px; }\n  .bp5-dark .bp5-divider{\n    border-color:rgba(16, 22, 26, 0.4); }\n.bp5-control-group{\n  -webkit-transform:translateZ(0);\n          transform:translateZ(0);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:stretch;\n      -ms-flex-align:stretch;\n          align-items:stretch; }\n  .bp5-control-group > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-control-group > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-control-group .bp5-button,\n  .bp5-control-group .bp5-html-select,\n  .bp5-control-group .bp5-input,\n  .bp5-control-group .bp5-select{\n    position:relative; }\n  .bp5-control-group .bp5-input{\n    border-radius:inherit;\n    z-index:2; }\n    .bp5-control-group .bp5-input:focus{\n      border-radius:3px;\n      z-index:14; }\n    .bp5-control-group .bp5-input[class*=\"bp5-intent\"]{\n      z-index:13; }\n      .bp5-control-group .bp5-input[class*=\"bp5-intent\"]:focus{\n        z-index:15; }\n    .bp5-control-group .bp5-input[readonly], .bp5-control-group .bp5-input:disabled, .bp5-control-group .bp5-input.bp5-disabled{\n      z-index:1; }\n  .bp5-control-group .bp5-input-group[class*=\"bp5-intent\"] .bp5-input{\n    z-index:13; }\n    .bp5-control-group .bp5-input-group[class*=\"bp5-intent\"] .bp5-input:focus{\n      z-index:15; }\n  .bp5-control-group .bp5-button,\n  .bp5-control-group .bp5-html-select select,\n  .bp5-control-group .bp5-select select{\n    -webkit-transform:translateZ(0);\n            transform:translateZ(0);\n    border-radius:inherit;\n    z-index:4; }\n    .bp5-control-group .bp5-button:focus,\n    .bp5-control-group .bp5-html-select select:focus,\n    .bp5-control-group .bp5-select select:focus{\n      z-index:5; }\n    .bp5-control-group .bp5-button:hover,\n    .bp5-control-group .bp5-html-select select:hover,\n    .bp5-control-group .bp5-select select:hover{\n      z-index:6; }\n    .bp5-control-group .bp5-button:active,\n    .bp5-control-group .bp5-html-select select:active,\n    .bp5-control-group .bp5-select select:active{\n      z-index:7; }\n    .bp5-control-group .bp5-button[readonly], .bp5-control-group .bp5-button:disabled, .bp5-control-group .bp5-button.bp5-disabled,\n    .bp5-control-group .bp5-html-select select[readonly],\n    .bp5-control-group .bp5-html-select select:disabled,\n    .bp5-control-group .bp5-html-select select.bp5-disabled,\n    .bp5-control-group .bp5-select select[readonly],\n    .bp5-control-group .bp5-select select:disabled,\n    .bp5-control-group .bp5-select select.bp5-disabled{\n      z-index:3; }\n    .bp5-control-group .bp5-button[class*=\"bp5-intent\"],\n    .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"],\n    .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]{\n      z-index:9; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:focus,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:focus,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:focus{\n        z-index:10; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:hover,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:hover,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:hover{\n        z-index:11; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:active,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:active,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:active{\n        z-index:12; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"][readonly], .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:disabled, .bp5-control-group .bp5-button[class*=\"bp5-intent\"].bp5-disabled,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"][readonly],\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:disabled,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"].bp5-disabled,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"][readonly],\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:disabled,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"].bp5-disabled{\n        z-index:8; }\n  .bp5-control-group .bp5-input-group > .bp5-icon,\n  .bp5-control-group .bp5-input-group > .bp5-button,\n  .bp5-control-group .bp5-input-group > .bp5-input-left-container,\n  .bp5-control-group .bp5-input-group > .bp5-input-action{\n    z-index:16; }\n  .bp5-control-group .bp5-select::after,\n  .bp5-control-group .bp5-html-select::after,\n  .bp5-control-group .bp5-select > .bp5-icon,\n  .bp5-control-group .bp5-html-select > .bp5-icon{\n    z-index:17; }\n  .bp5-control-group .bp5-select:focus-within{\n    z-index:5; }\n  .bp5-control-group:not(.bp5-vertical) > *:not(.bp5-divider){\n    margin-right:-1px; }\n  .bp5-control-group:not(.bp5-vertical) > .bp5-divider:not(:first-child){\n    margin-left:6px; }\n  .bp5-dark .bp5-control-group:not(.bp5-vertical) > *:not(.bp5-divider){\n    margin-right:0; }\n  .bp5-dark .bp5-control-group:not(.bp5-vertical) > .bp5-button + .bp5-button{\n    margin-left:1px; }\n  .bp5-control-group .bp5-popover-wrapper,\n  .bp5-control-group .bp5-popover-target{\n    border-radius:inherit; }\n  .bp5-control-group > :first-child{\n    border-radius:3px 0 0 3px; }\n  .bp5-control-group > :last-child{\n    border-radius:0 3px 3px 0;\n    margin-right:0; }\n  .bp5-control-group > :only-child{\n    border-radius:3px;\n    margin-right:0; }\n  .bp5-control-group .bp5-input-group .bp5-button{\n    border-radius:3px; }\n  .bp5-control-group .bp5-numeric-input:not(:first-child) .bp5-input-group{\n    border-bottom-left-radius:0;\n    border-top-left-radius:0; }\n  .bp5-control-group.bp5-fill{\n    width:100%; }\n  .bp5-control-group > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-control-group.bp5-fill > *:not(.bp5-fixed){\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-control-group.bp5-vertical{\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:column;\n            flex-direction:column; }\n    .bp5-control-group.bp5-vertical > *{\n      margin-top:-1px; }\n    .bp5-control-group.bp5-vertical > :first-child{\n      border-radius:3px 3px 0 0;\n      margin-top:0; }\n    .bp5-control-group.bp5-vertical > :last-child{\n      border-radius:0 0 3px 3px; }\n.bp5-control{\n  cursor:pointer;\n  display:block;\n  margin-bottom:10px;\n  position:relative;\n  text-transform:none; }\n  .bp5-control input:checked ~ .bp5-control-indicator{\n    background-color:#137cbd;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n  .bp5-control:hover input:checked ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n  .bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n    background:#0e5a8a;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n    background:rgba(19, 124, 189, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-dark .bp5-control input:checked ~ .bp5-control-indicator{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control:hover input:checked ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n    background-color:#0e5a8a;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n    background:rgba(14, 90, 138, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-control:not(.bp5-align-right){\n    padding-left:26px; }\n    .bp5-control:not(.bp5-align-right) .bp5-control-indicator{\n      margin-left:-26px; }\n  .bp5-control.bp5-align-right{\n    padding-right:26px; }\n    .bp5-control.bp5-align-right .bp5-control-indicator{\n      margin-right:-26px; }\n  .bp5-control.bp5-disabled{\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n  .bp5-control.bp5-inline{\n    display:inline-block;\n    margin-right:20px; }\n  .bp5-control input{\n    left:0;\n    opacity:0;\n    position:absolute;\n    top:0;\n    z-index:-1; }\n  .bp5-control .bp5-control-indicator{\n    background-clip:padding-box;\n    background-color:#f5f8fa;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n    border:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    cursor:pointer;\n    display:inline-block;\n    font-size:16px;\n    height:1em;\n    margin-right:10px;\n    margin-top:-3px;\n    position:relative;\n    -webkit-user-select:none;\n       -moz-user-select:none;\n        -ms-user-select:none;\n            user-select:none;\n    vertical-align:middle;\n    width:1em; }\n    .bp5-control .bp5-control-indicator::before{\n      content:\"\";\n      display:block;\n      height:1em;\n      width:1em; }\n  .bp5-control:hover .bp5-control-indicator{\n    background-color:#ebf1f5; }\n  .bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n    background:#d8e1e8;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-control input:disabled ~ .bp5-control-indicator{\n    background:rgba(206, 217, 224, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    cursor:not-allowed; }\n  .bp5-control input:focus ~ .bp5-control-indicator{\n    outline:rgba(19, 124, 189, 0.6) auto 2px;\n    outline-offset:2px;\n    -moz-outline-radius:6px; }\n  .bp5-control.bp5-align-right .bp5-control-indicator{\n    float:right;\n    margin-left:10px;\n    margin-top:1px; }\n  .bp5-control.bp5-large{\n    font-size:16px; }\n    .bp5-control.bp5-large:not(.bp5-align-right){\n      padding-left:30px; }\n      .bp5-control.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n        margin-left:-30px; }\n    .bp5-control.bp5-large.bp5-align-right{\n      padding-right:30px; }\n      .bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n        margin-right:-30px; }\n    .bp5-control.bp5-large .bp5-control-indicator{\n      font-size:20px; }\n    .bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n      margin-top:0; }\n  .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    background-color:#137cbd;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n  .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n  .bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n    background:#0e5a8a;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n    background:rgba(19, 124, 189, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-dark .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n    background-color:#0e5a8a;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n    background:rgba(14, 90, 138, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-control.bp5-checkbox .bp5-control-indicator{\n    border-radius:3px; }\n  .bp5-control.bp5-checkbox input:checked ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 5c-.28 0-.53.11-.71.29L7 9.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29s.53-.11.71-.29l5-5A1.003 1.003 0 0012 5z' fill='white'/%3e%3c/svg%3e\"); }\n  .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 7H5c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1z' fill='white'/%3e%3c/svg%3e\"); }\n  .bp5-control.bp5-radio .bp5-control-indicator{\n    border-radius:50%; }\n  .bp5-control.bp5-radio input:checked ~ .bp5-control-indicator::before{\n    background-image:radial-gradient(#ffffff, #ffffff 28%, transparent 32%); }\n  .bp5-control.bp5-radio input:checked:disabled ~ .bp5-control-indicator::before{\n    opacity:0.5; }\n  .bp5-control.bp5-radio input:focus ~ .bp5-control-indicator{\n    -moz-outline-radius:16px; }\n  .bp5-control.bp5-switch input ~ .bp5-control-indicator{\n    background:rgba(167, 182, 194, 0.5); }\n  .bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n    background:rgba(115, 134, 148, 0.5); }\n  .bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n    background:rgba(92, 112, 128, 0.5); }\n  .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n    background:rgba(206, 217, 224, 0.5); }\n    .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n      background:rgba(255, 255, 255, 0.8); }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:#137cbd; }\n  .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:#106ba3; }\n  .bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n    background:#0e5a8a; }\n  .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background:rgba(19, 124, 189, 0.5); }\n    .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n      background:rgba(255, 255, 255, 0.8); }\n  .bp5-control.bp5-switch:not(.bp5-align-right){\n    padding-left:38px; }\n    .bp5-control.bp5-switch:not(.bp5-align-right) .bp5-control-indicator{\n      margin-left:-38px; }\n  .bp5-control.bp5-switch.bp5-align-right{\n    padding-right:38px; }\n    .bp5-control.bp5-switch.bp5-align-right .bp5-control-indicator{\n      margin-right:-38px; }\n  .bp5-control.bp5-switch .bp5-control-indicator{\n    border:none;\n    border-radius:1.75em;\n    -webkit-box-shadow:none !important;\n            box-shadow:none !important;\n    min-width:1.75em;\n    -webkit-transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    width:auto; }\n    .bp5-control.bp5-switch .bp5-control-indicator::before{\n      background:#ffffff;\n      border-radius:50%;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n      height:calc(1em - 4px);\n      left:0;\n      margin:2px;\n      position:absolute;\n      -webkit-transition:left 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n      transition:left 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n      width:calc(1em - 4px); }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n    left:calc(100% - 1em); }\n  .bp5-control.bp5-switch.bp5-large:not(.bp5-align-right){\n    padding-left:45px; }\n    .bp5-control.bp5-switch.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n      margin-left:-45px; }\n  .bp5-control.bp5-switch.bp5-large.bp5-align-right{\n    padding-right:45px; }\n    .bp5-control.bp5-switch.bp5-large.bp5-align-right .bp5-control-indicator{\n      margin-right:-45px; }\n  .bp5-dark .bp5-control.bp5-switch input ~ .bp5-control-indicator{\n    background:rgba(16, 22, 26, 0.5); }\n  .bp5-dark .bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n    background:rgba(16, 22, 26, 0.7); }\n  .bp5-dark .bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n    background:rgba(16, 22, 26, 0.9); }\n  .bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n    background:rgba(57, 75, 89, 0.5); }\n    .bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n      background:rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:#137cbd; }\n  .bp5-dark .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:#106ba3; }\n  .bp5-dark .bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n    background:#0e5a8a; }\n  .bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background:rgba(14, 90, 138, 0.5); }\n    .bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n      background:rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-switch .bp5-control-indicator::before{\n    background:#394b59;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-control.bp5-switch .bp5-switch-inner-text{\n    font-size:0.7em;\n    text-align:center; }\n  .bp5-control.bp5-switch .bp5-control-indicator-child:first-child{\n    line-height:0;\n    margin-left:0.5em;\n    margin-right:1.2em;\n    visibility:hidden; }\n  .bp5-control.bp5-switch .bp5-control-indicator-child:last-child{\n    line-height:1em;\n    margin-left:1.2em;\n    margin-right:0.5em;\n    visibility:visible; }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:first-child{\n    line-height:1em;\n    visibility:visible; }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:last-child{\n    line-height:0;\n    visibility:hidden; }\n  .bp5-dark .bp5-control{\n    color:#f5f8fa; }\n    .bp5-dark .bp5-control.bp5-disabled{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-control .bp5-control-indicator{\n      background-color:#394b59;\n      background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n      background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-control:hover .bp5-control-indicator{\n      background-color:#30404d; }\n    .bp5-dark .bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n      background:#202b33;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-control input:disabled ~ .bp5-control-indicator{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      cursor:not-allowed; }\n    .bp5-dark .bp5-control.bp5-checkbox input:disabled:checked ~ .bp5-control-indicator, .bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n      color:rgba(167, 182, 194, 0.6); }\n.bp5-file-input{\n  cursor:pointer;\n  display:inline-block;\n  height:30px;\n  position:relative; }\n  .bp5-file-input input{\n    margin:0;\n    min-width:200px;\n    opacity:0; }\n    .bp5-file-input input:disabled + .bp5-file-upload-input,\n    .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n      background:rgba(206, 217, 224, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed;\n      resize:none; }\n      .bp5-file-input input:disabled + .bp5-file-upload-input::after,\n      .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n        background-color:rgba(206, 217, 224, 0.5);\n        background-image:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:rgba(92, 112, 128, 0.6);\n        cursor:not-allowed;\n        outline:none; }\n        .bp5-file-input input:disabled + .bp5-file-upload-input::after.bp5-active, .bp5-file-input input:disabled + .bp5-file-upload-input::after.bp5-active:hover,\n        .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after.bp5-active,\n        .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after.bp5-active:hover{\n          background:rgba(206, 217, 224, 0.7); }\n      .bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input, .bp5-dark\n      .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n        background:rgba(57, 75, 89, 0.5);\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:rgba(167, 182, 194, 0.6); }\n        .bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input::after, .bp5-dark\n        .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n          background-color:rgba(57, 75, 89, 0.5);\n          background-image:none;\n          -webkit-box-shadow:none;\n                  box-shadow:none;\n          color:rgba(167, 182, 194, 0.6); }\n          .bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input::after.bp5-active, .bp5-dark\n          .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after.bp5-active{\n            background:rgba(57, 75, 89, 0.7); }\n  .bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n    color:#182026; }\n  .bp5-dark .bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n    color:#f5f8fa; }\n  .bp5-file-input.bp5-fill{\n    width:100%; }\n  .bp5-file-input.bp5-large,\n  .bp5-large .bp5-file-input{\n    height:40px; }\n  .bp5-file-input .bp5-file-upload-input-custom-text::after{\n    content:attr(bp5-button-text); }\n\n.bp5-file-upload-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  color:#182026;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  -webkit-transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  color:rgba(92, 112, 128, 0.6);\n  left:0;\n  padding-right:80px;\n  position:absolute;\n  right:0;\n  top:0;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-file-upload-input::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input:focus, .bp5-file-upload-input.bp5-active{\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-file-upload-input[type=\"search\"], .bp5-file-upload-input.bp5-round{\n    border-radius:30px;\n    -webkit-box-sizing:border-box;\n            box-sizing:border-box;\n    padding-left:10px; }\n  .bp5-file-upload-input[readonly]{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15); }\n  .bp5-file-upload-input:disabled, .bp5-file-upload-input.bp5-disabled{\n    background:rgba(206, 217, 224, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    resize:none; }\n  .bp5-file-upload-input::after{\n    background-color:#f5f8fa;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    color:#182026;\n    min-height:24px;\n    min-width:24px;\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    border-radius:3px;\n    content:\"Browse\";\n    line-height:24px;\n    margin:3px;\n    position:absolute;\n    right:0;\n    text-align:center;\n    top:0;\n    width:70px; }\n    .bp5-file-upload-input::after:hover{\n      background-clip:padding-box;\n      background-color:#ebf1f5;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n    .bp5-file-upload-input::after:active, .bp5-file-upload-input::after.bp5-active{\n      background-color:#d8e1e8;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-file-upload-input::after:disabled, .bp5-file-upload-input::after.bp5-disabled{\n      background-color:rgba(206, 217, 224, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed;\n      outline:none; }\n      .bp5-file-upload-input::after:disabled.bp5-active, .bp5-file-upload-input::after:disabled.bp5-active:hover, .bp5-file-upload-input::after.bp5-disabled.bp5-active, .bp5-file-upload-input::after.bp5-disabled.bp5-active:hover{\n        background:rgba(206, 217, 224, 0.7); }\n  .bp5-file-upload-input:hover::after{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n  .bp5-file-upload-input:active::after{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-large .bp5-file-upload-input{\n    font-size:16px;\n    height:40px;\n    line-height:40px;\n    padding-right:95px; }\n    .bp5-large .bp5-file-upload-input[type=\"search\"], .bp5-large .bp5-file-upload-input.bp5-round{\n      padding:0 15px; }\n    .bp5-large .bp5-file-upload-input::after{\n      min-height:30px;\n      min-width:30px;\n      line-height:30px;\n      margin:5px;\n      width:85px; }\n  .bp5-dark .bp5-file-upload-input{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa;\n    color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-file-upload-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-file-upload-input:disabled, .bp5-dark .bp5-file-upload-input.bp5-disabled{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::after{\n      background-color:#394b59;\n      background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n      background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n      color:#f5f8fa; }\n      .bp5-dark .bp5-file-upload-input::after:hover, .bp5-dark .bp5-file-upload-input::after:active, .bp5-dark .bp5-file-upload-input::after.bp5-active{\n        color:#f5f8fa; }\n      .bp5-dark .bp5-file-upload-input::after:hover{\n        background-color:#30404d;\n        -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-file-upload-input::after:active, .bp5-dark .bp5-file-upload-input::after.bp5-active{\n        background-color:#202b33;\n        background-image:none;\n        -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n                box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n      .bp5-dark .bp5-file-upload-input::after:disabled, .bp5-dark .bp5-file-upload-input::after.bp5-disabled{\n        background-color:rgba(57, 75, 89, 0.5);\n        background-image:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:rgba(167, 182, 194, 0.6); }\n        .bp5-dark .bp5-file-upload-input::after:disabled.bp5-active, .bp5-dark .bp5-file-upload-input::after.bp5-disabled.bp5-active{\n          background:rgba(57, 75, 89, 0.7); }\n      .bp5-dark .bp5-file-upload-input::after .bp5-button-spinner .bp5-spinner-head{\n        background:rgba(16, 22, 26, 0.5);\n        stroke:#8a9ba8; }\n    .bp5-dark .bp5-file-upload-input:hover::after{\n      background-color:#30404d;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-file-upload-input:active::after{\n      background-color:#202b33;\n      background-image:none;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n.bp5-file-upload-input::after{\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n.bp5-form-group{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin:0 0 15px; }\n  .bp5-form-group label.bp5-label{\n    margin-bottom:5px; }\n  .bp5-form-group .bp5-control{\n    margin-top:7px; }\n  .bp5-form-group .bp5-form-group-sub-label,\n  .bp5-form-group .bp5-form-helper-text{\n    color:#5c7080;\n    font-size:12px; }\n  .bp5-form-group .bp5-form-group-sub-label{\n    margin-bottom:5px; }\n  .bp5-form-group .bp5-form-helper-text{\n    margin-top:5px; }\n  .bp5-form-group.bp5-intent-primary .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n    color:#106ba3; }\n  .bp5-form-group.bp5-intent-success .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n    color:#0d8050; }\n  .bp5-form-group.bp5-intent-warning .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n    color:#bf7326; }\n  .bp5-form-group.bp5-intent-danger .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n    color:#c23030; }\n  .bp5-form-group.bp5-inline{\n    -webkit-box-align:start;\n        -ms-flex-align:start;\n            align-items:flex-start;\n    -webkit-box-orient:horizontal;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:row;\n            flex-direction:row; }\n    .bp5-form-group.bp5-inline.bp5-large label.bp5-label{\n      line-height:40px;\n      margin:0 10px 0 0; }\n    .bp5-form-group.bp5-inline label.bp5-label{\n      line-height:30px;\n      margin:0 10px 0 0; }\n  .bp5-form-group.bp5-disabled .bp5-label,\n  .bp5-form-group.bp5-disabled .bp5-text-muted,\n  .bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-disabled .bp5-form-helper-text{\n    color:rgba(92, 112, 128, 0.6) !important; }\n  .bp5-dark .bp5-form-group.bp5-intent-primary .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n    color:#48aff0; }\n  .bp5-dark .bp5-form-group.bp5-intent-success .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n    color:#3dcc91; }\n  .bp5-dark .bp5-form-group.bp5-intent-warning .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n    color:#ffb366; }\n  .bp5-dark .bp5-form-group.bp5-intent-danger .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n    color:#ff7373; }\n  .bp5-dark .bp5-form-group .bp5-form-helper-text{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-label,\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-text-muted,\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-form-helper-text{\n    color:rgba(167, 182, 194, 0.6) !important; }\n.bp5-input-group{\n  display:block;\n  position:relative; }\n  .bp5-input-group .bp5-input{\n    position:relative;\n    width:100%; }\n    .bp5-input-group .bp5-input:not(:first-child){\n      padding-left:30px; }\n    .bp5-input-group .bp5-input:not(:last-child){\n      padding-right:30px; }\n  .bp5-input-group .bp5-input-action,\n  .bp5-input-group > .bp5-input-left-container,\n  .bp5-input-group > .bp5-button,\n  .bp5-input-group > .bp5-icon{\n    position:absolute;\n    top:0; }\n    .bp5-input-group .bp5-input-action:first-child,\n    .bp5-input-group > .bp5-input-left-container:first-child,\n    .bp5-input-group > .bp5-button:first-child,\n    .bp5-input-group > .bp5-icon:first-child{\n      left:0; }\n    .bp5-input-group .bp5-input-action:last-child,\n    .bp5-input-group > .bp5-input-left-container:last-child,\n    .bp5-input-group > .bp5-button:last-child,\n    .bp5-input-group > .bp5-icon:last-child{\n      right:0; }\n  .bp5-input-group .bp5-button{\n    min-height:24px;\n    min-width:24px;\n    margin:3px;\n    padding:0 7px; }\n    .bp5-input-group .bp5-button:empty{\n      padding:0; }\n  .bp5-input-group > .bp5-input-left-container,\n  .bp5-input-group > .bp5-icon{\n    z-index:1; }\n  .bp5-input-group > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group > .bp5-icon{\n    color:#5c7080; }\n    .bp5-input-group > .bp5-input-left-container > .bp5-icon:empty,\n    .bp5-input-group > .bp5-icon:empty{\n      font-family:\"blueprint-icons-16\", sans-serif;\n      font-size:16px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1;\n      -moz-osx-font-smoothing:grayscale;\n      -webkit-font-smoothing:antialiased; }\n  .bp5-input-group > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group > .bp5-icon,\n  .bp5-input-group .bp5-input-action > .bp5-spinner{\n    margin:7px; }\n  .bp5-input-group .bp5-tag{\n    margin:5px; }\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus),\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n    color:#5c7080; }\n    .bp5-dark .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus), .bp5-dark\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n      color:#a7b6c2; }\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large{\n      color:#5c7080; }\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled,\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled{\n    color:rgba(92, 112, 128, 0.6) !important; }\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-large,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-standard,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-large{\n      color:rgba(92, 112, 128, 0.6) !important; }\n  .bp5-input-group.bp5-disabled{\n    cursor:not-allowed; }\n    .bp5-input-group.bp5-disabled .bp5-icon{\n      color:rgba(92, 112, 128, 0.6); }\n  .bp5-input-group.bp5-large .bp5-button{\n    min-height:30px;\n    min-width:30px;\n    margin:5px; }\n  .bp5-input-group.bp5-large > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group.bp5-large > .bp5-icon,\n  .bp5-input-group.bp5-large .bp5-input-action > .bp5-spinner{\n    margin:12px; }\n  .bp5-input-group.bp5-large .bp5-input{\n    font-size:16px;\n    height:40px;\n    line-height:40px; }\n    .bp5-input-group.bp5-large .bp5-input[type=\"search\"], .bp5-input-group.bp5-large .bp5-input.bp5-round{\n      padding:0 15px; }\n    .bp5-input-group.bp5-large .bp5-input:not(:first-child){\n      padding-left:40px; }\n    .bp5-input-group.bp5-large .bp5-input:not(:last-child){\n      padding-right:40px; }\n  .bp5-input-group.bp5-small .bp5-button{\n    min-height:20px;\n    min-width:20px;\n    margin:2px; }\n  .bp5-input-group.bp5-small .bp5-tag{\n    min-height:20px;\n    min-width:20px;\n    margin:2px; }\n  .bp5-input-group.bp5-small > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group.bp5-small > .bp5-icon,\n  .bp5-input-group.bp5-small .bp5-input-action > .bp5-spinner{\n    margin:4px; }\n  .bp5-input-group.bp5-small .bp5-input{\n    font-size:12px;\n    height:24px;\n    line-height:24px;\n    padding-left:8px;\n    padding-right:8px; }\n    .bp5-input-group.bp5-small .bp5-input[type=\"search\"], .bp5-input-group.bp5-small .bp5-input.bp5-round{\n      padding:0 12px; }\n    .bp5-input-group.bp5-small .bp5-input:not(:first-child){\n      padding-left:24px; }\n    .bp5-input-group.bp5-small .bp5-input:not(:last-child){\n      padding-right:24px; }\n  .bp5-input-group.bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    width:100%; }\n  .bp5-input-group.bp5-round .bp5-button,\n  .bp5-input-group.bp5-round .bp5-input,\n  .bp5-input-group.bp5-round .bp5-tag{\n    border-radius:30px; }\n  .bp5-dark .bp5-input-group .bp5-icon{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-input-group.bp5-disabled .bp5-icon{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-input-group.bp5-intent-primary .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-primary .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-primary .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n              box-shadow:inset 0 0 0 1px #137cbd; }\n    .bp5-input-group.bp5-intent-primary .bp5-input:disabled, .bp5-input-group.bp5-intent-primary .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-primary > .bp5-icon{\n    color:#106ba3; }\n    .bp5-dark .bp5-input-group.bp5-intent-primary > .bp5-icon{\n      color:#48aff0; }\n  .bp5-input-group.bp5-intent-success .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-success .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-success .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n              box-shadow:inset 0 0 0 1px #0f9960; }\n    .bp5-input-group.bp5-intent-success .bp5-input:disabled, .bp5-input-group.bp5-intent-success .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-success > .bp5-icon{\n    color:#0d8050; }\n    .bp5-dark .bp5-input-group.bp5-intent-success > .bp5-icon{\n      color:#3dcc91; }\n  .bp5-input-group.bp5-intent-warning .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-warning .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-warning .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n              box-shadow:inset 0 0 0 1px #d9822b; }\n    .bp5-input-group.bp5-intent-warning .bp5-input:disabled, .bp5-input-group.bp5-intent-warning .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-warning > .bp5-icon{\n    color:#bf7326; }\n    .bp5-dark .bp5-input-group.bp5-intent-warning > .bp5-icon{\n      color:#ffb366; }\n  .bp5-input-group.bp5-intent-danger .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-danger .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-danger .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #db3737;\n              box-shadow:inset 0 0 0 1px #db3737; }\n    .bp5-input-group.bp5-intent-danger .bp5-input:disabled, .bp5-input-group.bp5-intent-danger .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-danger > .bp5-icon{\n    color:#c23030; }\n    .bp5-dark .bp5-input-group.bp5-intent-danger > .bp5-icon{\n      color:#ff7373; }\n.bp5-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  color:#182026;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  -webkit-transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle; }\n  .bp5-input::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input:focus, .bp5-input.bp5-active{\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-input[type=\"search\"], .bp5-input.bp5-round{\n    border-radius:30px;\n    -webkit-box-sizing:border-box;\n            box-sizing:border-box;\n    padding-left:10px; }\n  .bp5-input[readonly]{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15); }\n  .bp5-input:disabled, .bp5-input.bp5-disabled{\n    background:rgba(206, 217, 224, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    resize:none; }\n  .bp5-input.bp5-large{\n    font-size:16px;\n    height:40px;\n    line-height:40px; }\n    .bp5-input.bp5-large[type=\"search\"], .bp5-input.bp5-large.bp5-round{\n      padding:0 15px; }\n  .bp5-input.bp5-small{\n    font-size:12px;\n    height:24px;\n    line-height:24px;\n    padding-left:8px;\n    padding-right:8px; }\n    .bp5-input.bp5-small[type=\"search\"], .bp5-input.bp5-small.bp5-round{\n      padding:0 12px; }\n  .bp5-input.bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    width:100%; }\n  .bp5-dark .bp5-input{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark .bp5-input::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-input:disabled, .bp5-dark .bp5-input.bp5-disabled{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n  .bp5-input.bp5-intent-primary{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-primary:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-primary[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n              box-shadow:inset 0 0 0 1px #137cbd; }\n    .bp5-input.bp5-intent-primary:disabled, .bp5-input.bp5-intent-primary.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-primary{\n      -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-primary:focus{\n        -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-primary[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n                box-shadow:inset 0 0 0 1px #137cbd; }\n      .bp5-dark .bp5-input.bp5-intent-primary:disabled, .bp5-dark .bp5-input.bp5-intent-primary.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input.bp5-intent-success{\n    -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-success:focus{\n      -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-success[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n              box-shadow:inset 0 0 0 1px #0f9960; }\n    .bp5-input.bp5-intent-success:disabled, .bp5-input.bp5-intent-success.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-success{\n      -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-success:focus{\n        -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-success[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n                box-shadow:inset 0 0 0 1px #0f9960; }\n      .bp5-dark .bp5-input.bp5-intent-success:disabled, .bp5-dark .bp5-input.bp5-intent-success.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input.bp5-intent-warning{\n    -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-warning:focus{\n      -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-warning[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n              box-shadow:inset 0 0 0 1px #d9822b; }\n    .bp5-input.bp5-intent-warning:disabled, .bp5-input.bp5-intent-warning.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-warning{\n      -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-warning:focus{\n        -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-warning[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n                box-shadow:inset 0 0 0 1px #d9822b; }\n      .bp5-dark .bp5-input.bp5-intent-warning:disabled, .bp5-dark .bp5-input.bp5-intent-warning.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input.bp5-intent-danger{\n    -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-danger:focus{\n      -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-danger[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #db3737;\n              box-shadow:inset 0 0 0 1px #db3737; }\n    .bp5-input.bp5-intent-danger:disabled, .bp5-input.bp5-intent-danger.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-danger{\n      -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-danger:focus{\n        -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-danger[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #db3737;\n                box-shadow:inset 0 0 0 1px #db3737; }\n      .bp5-dark .bp5-input.bp5-intent-danger:disabled, .bp5-dark .bp5-input.bp5-intent-danger.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input::-ms-clear{\n    display:none; }\ntextarea.bp5-input{\n  max-width:100%;\n  padding:10px; }\n  textarea.bp5-input, textarea.bp5-input.bp5-large, textarea.bp5-input.bp5-small{\n    height:auto;\n    line-height:inherit; }\n  textarea.bp5-input.bp5-small{\n    padding:8px; }\n  .bp5-dark textarea.bp5-input{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark textarea.bp5-input::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark textarea.bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark textarea.bp5-input:disabled, .bp5-dark textarea.bp5-input.bp5-disabled{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\nlabel.bp5-label{\n  display:block;\n  margin-bottom:15px;\n  margin-top:0; }\n  label.bp5-label .bp5-html-select,\n  label.bp5-label .bp5-input,\n  label.bp5-label .bp5-select,\n  label.bp5-label .bp5-slider,\n  label.bp5-label .bp5-popover-wrapper{\n    display:block;\n    margin-top:5px;\n    text-transform:none; }\n  label.bp5-label .bp5-button-group{\n    margin-top:5px; }\n  label.bp5-label .bp5-select select,\n  label.bp5-label .bp5-html-select select{\n    font-weight:400;\n    vertical-align:top;\n    width:100%; }\n  label.bp5-label.bp5-disabled,\n  label.bp5-label.bp5-disabled .bp5-text-muted{\n    color:rgba(92, 112, 128, 0.6); }\n  label.bp5-label.bp5-inline{\n    line-height:30px; }\n    label.bp5-label.bp5-inline .bp5-html-select,\n    label.bp5-label.bp5-inline .bp5-input,\n    label.bp5-label.bp5-inline .bp5-input-group,\n    label.bp5-label.bp5-inline .bp5-select,\n    label.bp5-label.bp5-inline .bp5-popover-wrapper{\n      display:inline-block;\n      margin:0 0 0 5px;\n      vertical-align:top; }\n    label.bp5-label.bp5-inline .bp5-button-group{\n      margin:0 0 0 5px; }\n    label.bp5-label.bp5-inline .bp5-input-group .bp5-input{\n      margin-left:0; }\n    label.bp5-label.bp5-inline.bp5-large{\n      line-height:40px; }\n  label.bp5-label:not(.bp5-inline) .bp5-popover-target{\n    display:block; }\n  .bp5-dark label.bp5-label{\n    color:#f5f8fa; }\n    .bp5-dark label.bp5-label.bp5-disabled,\n    .bp5-dark label.bp5-label.bp5-disabled .bp5-text-muted{\n      color:rgba(167, 182, 194, 0.6); }\n.bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 14px;\n          flex:1 1 14px;\n  min-height:0;\n  padding:0;\n  width:30px; }\n  .bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button:first-child{\n    border-radius:0 3px 0 0; }\n  .bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button:last-child{\n    border-radius:0 0 3px 0; }\n\n.bp5-numeric-input .bp5-button-group.bp5-vertical:first-child > .bp5-button:first-child{\n  border-radius:3px 0 0 0; }\n\n.bp5-numeric-input .bp5-button-group.bp5-vertical:first-child > .bp5-button:last-child{\n  border-radius:0 0 0 3px; }\n\n.bp5-numeric-input.bp5-large .bp5-button-group.bp5-vertical > .bp5-button{\n  width:40px; }\n\nform{\n  display:block; }\n.bp5-html-select select,\n.bp5-select select{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  border:none;\n  border-radius:3px;\n  cursor:pointer;\n  font-size:14px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  background-color:#f5f8fa;\n  background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n  background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n  color:#182026;\n  -moz-appearance:none;\n  -webkit-appearance:none;\n  border-radius:3px;\n  height:30px;\n  padding:0 25px 0 10px;\n  width:100%; }\n  .bp5-html-select select > *, .bp5-select select > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-html-select select > .bp5-fill, .bp5-select select > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-html-select select::before,\n  .bp5-select select::before, .bp5-html-select select > *, .bp5-select select > *{\n    margin-right:7px; }\n  .bp5-html-select select:empty::before,\n  .bp5-select select:empty::before,\n  .bp5-html-select select > :last-child,\n  .bp5-select select > :last-child{\n    margin-right:0; }\n  .bp5-html-select select:hover,\n  .bp5-select select:hover{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n  .bp5-html-select select:active,\n  .bp5-select select:active, .bp5-html-select select.bp5-active,\n  .bp5-select select.bp5-active{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-html-select select:disabled,\n  .bp5-select select:disabled, .bp5-html-select select.bp5-disabled,\n  .bp5-select select.bp5-disabled{\n    background-color:rgba(206, 217, 224, 0.5);\n    background-image:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    outline:none; }\n    .bp5-html-select select:disabled.bp5-active,\n    .bp5-select select:disabled.bp5-active, .bp5-html-select select:disabled.bp5-active:hover,\n    .bp5-select select:disabled.bp5-active:hover, .bp5-html-select select.bp5-disabled.bp5-active,\n    .bp5-select select.bp5-disabled.bp5-active, .bp5-html-select select.bp5-disabled.bp5-active:hover,\n    .bp5-select select.bp5-disabled.bp5-active:hover{\n      background:rgba(206, 217, 224, 0.7); }\n\n.bp5-html-select.bp5-minimal select,\n.bp5-select.bp5-minimal select{\n  background:none;\n  -webkit-box-shadow:none;\n          box-shadow:none; }\n  .bp5-html-select.bp5-minimal select:hover,\n  .bp5-select.bp5-minimal select:hover{\n    background:rgba(167, 182, 194, 0.3);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:#182026;\n    text-decoration:none; }\n  .bp5-html-select.bp5-minimal select:active,\n  .bp5-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal select.bp5-active,\n  .bp5-select.bp5-minimal select.bp5-active{\n    background:rgba(115, 134, 148, 0.3);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:#182026; }\n  .bp5-html-select.bp5-minimal select:disabled,\n  .bp5-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal select:disabled:hover,\n  .bp5-select.bp5-minimal select:disabled:hover, .bp5-html-select.bp5-minimal select.bp5-disabled,\n  .bp5-select.bp5-minimal select.bp5-disabled, .bp5-html-select.bp5-minimal select.bp5-disabled:hover,\n  .bp5-select.bp5-minimal select.bp5-disabled:hover{\n    background:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n    .bp5-html-select.bp5-minimal select:disabled.bp5-active,\n    .bp5-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal select:disabled:hover.bp5-active,\n    .bp5-select.bp5-minimal select:disabled:hover.bp5-active, .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-disabled:hover.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-disabled:hover.bp5-active{\n      background:rgba(115, 134, 148, 0.3); }\n  .bp5-dark .bp5-html-select.bp5-minimal select, .bp5-html-select.bp5-minimal .bp5-dark select,\n  .bp5-dark .bp5-select.bp5-minimal select, .bp5-select.bp5-minimal .bp5-dark select{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:inherit; }\n    .bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n    .bp5-dark .bp5-select.bp5-minimal select:hover, .bp5-select.bp5-minimal .bp5-dark select:hover, .bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n    .bp5-dark .bp5-select.bp5-minimal select:active, .bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n    .bp5-dark .bp5-select.bp5-minimal select:hover, .bp5-select.bp5-minimal .bp5-dark select:hover{\n      background:rgba(138, 155, 168, 0.15); }\n    .bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n    .bp5-dark .bp5-select.bp5-minimal select:active, .bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n      background:rgba(138, 155, 168, 0.3);\n      color:#f5f8fa; }\n    .bp5-dark .bp5-html-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal .bp5-dark select:disabled,\n    .bp5-dark .bp5-select.bp5-minimal select:disabled, .bp5-select.bp5-minimal .bp5-dark select:disabled, .bp5-dark .bp5-html-select.bp5-minimal select:disabled:hover, .bp5-html-select.bp5-minimal .bp5-dark select:disabled:hover,\n    .bp5-dark .bp5-select.bp5-minimal select:disabled:hover, .bp5-select.bp5-minimal .bp5-dark select:disabled:hover, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled:hover,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled:hover{\n      background:none;\n      color:rgba(167, 182, 194, 0.6);\n      cursor:not-allowed; }\n      .bp5-dark .bp5-html-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select:disabled.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select:disabled:hover.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select:disabled:hover.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select:disabled:hover.bp5-active, .bp5-select.bp5-minimal .bp5-dark select:disabled:hover.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled:hover.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled:hover.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled:hover.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled:hover.bp5-active{\n        background:rgba(138, 155, 168, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-primary,\n  .bp5-select.bp5-minimal select.bp5-intent-primary{\n    color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:hover{\n      background:rgba(19, 124, 189, 0.15);\n      color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n      background:rgba(19, 124, 189, 0.3);\n      color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled{\n      background:none;\n      color:rgba(16, 107, 163, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active{\n        background:rgba(19, 124, 189, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n      stroke:#106ba3; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary{\n      color:#48aff0; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.2);\n        color:#48aff0; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#48aff0; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(72, 175, 240, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-success,\n  .bp5-select.bp5-minimal select.bp5-intent-success{\n    color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n    .bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-success:hover{\n      background:rgba(15, 153, 96, 0.15);\n      color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n    .bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n      background:rgba(15, 153, 96, 0.3);\n      color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled{\n      background:none;\n      color:rgba(13, 128, 80, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active{\n        background:rgba(15, 153, 96, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n      stroke:#0d8050; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success{\n      color:#3dcc91; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.2);\n        color:#3dcc91; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#3dcc91; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(61, 204, 145, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-warning,\n  .bp5-select.bp5-minimal select.bp5-intent-warning{\n    color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:hover{\n      background:rgba(217, 130, 43, 0.15);\n      color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n      background:rgba(217, 130, 43, 0.3);\n      color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled{\n      background:none;\n      color:rgba(191, 115, 38, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active{\n        background:rgba(217, 130, 43, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n      stroke:#bf7326; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning{\n      color:#ffb366; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.2);\n        color:#ffb366; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#ffb366; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(255, 179, 102, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-danger,\n  .bp5-select.bp5-minimal select.bp5-intent-danger{\n    color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:hover{\n      background:rgba(219, 55, 55, 0.15);\n      color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n      background:rgba(219, 55, 55, 0.3);\n      color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled{\n      background:none;\n      color:rgba(194, 48, 48, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active{\n        background:rgba(219, 55, 55, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n      stroke:#c23030; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger{\n      color:#ff7373; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.2);\n        color:#ff7373; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#ff7373; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(255, 115, 115, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n\n.bp5-html-select.bp5-large select,\n.bp5-select.bp5-large select{\n  font-size:16px;\n  height:40px;\n  padding-right:35px; }\n\n.bp5-dark .bp5-html-select select, .bp5-dark .bp5-select select{\n  background-color:#394b59;\n  background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n  background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n  color:#f5f8fa; }\n  .bp5-dark .bp5-html-select select:hover, .bp5-dark .bp5-select select:hover, .bp5-dark .bp5-html-select select:active, .bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active, .bp5-dark .bp5-select select.bp5-active{\n    color:#f5f8fa; }\n  .bp5-dark .bp5-html-select select:hover, .bp5-dark .bp5-select select:hover{\n    background-color:#30404d;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-html-select select:active, .bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active, .bp5-dark .bp5-select select.bp5-active{\n    background-color:#202b33;\n    background-image:none;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-html-select select:disabled, .bp5-dark .bp5-select select:disabled, .bp5-dark .bp5-html-select select.bp5-disabled, .bp5-dark .bp5-select select.bp5-disabled{\n    background-color:rgba(57, 75, 89, 0.5);\n    background-image:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-html-select select:disabled.bp5-active, .bp5-dark .bp5-select select:disabled.bp5-active, .bp5-dark .bp5-html-select select.bp5-disabled.bp5-active, .bp5-dark .bp5-select select.bp5-disabled.bp5-active{\n      background:rgba(57, 75, 89, 0.7); }\n  .bp5-dark .bp5-html-select select .bp5-button-spinner .bp5-spinner-head, .bp5-dark .bp5-select select .bp5-button-spinner .bp5-spinner-head{\n    background:rgba(16, 22, 26, 0.5);\n    stroke:#8a9ba8; }\n\n.bp5-html-select select:disabled,\n.bp5-select select:disabled{\n  background-color:rgba(206, 217, 224, 0.5);\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-html-select .bp5-icon,\n.bp5-select .bp5-icon, .bp5-select::after{\n  color:#5c7080;\n  pointer-events:none;\n  position:absolute;\n  right:7px;\n  top:7px; }\n  .bp5-html-select .bp5-disabled.bp5-icon,\n  .bp5-select .bp5-disabled.bp5-icon, .bp5-disabled.bp5-select::after{\n    color:rgba(92, 112, 128, 0.6); }\n.bp5-html-select,\n.bp5-select{\n  display:inline-block;\n  letter-spacing:normal;\n  position:relative;\n  vertical-align:middle; }\n  .bp5-html-select select::-ms-expand,\n  .bp5-select select::-ms-expand{\n    display:none; }\n  .bp5-html-select .bp5-icon,\n  .bp5-select .bp5-icon{\n    color:#5c7080; }\n    .bp5-html-select .bp5-icon:hover,\n    .bp5-select .bp5-icon:hover{\n      color:#182026; }\n    .bp5-dark .bp5-html-select .bp5-icon, .bp5-dark\n    .bp5-select .bp5-icon{\n      color:#a7b6c2; }\n      .bp5-dark .bp5-html-select .bp5-icon:hover, .bp5-dark\n      .bp5-select .bp5-icon:hover{\n        color:#f5f8fa; }\n  .bp5-html-select.bp5-large::after,\n  .bp5-html-select.bp5-large .bp5-icon,\n  .bp5-select.bp5-large::after,\n  .bp5-select.bp5-large .bp5-icon{\n    right:12px;\n    top:12px; }\n  .bp5-html-select.bp5-fill,\n  .bp5-html-select.bp5-fill select,\n  .bp5-select.bp5-fill,\n  .bp5-select.bp5-fill select{\n    width:100%; }\n  .bp5-dark .bp5-html-select option, .bp5-dark\n  .bp5-select option{\n    background-color:#30404d;\n    color:#f5f8fa; }\n  .bp5-dark .bp5-html-select option:disabled, .bp5-dark\n  .bp5-select option:disabled{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-dark .bp5-html-select::after, .bp5-dark\n  .bp5-select::after{\n    color:#a7b6c2; }\n\n.bp5-select::after{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-weight:400;\n  line-height:1;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  content:\"\\f184\"; }\n.bp5-running-text table, table.bp5-html-table{\n  border-spacing:0;\n  font-size:14px; }\n  .bp5-running-text table th, table.bp5-html-table th,\n  .bp5-running-text table td,\n  table.bp5-html-table td{\n    padding:11px;\n    text-align:left;\n    vertical-align:top; }\n  .bp5-running-text table th, table.bp5-html-table th{\n    color:#182026;\n    font-weight:600; }\n\n  .bp5-running-text table td,\n  table.bp5-html-table td{\n    color:#182026; }\n  .bp5-running-text table tbody tr:first-child th, table.bp5-html-table tbody tr:first-child th,\n  .bp5-running-text table tbody tr:first-child td,\n  table.bp5-html-table tbody tr:first-child td,\n  .bp5-running-text table tfoot tr:first-child th,\n  table.bp5-html-table tfoot tr:first-child th,\n  .bp5-running-text table tfoot tr:first-child td,\n  table.bp5-html-table tfoot tr:first-child td{\n    -webkit-box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15);\n            box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15); }\n  .bp5-dark .bp5-running-text table th, .bp5-running-text .bp5-dark table th, .bp5-dark table.bp5-html-table th{\n    color:#f5f8fa; }\n  .bp5-dark .bp5-running-text table td, .bp5-running-text .bp5-dark table td, .bp5-dark table.bp5-html-table td{\n    color:#f5f8fa; }\n  .bp5-dark .bp5-running-text table tbody tr:first-child th, .bp5-running-text .bp5-dark table tbody tr:first-child th, .bp5-dark table.bp5-html-table tbody tr:first-child th,\n  .bp5-dark .bp5-running-text table tbody tr:first-child td,\n  .bp5-running-text .bp5-dark table tbody tr:first-child td,\n  .bp5-dark table.bp5-html-table tbody tr:first-child td,\n  .bp5-dark .bp5-running-text table tfoot tr:first-child th,\n  .bp5-running-text .bp5-dark table tfoot tr:first-child th,\n  .bp5-dark table.bp5-html-table tfoot tr:first-child th,\n  .bp5-dark .bp5-running-text table tfoot tr:first-child td,\n  .bp5-running-text .bp5-dark table tfoot tr:first-child td,\n  .bp5-dark table.bp5-html-table tfoot tr:first-child td{\n    -webkit-box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-condensed th,\ntable.bp5-html-table.bp5-html-table-condensed td, table.bp5-html-table.bp5-small th,\ntable.bp5-html-table.bp5-small td{\n  padding-bottom:6px;\n  padding-top:6px; }\n\ntable.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n  background:rgba(191, 204, 214, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n  -webkit-box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15);\n          box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-bordered tbody tr td,\ntable.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n  -webkit-box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15);\n          box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15); }\n  table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n  table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n    -webkit-box-shadow:inset 1px 1px 0 0 rgba(16, 22, 26, 0.15);\n            box-shadow:inset 1px 1px 0 0 rgba(16, 22, 26, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n  -webkit-box-shadow:none;\n          box-shadow:none; }\n  table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:not(:first-child){\n    -webkit-box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15);\n            box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15); }\n\ntable.bp5-html-table.bp5-interactive tbody tr:hover td{\n  background-color:rgba(191, 204, 214, 0.3);\n  cursor:pointer; }\n\ntable.bp5-html-table.bp5-interactive tbody tr:active td{\n  background-color:rgba(191, 204, 214, 0.4); }\n\n.bp5-dark table.bp5-html-table{ }\n  .bp5-dark table.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n    background:rgba(92, 112, 128, 0.15); }\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n    -webkit-box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15); }\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td,\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n    -webkit-box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15); }\n    .bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n    .bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n      -webkit-box-shadow:inset 1px 1px 0 0 rgba(255, 255, 255, 0.15);\n              box-shadow:inset 1px 1px 0 0 rgba(255, 255, 255, 0.15); }\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n    -webkit-box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15); }\n    .bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:first-child{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-dark table.bp5-html-table.bp5-interactive tbody tr:hover td{\n    background-color:rgba(92, 112, 128, 0.3);\n    cursor:pointer; }\n  .bp5-dark table.bp5-html-table.bp5-interactive tbody tr:active td{\n    background-color:rgba(92, 112, 128, 0.4); }\n.bp5-key-combo{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center; }\n  .bp5-key-combo > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-key-combo > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-key-combo::before,\n  .bp5-key-combo > *{\n    margin-right:5px; }\n  .bp5-key-combo:empty::before,\n  .bp5-key-combo > :last-child{\n    margin-right:0; }\n\n.bp5-hotkey-dialog{\n  padding-bottom:0;\n  top:40px; }\n  .bp5-hotkey-dialog .bp5-dialog-body{\n    margin:0;\n    padding:0; }\n  .bp5-hotkey-dialog .bp5-hotkey-label{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1; }\n\n.bp5-hotkey-column{\n  margin:auto;\n  max-height:80vh;\n  overflow-y:auto;\n  padding:30px; }\n  .bp5-hotkey-column .bp5-heading{\n    margin-bottom:20px; }\n    .bp5-hotkey-column .bp5-heading:not(:first-child){\n      margin-top:40px; }\n\n.bp5-hotkey{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:justify;\n      -ms-flex-pack:justify;\n          justify-content:space-between;\n  margin-left:0;\n  margin-right:0; }\n  .bp5-hotkey:not(:last-child){\n    margin-bottom:10px; }\n.bp5-icon{\n  display:inline-block;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  vertical-align:text-bottom; }\n  .bp5-icon:not(:empty)::before{\n    content:\"\" !important;\n    content:unset !important; }\n  .bp5-icon > svg{\n    display:block; }\n    .bp5-icon > svg:not([fill]){\n      fill:currentColor; }\n\n.bp5-icon.bp5-intent-primary, .bp5-icon-standard.bp5-intent-primary, .bp5-icon-large.bp5-intent-primary{\n  color:#106ba3; }\n  .bp5-dark .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-icon-large.bp5-intent-primary{\n    color:#48aff0; }\n\n.bp5-icon.bp5-intent-success, .bp5-icon-standard.bp5-intent-success, .bp5-icon-large.bp5-intent-success{\n  color:#0d8050; }\n  .bp5-dark .bp5-icon.bp5-intent-success, .bp5-dark .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-icon-large.bp5-intent-success{\n    color:#3dcc91; }\n\n.bp5-icon.bp5-intent-warning, .bp5-icon-standard.bp5-intent-warning, .bp5-icon-large.bp5-intent-warning{\n  color:#bf7326; }\n  .bp5-dark .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-icon-large.bp5-intent-warning{\n    color:#ffb366; }\n\n.bp5-icon.bp5-intent-danger, .bp5-icon-standard.bp5-intent-danger, .bp5-icon-large.bp5-intent-danger{\n  color:#c23030; }\n  .bp5-dark .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-icon-large.bp5-intent-danger{\n    color:#ff7373; }\n\nspan.bp5-icon-standard{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon-large{\n  font-family:\"blueprint-icons-20\", sans-serif;\n  font-size:20px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:20px;\n  line-height:1;\n  width:20px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon:empty{\n  font-family:\"blueprint-icons-20\";\n  font-size:inherit;\n  font-style:normal;\n  font-weight:400;\n  line-height:1;\n}\nspan.bp5-icon:empty::before{\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n}\nspan.bp5-icon:empty.bp5-icon-standard{\n  font-size:16px;\n}\nspan.bp5-icon:empty.bp5-icon-large{\n  font-size:20px;\n}\n\n.bp5-icon-add::before{\n  content:\"\\f109\";\n}\n\n.bp5-icon-add-clip::before{\n  content:\"\\f101\";\n}\n\n.bp5-icon-add-column-left::before{\n  content:\"\\f102\";\n}\n\n.bp5-icon-add-column-right::before{\n  content:\"\\f103\";\n}\n\n.bp5-icon-add-location::before{\n  content:\"\\f104\";\n}\n\n.bp5-icon-add-row-bottom::before{\n  content:\"\\f105\";\n}\n\n.bp5-icon-add-row-top::before{\n  content:\"\\f106\";\n}\n\n.bp5-icon-add-to-artifact::before{\n  content:\"\\f107\";\n}\n\n.bp5-icon-add-to-folder::before{\n  content:\"\\f108\";\n}\n\n.bp5-icon-aimpoints-target::before{\n  content:\"\\f335\";\n}\n\n.bp5-icon-airplane::before{\n  content:\"\\f10a\";\n}\n\n.bp5-icon-align-center::before{\n  content:\"\\f10b\";\n}\n\n.bp5-icon-align-justify::before{\n  content:\"\\f10c\";\n}\n\n.bp5-icon-align-left::before{\n  content:\"\\f10d\";\n}\n\n.bp5-icon-align-right::before{\n  content:\"\\f10e\";\n}\n\n.bp5-icon-alignment-bottom::before{\n  content:\"\\f10f\";\n}\n\n.bp5-icon-alignment-horizontal-center::before{\n  content:\"\\f110\";\n}\n\n.bp5-icon-alignment-left::before{\n  content:\"\\f111\";\n}\n\n.bp5-icon-alignment-right::before{\n  content:\"\\f112\";\n}\n\n.bp5-icon-alignment-top::before{\n  content:\"\\f113\";\n}\n\n.bp5-icon-alignment-vertical-center::before{\n  content:\"\\f114\";\n}\n\n.bp5-icon-ammunition::before{\n  content:\"\\f342\";\n}\n\n.bp5-icon-anchor::before{\n  content:\"\\f330\";\n}\n\n.bp5-icon-annotation::before{\n  content:\"\\f115\";\n}\n\n.bp5-icon-announcement::before{\n  content:\"\\f396\";\n}\n\n.bp5-icon-antenna::before{\n  content:\"\\f116\";\n}\n\n.bp5-icon-app-header::before{\n  content:\"\\f117\";\n}\n\n.bp5-icon-application::before{\n  content:\"\\f118\";\n}\n\n.bp5-icon-applications::before{\n  content:\"\\f119\";\n}\n\n.bp5-icon-archive::before{\n  content:\"\\f11a\";\n}\n\n.bp5-icon-area-of-interest::before{\n  content:\"\\f11b\";\n}\n\n.bp5-icon-array::before{\n  content:\"\\f121\";\n}\n\n.bp5-icon-array-boolean::before{\n  content:\"\\f11c\";\n}\n\n.bp5-icon-array-date::before{\n  content:\"\\f11d\";\n}\n\n.bp5-icon-array-floating-point::before{\n  content:\"\\f32d\";\n}\n\n.bp5-icon-array-numeric::before{\n  content:\"\\f11e\";\n}\n\n.bp5-icon-array-object::before{\n  content:\"\\f394\";\n}\n\n.bp5-icon-array-string::before{\n  content:\"\\f11f\";\n}\n\n.bp5-icon-array-timestamp::before{\n  content:\"\\f120\";\n}\n\n.bp5-icon-arrow-bottom-left::before{\n  content:\"\\f122\";\n}\n\n.bp5-icon-arrow-bottom-right::before{\n  content:\"\\f123\";\n}\n\n.bp5-icon-arrow-down::before{\n  content:\"\\f124\";\n}\n\n.bp5-icon-arrow-left::before{\n  content:\"\\f125\";\n}\n\n.bp5-icon-arrow-right::before{\n  content:\"\\f126\";\n}\n\n.bp5-icon-arrow-top-left::before{\n  content:\"\\f127\";\n}\n\n.bp5-icon-arrow-top-right::before{\n  content:\"\\f128\";\n}\n\n.bp5-icon-arrow-up::before{\n  content:\"\\f129\";\n}\n\n.bp5-icon-arrows-arc::before{\n  content:\"\\f387\";\n}\n\n.bp5-icon-arrows-horizontal::before{\n  content:\"\\f12a\";\n}\n\n.bp5-icon-arrows-vertical::before{\n  content:\"\\f12b\";\n}\n\n.bp5-icon-asterisk::before{\n  content:\"\\f12c\";\n}\n\n.bp5-icon-at::before{\n  content:\"\\f331\";\n}\n\n.bp5-icon-automatic-updates::before{\n  content:\"\\f12d\";\n}\n\n.bp5-icon-axle::before{\n  content:\"\\f338\";\n}\n\n.bp5-icon-backlink::before{\n  content:\"\\f12e\";\n}\n\n.bp5-icon-backward-ten::before{\n  content:\"\\f35c\";\n}\n\n.bp5-icon-badge::before{\n  content:\"\\f12f\";\n}\n\n.bp5-icon-ban-circle::before{\n  content:\"\\f130\";\n}\n\n.bp5-icon-bank-account::before{\n  content:\"\\f131\";\n}\n\n.bp5-icon-barcode::before{\n  content:\"\\f132\";\n}\n\n.bp5-icon-binary-number::before{\n  content:\"\\f357\";\n}\n\n.bp5-icon-blank::before{\n  content:\"\\f133\";\n}\n\n.bp5-icon-block-promote::before{\n  content:\"\\f372\";\n}\n\n.bp5-icon-blocked-person::before{\n  content:\"\\f134\";\n}\n\n.bp5-icon-bold::before{\n  content:\"\\f135\";\n}\n\n.bp5-icon-book::before{\n  content:\"\\f136\";\n}\n\n.bp5-icon-bookmark::before{\n  content:\"\\f137\";\n}\n\n.bp5-icon-box::before{\n  content:\"\\f138\";\n}\n\n.bp5-icon-briefcase::before{\n  content:\"\\f139\";\n}\n\n.bp5-icon-bring-data::before{\n  content:\"\\f13a\";\n}\n\n.bp5-icon-bring-forward::before{\n  content:\"\\f354\";\n}\n\n.bp5-icon-british-pound::before{\n  content:\"\\f386\";\n}\n\n.bp5-icon-bug::before{\n  content:\"\\f32e\";\n}\n\n.bp5-icon-buggy::before{\n  content:\"\\f13b\";\n}\n\n.bp5-icon-build::before{\n  content:\"\\f13c\";\n}\n\n.bp5-icon-bullseye::before{\n  content:\"\\f359\";\n}\n\n.bp5-icon-calculator::before{\n  content:\"\\f13d\";\n}\n\n.bp5-icon-calendar::before{\n  content:\"\\f13e\";\n}\n\n.bp5-icon-camera::before{\n  content:\"\\f13f\";\n}\n\n.bp5-icon-caret-down::before{\n  content:\"\\f140\";\n}\n\n.bp5-icon-caret-left::before{\n  content:\"\\f141\";\n}\n\n.bp5-icon-caret-right::before{\n  content:\"\\f142\";\n}\n\n.bp5-icon-caret-up::before{\n  content:\"\\f143\";\n}\n\n.bp5-icon-cargo-ship::before{\n  content:\"\\f144\";\n}\n\n.bp5-icon-cell-tower::before{\n  content:\"\\f145\";\n}\n\n.bp5-icon-changes::before{\n  content:\"\\f146\";\n}\n\n.bp5-icon-chart::before{\n  content:\"\\f147\";\n}\n\n.bp5-icon-chat::before{\n  content:\"\\f148\";\n}\n\n.bp5-icon-chevron-backward::before{\n  content:\"\\f149\";\n}\n\n.bp5-icon-chevron-down::before{\n  content:\"\\f14a\";\n}\n\n.bp5-icon-chevron-forward::before{\n  content:\"\\f14b\";\n}\n\n.bp5-icon-chevron-left::before{\n  content:\"\\f14c\";\n}\n\n.bp5-icon-chevron-right::before{\n  content:\"\\f14d\";\n}\n\n.bp5-icon-chevron-up::before{\n  content:\"\\f14e\";\n}\n\n.bp5-icon-circle::before{\n  content:\"\\f153\";\n}\n\n.bp5-icon-circle-arrow-down::before{\n  content:\"\\f14f\";\n}\n\n.bp5-icon-circle-arrow-left::before{\n  content:\"\\f150\";\n}\n\n.bp5-icon-circle-arrow-right::before{\n  content:\"\\f151\";\n}\n\n.bp5-icon-circle-arrow-up::before{\n  content:\"\\f152\";\n}\n\n.bp5-icon-citation::before{\n  content:\"\\f154\";\n}\n\n.bp5-icon-clean::before{\n  content:\"\\f155\";\n}\n\n.bp5-icon-clip::before{\n  content:\"\\f156\";\n}\n\n.bp5-icon-clipboard::before{\n  content:\"\\f157\";\n}\n\n.bp5-icon-clipboard-file::before{\n  content:\"\\f35b\";\n}\n\n.bp5-icon-cloud::before{\n  content:\"\\f15a\";\n}\n\n.bp5-icon-cloud-download::before{\n  content:\"\\f158\";\n}\n\n.bp5-icon-cloud-server::before{\n  content:\"\\f35a\";\n}\n\n.bp5-icon-cloud-tick::before{\n  content:\"\\f34e\";\n}\n\n.bp5-icon-cloud-upload::before{\n  content:\"\\f159\";\n}\n\n.bp5-icon-code::before{\n  content:\"\\f15c\";\n}\n\n.bp5-icon-code-block::before{\n  content:\"\\f15b\";\n}\n\n.bp5-icon-cog::before{\n  content:\"\\f15d\";\n}\n\n.bp5-icon-collapse-all::before{\n  content:\"\\f15e\";\n}\n\n.bp5-icon-color-fill::before{\n  content:\"\\f328\";\n}\n\n.bp5-icon-column-layout::before{\n  content:\"\\f15f\";\n}\n\n.bp5-icon-comment::before{\n  content:\"\\f160\";\n}\n\n.bp5-icon-comparison::before{\n  content:\"\\f161\";\n}\n\n.bp5-icon-compass::before{\n  content:\"\\f162\";\n}\n\n.bp5-icon-compressed::before{\n  content:\"\\f163\";\n}\n\n.bp5-icon-confirm::before{\n  content:\"\\f164\";\n}\n\n.bp5-icon-console::before{\n  content:\"\\f165\";\n}\n\n.bp5-icon-construction::before{\n  content:\"\\f395\";\n}\n\n.bp5-icon-contrast::before{\n  content:\"\\f166\";\n}\n\n.bp5-icon-control::before{\n  content:\"\\f167\";\n}\n\n.bp5-icon-credit-card::before{\n  content:\"\\f168\";\n}\n\n.bp5-icon-crop::before{\n  content:\"\\f353\";\n}\n\n.bp5-icon-cross::before{\n  content:\"\\f169\";\n}\n\n.bp5-icon-cross-circle::before{\n  content:\"\\f336\";\n}\n\n.bp5-icon-crown::before{\n  content:\"\\f16a\";\n}\n\n.bp5-icon-css-style::before{\n  content:\"\\f36b\";\n}\n\n.bp5-icon-cube::before{\n  content:\"\\f16d\";\n}\n\n.bp5-icon-cube-add::before{\n  content:\"\\f16b\";\n}\n\n.bp5-icon-cube-edit::before{\n  content:\"\\f383\";\n}\n\n.bp5-icon-cube-remove::before{\n  content:\"\\f16c\";\n}\n\n.bp5-icon-cubes::before{\n  content:\"\\f373\";\n}\n\n.bp5-icon-curly-braces::before{\n  content:\"\\f358\";\n}\n\n.bp5-icon-curved-range-chart::before{\n  content:\"\\f16e\";\n}\n\n.bp5-icon-cut::before{\n  content:\"\\f16f\";\n}\n\n.bp5-icon-cycle::before{\n  content:\"\\f170\";\n}\n\n.bp5-icon-dashboard::before{\n  content:\"\\f171\";\n}\n\n.bp5-icon-data-cloud::before{\n  content:\"\\f391\";\n}\n\n.bp5-icon-data-connection::before{\n  content:\"\\f172\";\n}\n\n.bp5-icon-data-lineage::before{\n  content:\"\\f173\";\n}\n\n.bp5-icon-data-search::before{\n  content:\"\\f36f\";\n}\n\n.bp5-icon-data-sync::before{\n  content:\"\\f36c\";\n}\n\n.bp5-icon-database::before{\n  content:\"\\f174\";\n}\n\n.bp5-icon-delete::before{\n  content:\"\\f175\";\n}\n\n.bp5-icon-delta::before{\n  content:\"\\f176\";\n}\n\n.bp5-icon-derive-column::before{\n  content:\"\\f177\";\n}\n\n.bp5-icon-desktop::before{\n  content:\"\\f178\";\n}\n\n.bp5-icon-detection::before{\n  content:\"\\f341\";\n}\n\n.bp5-icon-diagnosis::before{\n  content:\"\\f179\";\n}\n\n.bp5-icon-diagram-tree::before{\n  content:\"\\f17a\";\n}\n\n.bp5-icon-direction-left::before{\n  content:\"\\f17b\";\n}\n\n.bp5-icon-direction-right::before{\n  content:\"\\f17c\";\n}\n\n.bp5-icon-disable::before{\n  content:\"\\f17d\";\n}\n\n.bp5-icon-divide::before{\n  content:\"\\f327\";\n}\n\n.bp5-icon-document::before{\n  content:\"\\f180\";\n}\n\n.bp5-icon-document-open::before{\n  content:\"\\f17e\";\n}\n\n.bp5-icon-document-share::before{\n  content:\"\\f17f\";\n}\n\n.bp5-icon-dollar::before{\n  content:\"\\f181\";\n}\n\n.bp5-icon-dot::before{\n  content:\"\\f182\";\n}\n\n.bp5-icon-double-caret-horizontal::before{\n  content:\"\\f183\";\n}\n\n.bp5-icon-double-caret-vertical::before{\n  content:\"\\f184\";\n}\n\n.bp5-icon-double-chevron-down::before{\n  content:\"\\f185\";\n}\n\n.bp5-icon-double-chevron-left::before{\n  content:\"\\f186\";\n}\n\n.bp5-icon-double-chevron-right::before{\n  content:\"\\f187\";\n}\n\n.bp5-icon-double-chevron-up::before{\n  content:\"\\f188\";\n}\n\n.bp5-icon-doughnut-chart::before{\n  content:\"\\f189\";\n}\n\n.bp5-icon-download::before{\n  content:\"\\f18a\";\n}\n\n.bp5-icon-drag-handle-horizontal::before{\n  content:\"\\f18b\";\n}\n\n.bp5-icon-drag-handle-vertical::before{\n  content:\"\\f18c\";\n}\n\n.bp5-icon-draw::before{\n  content:\"\\f18d\";\n}\n\n.bp5-icon-drawer-left::before{\n  content:\"\\f18f\";\n}\n\n.bp5-icon-drawer-left-filled::before{\n  content:\"\\f18e\";\n}\n\n.bp5-icon-drawer-right::before{\n  content:\"\\f191\";\n}\n\n.bp5-icon-drawer-right-filled::before{\n  content:\"\\f190\";\n}\n\n.bp5-icon-drive-time::before{\n  content:\"\\f192\";\n}\n\n.bp5-icon-duplicate::before{\n  content:\"\\f193\";\n}\n\n.bp5-icon-edit::before{\n  content:\"\\f194\";\n}\n\n.bp5-icon-eject::before{\n  content:\"\\f195\";\n}\n\n.bp5-icon-emoji::before{\n  content:\"\\f196\";\n}\n\n.bp5-icon-endnote::before{\n  content:\"\\f356\";\n}\n\n.bp5-icon-endorsed::before{\n  content:\"\\f197\";\n}\n\n.bp5-icon-envelope::before{\n  content:\"\\f198\";\n}\n\n.bp5-icon-equals::before{\n  content:\"\\f199\";\n}\n\n.bp5-icon-eraser::before{\n  content:\"\\f19a\";\n}\n\n.bp5-icon-error::before{\n  content:\"\\f19b\";\n}\n\n.bp5-icon-euro::before{\n  content:\"\\f19c\";\n}\n\n.bp5-icon-excavator::before{\n  content:\"\\f36d\";\n}\n\n.bp5-icon-exchange::before{\n  content:\"\\f19d\";\n}\n\n.bp5-icon-exclude-row::before{\n  content:\"\\f19e\";\n}\n\n.bp5-icon-expand-all::before{\n  content:\"\\f19f\";\n}\n\n.bp5-icon-explain::before{\n  content:\"\\f34d\";\n}\n\n.bp5-icon-export::before{\n  content:\"\\f1a0\";\n}\n\n.bp5-icon-eye-off::before{\n  content:\"\\f1a1\";\n}\n\n.bp5-icon-eye-on::before{\n  content:\"\\f1a2\";\n}\n\n.bp5-icon-eye-open::before{\n  content:\"\\f1a3\";\n}\n\n.bp5-icon-fast-backward::before{\n  content:\"\\f1a4\";\n}\n\n.bp5-icon-fast-forward::before{\n  content:\"\\f1a5\";\n}\n\n.bp5-icon-feed::before{\n  content:\"\\f1a7\";\n}\n\n.bp5-icon-feed-subscribed::before{\n  content:\"\\f1a6\";\n}\n\n.bp5-icon-fighter-jet::before{\n  content:\"\\f384\";\n}\n\n.bp5-icon-film::before{\n  content:\"\\f1a8\";\n}\n\n.bp5-icon-filter::before{\n  content:\"\\f1ad\";\n}\n\n.bp5-icon-filter-keep::before{\n  content:\"\\f1a9\";\n}\n\n.bp5-icon-filter-list::before{\n  content:\"\\f1aa\";\n}\n\n.bp5-icon-filter-open::before{\n  content:\"\\f1ab\";\n}\n\n.bp5-icon-filter-remove::before{\n  content:\"\\f1ac\";\n}\n\n.bp5-icon-filter-sort-asc::before{\n  content:\"\\f38e\";\n}\n\n.bp5-icon-filter-sort-desc::before{\n  content:\"\\f38f\";\n}\n\n.bp5-icon-flag::before{\n  content:\"\\f1ae\";\n}\n\n.bp5-icon-flame::before{\n  content:\"\\f1af\";\n}\n\n.bp5-icon-flash::before{\n  content:\"\\f1b0\";\n}\n\n.bp5-icon-floating-point::before{\n  content:\"\\f32c\";\n}\n\n.bp5-icon-floppy-disk::before{\n  content:\"\\f1b1\";\n}\n\n.bp5-icon-flow-branch::before{\n  content:\"\\f1b2\";\n}\n\n.bp5-icon-flow-end::before{\n  content:\"\\f1b3\";\n}\n\n.bp5-icon-flow-linear::before{\n  content:\"\\f1b4\";\n}\n\n.bp5-icon-flow-review::before{\n  content:\"\\f1b6\";\n}\n\n.bp5-icon-flow-review-branch::before{\n  content:\"\\f1b5\";\n}\n\n.bp5-icon-flows::before{\n  content:\"\\f1b7\";\n}\n\n.bp5-icon-folder-close::before{\n  content:\"\\f1b8\";\n}\n\n.bp5-icon-folder-new::before{\n  content:\"\\f1b9\";\n}\n\n.bp5-icon-folder-open::before{\n  content:\"\\f1ba\";\n}\n\n.bp5-icon-folder-shared::before{\n  content:\"\\f1bc\";\n}\n\n.bp5-icon-folder-shared-open::before{\n  content:\"\\f1bb\";\n}\n\n.bp5-icon-follower::before{\n  content:\"\\f1bd\";\n}\n\n.bp5-icon-following::before{\n  content:\"\\f1be\";\n}\n\n.bp5-icon-font::before{\n  content:\"\\f1bf\";\n}\n\n.bp5-icon-fork::before{\n  content:\"\\f1c0\";\n}\n\n.bp5-icon-form::before{\n  content:\"\\f1c1\";\n}\n\n.bp5-icon-forward-ten::before{\n  content:\"\\f35d\";\n}\n\n.bp5-icon-fuel::before{\n  content:\"\\f323\";\n}\n\n.bp5-icon-full-circle::before{\n  content:\"\\f1c2\";\n}\n\n.bp5-icon-full-stacked-chart::before{\n  content:\"\\f1c3\";\n}\n\n.bp5-icon-fullscreen::before{\n  content:\"\\f1c4\";\n}\n\n.bp5-icon-function::before{\n  content:\"\\f1c5\";\n}\n\n.bp5-icon-gantt-chart::before{\n  content:\"\\f1c6\";\n}\n\n.bp5-icon-generate::before{\n  content:\"\\f34c\";\n}\n\n.bp5-icon-geofence::before{\n  content:\"\\f1c7\";\n}\n\n.bp5-icon-geolocation::before{\n  content:\"\\f1c8\";\n}\n\n.bp5-icon-geosearch::before{\n  content:\"\\f1c9\";\n}\n\n.bp5-icon-geotime::before{\n  content:\"\\f344\";\n}\n\n.bp5-icon-git-branch::before{\n  content:\"\\f1ca\";\n}\n\n.bp5-icon-git-commit::before{\n  content:\"\\f1cb\";\n}\n\n.bp5-icon-git-merge::before{\n  content:\"\\f1cc\";\n}\n\n.bp5-icon-git-new-branch::before{\n  content:\"\\f1cd\";\n}\n\n.bp5-icon-git-pull::before{\n  content:\"\\f1ce\";\n}\n\n.bp5-icon-git-push::before{\n  content:\"\\f1cf\";\n}\n\n.bp5-icon-git-repo::before{\n  content:\"\\f1d0\";\n}\n\n.bp5-icon-glass::before{\n  content:\"\\f1d1\";\n}\n\n.bp5-icon-globe::before{\n  content:\"\\f1d3\";\n}\n\n.bp5-icon-globe-network::before{\n  content:\"\\f1d2\";\n}\n\n.bp5-icon-globe-network-add::before{\n  content:\"\\f382\";\n}\n\n.bp5-icon-graph::before{\n  content:\"\\f1d5\";\n}\n\n.bp5-icon-graph-remove::before{\n  content:\"\\f1d4\";\n}\n\n.bp5-icon-greater-than::before{\n  content:\"\\f1d7\";\n}\n\n.bp5-icon-greater-than-or-equal-to::before{\n  content:\"\\f1d6\";\n}\n\n.bp5-icon-grid::before{\n  content:\"\\f1d9\";\n}\n\n.bp5-icon-grid-view::before{\n  content:\"\\f1d8\";\n}\n\n.bp5-icon-group-item::before{\n  content:\"\\f34a\";\n}\n\n.bp5-icon-group-objects::before{\n  content:\"\\f1da\";\n}\n\n.bp5-icon-grouped-bar-chart::before{\n  content:\"\\f1db\";\n}\n\n.bp5-icon-hand::before{\n  content:\"\\f1e0\";\n}\n\n.bp5-icon-hand-down::before{\n  content:\"\\f1dc\";\n}\n\n.bp5-icon-hand-left::before{\n  content:\"\\f1dd\";\n}\n\n.bp5-icon-hand-right::before{\n  content:\"\\f1de\";\n}\n\n.bp5-icon-hand-up::before{\n  content:\"\\f1df\";\n}\n\n.bp5-icon-hat::before{\n  content:\"\\f1e1\";\n}\n\n.bp5-icon-header::before{\n  content:\"\\f1e5\";\n}\n\n.bp5-icon-header-one::before{\n  content:\"\\f1e2\";\n}\n\n.bp5-icon-header-three::before{\n  content:\"\\f1e3\";\n}\n\n.bp5-icon-header-two::before{\n  content:\"\\f1e4\";\n}\n\n.bp5-icon-headset::before{\n  content:\"\\f1e6\";\n}\n\n.bp5-icon-heart::before{\n  content:\"\\f1e8\";\n}\n\n.bp5-icon-heart-broken::before{\n  content:\"\\f1e7\";\n}\n\n.bp5-icon-heat-grid::before{\n  content:\"\\f1e9\";\n}\n\n.bp5-icon-heatmap::before{\n  content:\"\\f1ea\";\n}\n\n.bp5-icon-helicopter::before{\n  content:\"\\f1eb\";\n}\n\n.bp5-icon-help::before{\n  content:\"\\f1ec\";\n}\n\n.bp5-icon-helper-management::before{\n  content:\"\\f1ed\";\n}\n\n.bp5-icon-hexagon::before{\n  content:\"\\f374\";\n}\n\n.bp5-icon-high-priority::before{\n  content:\"\\f1ee\";\n}\n\n.bp5-icon-high-voltage-pole::before{\n  content:\"\\f333\";\n}\n\n.bp5-icon-highlight::before{\n  content:\"\\f1ef\";\n}\n\n.bp5-icon-history::before{\n  content:\"\\f1f0\";\n}\n\n.bp5-icon-home::before{\n  content:\"\\f1f1\";\n}\n\n.bp5-icon-horizontal-bar-chart::before{\n  content:\"\\f1f4\";\n}\n\n.bp5-icon-horizontal-bar-chart-asc::before{\n  content:\"\\f1f2\";\n}\n\n.bp5-icon-horizontal-bar-chart-desc::before{\n  content:\"\\f1f3\";\n}\n\n.bp5-icon-horizontal-distribution::before{\n  content:\"\\f1f5\";\n}\n\n.bp5-icon-horizontal-inbetween::before{\n  content:\"\\f329\";\n}\n\n.bp5-icon-hurricane::before{\n  content:\"\\f1f6\";\n}\n\n.bp5-icon-id-number::before{\n  content:\"\\f1f7\";\n}\n\n.bp5-icon-image-rotate-left::before{\n  content:\"\\f1f8\";\n}\n\n.bp5-icon-image-rotate-right::before{\n  content:\"\\f1f9\";\n}\n\n.bp5-icon-import::before{\n  content:\"\\f1fa\";\n}\n\n.bp5-icon-inbox::before{\n  content:\"\\f1ff\";\n}\n\n.bp5-icon-inbox-filtered::before{\n  content:\"\\f1fb\";\n}\n\n.bp5-icon-inbox-geo::before{\n  content:\"\\f1fc\";\n}\n\n.bp5-icon-inbox-search::before{\n  content:\"\\f1fd\";\n}\n\n.bp5-icon-inbox-update::before{\n  content:\"\\f1fe\";\n}\n\n.bp5-icon-info-sign::before{\n  content:\"\\f200\";\n}\n\n.bp5-icon-inheritance::before{\n  content:\"\\f201\";\n}\n\n.bp5-icon-inherited-group::before{\n  content:\"\\f202\";\n}\n\n.bp5-icon-inner-join::before{\n  content:\"\\f203\";\n}\n\n.bp5-icon-input::before{\n  content:\"\\f34b\";\n}\n\n.bp5-icon-insert::before{\n  content:\"\\f204\";\n}\n\n.bp5-icon-intelligence::before{\n  content:\"\\f337\";\n}\n\n.bp5-icon-intersection::before{\n  content:\"\\f205\";\n}\n\n.bp5-icon-ip-address::before{\n  content:\"\\f206\";\n}\n\n.bp5-icon-issue::before{\n  content:\"\\f209\";\n}\n\n.bp5-icon-issue-closed::before{\n  content:\"\\f207\";\n}\n\n.bp5-icon-issue-new::before{\n  content:\"\\f208\";\n}\n\n.bp5-icon-italic::before{\n  content:\"\\f20a\";\n}\n\n.bp5-icon-join-table::before{\n  content:\"\\f20b\";\n}\n\n.bp5-icon-key::before{\n  content:\"\\f215\";\n}\n\n.bp5-icon-key-backspace::before{\n  content:\"\\f20c\";\n}\n\n.bp5-icon-key-command::before{\n  content:\"\\f20d\";\n}\n\n.bp5-icon-key-control::before{\n  content:\"\\f20e\";\n}\n\n.bp5-icon-key-delete::before{\n  content:\"\\f20f\";\n}\n\n.bp5-icon-key-enter::before{\n  content:\"\\f210\";\n}\n\n.bp5-icon-key-escape::before{\n  content:\"\\f211\";\n}\n\n.bp5-icon-key-option::before{\n  content:\"\\f212\";\n}\n\n.bp5-icon-key-shift::before{\n  content:\"\\f213\";\n}\n\n.bp5-icon-key-tab::before{\n  content:\"\\f214\";\n}\n\n.bp5-icon-known-vehicle::before{\n  content:\"\\f216\";\n}\n\n.bp5-icon-lab-test::before{\n  content:\"\\f217\";\n}\n\n.bp5-icon-label::before{\n  content:\"\\f218\";\n}\n\n.bp5-icon-layer::before{\n  content:\"\\f21a\";\n}\n\n.bp5-icon-layer-outline::before{\n  content:\"\\f219\";\n}\n\n.bp5-icon-layers::before{\n  content:\"\\f21b\";\n}\n\n.bp5-icon-layout::before{\n  content:\"\\f225\";\n}\n\n.bp5-icon-layout-auto::before{\n  content:\"\\f21c\";\n}\n\n.bp5-icon-layout-balloon::before{\n  content:\"\\f21d\";\n}\n\n.bp5-icon-layout-bottom-row-three-tiles::before{\n  content:\"\\f364\";\n}\n\n.bp5-icon-layout-bottom-row-two-tiles::before{\n  content:\"\\f363\";\n}\n\n.bp5-icon-layout-circle::before{\n  content:\"\\f21e\";\n}\n\n.bp5-icon-layout-grid::before{\n  content:\"\\f21f\";\n}\n\n.bp5-icon-layout-group-by::before{\n  content:\"\\f220\";\n}\n\n.bp5-icon-layout-hierarchy::before{\n  content:\"\\f221\";\n}\n\n.bp5-icon-layout-left-column-three-tiles::before{\n  content:\"\\f366\";\n}\n\n.bp5-icon-layout-left-column-two-tiles::before{\n  content:\"\\f365\";\n}\n\n.bp5-icon-layout-linear::before{\n  content:\"\\f222\";\n}\n\n.bp5-icon-layout-right-column-three-tiles::before{\n  content:\"\\f368\";\n}\n\n.bp5-icon-layout-right-column-two-tiles::before{\n  content:\"\\f367\";\n}\n\n.bp5-icon-layout-skew-grid::before{\n  content:\"\\f223\";\n}\n\n.bp5-icon-layout-sorted-clusters::before{\n  content:\"\\f224\";\n}\n\n.bp5-icon-layout-three-columns::before{\n  content:\"\\f361\";\n}\n\n.bp5-icon-layout-three-rows::before{\n  content:\"\\f362\";\n}\n\n.bp5-icon-layout-top-row-three-tiles::before{\n  content:\"\\f36a\";\n}\n\n.bp5-icon-layout-top-row-two-tiles::before{\n  content:\"\\f369\";\n}\n\n.bp5-icon-layout-two-columns::before{\n  content:\"\\f35f\";\n}\n\n.bp5-icon-layout-two-rows::before{\n  content:\"\\f360\";\n}\n\n.bp5-icon-learning::before{\n  content:\"\\f226\";\n}\n\n.bp5-icon-left-join::before{\n  content:\"\\f227\";\n}\n\n.bp5-icon-lengthen-text::before{\n  content:\"\\f33e\";\n}\n\n.bp5-icon-less-than::before{\n  content:\"\\f229\";\n}\n\n.bp5-icon-less-than-or-equal-to::before{\n  content:\"\\f228\";\n}\n\n.bp5-icon-lifesaver::before{\n  content:\"\\f22a\";\n}\n\n.bp5-icon-lightbulb::before{\n  content:\"\\f22b\";\n}\n\n.bp5-icon-lightning::before{\n  content:\"\\f22c\";\n}\n\n.bp5-icon-link::before{\n  content:\"\\f22d\";\n}\n\n.bp5-icon-linked-squares::before{\n  content:\"\\f385\";\n}\n\n.bp5-icon-list::before{\n  content:\"\\f230\";\n}\n\n.bp5-icon-list-columns::before{\n  content:\"\\f22e\";\n}\n\n.bp5-icon-list-detail-view::before{\n  content:\"\\f22f\";\n}\n\n.bp5-icon-locate::before{\n  content:\"\\f231\";\n}\n\n.bp5-icon-lock::before{\n  content:\"\\f232\";\n}\n\n.bp5-icon-locomotive::before{\n  content:\"\\f33b\";\n}\n\n.bp5-icon-log-in::before{\n  content:\"\\f233\";\n}\n\n.bp5-icon-log-out::before{\n  content:\"\\f234\";\n}\n\n.bp5-icon-low-voltage-pole::before{\n  content:\"\\f332\";\n}\n\n.bp5-icon-manual::before{\n  content:\"\\f235\";\n}\n\n.bp5-icon-manually-entered-data::before{\n  content:\"\\f236\";\n}\n\n.bp5-icon-many-to-many::before{\n  content:\"\\f237\";\n}\n\n.bp5-icon-many-to-one::before{\n  content:\"\\f238\";\n}\n\n.bp5-icon-map::before{\n  content:\"\\f23b\";\n}\n\n.bp5-icon-map-create::before{\n  content:\"\\f239\";\n}\n\n.bp5-icon-map-marker::before{\n  content:\"\\f23a\";\n}\n\n.bp5-icon-maximize::before{\n  content:\"\\f23c\";\n}\n\n.bp5-icon-media::before{\n  content:\"\\f23d\";\n}\n\n.bp5-icon-menu::before{\n  content:\"\\f240\";\n}\n\n.bp5-icon-menu-closed::before{\n  content:\"\\f23e\";\n}\n\n.bp5-icon-menu-open::before{\n  content:\"\\f23f\";\n}\n\n.bp5-icon-merge-columns::before{\n  content:\"\\f241\";\n}\n\n.bp5-icon-merge-links::before{\n  content:\"\\f242\";\n}\n\n.bp5-icon-microphone::before{\n  content:\"\\f343\";\n}\n\n.bp5-icon-minimize::before{\n  content:\"\\f243\";\n}\n\n.bp5-icon-minus::before{\n  content:\"\\f244\";\n}\n\n.bp5-icon-mobile-phone::before{\n  content:\"\\f245\";\n}\n\n.bp5-icon-mobile-video::before{\n  content:\"\\f246\";\n}\n\n.bp5-icon-modal::before{\n  content:\"\\f248\";\n}\n\n.bp5-icon-modal-filled::before{\n  content:\"\\f247\";\n}\n\n.bp5-icon-model::before{\n  content:\"\\f33d\";\n}\n\n.bp5-icon-moon::before{\n  content:\"\\f249\";\n}\n\n.bp5-icon-more::before{\n  content:\"\\f24a\";\n}\n\n.bp5-icon-mountain::before{\n  content:\"\\f24b\";\n}\n\n.bp5-icon-move::before{\n  content:\"\\f24c\";\n}\n\n.bp5-icon-mugshot::before{\n  content:\"\\f24d\";\n}\n\n.bp5-icon-multi-select::before{\n  content:\"\\f24e\";\n}\n\n.bp5-icon-music::before{\n  content:\"\\f24f\";\n}\n\n.bp5-icon-nest::before{\n  content:\"\\f250\";\n}\n\n.bp5-icon-new-drawing::before{\n  content:\"\\f251\";\n}\n\n.bp5-icon-new-grid-item::before{\n  content:\"\\f252\";\n}\n\n.bp5-icon-new-layer::before{\n  content:\"\\f253\";\n}\n\n.bp5-icon-new-layers::before{\n  content:\"\\f254\";\n}\n\n.bp5-icon-new-link::before{\n  content:\"\\f255\";\n}\n\n.bp5-icon-new-object::before{\n  content:\"\\f256\";\n}\n\n.bp5-icon-new-person::before{\n  content:\"\\f257\";\n}\n\n.bp5-icon-new-prescription::before{\n  content:\"\\f258\";\n}\n\n.bp5-icon-new-shield::before{\n  content:\"\\f349\";\n}\n\n.bp5-icon-new-text-box::before{\n  content:\"\\f259\";\n}\n\n.bp5-icon-ninja::before{\n  content:\"\\f25a\";\n}\n\n.bp5-icon-not-equal-to::before{\n  content:\"\\f25b\";\n}\n\n.bp5-icon-notifications::before{\n  content:\"\\f25e\";\n}\n\n.bp5-icon-notifications-add::before{\n  content:\"\\f398\";\n}\n\n.bp5-icon-notifications-snooze::before{\n  content:\"\\f25c\";\n}\n\n.bp5-icon-notifications-updated::before{\n  content:\"\\f25d\";\n}\n\n.bp5-icon-numbered-list::before{\n  content:\"\\f25f\";\n}\n\n.bp5-icon-numerical::before{\n  content:\"\\f260\";\n}\n\n.bp5-icon-object-view::before{\n  content:\"\\f390\";\n}\n\n.bp5-icon-office::before{\n  content:\"\\f261\";\n}\n\n.bp5-icon-offline::before{\n  content:\"\\f262\";\n}\n\n.bp5-icon-oil-field::before{\n  content:\"\\f263\";\n}\n\n.bp5-icon-one-column::before{\n  content:\"\\f264\";\n}\n\n.bp5-icon-one-to-many::before{\n  content:\"\\f265\";\n}\n\n.bp5-icon-one-to-one::before{\n  content:\"\\f266\";\n}\n\n.bp5-icon-open-application::before{\n  content:\"\\f32b\";\n}\n\n.bp5-icon-outdated::before{\n  content:\"\\f267\";\n}\n\n.bp5-icon-output::before{\n  content:\"\\f370\";\n}\n\n.bp5-icon-package::before{\n  content:\"\\f375\";\n}\n\n.bp5-icon-page-layout::before{\n  content:\"\\f268\";\n}\n\n.bp5-icon-panel::before{\n  content:\"\\f381\";\n}\n\n.bp5-icon-panel-stats::before{\n  content:\"\\f269\";\n}\n\n.bp5-icon-panel-table::before{\n  content:\"\\f26a\";\n}\n\n.bp5-icon-paperclip::before{\n  content:\"\\f26b\";\n}\n\n.bp5-icon-paragraph::before{\n  content:\"\\f26c\";\n}\n\n.bp5-icon-paste-variable::before{\n  content:\"\\f346\";\n}\n\n.bp5-icon-path::before{\n  content:\"\\f26e\";\n}\n\n.bp5-icon-path-search::before{\n  content:\"\\f26d\";\n}\n\n.bp5-icon-pause::before{\n  content:\"\\f26f\";\n}\n\n.bp5-icon-people::before{\n  content:\"\\f270\";\n}\n\n.bp5-icon-percentage::before{\n  content:\"\\f271\";\n}\n\n.bp5-icon-person::before{\n  content:\"\\f272\";\n}\n\n.bp5-icon-phone::before{\n  content:\"\\f273\";\n}\n\n.bp5-icon-phone-call::before{\n  content:\"\\f347\";\n}\n\n.bp5-icon-phone-forward::before{\n  content:\"\\f348\";\n}\n\n.bp5-icon-phone-search::before{\n  content:\"\\f397\";\n}\n\n.bp5-icon-pie-chart::before{\n  content:\"\\f274\";\n}\n\n.bp5-icon-pill::before{\n  content:\"\\f376\";\n}\n\n.bp5-icon-pin::before{\n  content:\"\\f275\";\n}\n\n.bp5-icon-pivot::before{\n  content:\"\\f277\";\n}\n\n.bp5-icon-pivot-table::before{\n  content:\"\\f276\";\n}\n\n.bp5-icon-play::before{\n  content:\"\\f278\";\n}\n\n.bp5-icon-playbook::before{\n  content:\"\\f324\";\n}\n\n.bp5-icon-plus::before{\n  content:\"\\f279\";\n}\n\n.bp5-icon-polygon-filter::before{\n  content:\"\\f27a\";\n}\n\n.bp5-icon-power::before{\n  content:\"\\f27b\";\n}\n\n.bp5-icon-predictive-analysis::before{\n  content:\"\\f27c\";\n}\n\n.bp5-icon-prescription::before{\n  content:\"\\f27d\";\n}\n\n.bp5-icon-presentation::before{\n  content:\"\\f27e\";\n}\n\n.bp5-icon-print::before{\n  content:\"\\f27f\";\n}\n\n.bp5-icon-projects::before{\n  content:\"\\f280\";\n}\n\n.bp5-icon-properties::before{\n  content:\"\\f281\";\n}\n\n.bp5-icon-property::before{\n  content:\"\\f282\";\n}\n\n.bp5-icon-publish-function::before{\n  content:\"\\f283\";\n}\n\n.bp5-icon-pulse::before{\n  content:\"\\f284\";\n}\n\n.bp5-icon-rain::before{\n  content:\"\\f285\";\n}\n\n.bp5-icon-random::before{\n  content:\"\\f286\";\n}\n\n.bp5-icon-range-ring::before{\n  content:\"\\f371\";\n}\n\n.bp5-icon-record::before{\n  content:\"\\f287\";\n}\n\n.bp5-icon-rect-height::before{\n  content:\"\\f325\";\n}\n\n.bp5-icon-rect-width::before{\n  content:\"\\f326\";\n}\n\n.bp5-icon-rectangle::before{\n  content:\"\\f321\";\n}\n\n.bp5-icon-redo::before{\n  content:\"\\f288\";\n}\n\n.bp5-icon-refresh::before{\n  content:\"\\f289\";\n}\n\n.bp5-icon-regex::before{\n  content:\"\\f32f\";\n}\n\n.bp5-icon-regression-chart::before{\n  content:\"\\f28a\";\n}\n\n.bp5-icon-remove::before{\n  content:\"\\f290\";\n}\n\n.bp5-icon-remove-column::before{\n  content:\"\\f28d\";\n}\n\n.bp5-icon-remove-column-left::before{\n  content:\"\\f28b\";\n}\n\n.bp5-icon-remove-column-right::before{\n  content:\"\\f28c\";\n}\n\n.bp5-icon-remove-row-bottom::before{\n  content:\"\\f28e\";\n}\n\n.bp5-icon-remove-row-top::before{\n  content:\"\\f28f\";\n}\n\n.bp5-icon-repeat::before{\n  content:\"\\f291\";\n}\n\n.bp5-icon-reset::before{\n  content:\"\\f292\";\n}\n\n.bp5-icon-resolve::before{\n  content:\"\\f293\";\n}\n\n.bp5-icon-rig::before{\n  content:\"\\f294\";\n}\n\n.bp5-icon-right-join::before{\n  content:\"\\f295\";\n}\n\n.bp5-icon-ring::before{\n  content:\"\\f296\";\n}\n\n.bp5-icon-rocket::before{\n  content:\"\\f298\";\n}\n\n.bp5-icon-rocket-slant::before{\n  content:\"\\f297\";\n}\n\n.bp5-icon-rotate-ccw::before{\n  content:\"\\f389\";\n}\n\n.bp5-icon-rotate-cw::before{\n  content:\"\\f388\";\n}\n\n.bp5-icon-rotate-document::before{\n  content:\"\\f299\";\n}\n\n.bp5-icon-rotate-page::before{\n  content:\"\\f29a\";\n}\n\n.bp5-icon-route::before{\n  content:\"\\f29b\";\n}\n\n.bp5-icon-run-history::before{\n  content:\"\\f393\";\n}\n\n.bp5-icon-satellite::before{\n  content:\"\\f29c\";\n}\n\n.bp5-icon-saved::before{\n  content:\"\\f29d\";\n}\n\n.bp5-icon-scatter-plot::before{\n  content:\"\\f29e\";\n}\n\n.bp5-icon-search::before{\n  content:\"\\f2a2\";\n}\n\n.bp5-icon-search-around::before{\n  content:\"\\f29f\";\n}\n\n.bp5-icon-search-template::before{\n  content:\"\\f2a0\";\n}\n\n.bp5-icon-search-text::before{\n  content:\"\\f2a1\";\n}\n\n.bp5-icon-segmented-control::before{\n  content:\"\\f2a3\";\n}\n\n.bp5-icon-select::before{\n  content:\"\\f2a4\";\n}\n\n.bp5-icon-selection::before{\n  content:\"\\f2a5\";\n}\n\n.bp5-icon-selection-box::before{\n  content:\"\\f399\";\n}\n\n.bp5-icon-selection-box-add::before{\n  content:\"\\f39a\";\n}\n\n.bp5-icon-selection-box-edit::before{\n  content:\"\\f39b\";\n}\n\n.bp5-icon-send-backward::before{\n  content:\"\\f355\";\n}\n\n.bp5-icon-send-message::before{\n  content:\"\\f2a6\";\n}\n\n.bp5-icon-send-to::before{\n  content:\"\\f2a9\";\n}\n\n.bp5-icon-send-to-graph::before{\n  content:\"\\f2a7\";\n}\n\n.bp5-icon-send-to-map::before{\n  content:\"\\f2a8\";\n}\n\n.bp5-icon-sensor::before{\n  content:\"\\f33c\";\n}\n\n.bp5-icon-series-add::before{\n  content:\"\\f2aa\";\n}\n\n.bp5-icon-series-configuration::before{\n  content:\"\\f2ab\";\n}\n\n.bp5-icon-series-derived::before{\n  content:\"\\f2ac\";\n}\n\n.bp5-icon-series-filtered::before{\n  content:\"\\f2ad\";\n}\n\n.bp5-icon-series-search::before{\n  content:\"\\f2ae\";\n}\n\n.bp5-icon-server::before{\n  content:\"\\f378\";\n}\n\n.bp5-icon-server-install::before{\n  content:\"\\f377\";\n}\n\n.bp5-icon-settings::before{\n  content:\"\\f2af\";\n}\n\n.bp5-icon-shapes::before{\n  content:\"\\f2b0\";\n}\n\n.bp5-icon-share::before{\n  content:\"\\f2b1\";\n}\n\n.bp5-icon-shared-filter::before{\n  content:\"\\f2b2\";\n}\n\n.bp5-icon-shield::before{\n  content:\"\\f2b3\";\n}\n\n.bp5-icon-ship::before{\n  content:\"\\f2b4\";\n}\n\n.bp5-icon-shop::before{\n  content:\"\\f2b5\";\n}\n\n.bp5-icon-shopping-cart::before{\n  content:\"\\f2b6\";\n}\n\n.bp5-icon-shorten-text::before{\n  content:\"\\f33f\";\n}\n\n.bp5-icon-signal-search::before{\n  content:\"\\f2b7\";\n}\n\n.bp5-icon-sim-card::before{\n  content:\"\\f2b8\";\n}\n\n.bp5-icon-slash::before{\n  content:\"\\f2b9\";\n}\n\n.bp5-icon-small-cross::before{\n  content:\"\\f2ba\";\n}\n\n.bp5-icon-small-info-sign::before{\n  content:\"\\f334\";\n}\n\n.bp5-icon-small-minus::before{\n  content:\"\\f2bb\";\n}\n\n.bp5-icon-small-plus::before{\n  content:\"\\f2bc\";\n}\n\n.bp5-icon-small-square::before{\n  content:\"\\f2bd\";\n}\n\n.bp5-icon-small-tick::before{\n  content:\"\\f2be\";\n}\n\n.bp5-icon-snowflake::before{\n  content:\"\\f2bf\";\n}\n\n.bp5-icon-soccer-ball::before{\n  content:\"\\f350\";\n}\n\n.bp5-icon-social-media::before{\n  content:\"\\f2c0\";\n}\n\n.bp5-icon-sort::before{\n  content:\"\\f2c7\";\n}\n\n.bp5-icon-sort-alphabetical::before{\n  content:\"\\f2c2\";\n}\n\n.bp5-icon-sort-alphabetical-desc::before{\n  content:\"\\f2c1\";\n}\n\n.bp5-icon-sort-asc::before{\n  content:\"\\f2c3\";\n}\n\n.bp5-icon-sort-desc::before{\n  content:\"\\f2c4\";\n}\n\n.bp5-icon-sort-numerical::before{\n  content:\"\\f2c6\";\n}\n\n.bp5-icon-sort-numerical-desc::before{\n  content:\"\\f2c5\";\n}\n\n.bp5-icon-spell-check::before{\n  content:\"\\f340\";\n}\n\n.bp5-icon-split-columns::before{\n  content:\"\\f2c8\";\n}\n\n.bp5-icon-sports-stadium::before{\n  content:\"\\f351\";\n}\n\n.bp5-icon-square::before{\n  content:\"\\f2c9\";\n}\n\n.bp5-icon-stacked-chart::before{\n  content:\"\\f2ca\";\n}\n\n.bp5-icon-stadium-geometry::before{\n  content:\"\\f2cb\";\n}\n\n.bp5-icon-star::before{\n  content:\"\\f2cd\";\n}\n\n.bp5-icon-star-empty::before{\n  content:\"\\f2cc\";\n}\n\n.bp5-icon-step-backward::before{\n  content:\"\\f2ce\";\n}\n\n.bp5-icon-step-chart::before{\n  content:\"\\f2cf\";\n}\n\n.bp5-icon-step-forward::before{\n  content:\"\\f2d0\";\n}\n\n.bp5-icon-stop::before{\n  content:\"\\f2d1\";\n}\n\n.bp5-icon-stopwatch::before{\n  content:\"\\f2d2\";\n}\n\n.bp5-icon-strikethrough::before{\n  content:\"\\f2d3\";\n}\n\n.bp5-icon-style::before{\n  content:\"\\f2d4\";\n}\n\n.bp5-icon-subscript::before{\n  content:\"\\f339\";\n}\n\n.bp5-icon-subtract-right-join::before{\n  content:\"\\f392\";\n}\n\n.bp5-icon-superscript::before{\n  content:\"\\f33a\";\n}\n\n.bp5-icon-swap-horizontal::before{\n  content:\"\\f2d5\";\n}\n\n.bp5-icon-swap-vertical::before{\n  content:\"\\f2d6\";\n}\n\n.bp5-icon-switch::before{\n  content:\"\\f2d7\";\n}\n\n.bp5-icon-symbol-circle::before{\n  content:\"\\f2d8\";\n}\n\n.bp5-icon-symbol-cross::before{\n  content:\"\\f2d9\";\n}\n\n.bp5-icon-symbol-diamond::before{\n  content:\"\\f2da\";\n}\n\n.bp5-icon-symbol-rectangle::before{\n  content:\"\\f322\";\n}\n\n.bp5-icon-symbol-square::before{\n  content:\"\\f2db\";\n}\n\n.bp5-icon-symbol-triangle-down::before{\n  content:\"\\f2dc\";\n}\n\n.bp5-icon-symbol-triangle-up::before{\n  content:\"\\f2dd\";\n}\n\n.bp5-icon-syringe::before{\n  content:\"\\f2de\";\n}\n\n.bp5-icon-table-sync::before{\n  content:\"\\f36e\";\n}\n\n.bp5-icon-tag::before{\n  content:\"\\f2df\";\n}\n\n.bp5-icon-tag-add::before{\n  content:\"\\f379\";\n}\n\n.bp5-icon-tag-promote::before{\n  content:\"\\f37a\";\n}\n\n.bp5-icon-tag-refresh::before{\n  content:\"\\f37b\";\n}\n\n.bp5-icon-tag-undo::before{\n  content:\"\\f37c\";\n}\n\n.bp5-icon-tags::before{\n  content:\"\\f37d\";\n}\n\n.bp5-icon-take-action::before{\n  content:\"\\f2e0\";\n}\n\n.bp5-icon-tank::before{\n  content:\"\\f2e1\";\n}\n\n.bp5-icon-target::before{\n  content:\"\\f2e2\";\n}\n\n.bp5-icon-taxi::before{\n  content:\"\\f2e3\";\n}\n\n.bp5-icon-team::before{\n  content:\"\\f352\";\n}\n\n.bp5-icon-temperature::before{\n  content:\"\\f2e4\";\n}\n\n.bp5-icon-text-highlight::before{\n  content:\"\\f2e5\";\n}\n\n.bp5-icon-th::before{\n  content:\"\\f2ea\";\n}\n\n.bp5-icon-th-add::before{\n  content:\"\\f38a\";\n}\n\n.bp5-icon-th-derived::before{\n  content:\"\\f2e6\";\n}\n\n.bp5-icon-th-disconnect::before{\n  content:\"\\f2e7\";\n}\n\n.bp5-icon-th-filtered::before{\n  content:\"\\f2e8\";\n}\n\n.bp5-icon-th-list::before{\n  content:\"\\f2e9\";\n}\n\n.bp5-icon-th-list-add::before{\n  content:\"\\f38b\";\n}\n\n.bp5-icon-th-virtual::before{\n  content:\"\\f38c\";\n}\n\n.bp5-icon-th-virtual-add::before{\n  content:\"\\f38d\";\n}\n\n.bp5-icon-third-party::before{\n  content:\"\\f2eb\";\n}\n\n.bp5-icon-thumbs-down::before{\n  content:\"\\f2ec\";\n}\n\n.bp5-icon-thumbs-up::before{\n  content:\"\\f2ed\";\n}\n\n.bp5-icon-tick::before{\n  content:\"\\f2ef\";\n}\n\n.bp5-icon-tick-circle::before{\n  content:\"\\f2ee\";\n}\n\n.bp5-icon-time::before{\n  content:\"\\f2f0\";\n}\n\n.bp5-icon-timeline-area-chart::before{\n  content:\"\\f2f1\";\n}\n\n.bp5-icon-timeline-bar-chart::before{\n  content:\"\\f2f2\";\n}\n\n.bp5-icon-timeline-events::before{\n  content:\"\\f2f3\";\n}\n\n.bp5-icon-timeline-line-chart::before{\n  content:\"\\f2f4\";\n}\n\n.bp5-icon-tint::before{\n  content:\"\\f2f5\";\n}\n\n.bp5-icon-torch::before{\n  content:\"\\f2f6\";\n}\n\n.bp5-icon-tractor::before{\n  content:\"\\f2f7\";\n}\n\n.bp5-icon-train::before{\n  content:\"\\f2f8\";\n}\n\n.bp5-icon-translate::before{\n  content:\"\\f2f9\";\n}\n\n.bp5-icon-trash::before{\n  content:\"\\f2fa\";\n}\n\n.bp5-icon-tree::before{\n  content:\"\\f2fb\";\n}\n\n.bp5-icon-trending-down::before{\n  content:\"\\f2fc\";\n}\n\n.bp5-icon-trending-up::before{\n  content:\"\\f2fd\";\n}\n\n.bp5-icon-trophy::before{\n  content:\"\\f34f\";\n}\n\n.bp5-icon-truck::before{\n  content:\"\\f2fe\";\n}\n\n.bp5-icon-two-columns::before{\n  content:\"\\f2ff\";\n}\n\n.bp5-icon-unarchive::before{\n  content:\"\\f300\";\n}\n\n.bp5-icon-underline::before{\n  content:\"\\f301\";\n}\n\n.bp5-icon-undo::before{\n  content:\"\\f302\";\n}\n\n.bp5-icon-ungroup-objects::before{\n  content:\"\\f303\";\n}\n\n.bp5-icon-unknown-vehicle::before{\n  content:\"\\f304\";\n}\n\n.bp5-icon-unlink::before{\n  content:\"\\f345\";\n}\n\n.bp5-icon-unlock::before{\n  content:\"\\f305\";\n}\n\n.bp5-icon-unpin::before{\n  content:\"\\f306\";\n}\n\n.bp5-icon-unresolve::before{\n  content:\"\\f307\";\n}\n\n.bp5-icon-updated::before{\n  content:\"\\f308\";\n}\n\n.bp5-icon-upload::before{\n  content:\"\\f309\";\n}\n\n.bp5-icon-user::before{\n  content:\"\\f30a\";\n}\n\n.bp5-icon-variable::before{\n  content:\"\\f30b\";\n}\n\n.bp5-icon-vector::before{\n  content:\"\\f35e\";\n}\n\n.bp5-icon-vertical-bar-chart-asc::before{\n  content:\"\\f30c\";\n}\n\n.bp5-icon-vertical-bar-chart-desc::before{\n  content:\"\\f30d\";\n}\n\n.bp5-icon-vertical-distribution::before{\n  content:\"\\f30e\";\n}\n\n.bp5-icon-vertical-inbetween::before{\n  content:\"\\f32a\";\n}\n\n.bp5-icon-video::before{\n  content:\"\\f30f\";\n}\n\n.bp5-icon-virus::before{\n  content:\"\\f310\";\n}\n\n.bp5-icon-volume-down::before{\n  content:\"\\f311\";\n}\n\n.bp5-icon-volume-off::before{\n  content:\"\\f312\";\n}\n\n.bp5-icon-volume-up::before{\n  content:\"\\f313\";\n}\n\n.bp5-icon-walk::before{\n  content:\"\\f314\";\n}\n\n.bp5-icon-warning-sign::before{\n  content:\"\\f315\";\n}\n\n.bp5-icon-waterfall-chart::before{\n  content:\"\\f316\";\n}\n\n.bp5-icon-waves::before{\n  content:\"\\f317\";\n}\n\n.bp5-icon-widget::before{\n  content:\"\\f31b\";\n}\n\n.bp5-icon-widget-button::before{\n  content:\"\\f318\";\n}\n\n.bp5-icon-widget-footer::before{\n  content:\"\\f319\";\n}\n\n.bp5-icon-widget-header::before{\n  content:\"\\f31a\";\n}\n\n.bp5-icon-wind::before{\n  content:\"\\f31c\";\n}\n\n.bp5-icon-wrench::before{\n  content:\"\\f31d\";\n}\n\n.bp5-icon-wrench-redo::before{\n  content:\"\\f37e\";\n}\n\n.bp5-icon-wrench-snooze::before{\n  content:\"\\f37f\";\n}\n\n.bp5-icon-wrench-time::before{\n  content:\"\\f380\";\n}\n\n.bp5-icon-zoom-in::before{\n  content:\"\\f31e\";\n}\n\n.bp5-icon-zoom-out::before{\n  content:\"\\f31f\";\n}\n\n.bp5-icon-zoom-to-fit::before{\n  content:\"\\f320\";\n}\n\n.bp5-submenu > .bp5-popover-wrapper{\n  display:block; }\n\n.bp5-submenu .bp5-popover-target{\n  display:block; }\n  .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{ }\n\n.bp5-submenu.bp5-popover{\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  padding:0 5px; }\n  .bp5-submenu.bp5-popover > .bp5-popover-content{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-submenu.bp5-popover, .bp5-submenu.bp5-popover.bp5-dark{\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n    .bp5-dark .bp5-submenu.bp5-popover > .bp5-popover-content, .bp5-submenu.bp5-popover.bp5-dark > .bp5-popover-content{\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n.bp5-menu{\n  background:#ffffff;\n  border-radius:3px;\n  color:#182026;\n  list-style:none;\n  margin:0;\n  min-width:180px;\n  padding:5px;\n  text-align:left; }\n\n.bp5-menu-divider{\n  border-top:1px solid rgba(16, 22, 26, 0.15);\n  display:block;\n  margin:5px; }\n  .bp5-dark .bp5-menu-divider{\n    border-top-color:rgba(255, 255, 255, 0.15); }\n\n.bp5-menu-item{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:start;\n      -ms-flex-align:start;\n          align-items:flex-start;\n  border-radius:2px;\n  color:inherit;\n  line-height:20px;\n  padding:5px 7px;\n  text-decoration:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-menu-item > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-menu-item > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-menu-item::before,\n  .bp5-menu-item > *{\n    margin-right:7px; }\n  .bp5-menu-item:empty::before,\n  .bp5-menu-item > :last-child{\n    margin-right:0; }\n  .bp5-menu-item > .bp5-fill{\n    word-break:break-word; }\n  .bp5-menu-item:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n    background-color:rgba(167, 182, 194, 0.3);\n    cursor:pointer;\n    text-decoration:none; }\n  .bp5-menu-item.bp5-disabled{\n    background-color:inherit;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n  .bp5-dark .bp5-menu-item{\n    color:inherit; }\n    .bp5-dark .bp5-menu-item:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n      background-color:rgba(138, 155, 168, 0.15);\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-disabled{\n      background-color:inherit;\n      color:rgba(167, 182, 194, 0.6); }\n  .bp5-menu-item.bp5-intent-primary{\n    color:#106ba3; }\n    .bp5-menu-item.bp5-intent-primary .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-primary::before, .bp5-menu-item.bp5-intent-primary::after,\n    .bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n      color:#106ba3; }\n    .bp5-menu-item.bp5-intent-primary:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-menu-item.bp5-intent-primary.bp5-active{\n      background-color:#137cbd; }\n    .bp5-menu-item.bp5-intent-primary:active{\n      background-color:#106ba3; }\n    .bp5-menu-item.bp5-intent-primary:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-menu-item.bp5-intent-primary:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::before, .bp5-menu-item.bp5-intent-primary:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-primary:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-primary:active, .bp5-menu-item.bp5-intent-primary:active::before, .bp5-menu-item.bp5-intent-primary:active::after,\n    .bp5-menu-item.bp5-intent-primary:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-primary.bp5-active, .bp5-menu-item.bp5-intent-primary.bp5-active::before, .bp5-menu-item.bp5-intent-primary.bp5-active::after,\n    .bp5-menu-item.bp5-intent-primary.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item.bp5-intent-success{\n    color:#0d8050; }\n    .bp5-menu-item.bp5-intent-success .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-success::before, .bp5-menu-item.bp5-intent-success::after,\n    .bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n      color:#0d8050; }\n    .bp5-menu-item.bp5-intent-success:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-menu-item.bp5-intent-success.bp5-active{\n      background-color:#0f9960; }\n    .bp5-menu-item.bp5-intent-success:active{\n      background-color:#0d8050; }\n    .bp5-menu-item.bp5-intent-success:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-menu-item.bp5-intent-success:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::before, .bp5-menu-item.bp5-intent-success:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-success:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-success:active, .bp5-menu-item.bp5-intent-success:active::before, .bp5-menu-item.bp5-intent-success:active::after,\n    .bp5-menu-item.bp5-intent-success:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-success.bp5-active, .bp5-menu-item.bp5-intent-success.bp5-active::before, .bp5-menu-item.bp5-intent-success.bp5-active::after,\n    .bp5-menu-item.bp5-intent-success.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item.bp5-intent-warning{\n    color:#bf7326; }\n    .bp5-menu-item.bp5-intent-warning .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-warning::before, .bp5-menu-item.bp5-intent-warning::after,\n    .bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n      color:#bf7326; }\n    .bp5-menu-item.bp5-intent-warning:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-menu-item.bp5-intent-warning.bp5-active{\n      background-color:#d9822b; }\n    .bp5-menu-item.bp5-intent-warning:active{\n      background-color:#bf7326; }\n    .bp5-menu-item.bp5-intent-warning:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-menu-item.bp5-intent-warning:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::before, .bp5-menu-item.bp5-intent-warning:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-warning:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-warning:active, .bp5-menu-item.bp5-intent-warning:active::before, .bp5-menu-item.bp5-intent-warning:active::after,\n    .bp5-menu-item.bp5-intent-warning:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-warning.bp5-active, .bp5-menu-item.bp5-intent-warning.bp5-active::before, .bp5-menu-item.bp5-intent-warning.bp5-active::after,\n    .bp5-menu-item.bp5-intent-warning.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item.bp5-intent-danger{\n    color:#c23030; }\n    .bp5-menu-item.bp5-intent-danger .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-danger::before, .bp5-menu-item.bp5-intent-danger::after,\n    .bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n      color:#c23030; }\n    .bp5-menu-item.bp5-intent-danger:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-menu-item.bp5-intent-danger.bp5-active{\n      background-color:#db3737; }\n    .bp5-menu-item.bp5-intent-danger:active{\n      background-color:#c23030; }\n    .bp5-menu-item.bp5-intent-danger:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-menu-item.bp5-intent-danger:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::before, .bp5-menu-item.bp5-intent-danger:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-danger:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-danger:active, .bp5-menu-item.bp5-intent-danger:active::before, .bp5-menu-item.bp5-intent-danger:active::after,\n    .bp5-menu-item.bp5-intent-danger:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-danger.bp5-active, .bp5-menu-item.bp5-intent-danger.bp5-active::before, .bp5-menu-item.bp5-intent-danger.bp5-active::after,\n    .bp5-menu-item.bp5-intent-danger.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item::before{\n    font-family:\"blueprint-icons-16\", sans-serif;\n    font-size:16px;\n    font-style:normal;\n    font-weight:400;\n    line-height:1;\n    -moz-osx-font-smoothing:grayscale;\n    -webkit-font-smoothing:antialiased;\n    margin-right:7px; }\n  .bp5-menu-item::before,\n  .bp5-menu-item > .bp5-icon{\n    color:#5c7080;\n    margin-top:2px; }\n  .bp5-menu-item .bp5-menu-item-label{\n    color:#5c7080; }\n  .bp5-menu-item:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n    color:inherit; }\n  .bp5-menu-item.bp5-active, .bp5-menu-item:active{\n    background-color:rgba(115, 134, 148, 0.3); }\n  .bp5-menu-item.bp5-disabled{\n    background-color:inherit !important;\n    color:rgba(92, 112, 128, 0.6) !important;\n    cursor:not-allowed !important;\n    outline:none !important; }\n    .bp5-menu-item.bp5-disabled::before,\n    .bp5-menu-item.bp5-disabled > .bp5-icon,\n    .bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n      color:rgba(92, 112, 128, 0.6) !important; }\n  .bp5-large .bp5-menu-item{\n    font-size:16px;\n    line-height:22px;\n    padding:9px 7px; }\n    .bp5-large .bp5-menu-item .bp5-icon{\n      margin-top:3px; }\n    .bp5-large .bp5-menu-item::before{\n      font-family:\"blueprint-icons-20\", sans-serif;\n      font-size:20px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1;\n      -moz-osx-font-smoothing:grayscale;\n      -webkit-font-smoothing:antialiased;\n      margin-right:10px;\n      margin-top:1px; }\n\nbutton.bp5-menu-item{\n  background:none;\n  border:none;\n  text-align:left;\n  width:100%; }\n.bp5-menu-header{\n  border-top:1px solid rgba(16, 22, 26, 0.15);\n  display:block;\n  margin:5px;\n  cursor:default;\n  padding-left:2px; }\n  .bp5-dark .bp5-menu-header{\n    border-top-color:rgba(255, 255, 255, 0.15); }\n  .bp5-menu-header:first-of-type{\n    border-top:none; }\n  .bp5-menu-header > h6{\n    color:#182026;\n    font-weight:600;\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    line-height:17px;\n    margin:0;\n    padding:10px 7px 0 1px; }\n    .bp5-dark .bp5-menu-header > h6{\n      color:#f5f8fa; }\n  .bp5-menu-header:first-of-type > h6{\n    padding-top:0; }\n  .bp5-large .bp5-menu-header > h6{\n    font-size:18px;\n    padding-bottom:5px;\n    padding-top:15px; }\n  .bp5-large .bp5-menu-header:first-of-type > h6{\n    padding-top:0; }\n\n.bp5-dark .bp5-menu{\n  background:#30404d;\n  color:#f5f8fa; }\n\n.bp5-dark .bp5-menu-item{ }\n  .bp5-dark .bp5-menu-item.bp5-intent-primary{\n    color:#48aff0; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary::before, .bp5-dark .bp5-menu-item.bp5-intent-primary::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n      color:#48aff0; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active{\n      background-color:#137cbd; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:active{\n      background-color:#106ba3; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-primary:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-primary:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-primary:active, .bp5-dark .bp5-menu-item.bp5-intent-primary:active::before, .bp5-dark .bp5-menu-item.bp5-intent-primary:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item.bp5-intent-success{\n    color:#3dcc91; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success::before, .bp5-dark .bp5-menu-item.bp5-intent-success::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n      color:#3dcc91; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active{\n      background-color:#0f9960; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success:active{\n      background-color:#0d8050; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-success:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-success:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-success:active, .bp5-dark .bp5-menu-item.bp5-intent-success:active::before, .bp5-dark .bp5-menu-item.bp5-intent-success:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item.bp5-intent-warning{\n    color:#ffb366; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning::before, .bp5-dark .bp5-menu-item.bp5-intent-warning::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n      color:#ffb366; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active{\n      background-color:#d9822b; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:active{\n      background-color:#bf7326; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-warning:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-warning:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-warning:active, .bp5-dark .bp5-menu-item.bp5-intent-warning:active::before, .bp5-dark .bp5-menu-item.bp5-intent-warning:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item.bp5-intent-danger{\n    color:#ff7373; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger::before, .bp5-dark .bp5-menu-item.bp5-intent-danger::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n      color:#ff7373; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active{\n      background-color:#db3737; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:active{\n      background-color:#c23030; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-danger:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-danger:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-danger:active, .bp5-dark .bp5-menu-item.bp5-intent-danger:active::before, .bp5-dark .bp5-menu-item.bp5-intent-danger:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item::before,\n  .bp5-dark .bp5-menu-item > .bp5-icon{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-menu-item .bp5-menu-item-label{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-menu-item.bp5-active, .bp5-dark .bp5-menu-item:active{\n    background-color:rgba(138, 155, 168, 0.3); }\n  .bp5-dark .bp5-menu-item.bp5-disabled{\n    color:rgba(167, 182, 194, 0.6) !important; }\n    .bp5-dark .bp5-menu-item.bp5-disabled::before,\n    .bp5-dark .bp5-menu-item.bp5-disabled > .bp5-icon,\n    .bp5-dark .bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n      color:rgba(167, 182, 194, 0.6) !important; }\n\n.bp5-dark .bp5-menu-divider,\n.bp5-dark .bp5-menu-header{\n  border-color:rgba(255, 255, 255, 0.15); }\n\n.bp5-dark .bp5-menu-header > h6{\n  color:#f5f8fa; }\n\n.bp5-label .bp5-menu{\n  margin-top:5px; }\n.bp5-navbar{\n  background-color:#ffffff;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n  height:50px;\n  padding:0 15px;\n  position:relative;\n  width:100%;\n  z-index:10; }\n  .bp5-navbar.bp5-dark,\n  .bp5-dark .bp5-navbar{\n    background-color:#394b59; }\n  .bp5-navbar.bp5-dark{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-navbar{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-navbar.bp5-fixed-top{\n    left:0;\n    position:fixed;\n    right:0;\n    top:0; }\n\n.bp5-navbar-heading{\n  font-size:16px;\n  margin-right:15px; }\n\n.bp5-navbar-group{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:50px; }\n  .bp5-navbar-group.bp5-align-left{\n    float:left; }\n  .bp5-navbar-group.bp5-align-right{\n    float:right; }\n\n.bp5-navbar-divider{\n  border-left:1px solid rgba(16, 22, 26, 0.15);\n  height:20px;\n  margin:0 10px; }\n  .bp5-dark .bp5-navbar-divider{\n    border-left-color:rgba(255, 255, 255, 0.15); }\n.bp5-non-ideal-state{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  height:100%;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  text-align:center;\n  width:100%; }\n  .bp5-non-ideal-state > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-non-ideal-state > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-non-ideal-state::before,\n  .bp5-non-ideal-state > *{\n    margin-bottom:20px; }\n  .bp5-non-ideal-state:empty::before,\n  .bp5-non-ideal-state > :last-child{\n    margin-bottom:0; }\n  .bp5-non-ideal-state > *{\n    max-width:400px; }\n\n.bp5-non-ideal-state-visual{\n  color:rgba(92, 112, 128, 0.6);\n  font-size:60px; }\n  .bp5-dark .bp5-non-ideal-state-visual{\n    color:rgba(167, 182, 194, 0.6); }\n\n.bp5-overflow-list{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-wrap:nowrap;\n      flex-wrap:nowrap;\n  min-width:0; }\n\n.bp5-overflow-list-spacer{\n  -ms-flex-negative:1;\n      flex-shrink:1;\n  width:1px; }\nbody.bp5-overlay-open{\n  overflow:hidden; }\n\n.bp5-overlay{\n  bottom:0;\n  left:0;\n  position:static;\n  right:0;\n  top:0;\n  z-index:20; }\n  .bp5-overlay:not(.bp5-overlay-open){\n    pointer-events:none; }\n  .bp5-overlay.bp5-overlay-container{\n    overflow:hidden;\n    position:fixed; }\n    .bp5-overlay.bp5-overlay-container.bp5-overlay-inline{\n      position:absolute; }\n  .bp5-overlay.bp5-overlay-scroll-container{\n    overflow:auto;\n    position:fixed; }\n    .bp5-overlay.bp5-overlay-scroll-container.bp5-overlay-inline{\n      position:absolute; }\n  .bp5-overlay.bp5-overlay-inline{\n    display:inline;\n    overflow:visible; }\n\n.bp5-overlay-content{\n  position:fixed;\n  z-index:20; }\n  .bp5-overlay-inline .bp5-overlay-content,\n  .bp5-overlay-scroll-container .bp5-overlay-content{\n    position:absolute; }\n\n.bp5-overlay-backdrop{\n  bottom:0;\n  left:0;\n  position:fixed;\n  right:0;\n  top:0;\n  opacity:1;\n  background-color:rgba(16, 22, 26, 0.7);\n  overflow:auto;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  z-index:20; }\n  .bp5-overlay-backdrop.bp5-overlay-enter, .bp5-overlay-backdrop.bp5-overlay-appear{\n    opacity:0; }\n  .bp5-overlay-backdrop.bp5-overlay-enter-active, .bp5-overlay-backdrop.bp5-overlay-appear-active{\n    opacity:1;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:200ms;\n            transition-duration:200ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-overlay-backdrop.bp5-overlay-exit{\n    opacity:1; }\n  .bp5-overlay-backdrop.bp5-overlay-exit-active{\n    opacity:0;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:200ms;\n            transition-duration:200ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-overlay-backdrop:focus{\n    outline:none; }\n  .bp5-overlay-inline .bp5-overlay-backdrop{\n    position:absolute; }\n.bp5-panel-stack{\n  overflow:hidden;\n  position:relative; }\n\n.bp5-panel-stack-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  -webkit-box-shadow:0 1px rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-negative:0;\n      flex-shrink:0;\n  height:30px;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack-header{\n    -webkit-box-shadow:0 1px rgba(255, 255, 255, 0.15);\n            box-shadow:0 1px rgba(255, 255, 255, 0.15); }\n  .bp5-panel-stack-header > span{\n    -webkit-box-align:stretch;\n        -ms-flex-align:stretch;\n            align-items:stretch;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-flex:1;\n        -ms-flex:1;\n            flex:1; }\n  .bp5-panel-stack-header .bp5-heading{\n    margin:0 5px; }\n\n.bp5-button.bp5-panel-stack-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap; }\n  .bp5-button.bp5-panel-stack-header-back .bp5-icon{\n    margin:0 2px; }\n\n.bp5-panel-stack-view{\n  bottom:0;\n  left:0;\n  position:absolute;\n  right:0;\n  top:0;\n  background-color:#ffffff;\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack-view{\n    background-color:#30404d; }\n  .bp5-panel-stack-view:nth-last-child(n + 4){\n    display:none; }\n\n.bp5-panel-stack-push .bp5-panel-stack-enter, .bp5-panel-stack-push .bp5-panel-stack-appear{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0; }\n\n.bp5-panel-stack-push .bp5-panel-stack-enter-active, .bp5-panel-stack-push .bp5-panel-stack-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack-push .bp5-panel-stack-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack-push .bp5-panel-stack-exit-active{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-enter, .bp5-panel-stack-pop .bp5-panel-stack-appear{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-enter-active, .bp5-panel-stack-pop .bp5-panel-stack-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-exit-active{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n.bp5-panel-stack2{\n  overflow:hidden;\n  position:relative; }\n\n.bp5-panel-stack2-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  -webkit-box-shadow:0 1px rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-negative:0;\n      flex-shrink:0;\n  height:30px;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack2-header{\n    -webkit-box-shadow:0 1px rgba(255, 255, 255, 0.15);\n            box-shadow:0 1px rgba(255, 255, 255, 0.15); }\n  .bp5-panel-stack2-header > span{\n    -webkit-box-align:stretch;\n        -ms-flex-align:stretch;\n            align-items:stretch;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-flex:1;\n        -ms-flex:1;\n            flex:1; }\n  .bp5-panel-stack2-header .bp5-heading{\n    margin:0 5px; }\n\n.bp5-button.bp5-panel-stack2-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap; }\n  .bp5-button.bp5-panel-stack2-header-back .bp5-icon{\n    margin:0 2px; }\n\n.bp5-panel-stack2-view{\n  bottom:0;\n  left:0;\n  position:absolute;\n  right:0;\n  top:0;\n  background-color:#ffffff;\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack2-view{\n    background-color:#30404d; }\n  .bp5-panel-stack2-view:nth-last-child(n + 4){\n    display:none; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-enter, .bp5-panel-stack2-push .bp5-panel-stack2-appear{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-enter-active, .bp5-panel-stack2-push .bp5-panel-stack2-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-exit-active{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter, .bp5-panel-stack2-pop .bp5-panel-stack2-appear{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter-active, .bp5-panel-stack2-pop .bp5-panel-stack2-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit-active{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n.bp5-popover{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  -webkit-transform:scale(1);\n          transform:scale(1);\n  border-radius:3px;\n  display:inline-block;\n  z-index:20; }\n  .bp5-popover .bp5-popover-arrow{\n    height:30px;\n    position:absolute;\n    width:30px; }\n    .bp5-popover .bp5-popover-arrow::before{\n      height:20px;\n      margin:5px;\n      width:20px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-popover{\n    margin-bottom:17px;\n    margin-top:-17px; }\n    .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-popover > .bp5-popover-arrow{\n      bottom:-11px; }\n      .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(-90deg);\n                transform:rotate(-90deg); }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-popover{\n    margin-left:17px; }\n    .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-popover > .bp5-popover-arrow{\n      left:-11px; }\n      .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(0);\n                transform:rotate(0); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-popover{\n    margin-top:17px; }\n    .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-popover > .bp5-popover-arrow{\n      top:-11px; }\n      .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(90deg);\n                transform:rotate(90deg); }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-popover{\n    margin-left:-17px;\n    margin-right:17px; }\n    .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-popover > .bp5-popover-arrow{\n      right:-11px; }\n      .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(180deg);\n                transform:rotate(180deg); }\n  .bp5-tether-element-attached-middle > .bp5-popover > .bp5-popover-arrow{\n    top:50%;\n    -webkit-transform:translateY(-50%);\n            transform:translateY(-50%); }\n  .bp5-tether-element-attached-center > .bp5-popover > .bp5-popover-arrow{\n    right:50%;\n    -webkit-transform:translateX(50%);\n            transform:translateX(50%); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-top > .bp5-popover > .bp5-popover-arrow{\n    top:-0.3934px; }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-right > .bp5-popover > .bp5-popover-arrow{\n    right:-0.3934px; }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-left > .bp5-popover > .bp5-popover-arrow{\n    left:-0.3934px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-bottom > .bp5-popover > .bp5-popover-arrow{\n    bottom:-0.3934px; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-left > .bp5-popover{\n    -webkit-transform-origin:top left;\n            transform-origin:top left; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-center > .bp5-popover{\n    -webkit-transform-origin:top center;\n            transform-origin:top center; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-right > .bp5-popover{\n    -webkit-transform-origin:top right;\n            transform-origin:top right; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-left > .bp5-popover{\n    -webkit-transform-origin:center left;\n            transform-origin:center left; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-center > .bp5-popover{\n    -webkit-transform-origin:center center;\n            transform-origin:center center; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-right > .bp5-popover{\n    -webkit-transform-origin:center right;\n            transform-origin:center right; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-left > .bp5-popover{\n    -webkit-transform-origin:bottom left;\n            transform-origin:bottom left; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-center > .bp5-popover{\n    -webkit-transform-origin:bottom center;\n            transform-origin:bottom center; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-right > .bp5-popover{\n    -webkit-transform-origin:bottom right;\n            transform-origin:bottom right; }\n  .bp5-popover .bp5-popover-content{\n    background:#ffffff; }\n  .bp5-popover .bp5-popover-content,\n  .bp5-popover .bp5-heading{\n    color:inherit; }\n  .bp5-popover .bp5-popover-arrow::before{\n    -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2);\n            box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-popover .bp5-popover-arrow-border{\n    fill:#10161a;\n    fill-opacity:0.1; }\n  .bp5-popover .bp5-popover-arrow-fill{\n    fill:#ffffff; }\n  .bp5-popover-enter > .bp5-popover, .bp5-popover-appear > .bp5-popover{\n    -webkit-transform:scale(0.3);\n            transform:scale(0.3); }\n  .bp5-popover-enter-active > .bp5-popover, .bp5-popover-appear-active > .bp5-popover{\n    -webkit-transform:scale(1);\n            transform:scale(1);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-popover-exit > .bp5-popover{\n    -webkit-transform:scale(1);\n            transform:scale(1); }\n  .bp5-popover-exit-active > .bp5-popover{\n    -webkit-transform:scale(0.3);\n            transform:scale(0.3);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-popover .bp5-popover-content{\n    border-radius:3px;\n    position:relative; }\n  .bp5-popover.bp5-popover-content-sizing .bp5-popover-content{\n    max-width:350px;\n    padding:20px; }\n  .bp5-popover-target + .bp5-overlay .bp5-popover.bp5-popover-content-sizing{\n    width:350px; }\n  .bp5-popover.bp5-minimal{\n    margin:0 !important; }\n    .bp5-popover.bp5-minimal .bp5-popover-arrow{\n      display:none; }\n    .bp5-popover.bp5-minimal.bp5-popover{\n      -webkit-transform:scale(1);\n              transform:scale(1); }\n      .bp5-popover-enter > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1); }\n      .bp5-popover-enter-active > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear-active > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1);\n        -webkit-transition-delay:0;\n                transition-delay:0;\n        -webkit-transition-duration:100ms;\n                transition-duration:100ms;\n        -webkit-transition-property:-webkit-transform;\n        transition-property:-webkit-transform;\n        transition-property:transform;\n        transition-property:transform, -webkit-transform;\n        -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n                transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n      .bp5-popover-exit > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1); }\n      .bp5-popover-exit-active > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1);\n        -webkit-transition-delay:0;\n                transition-delay:0;\n        -webkit-transition-duration:100ms;\n                transition-duration:100ms;\n        -webkit-transition-property:-webkit-transform;\n        transition-property:-webkit-transform;\n        transition-property:transform;\n        transition-property:transform, -webkit-transform;\n        -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n                transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-popover.bp5-dark,\n  .bp5-dark .bp5-popover{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n    .bp5-popover.bp5-dark .bp5-popover-content,\n    .bp5-dark .bp5-popover .bp5-popover-content{\n      background:#30404d; }\n    .bp5-popover.bp5-dark .bp5-popover-content,\n    .bp5-popover.bp5-dark .bp5-heading,\n    .bp5-dark .bp5-popover .bp5-popover-content,\n    .bp5-dark .bp5-popover .bp5-heading{\n      color:inherit; }\n    .bp5-popover.bp5-dark .bp5-popover-arrow::before,\n    .bp5-dark .bp5-popover .bp5-popover-arrow::before{\n      -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4);\n              box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4); }\n    .bp5-popover.bp5-dark .bp5-popover-arrow-border,\n    .bp5-dark .bp5-popover .bp5-popover-arrow-border{\n      fill:#10161a;\n      fill-opacity:0.2; }\n    .bp5-popover.bp5-dark .bp5-popover-arrow-fill,\n    .bp5-dark .bp5-popover .bp5-popover-arrow-fill{\n      fill:#30404d; }\n\n.bp5-popover-arrow::before{\n  border-radius:2px;\n  content:\"\";\n  display:block;\n  position:absolute;\n  -webkit-transform:rotate(45deg);\n          transform:rotate(45deg); }\n\n.bp5-tether-pinned .bp5-popover-arrow{\n  display:none; }\n\n.bp5-popover-backdrop{\n  background:rgba(255, 255, 255, 0); }\n\n.bp5-transition-container{\n  opacity:1;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  z-index:20; }\n  .bp5-transition-container.bp5-popover-enter, .bp5-transition-container.bp5-popover-appear{\n    opacity:0; }\n  .bp5-transition-container.bp5-popover-enter-active, .bp5-transition-container.bp5-popover-appear-active{\n    opacity:1;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-transition-container.bp5-popover-exit{\n    opacity:1; }\n  .bp5-transition-container.bp5-popover-exit-active{\n    opacity:0;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-transition-container:focus{\n    outline:none; }\n  .bp5-transition-container.bp5-popover-leave .bp5-popover-content{\n    pointer-events:none; }\n  .bp5-transition-container[data-x-out-of-boundaries]{\n    display:none; }\n\nspan.bp5-popover-target{\n  display:inline-block; }\n\n.bp5-popover-wrapper.bp5-fill{\n  width:100%; }\n\n.bp5-portal{\n  left:0;\n  position:absolute;\n  right:0;\n  top:0; }\n@-webkit-keyframes linear-progress-bar-stripes{\n  from{\n    background-position:0 0; }\n  to{\n    background-position:30px 0; } }\n@keyframes linear-progress-bar-stripes{\n  from{\n    background-position:0 0; }\n  to{\n    background-position:30px 0; } }\n\n.bp5-progress-bar{\n  background:rgba(92, 112, 128, 0.2);\n  border-radius:40px;\n  display:block;\n  height:8px;\n  overflow:hidden;\n  position:relative;\n  width:100%; }\n  .bp5-progress-bar .bp5-progress-meter{\n    background:linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%);\n    background-color:rgba(92, 112, 128, 0.8);\n    background-size:30px 30px;\n    border-radius:40px;\n    height:100%;\n    position:absolute;\n    -webkit-transition:width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    width:100%; }\n  .bp5-progress-bar:not(.bp5-no-animation):not(.bp5-no-stripes) .bp5-progress-meter{\n    animation:linear-progress-bar-stripes 300ms linear infinite reverse; }\n  .bp5-progress-bar.bp5-no-stripes .bp5-progress-meter{\n    background-image:none; }\n\n.bp5-dark .bp5-progress-bar{\n  background:rgba(16, 22, 26, 0.5); }\n  .bp5-dark .bp5-progress-bar .bp5-progress-meter{\n    background-color:#8a9ba8; }\n\n.bp5-progress-bar.bp5-intent-primary .bp5-progress-meter{\n  background-color:#137cbd; }\n\n.bp5-progress-bar.bp5-intent-success .bp5-progress-meter{\n  background-color:#0f9960; }\n\n.bp5-progress-bar.bp5-intent-warning .bp5-progress-meter{\n  background-color:#d9822b; }\n\n.bp5-progress-bar.bp5-intent-danger .bp5-progress-meter{\n  background-color:#db3737; }\n@-webkit-keyframes skeleton-glow{\n  from{\n    background:rgba(206, 217, 224, 0.2);\n    border-color:rgba(206, 217, 224, 0.2); }\n  to{\n    background:rgba(92, 112, 128, 0.2);\n    border-color:rgba(92, 112, 128, 0.2); } }\n@keyframes skeleton-glow{\n  from{\n    background:rgba(206, 217, 224, 0.2);\n    border-color:rgba(206, 217, 224, 0.2); }\n  to{\n    background:rgba(92, 112, 128, 0.2);\n    border-color:rgba(92, 112, 128, 0.2); } }\n.bp5-skeleton{\n  -webkit-animation:1000ms linear infinite alternate skeleton-glow;\n          animation:1000ms linear infinite alternate skeleton-glow;\n  background:rgba(206, 217, 224, 0.2);\n  background-clip:padding-box !important;\n  border-color:rgba(206, 217, 224, 0.2) !important;\n  border-radius:2px;\n  -webkit-box-shadow:none !important;\n          box-shadow:none !important;\n  color:transparent !important;\n  cursor:default;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-skeleton::before, .bp5-skeleton::after,\n  .bp5-skeleton *{\n    visibility:hidden !important; }\n.bp5-slider{\n  height:40px;\n  min-width:150px;\n  width:100%;\n  cursor:default;\n  outline:none;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-slider:hover{\n    cursor:pointer; }\n  .bp5-slider:active{\n    cursor:-webkit-grabbing;\n    cursor:grabbing; }\n  .bp5-slider.bp5-disabled{\n    cursor:not-allowed;\n    opacity:0.5; }\n  .bp5-slider.bp5-slider-unlabeled{\n    height:16px; }\n\n.bp5-slider-track,\n.bp5-slider-progress{\n  height:6px;\n  left:0;\n  right:0;\n  top:5px;\n  position:absolute; }\n\n.bp5-slider-track{\n  border-radius:3px;\n  overflow:hidden; }\n\n.bp5-slider-progress{\n  background:rgba(92, 112, 128, 0.2); }\n  .bp5-dark .bp5-slider-progress{\n    background:rgba(16, 22, 26, 0.5); }\n  .bp5-slider-progress.bp5-intent-primary{\n    background-color:#137cbd; }\n  .bp5-slider-progress.bp5-intent-success{\n    background-color:#0f9960; }\n  .bp5-slider-progress.bp5-intent-warning{\n    background-color:#d9822b; }\n  .bp5-slider-progress.bp5-intent-danger{\n    background-color:#db3737; }\n\n.bp5-slider-handle{\n  background-color:#f5f8fa;\n  background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n  background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n  color:#182026;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n  cursor:pointer;\n  height:16px;\n  left:0;\n  position:absolute;\n  top:0;\n  width:16px; }\n  .bp5-slider-handle:hover{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n  .bp5-slider-handle:active, .bp5-slider-handle.bp5-active{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-slider-handle:disabled, .bp5-slider-handle.bp5-disabled{\n    background-color:rgba(206, 217, 224, 0.5);\n    background-image:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    outline:none; }\n    .bp5-slider-handle:disabled.bp5-active, .bp5-slider-handle:disabled.bp5-active:hover, .bp5-slider-handle.bp5-disabled.bp5-active, .bp5-slider-handle.bp5-disabled.bp5-active:hover{\n      background:rgba(206, 217, 224, 0.7); }\n  .bp5-slider-handle:focus{\n    z-index:1; }\n  .bp5-slider-handle:hover{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n    cursor:-webkit-grab;\n    cursor:grab;\n    z-index:2; }\n  .bp5-slider-handle.bp5-active{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 1px rgba(16, 22, 26, 0.1);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 1px rgba(16, 22, 26, 0.1);\n    cursor:-webkit-grabbing;\n    cursor:grabbing; }\n  .bp5-disabled .bp5-slider-handle{\n    background:#bfccd6;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    pointer-events:none; }\n  .bp5-dark .bp5-slider-handle{\n    background-color:#394b59;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark .bp5-slider-handle:hover, .bp5-dark .bp5-slider-handle:active, .bp5-dark .bp5-slider-handle.bp5-active{\n      color:#f5f8fa; }\n    .bp5-dark .bp5-slider-handle:hover{\n      background-color:#30404d;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-slider-handle:active, .bp5-dark .bp5-slider-handle.bp5-active{\n      background-color:#202b33;\n      background-image:none;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-slider-handle:disabled, .bp5-dark .bp5-slider-handle.bp5-disabled{\n      background-color:rgba(57, 75, 89, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n      .bp5-dark .bp5-slider-handle:disabled.bp5-active, .bp5-dark .bp5-slider-handle.bp5-disabled.bp5-active{\n        background:rgba(57, 75, 89, 0.7); }\n    .bp5-dark .bp5-slider-handle .bp5-button-spinner .bp5-spinner-head{\n      background:rgba(16, 22, 26, 0.5);\n      stroke:#8a9ba8; }\n    .bp5-dark .bp5-slider-handle, .bp5-dark .bp5-slider-handle:hover{\n      background-color:#394b59; }\n    .bp5-dark .bp5-slider-handle.bp5-active{\n      background-color:#293742; }\n  .bp5-dark .bp5-disabled .bp5-slider-handle{\n    background:#5c7080;\n    border-color:#5c7080;\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-slider-handle .bp5-slider-label{\n    background:#394b59;\n    border-radius:3px;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n    color:#f5f8fa;\n    margin-left:8px; }\n    .bp5-dark .bp5-slider-handle .bp5-slider-label{\n      background:#e1e8ed;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n      color:#394b59; }\n    .bp5-disabled .bp5-slider-handle .bp5-slider-label{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-slider-handle.bp5-start, .bp5-slider-handle.bp5-end{\n    width:8px; }\n  .bp5-slider-handle.bp5-start{\n    border-bottom-right-radius:0;\n    border-top-right-radius:0; }\n  .bp5-slider-handle.bp5-end{\n    border-bottom-left-radius:0;\n    border-top-left-radius:0;\n    margin-left:8px; }\n    .bp5-slider-handle.bp5-end .bp5-slider-label{\n      margin-left:0; }\n\n.bp5-slider-label{\n  -webkit-transform:translate(-50%, 20px);\n          transform:translate(-50%, 20px);\n  display:inline-block;\n  font-size:12px;\n  line-height:1;\n  padding:2px 5px;\n  position:absolute;\n  vertical-align:top; }\n\n.bp5-slider.bp5-vertical{\n  height:150px;\n  min-width:40px;\n  width:40px; }\n  .bp5-slider.bp5-vertical .bp5-slider-track,\n  .bp5-slider.bp5-vertical .bp5-slider-progress{\n    bottom:0;\n    height:auto;\n    left:5px;\n    top:0;\n    width:6px; }\n  .bp5-slider.bp5-vertical .bp5-slider-progress{\n    top:auto; }\n  .bp5-slider.bp5-vertical .bp5-slider-label{\n    -webkit-transform:translate(20px, 50%);\n            transform:translate(20px, 50%); }\n  .bp5-slider.bp5-vertical .bp5-slider-handle{\n    top:auto; }\n    .bp5-slider.bp5-vertical .bp5-slider-handle .bp5-slider-label{\n      margin-left:0;\n      margin-top:-8px; }\n    .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end, .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n      height:8px;\n      margin-left:0;\n      width:16px; }\n    .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n      border-bottom-right-radius:3px;\n      border-top-left-radius:0; }\n      .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start .bp5-slider-label{\n        -webkit-transform:translate(20px);\n                transform:translate(20px); }\n    .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end{\n      border-bottom-left-radius:0;\n      border-bottom-right-radius:0;\n      border-top-left-radius:3px;\n      margin-bottom:8px; }\n@-webkit-keyframes pt-spinner-animation{\n  from{\n    -webkit-transform:rotate(0deg);\n            transform:rotate(0deg); }\n  to{\n    -webkit-transform:rotate(360deg);\n            transform:rotate(360deg); } }\n@keyframes pt-spinner-animation{\n  from{\n    -webkit-transform:rotate(0deg);\n            transform:rotate(0deg); }\n  to{\n    -webkit-transform:rotate(360deg);\n            transform:rotate(360deg); } }\n\n.bp5-spinner{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  overflow:visible;\n  vertical-align:middle; }\n  .bp5-spinner svg{\n    display:block; }\n  .bp5-spinner path{\n    fill-opacity:0; }\n  .bp5-spinner .bp5-spinner-head{\n    stroke:rgba(92, 112, 128, 0.8);\n    stroke-linecap:round;\n    -webkit-transform-origin:center;\n            transform-origin:center;\n    -webkit-transition:stroke-dashoffset 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:stroke-dashoffset 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-spinner .bp5-spinner-track{\n    stroke:rgba(92, 112, 128, 0.2); }\n\n.bp5-spinner-animation{\n  -webkit-animation:pt-spinner-animation 500ms linear infinite;\n          animation:pt-spinner-animation 500ms linear infinite; }\n  .bp5-no-spin > .bp5-spinner-animation{\n    -webkit-animation:none;\n            animation:none; }\n\n.bp5-dark .bp5-spinner .bp5-spinner-head{\n  stroke:#8a9ba8; }\n\n.bp5-dark .bp5-spinner .bp5-spinner-track{\n  stroke:rgba(16, 22, 26, 0.5); }\n\n.bp5-spinner.bp5-intent-primary .bp5-spinner-head{\n  stroke:#137cbd; }\n\n.bp5-spinner.bp5-intent-success .bp5-spinner-head{\n  stroke:#0f9960; }\n\n.bp5-spinner.bp5-intent-warning .bp5-spinner-head{\n  stroke:#d9822b; }\n\n.bp5-spinner.bp5-intent-danger .bp5-spinner-head{\n  stroke:#db3737; }\n.bp5-tabs.bp5-vertical{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex; }\n  .bp5-tabs.bp5-vertical > .bp5-tab-list{\n    -webkit-box-align:start;\n        -ms-flex-align:start;\n            align-items:flex-start;\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:column;\n            flex-direction:column; }\n    .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab{\n      border-radius:3px;\n      padding:0 10px;\n      width:100%; }\n      .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab[aria-selected=\"true\"]{\n        background-color:rgba(19, 124, 189, 0.2);\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n    .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n      background-color:rgba(19, 124, 189, 0.2);\n      border-radius:3px;\n      bottom:0;\n      height:auto;\n      left:0;\n      right:0;\n      top:0; }\n  .bp5-tabs.bp5-vertical > .bp5-tab-panel{\n    margin-top:0;\n    padding-left:20px; }\n\n.bp5-tab-list{\n  -webkit-box-align:end;\n      -ms-flex-align:end;\n          align-items:flex-end;\n  border:none;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  list-style:none;\n  margin:0;\n  padding:0;\n  position:relative; }\n  .bp5-tab-list > *:not(:last-child){\n    margin-right:20px; }\n\n.bp5-tab{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  color:#182026;\n  cursor:pointer;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  font-size:14px;\n  line-height:30px;\n  max-width:100%;\n  position:relative;\n  vertical-align:top; }\n  .bp5-tab a{\n    color:inherit;\n    display:block;\n    text-decoration:none; }\n  .bp5-tab-indicator-wrapper ~ .bp5-tab{\n    background-color:transparent !important;\n    -webkit-box-shadow:none !important;\n            box-shadow:none !important; }\n  .bp5-tab[aria-disabled=\"true\"]{\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n  .bp5-tab[aria-selected=\"true\"]{\n    border-radius:0;\n    -webkit-box-shadow:inset 0 -3px 0 #106ba3;\n            box-shadow:inset 0 -3px 0 #106ba3; }\n  .bp5-tab[aria-selected=\"true\"], .bp5-tab:not([aria-disabled=\"true\"]):hover{\n    color:#106ba3; }\n  .bp5-tab:focus{\n    -moz-outline-radius:0; }\n  .bp5-large > .bp5-tab{\n    font-size:16px;\n    line-height:40px; }\n\n.bp5-tab-panel{\n  margin-top:20px; }\n  .bp5-tab-panel[aria-hidden=\"true\"]{\n    display:none; }\n\n.bp5-tab-indicator-wrapper{\n  left:0;\n  pointer-events:none;\n  position:absolute;\n  top:0;\n  -webkit-transform:translateX(0), translateY(0);\n          transform:translateX(0), translateY(0);\n  -webkit-transition:height, width, -webkit-transform;\n  transition:height, width, -webkit-transform;\n  transition:height, transform, width;\n  transition:height, transform, width, -webkit-transform;\n  -webkit-transition-duration:200ms;\n          transition-duration:200ms;\n  -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n          transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n    background-color:#106ba3;\n    bottom:0;\n    height:3px;\n    left:0;\n    position:absolute;\n    right:0; }\n  .bp5-tab-indicator-wrapper.bp5-no-animation{\n    -webkit-transition:none;\n    transition:none; }\n\n.bp5-dark .bp5-tab{\n  color:#f5f8fa; }\n  .bp5-dark .bp5-tab[aria-disabled=\"true\"]{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-dark .bp5-tab[aria-selected=\"true\"]{\n    -webkit-box-shadow:inset 0 -3px 0 #48aff0;\n            box-shadow:inset 0 -3px 0 #48aff0; }\n  .bp5-dark .bp5-tab[aria-selected=\"true\"], .bp5-dark .bp5-tab:not([aria-disabled=\"true\"]):hover{\n    color:#48aff0; }\n\n.bp5-dark .bp5-tab-indicator{\n  background-color:#48aff0; }\n\n.bp5-flex-expander{\n  -webkit-box-flex:1;\n      -ms-flex:1 1;\n          flex:1 1; }\n.bp5-tag{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background-color:#5c7080;\n  border:none;\n  border-radius:3px;\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  color:#f5f8fa;\n  font-size:12px;\n  line-height:16px;\n  max-width:100%;\n  min-height:20px;\n  min-width:20px;\n  padding:2px 6px;\n  position:relative; }\n  .bp5-tag.bp5-interactive{\n    cursor:pointer; }\n    .bp5-tag.bp5-interactive:hover{\n      background-color:rgba(92, 112, 128, 0.85); }\n    .bp5-tag.bp5-interactive.bp5-active, .bp5-tag.bp5-interactive:active{\n      background-color:rgba(92, 112, 128, 0.7); }\n  .bp5-tag > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-tag > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-tag::before,\n  .bp5-tag > *{\n    margin-right:4px; }\n  .bp5-tag:empty::before,\n  .bp5-tag > :last-child{\n    margin-right:0; }\n  .bp5-tag:focus{\n    outline:rgba(19, 124, 189, 0.6) auto 2px;\n    outline-offset:0;\n    -moz-outline-radius:6px; }\n  .bp5-tag.bp5-round{\n    border-radius:30px;\n    padding-left:8px;\n    padding-right:8px; }\n  .bp5-dark .bp5-tag{\n    background-color:#bfccd6;\n    color:#182026; }\n    .bp5-dark .bp5-tag.bp5-interactive{\n      cursor:pointer; }\n      .bp5-dark .bp5-tag.bp5-interactive:hover{\n        background-color:rgba(191, 204, 214, 0.85); }\n      .bp5-dark .bp5-tag.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-interactive:active{\n        background-color:rgba(191, 204, 214, 0.7); }\n    .bp5-dark .bp5-tag > .bp5-icon, .bp5-dark .bp5-tag .bp5-icon-standard, .bp5-dark .bp5-tag .bp5-icon-large{\n      fill:currentColor; }\n  .bp5-tag > .bp5-icon, .bp5-tag .bp5-icon-standard, .bp5-tag .bp5-icon-large{\n    fill:#ffffff; }\n  .bp5-tag.bp5-large,\n  .bp5-large .bp5-tag{\n    font-size:14px;\n    line-height:20px;\n    min-height:30px;\n    min-width:30px;\n    padding:5px 10px; }\n    .bp5-tag.bp5-large::before,\n    .bp5-tag.bp5-large > *,\n    .bp5-large .bp5-tag::before,\n    .bp5-large .bp5-tag > *{\n      margin-right:7px; }\n    .bp5-tag.bp5-large:empty::before,\n    .bp5-tag.bp5-large > :last-child,\n    .bp5-large .bp5-tag:empty::before,\n    .bp5-large .bp5-tag > :last-child{\n      margin-right:0; }\n    .bp5-tag.bp5-large.bp5-round,\n    .bp5-large .bp5-tag.bp5-round{\n      padding-left:12px;\n      padding-right:12px; }\n  .bp5-tag.bp5-intent-primary{\n    background:#137cbd;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-primary.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-primary.bp5-interactive:hover{\n        background-color:rgba(19, 124, 189, 0.85); }\n      .bp5-tag.bp5-intent-primary.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-primary.bp5-interactive:active{\n        background-color:rgba(19, 124, 189, 0.7); }\n  .bp5-tag.bp5-intent-success{\n    background:#0f9960;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-success.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-success.bp5-interactive:hover{\n        background-color:rgba(15, 153, 96, 0.85); }\n      .bp5-tag.bp5-intent-success.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-success.bp5-interactive:active{\n        background-color:rgba(15, 153, 96, 0.7); }\n  .bp5-tag.bp5-intent-warning{\n    background:#d9822b;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-warning.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-warning.bp5-interactive:hover{\n        background-color:rgba(217, 130, 43, 0.85); }\n      .bp5-tag.bp5-intent-warning.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-warning.bp5-interactive:active{\n        background-color:rgba(217, 130, 43, 0.7); }\n  .bp5-tag.bp5-intent-danger{\n    background:#db3737;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-danger.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-danger.bp5-interactive:hover{\n        background-color:rgba(219, 55, 55, 0.85); }\n      .bp5-tag.bp5-intent-danger.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-danger.bp5-interactive:active{\n        background-color:rgba(219, 55, 55, 0.7); }\n  .bp5-tag.bp5-fill{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    width:100%; }\n  .bp5-tag.bp5-minimal > .bp5-icon, .bp5-tag.bp5-minimal .bp5-icon-standard, .bp5-tag.bp5-minimal .bp5-icon-large{\n    fill:#5c7080; }\n  .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]){\n    background-color:rgba(138, 155, 168, 0.2);\n    color:#182026; }\n    .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:hover{\n        background-color:rgba(92, 112, 128, 0.3); }\n      .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive.bp5-active, .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:active{\n        background-color:rgba(92, 112, 128, 0.4); }\n    .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]){\n      color:#f5f8fa; }\n      .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:hover{\n          background-color:rgba(191, 204, 214, 0.3); }\n        .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:active{\n          background-color:rgba(191, 204, 214, 0.4); }\n      .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]) > .bp5-icon, .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]) .bp5-icon-standard, .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]) .bp5-icon-large{\n        fill:#a7b6c2; }\n  .bp5-tag.bp5-minimal.bp5-intent-primary{\n    background-color:rgba(19, 124, 189, 0.15);\n    color:#106ba3; }\n    .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n        background-color:rgba(19, 124, 189, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active{\n        background-color:rgba(19, 124, 189, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-primary > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-large{\n      fill:#137cbd; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary{\n      background-color:rgba(19, 124, 189, 0.25);\n      color:#48aff0; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n          background-color:rgba(19, 124, 189, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active{\n          background-color:rgba(19, 124, 189, 0.45); }\n  .bp5-tag.bp5-minimal.bp5-intent-success{\n    background-color:rgba(15, 153, 96, 0.15);\n    color:#0d8050; }\n    .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n        background-color:rgba(15, 153, 96, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active{\n        background-color:rgba(15, 153, 96, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-success > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-large{\n      fill:#0f9960; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success{\n      background-color:rgba(15, 153, 96, 0.25);\n      color:#3dcc91; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n          background-color:rgba(15, 153, 96, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active{\n          background-color:rgba(15, 153, 96, 0.45); }\n  .bp5-tag.bp5-minimal.bp5-intent-warning{\n    background-color:rgba(217, 130, 43, 0.15);\n    color:#bf7326; }\n    .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n        background-color:rgba(217, 130, 43, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active{\n        background-color:rgba(217, 130, 43, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-warning > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-large{\n      fill:#d9822b; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning{\n      background-color:rgba(217, 130, 43, 0.25);\n      color:#ffb366; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n          background-color:rgba(217, 130, 43, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active{\n          background-color:rgba(217, 130, 43, 0.45); }\n  .bp5-tag.bp5-minimal.bp5-intent-danger{\n    background-color:rgba(219, 55, 55, 0.15);\n    color:#c23030; }\n    .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n        background-color:rgba(219, 55, 55, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active{\n        background-color:rgba(219, 55, 55, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-danger > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-large{\n      fill:#db3737; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger{\n      background-color:rgba(219, 55, 55, 0.25);\n      color:#ff7373; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n          background-color:rgba(219, 55, 55, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active{\n          background-color:rgba(219, 55, 55, 0.45); }\n\n.bp5-tag-remove{\n  background:none;\n  border:none;\n  color:inherit;\n  cursor:pointer;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  margin-bottom:-2px;\n  margin-right:-6px !important;\n  margin-top:-2px;\n  opacity:0.5;\n  padding:2px;\n  padding-left:0; }\n  .bp5-tag-remove:hover{\n    background:none;\n    opacity:0.8;\n    text-decoration:none; }\n  .bp5-tag-remove:active{\n    opacity:1; }\n  .bp5-tag-remove:empty::before{\n    font-family:\"blueprint-icons-16\", sans-serif;\n    font-size:16px;\n    font-style:normal;\n    font-weight:400;\n    line-height:1;\n    -moz-osx-font-smoothing:grayscale;\n    -webkit-font-smoothing:antialiased;\n    content:\"\\f2ba\"; }\n  .bp5-large .bp5-tag-remove{\n    margin-right:-10px !important;\n    padding:0 5px 0 0; }\n    .bp5-large .bp5-tag-remove:empty::before{\n      font-family:\"blueprint-icons-20\", sans-serif;\n      font-size:20px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1; }\n.bp5-tag-input{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:start;\n      -ms-flex-align:start;\n          align-items:flex-start;\n  cursor:text;\n  height:auto;\n  line-height:inherit;\n  min-height:30px;\n  padding-left:5px;\n  padding-right:0; }\n  .bp5-tag-input > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-tag-input > .bp5-tag-input-values{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-tag-input .bp5-tag-input-icon{\n    color:#5c7080;\n    margin-left:2px;\n    margin-right:7px;\n    margin-top:7px; }\n  .bp5-tag-input .bp5-tag-input-values{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-orient:horizontal;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:row;\n            flex-direction:row;\n    -webkit-box-align:center;\n        -ms-flex-align:center;\n            align-items:center;\n    -ms-flex-item-align:stretch;\n        align-self:stretch;\n    -ms-flex-wrap:wrap;\n        flex-wrap:wrap;\n    margin-right:7px;\n    margin-top:5px;\n    min-width:0; }\n    .bp5-tag-input .bp5-tag-input-values > *{\n      -webkit-box-flex:0;\n          -ms-flex-positive:0;\n              flex-grow:0;\n      -ms-flex-negative:0;\n          flex-shrink:0; }\n    .bp5-tag-input .bp5-tag-input-values > .bp5-fill{\n      -webkit-box-flex:1;\n          -ms-flex-positive:1;\n              flex-grow:1;\n      -ms-flex-negative:1;\n          flex-shrink:1; }\n    .bp5-tag-input .bp5-tag-input-values::before,\n    .bp5-tag-input .bp5-tag-input-values > *{\n      margin-right:5px; }\n    .bp5-tag-input .bp5-tag-input-values:empty::before,\n    .bp5-tag-input .bp5-tag-input-values > :last-child{\n      margin-right:0; }\n    .bp5-tag-input .bp5-tag-input-values:first-child .bp5-input-ghost:first-child{\n      padding-left:5px; }\n    .bp5-tag-input .bp5-tag-input-values > *{\n      margin-bottom:5px; }\n  .bp5-tag-input .bp5-tag{\n    overflow-wrap:break-word; }\n    .bp5-tag-input .bp5-tag.bp5-active{\n      outline:rgba(19, 124, 189, 0.6) auto 2px;\n      outline-offset:0;\n      -moz-outline-radius:6px; }\n  .bp5-tag-input .bp5-input-ghost{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    line-height:20px;\n    width:80px; }\n    .bp5-tag-input .bp5-input-ghost:disabled, .bp5-tag-input .bp5-input-ghost.bp5-disabled{\n      cursor:not-allowed; }\n  .bp5-tag-input .bp5-button,\n  .bp5-tag-input .bp5-spinner{\n    margin:3px;\n    margin-left:0; }\n  .bp5-tag-input .bp5-button{\n    min-height:24px;\n    min-width:24px;\n    padding:0 7px; }\n  .bp5-tag-input.bp5-large{\n    height:auto;\n    min-height:40px; }\n    .bp5-tag-input.bp5-large::before,\n    .bp5-tag-input.bp5-large > *{\n      margin-right:10px; }\n    .bp5-tag-input.bp5-large:empty::before,\n    .bp5-tag-input.bp5-large > :last-child{\n      margin-right:0; }\n    .bp5-tag-input.bp5-large .bp5-tag-input-icon{\n      margin-left:5px;\n      margin-top:10px; }\n    .bp5-tag-input.bp5-large .bp5-input-ghost{\n      line-height:30px; }\n    .bp5-tag-input.bp5-large .bp5-button{\n      min-height:30px;\n      min-width:30px;\n      padding:5px 10px;\n      margin:5px;\n      margin-left:0; }\n    .bp5-tag-input.bp5-large .bp5-spinner{\n      margin:8px;\n      margin-left:0; }\n  .bp5-tag-input.bp5-active{\n    background-color:#ffffff;\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-primary{\n      -webkit-box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-success{\n      -webkit-box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-warning{\n      -webkit-box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-danger{\n      -webkit-box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-tag-input .bp5-tag-input-icon, .bp5-tag-input.bp5-dark .bp5-tag-input-icon{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-tag-input .bp5-input-ghost, .bp5-tag-input.bp5-dark .bp5-input-ghost{\n    color:#f5f8fa; }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::-webkit-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::-moz-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost:-ms-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::-ms-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n  .bp5-dark .bp5-tag-input.bp5-active, .bp5-tag-input.bp5-dark.bp5-active{\n    background-color:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-primary, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-primary{\n      -webkit-box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-success, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-success{\n      -webkit-box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-warning, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-warning{\n      -webkit-box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-danger, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-danger{\n      -webkit-box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-input-ghost{\n  background:none;\n  border:none;\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  padding:0; }\n  .bp5-input-ghost::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost:focus{\n    outline:none !important; }\n.bp5-toast{\n  -webkit-box-align:start;\n      -ms-flex-align:start;\n          align-items:flex-start;\n  background-color:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  margin:20px 0 0;\n  max-width:500px;\n  min-width:300px;\n  pointer-events:all;\n  position:relative !important; }\n  .bp5-toast.bp5-toast-enter, .bp5-toast.bp5-toast-appear{\n    -webkit-transform:translateY(-40px);\n            transform:translateY(-40px); }\n  .bp5-toast.bp5-toast-enter-active, .bp5-toast.bp5-toast-appear-active{\n    -webkit-transform:translateY(0);\n            transform:translateY(0);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-toast.bp5-toast-enter ~ .bp5-toast, .bp5-toast.bp5-toast-appear ~ .bp5-toast{\n    -webkit-transform:translateY(-40px);\n            transform:translateY(-40px); }\n  .bp5-toast.bp5-toast-enter-active ~ .bp5-toast, .bp5-toast.bp5-toast-appear-active ~ .bp5-toast{\n    -webkit-transform:translateY(0);\n            transform:translateY(0);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-toast.bp5-toast-exit{\n    opacity:1;\n    -webkit-filter:blur(0);\n            filter:blur(0); }\n  .bp5-toast.bp5-toast-exit-active{\n    opacity:0;\n    -webkit-filter:blur(10px);\n            filter:blur(10px);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:opacity, -webkit-filter;\n    transition-property:opacity, -webkit-filter;\n    transition-property:opacity, filter;\n    transition-property:opacity, filter, -webkit-filter;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-toast.bp5-toast-exit ~ .bp5-toast{\n    -webkit-transform:translateY(0);\n            transform:translateY(0); }\n  .bp5-toast.bp5-toast-exit-active ~ .bp5-toast{\n    -webkit-transform:translateY(-40px);\n            transform:translateY(-40px);\n    -webkit-transition-delay:50ms;\n            transition-delay:50ms;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-toast .bp5-button-group{\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    padding:5px;\n    padding-left:0; }\n  .bp5-toast > .bp5-icon{\n    color:#5c7080;\n    margin:12px;\n    margin-right:0; }\n  .bp5-toast.bp5-dark,\n  .bp5-dark .bp5-toast{\n    background-color:#394b59;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n    .bp5-toast.bp5-dark > .bp5-icon,\n    .bp5-dark .bp5-toast > .bp5-icon{\n      color:#a7b6c2; }\n  .bp5-toast[class*=\"bp5-intent-\"] a{\n    color:rgba(255, 255, 255, 0.7); }\n    .bp5-toast[class*=\"bp5-intent-\"] a:hover{\n      color:#ffffff; }\n  .bp5-toast[class*=\"bp5-intent-\"] > .bp5-icon{\n    color:#ffffff; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button, .bp5-toast[class*=\"bp5-intent-\"] .bp5-button::before,\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button .bp5-icon, .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:active{\n    color:rgba(255, 255, 255, 0.7) !important; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:focus{\n    outline-color:rgba(255, 255, 255, 0.5); }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:hover{\n    background-color:rgba(255, 255, 255, 0.15) !important;\n    color:#ffffff !important; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:active{\n    background-color:rgba(255, 255, 255, 0.3) !important;\n    color:#ffffff !important; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button::after{\n    background:rgba(255, 255, 255, 0.3) !important; }\n  .bp5-toast.bp5-intent-primary{\n    background-color:#137cbd;\n    color:#ffffff; }\n  .bp5-toast.bp5-intent-success{\n    background-color:#0f9960;\n    color:#ffffff; }\n  .bp5-toast.bp5-intent-warning{\n    background-color:#d9822b;\n    color:#ffffff; }\n  .bp5-toast.bp5-intent-danger{\n    background-color:#db3737;\n    color:#ffffff; }\n\n.bp5-toast-message{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  padding:11px;\n  word-break:break-word; }\n\n.bp5-toast-container{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box !important;\n  display:-ms-flexbox !important;\n  display:flex !important;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  left:0;\n  overflow:hidden;\n  padding:0 20px 20px;\n  pointer-events:none;\n  right:0;\n  z-index:40; }\n  .bp5-toast-container.bp5-toast-container-in-portal{\n    position:fixed; }\n  .bp5-toast-container.bp5-toast-container-inline{\n    position:absolute; }\n  .bp5-toast-container.bp5-toast-container-top{\n    top:0; }\n  .bp5-toast-container.bp5-toast-container-bottom{\n    bottom:0;\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:reverse;\n        -ms-flex-direction:column-reverse;\n            flex-direction:column-reverse;\n    top:auto; }\n  .bp5-toast-container.bp5-toast-container-left{\n    -webkit-box-align:start;\n        -ms-flex-align:start;\n            align-items:flex-start; }\n  .bp5-toast-container.bp5-toast-container-right{\n    -webkit-box-align:end;\n        -ms-flex-align:end;\n            align-items:flex-end; }\n\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active),\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active) ~ .bp5-toast, .bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active),\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active) ~ .bp5-toast,\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-exit-active ~ .bp5-toast,\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-leave-active ~ .bp5-toast{\n  -webkit-transform:translateY(60px);\n          transform:translateY(60px); }\n.bp5-tooltip{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  -webkit-transform:scale(1);\n          transform:scale(1); }\n  .bp5-tooltip .bp5-popover-arrow{\n    height:22px;\n    position:absolute;\n    width:22px; }\n    .bp5-tooltip .bp5-popover-arrow::before{\n      height:14px;\n      margin:4px;\n      width:14px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-tooltip{\n    margin-bottom:11px;\n    margin-top:-11px; }\n    .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-tooltip > .bp5-popover-arrow{\n      bottom:-8px; }\n      .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(-90deg);\n                transform:rotate(-90deg); }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-tooltip{\n    margin-left:11px; }\n    .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-tooltip > .bp5-popover-arrow{\n      left:-8px; }\n      .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(0);\n                transform:rotate(0); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-tooltip{\n    margin-top:11px; }\n    .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-tooltip > .bp5-popover-arrow{\n      top:-8px; }\n      .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(90deg);\n                transform:rotate(90deg); }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-tooltip{\n    margin-left:-11px;\n    margin-right:11px; }\n    .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-tooltip > .bp5-popover-arrow{\n      right:-8px; }\n      .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(180deg);\n                transform:rotate(180deg); }\n  .bp5-tether-element-attached-middle > .bp5-tooltip > .bp5-popover-arrow{\n    top:50%;\n    -webkit-transform:translateY(-50%);\n            transform:translateY(-50%); }\n  .bp5-tether-element-attached-center > .bp5-tooltip > .bp5-popover-arrow{\n    right:50%;\n    -webkit-transform:translateX(50%);\n            transform:translateX(50%); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-top > .bp5-tooltip > .bp5-popover-arrow{\n    top:-0.22183px; }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-right > .bp5-tooltip > .bp5-popover-arrow{\n    right:-0.22183px; }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-left > .bp5-tooltip > .bp5-popover-arrow{\n    left:-0.22183px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-bottom > .bp5-tooltip > .bp5-popover-arrow{\n    bottom:-0.22183px; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-left > .bp5-tooltip{\n    -webkit-transform-origin:top left;\n            transform-origin:top left; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-center > .bp5-tooltip{\n    -webkit-transform-origin:top center;\n            transform-origin:top center; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-right > .bp5-tooltip{\n    -webkit-transform-origin:top right;\n            transform-origin:top right; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-left > .bp5-tooltip{\n    -webkit-transform-origin:center left;\n            transform-origin:center left; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-center > .bp5-tooltip{\n    -webkit-transform-origin:center center;\n            transform-origin:center center; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-right > .bp5-tooltip{\n    -webkit-transform-origin:center right;\n            transform-origin:center right; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-left > .bp5-tooltip{\n    -webkit-transform-origin:bottom left;\n            transform-origin:bottom left; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-center > .bp5-tooltip{\n    -webkit-transform-origin:bottom center;\n            transform-origin:bottom center; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-right > .bp5-tooltip{\n    -webkit-transform-origin:bottom right;\n            transform-origin:bottom right; }\n  .bp5-tooltip .bp5-popover-content{\n    background:#394b59; }\n  .bp5-tooltip .bp5-popover-content,\n  .bp5-tooltip .bp5-heading{\n    color:#f5f8fa; }\n  .bp5-tooltip .bp5-popover-arrow::before{\n    -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2);\n            box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-tooltip .bp5-popover-arrow-border{\n    fill:#10161a;\n    fill-opacity:0.1; }\n  .bp5-tooltip .bp5-popover-arrow-fill{\n    fill:#394b59; }\n  .bp5-popover-enter > .bp5-tooltip, .bp5-popover-appear > .bp5-tooltip{\n    -webkit-transform:scale(0.8);\n            transform:scale(0.8); }\n  .bp5-popover-enter-active > .bp5-tooltip, .bp5-popover-appear-active > .bp5-tooltip{\n    -webkit-transform:scale(1);\n            transform:scale(1);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-popover-exit > .bp5-tooltip{\n    -webkit-transform:scale(1);\n            transform:scale(1); }\n  .bp5-popover-exit-active > .bp5-tooltip{\n    -webkit-transform:scale(0.8);\n            transform:scale(0.8);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-tooltip .bp5-popover-content{\n    padding:10px 12px; }\n  .bp5-tooltip.bp5-dark,\n  .bp5-dark .bp5-tooltip{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n    .bp5-tooltip.bp5-dark .bp5-popover-content,\n    .bp5-dark .bp5-tooltip .bp5-popover-content{\n      background:#e1e8ed; }\n    .bp5-tooltip.bp5-dark .bp5-popover-content,\n    .bp5-tooltip.bp5-dark .bp5-heading,\n    .bp5-dark .bp5-tooltip .bp5-popover-content,\n    .bp5-dark .bp5-tooltip .bp5-heading{\n      color:#394b59; }\n    .bp5-tooltip.bp5-dark .bp5-popover-arrow::before,\n    .bp5-dark .bp5-tooltip .bp5-popover-arrow::before{\n      -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4);\n              box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4); }\n    .bp5-tooltip.bp5-dark .bp5-popover-arrow-border,\n    .bp5-dark .bp5-tooltip .bp5-popover-arrow-border{\n      fill:#10161a;\n      fill-opacity:0.2; }\n    .bp5-tooltip.bp5-dark .bp5-popover-arrow-fill,\n    .bp5-dark .bp5-tooltip .bp5-popover-arrow-fill{\n      fill:#e1e8ed; }\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-content{\n    background:#137cbd;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill{\n    fill:#137cbd; }\n  .bp5-tooltip.bp5-intent-success .bp5-popover-content{\n    background:#0f9960;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill{\n    fill:#0f9960; }\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-content{\n    background:#d9822b;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill{\n    fill:#d9822b; }\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-content{\n    background:#db3737;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill{\n    fill:#db3737; }\n\n.bp5-tooltip-indicator{\n  border-bottom:dotted 1px;\n  cursor:help; }\n.bp5-tree .bp5-icon, .bp5-tree .bp5-icon-standard, .bp5-tree .bp5-icon-large{\n  color:#5c7080; }\n  .bp5-tree .bp5-icon.bp5-intent-primary, .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-tree .bp5-icon-large.bp5-intent-primary{\n    color:#137cbd; }\n  .bp5-tree .bp5-icon.bp5-intent-success, .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-tree .bp5-icon-large.bp5-intent-success{\n    color:#0f9960; }\n  .bp5-tree .bp5-icon.bp5-intent-warning, .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-tree .bp5-icon-large.bp5-intent-warning{\n    color:#d9822b; }\n  .bp5-tree .bp5-icon.bp5-intent-danger, .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-tree .bp5-icon-large.bp5-intent-danger{\n    color:#db3737; }\n\n.bp5-tree-node-list{\n  list-style:none;\n  margin:0;\n  padding-left:0; }\n\n.bp5-tree-root{\n  background-color:transparent;\n  cursor:default;\n  padding-left:0;\n  position:relative; }\n\n.bp5-tree-node-content-0{\n  padding-left:0px; }\n\n.bp5-tree-node-content-1{\n  padding-left:23px; }\n\n.bp5-tree-node-content-2{\n  padding-left:46px; }\n\n.bp5-tree-node-content-3{\n  padding-left:69px; }\n\n.bp5-tree-node-content-4{\n  padding-left:92px; }\n\n.bp5-tree-node-content-5{\n  padding-left:115px; }\n\n.bp5-tree-node-content-6{\n  padding-left:138px; }\n\n.bp5-tree-node-content-7{\n  padding-left:161px; }\n\n.bp5-tree-node-content-8{\n  padding-left:184px; }\n\n.bp5-tree-node-content-9{\n  padding-left:207px; }\n\n.bp5-tree-node-content-10{\n  padding-left:230px; }\n\n.bp5-tree-node-content-11{\n  padding-left:253px; }\n\n.bp5-tree-node-content-12{\n  padding-left:276px; }\n\n.bp5-tree-node-content-13{\n  padding-left:299px; }\n\n.bp5-tree-node-content-14{\n  padding-left:322px; }\n\n.bp5-tree-node-content-15{\n  padding-left:345px; }\n\n.bp5-tree-node-content-16{\n  padding-left:368px; }\n\n.bp5-tree-node-content-17{\n  padding-left:391px; }\n\n.bp5-tree-node-content-18{\n  padding-left:414px; }\n\n.bp5-tree-node-content-19{\n  padding-left:437px; }\n\n.bp5-tree-node-content-20{\n  padding-left:460px; }\n\n.bp5-tree-node-content{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:30px;\n  padding-right:5px;\n  width:100%; }\n  .bp5-tree-node-content:hover{\n    background-color:rgba(191, 204, 214, 0.4); }\n\n.bp5-tree-node-caret,\n.bp5-tree-node-caret-none{\n  min-width:30px; }\n\n.bp5-tree-node-caret{\n  color:#5c7080;\n  cursor:pointer;\n  padding:7px;\n  -webkit-transform:rotate(0deg);\n          transform:rotate(0deg);\n  -webkit-transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-tree-node-caret:hover{\n    color:#182026; }\n  .bp5-dark .bp5-tree-node-caret{\n    color:#a7b6c2; }\n    .bp5-dark .bp5-tree-node-caret:hover{\n      color:#f5f8fa; }\n  .bp5-tree-node-caret.bp5-tree-node-caret-open{\n    -webkit-transform:rotate(90deg);\n            transform:rotate(90deg); }\n  .bp5-tree-node-caret.bp5-icon-standard::before{\n    content:\"\\f14d\"; }\n\n.bp5-tree-node-icon{\n  margin-right:7px;\n  position:relative; }\n\n.bp5-tree-node-label{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-tree-node-label span{\n    display:inline; }\n\n.bp5-tree-node-secondary-label{\n  padding:0 5px;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-tree-node-secondary-label .bp5-popover-wrapper,\n  .bp5-tree-node-secondary-label .bp5-popover-target{\n    -webkit-box-align:center;\n        -ms-flex-align:center;\n            align-items:center;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex; }\n\n.bp5-tree-node.bp5-disabled .bp5-tree-node-content{\n  background-color:inherit;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-tree-node.bp5-disabled .bp5-tree-node-caret,\n.bp5-tree-node.bp5-disabled .bp5-tree-node-icon{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#137cbd; }\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content,\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-standard, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-large{\n    color:#ffffff; }\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret::before{\n    color:rgba(255, 255, 255, 0.7); }\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret:hover::before{\n    color:#ffffff; }\n\n.bp5-dark .bp5-tree-node-content:hover{\n  background-color:rgba(92, 112, 128, 0.3); }\n\n.bp5-dark .bp5-tree .bp5-icon, .bp5-dark .bp5-tree .bp5-icon-standard, .bp5-dark .bp5-tree .bp5-icon-large{\n  color:#a7b6c2; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-primary{\n    color:#137cbd; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-success{\n    color:#0f9960; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-warning{\n    color:#d9822b; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-danger{\n    color:#db3737; }\n\n.bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#137cbd; }\n/*# sourceMappingURL=blueprint.css.map */"
  },
  {
    "path": "www/dist/static/blueprint5.css",
    "content": "@charset \"UTF-8\";\nhtml{\n  box-sizing:border-box;\n}\n\n*,\n*::before,\n*::after{\n  box-sizing:inherit;\n}\n\nbody{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none;\n  color:#1c2127;\n  font-family:-apple-system, \"BlinkMacSystemFont\", \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Open Sans\", \"Helvetica Neue\", \"blueprint-icons-16\", sans-serif;\n}\n\np{\n  margin-bottom:10px;\n  margin-top:0;\n}\n\nsmall{\n  font-size:12px;\n}\n\nstrong{\n  font-weight:600;\n}\n\n::-moz-selection{\n  background:rgba(125, 188, 255, 0.6);\n}\n\n::selection{\n  background:rgba(125, 188, 255, 0.6);\n}\n\n.bp5-icon{\n  display:inline-block;\n  flex:0 0 auto;\n  vertical-align:text-bottom;\n}\n.bp5-icon:not(:empty)::before{\n  content:\"\" !important;\n  content:unset !important;\n}\n.bp5-icon > svg{\n  display:block;\n}\n.bp5-icon > svg:not([fill]){\n  fill:currentcolor;\n}\n.bp5-icon.bp5-icon-muted svg{\n  fill-opacity:0.15;\n  overflow:visible;\n}\n.bp5-icon.bp5-icon-muted svg path{\n  stroke:#8f99a8;\n  stroke-opacity:0.5;\n  stroke-width:0.5px;\n}\n.bp5-dark .bp5-icon .bp5-icon-muted svg{\n  fill-opacity:0.2;\n}\n\nspan.bp5-icon-standard{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon-large{\n  font-family:\"blueprint-icons-20\", sans-serif;\n  font-size:20px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:20px;\n  line-height:1;\n  width:20px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon:empty{\n  font-family:\"blueprint-icons-20\";\n  font-size:inherit;\n  font-style:normal;\n  font-weight:400;\n  line-height:1;\n}\nspan.bp5-icon:empty::before{\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n}\nspan.bp5-icon:empty.bp5-icon-standard{\n  font-size:16px;\n}\nspan.bp5-icon:empty.bp5-icon-large{\n  font-size:20px;\n}\n\n.bp5-icon-add::before{\n  content:\"\\f109\";\n}\n\n.bp5-icon-add-clip::before{\n  content:\"\\f101\";\n}\n\n.bp5-icon-add-column-left::before{\n  content:\"\\f102\";\n}\n\n.bp5-icon-add-column-right::before{\n  content:\"\\f103\";\n}\n\n.bp5-icon-add-location::before{\n  content:\"\\f104\";\n}\n\n.bp5-icon-add-row-bottom::before{\n  content:\"\\f105\";\n}\n\n.bp5-icon-add-row-top::before{\n  content:\"\\f106\";\n}\n\n.bp5-icon-add-to-artifact::before{\n  content:\"\\f107\";\n}\n\n.bp5-icon-add-to-folder::before{\n  content:\"\\f108\";\n}\n\n.bp5-icon-aimpoints-target::before{\n  content:\"\\f335\";\n}\n\n.bp5-icon-airplane::before{\n  content:\"\\f10a\";\n}\n\n.bp5-icon-align-center::before{\n  content:\"\\f10b\";\n}\n\n.bp5-icon-align-justify::before{\n  content:\"\\f10c\";\n}\n\n.bp5-icon-align-left::before{\n  content:\"\\f10d\";\n}\n\n.bp5-icon-align-right::before{\n  content:\"\\f10e\";\n}\n\n.bp5-icon-alignment-bottom::before{\n  content:\"\\f10f\";\n}\n\n.bp5-icon-alignment-horizontal-center::before{\n  content:\"\\f110\";\n}\n\n.bp5-icon-alignment-left::before{\n  content:\"\\f111\";\n}\n\n.bp5-icon-alignment-right::before{\n  content:\"\\f112\";\n}\n\n.bp5-icon-alignment-top::before{\n  content:\"\\f113\";\n}\n\n.bp5-icon-alignment-vertical-center::before{\n  content:\"\\f114\";\n}\n\n.bp5-icon-ammunition::before{\n  content:\"\\f342\";\n}\n\n.bp5-icon-anchor::before{\n  content:\"\\f330\";\n}\n\n.bp5-icon-annotation::before{\n  content:\"\\f115\";\n}\n\n.bp5-icon-announcement::before{\n  content:\"\\f396\";\n}\n\n.bp5-icon-antenna::before{\n  content:\"\\f116\";\n}\n\n.bp5-icon-app-header::before{\n  content:\"\\f117\";\n}\n\n.bp5-icon-application::before{\n  content:\"\\f118\";\n}\n\n.bp5-icon-applications::before{\n  content:\"\\f119\";\n}\n\n.bp5-icon-archive::before{\n  content:\"\\f11a\";\n}\n\n.bp5-icon-area-of-interest::before{\n  content:\"\\f11b\";\n}\n\n.bp5-icon-array::before{\n  content:\"\\f121\";\n}\n\n.bp5-icon-array-boolean::before{\n  content:\"\\f11c\";\n}\n\n.bp5-icon-array-date::before{\n  content:\"\\f11d\";\n}\n\n.bp5-icon-array-floating-point::before{\n  content:\"\\f32d\";\n}\n\n.bp5-icon-array-numeric::before{\n  content:\"\\f11e\";\n}\n\n.bp5-icon-array-object::before{\n  content:\"\\f394\";\n}\n\n.bp5-icon-array-string::before{\n  content:\"\\f11f\";\n}\n\n.bp5-icon-array-timestamp::before{\n  content:\"\\f120\";\n}\n\n.bp5-icon-arrow-bottom-left::before{\n  content:\"\\f122\";\n}\n\n.bp5-icon-arrow-bottom-right::before{\n  content:\"\\f123\";\n}\n\n.bp5-icon-arrow-down::before{\n  content:\"\\f124\";\n}\n\n.bp5-icon-arrow-left::before{\n  content:\"\\f125\";\n}\n\n.bp5-icon-arrow-right::before{\n  content:\"\\f126\";\n}\n\n.bp5-icon-arrow-top-left::before{\n  content:\"\\f127\";\n}\n\n.bp5-icon-arrow-top-right::before{\n  content:\"\\f128\";\n}\n\n.bp5-icon-arrow-up::before{\n  content:\"\\f129\";\n}\n\n.bp5-icon-arrows-arc::before{\n  content:\"\\f387\";\n}\n\n.bp5-icon-arrows-horizontal::before{\n  content:\"\\f12a\";\n}\n\n.bp5-icon-arrows-vertical::before{\n  content:\"\\f12b\";\n}\n\n.bp5-icon-asterisk::before{\n  content:\"\\f12c\";\n}\n\n.bp5-icon-at::before{\n  content:\"\\f331\";\n}\n\n.bp5-icon-automatic-updates::before{\n  content:\"\\f12d\";\n}\n\n.bp5-icon-axle::before{\n  content:\"\\f338\";\n}\n\n.bp5-icon-backlink::before{\n  content:\"\\f12e\";\n}\n\n.bp5-icon-backward-ten::before{\n  content:\"\\f35c\";\n}\n\n.bp5-icon-badge::before{\n  content:\"\\f12f\";\n}\n\n.bp5-icon-ban-circle::before{\n  content:\"\\f130\";\n}\n\n.bp5-icon-bank-account::before{\n  content:\"\\f131\";\n}\n\n.bp5-icon-barcode::before{\n  content:\"\\f132\";\n}\n\n.bp5-icon-binary-number::before{\n  content:\"\\f357\";\n}\n\n.bp5-icon-blank::before{\n  content:\"\\f133\";\n}\n\n.bp5-icon-block-promote::before{\n  content:\"\\f372\";\n}\n\n.bp5-icon-blocked-person::before{\n  content:\"\\f134\";\n}\n\n.bp5-icon-bold::before{\n  content:\"\\f135\";\n}\n\n.bp5-icon-book::before{\n  content:\"\\f136\";\n}\n\n.bp5-icon-bookmark::before{\n  content:\"\\f137\";\n}\n\n.bp5-icon-box::before{\n  content:\"\\f138\";\n}\n\n.bp5-icon-briefcase::before{\n  content:\"\\f139\";\n}\n\n.bp5-icon-bring-data::before{\n  content:\"\\f13a\";\n}\n\n.bp5-icon-bring-forward::before{\n  content:\"\\f354\";\n}\n\n.bp5-icon-british-pound::before{\n  content:\"\\f386\";\n}\n\n.bp5-icon-bug::before{\n  content:\"\\f32e\";\n}\n\n.bp5-icon-buggy::before{\n  content:\"\\f13b\";\n}\n\n.bp5-icon-build::before{\n  content:\"\\f13c\";\n}\n\n.bp5-icon-bullseye::before{\n  content:\"\\f359\";\n}\n\n.bp5-icon-calculator::before{\n  content:\"\\f13d\";\n}\n\n.bp5-icon-calendar::before{\n  content:\"\\f13e\";\n}\n\n.bp5-icon-camera::before{\n  content:\"\\f13f\";\n}\n\n.bp5-icon-caret-down::before{\n  content:\"\\f140\";\n}\n\n.bp5-icon-caret-left::before{\n  content:\"\\f141\";\n}\n\n.bp5-icon-caret-right::before{\n  content:\"\\f142\";\n}\n\n.bp5-icon-caret-up::before{\n  content:\"\\f143\";\n}\n\n.bp5-icon-cargo-ship::before{\n  content:\"\\f144\";\n}\n\n.bp5-icon-cell-tower::before{\n  content:\"\\f145\";\n}\n\n.bp5-icon-changes::before{\n  content:\"\\f146\";\n}\n\n.bp5-icon-chart::before{\n  content:\"\\f147\";\n}\n\n.bp5-icon-chat::before{\n  content:\"\\f148\";\n}\n\n.bp5-icon-chevron-backward::before{\n  content:\"\\f149\";\n}\n\n.bp5-icon-chevron-down::before{\n  content:\"\\f14a\";\n}\n\n.bp5-icon-chevron-forward::before{\n  content:\"\\f14b\";\n}\n\n.bp5-icon-chevron-left::before{\n  content:\"\\f14c\";\n}\n\n.bp5-icon-chevron-right::before{\n  content:\"\\f14d\";\n}\n\n.bp5-icon-chevron-up::before{\n  content:\"\\f14e\";\n}\n\n.bp5-icon-circle::before{\n  content:\"\\f153\";\n}\n\n.bp5-icon-circle-arrow-down::before{\n  content:\"\\f14f\";\n}\n\n.bp5-icon-circle-arrow-left::before{\n  content:\"\\f150\";\n}\n\n.bp5-icon-circle-arrow-right::before{\n  content:\"\\f151\";\n}\n\n.bp5-icon-circle-arrow-up::before{\n  content:\"\\f152\";\n}\n\n.bp5-icon-citation::before{\n  content:\"\\f154\";\n}\n\n.bp5-icon-clean::before{\n  content:\"\\f155\";\n}\n\n.bp5-icon-clip::before{\n  content:\"\\f156\";\n}\n\n.bp5-icon-clipboard::before{\n  content:\"\\f157\";\n}\n\n.bp5-icon-clipboard-file::before{\n  content:\"\\f35b\";\n}\n\n.bp5-icon-cloud::before{\n  content:\"\\f15a\";\n}\n\n.bp5-icon-cloud-download::before{\n  content:\"\\f158\";\n}\n\n.bp5-icon-cloud-server::before{\n  content:\"\\f35a\";\n}\n\n.bp5-icon-cloud-tick::before{\n  content:\"\\f34e\";\n}\n\n.bp5-icon-cloud-upload::before{\n  content:\"\\f159\";\n}\n\n.bp5-icon-code::before{\n  content:\"\\f15c\";\n}\n\n.bp5-icon-code-block::before{\n  content:\"\\f15b\";\n}\n\n.bp5-icon-cog::before{\n  content:\"\\f15d\";\n}\n\n.bp5-icon-collapse-all::before{\n  content:\"\\f15e\";\n}\n\n.bp5-icon-color-fill::before{\n  content:\"\\f328\";\n}\n\n.bp5-icon-column-layout::before{\n  content:\"\\f15f\";\n}\n\n.bp5-icon-comment::before{\n  content:\"\\f160\";\n}\n\n.bp5-icon-comparison::before{\n  content:\"\\f161\";\n}\n\n.bp5-icon-compass::before{\n  content:\"\\f162\";\n}\n\n.bp5-icon-compressed::before{\n  content:\"\\f163\";\n}\n\n.bp5-icon-confirm::before{\n  content:\"\\f164\";\n}\n\n.bp5-icon-console::before{\n  content:\"\\f165\";\n}\n\n.bp5-icon-construction::before{\n  content:\"\\f395\";\n}\n\n.bp5-icon-contrast::before{\n  content:\"\\f166\";\n}\n\n.bp5-icon-control::before{\n  content:\"\\f167\";\n}\n\n.bp5-icon-credit-card::before{\n  content:\"\\f168\";\n}\n\n.bp5-icon-crop::before{\n  content:\"\\f353\";\n}\n\n.bp5-icon-cross::before{\n  content:\"\\f169\";\n}\n\n.bp5-icon-cross-circle::before{\n  content:\"\\f336\";\n}\n\n.bp5-icon-crown::before{\n  content:\"\\f16a\";\n}\n\n.bp5-icon-css-style::before{\n  content:\"\\f36b\";\n}\n\n.bp5-icon-cube::before{\n  content:\"\\f16d\";\n}\n\n.bp5-icon-cube-add::before{\n  content:\"\\f16b\";\n}\n\n.bp5-icon-cube-edit::before{\n  content:\"\\f383\";\n}\n\n.bp5-icon-cube-remove::before{\n  content:\"\\f16c\";\n}\n\n.bp5-icon-cubes::before{\n  content:\"\\f373\";\n}\n\n.bp5-icon-curly-braces::before{\n  content:\"\\f358\";\n}\n\n.bp5-icon-curved-range-chart::before{\n  content:\"\\f16e\";\n}\n\n.bp5-icon-cut::before{\n  content:\"\\f16f\";\n}\n\n.bp5-icon-cycle::before{\n  content:\"\\f170\";\n}\n\n.bp5-icon-dashboard::before{\n  content:\"\\f171\";\n}\n\n.bp5-icon-data-cloud::before{\n  content:\"\\f391\";\n}\n\n.bp5-icon-data-connection::before{\n  content:\"\\f172\";\n}\n\n.bp5-icon-data-lineage::before{\n  content:\"\\f173\";\n}\n\n.bp5-icon-data-search::before{\n  content:\"\\f36f\";\n}\n\n.bp5-icon-data-sync::before{\n  content:\"\\f36c\";\n}\n\n.bp5-icon-database::before{\n  content:\"\\f174\";\n}\n\n.bp5-icon-delete::before{\n  content:\"\\f175\";\n}\n\n.bp5-icon-delta::before{\n  content:\"\\f176\";\n}\n\n.bp5-icon-derive-column::before{\n  content:\"\\f177\";\n}\n\n.bp5-icon-desktop::before{\n  content:\"\\f178\";\n}\n\n.bp5-icon-detection::before{\n  content:\"\\f341\";\n}\n\n.bp5-icon-diagnosis::before{\n  content:\"\\f179\";\n}\n\n.bp5-icon-diagram-tree::before{\n  content:\"\\f17a\";\n}\n\n.bp5-icon-direction-left::before{\n  content:\"\\f17b\";\n}\n\n.bp5-icon-direction-right::before{\n  content:\"\\f17c\";\n}\n\n.bp5-icon-disable::before{\n  content:\"\\f17d\";\n}\n\n.bp5-icon-divide::before{\n  content:\"\\f327\";\n}\n\n.bp5-icon-document::before{\n  content:\"\\f180\";\n}\n\n.bp5-icon-document-open::before{\n  content:\"\\f17e\";\n}\n\n.bp5-icon-document-share::before{\n  content:\"\\f17f\";\n}\n\n.bp5-icon-dollar::before{\n  content:\"\\f181\";\n}\n\n.bp5-icon-dot::before{\n  content:\"\\f182\";\n}\n\n.bp5-icon-double-caret-horizontal::before{\n  content:\"\\f183\";\n}\n\n.bp5-icon-double-caret-vertical::before{\n  content:\"\\f184\";\n}\n\n.bp5-icon-double-chevron-down::before{\n  content:\"\\f185\";\n}\n\n.bp5-icon-double-chevron-left::before{\n  content:\"\\f186\";\n}\n\n.bp5-icon-double-chevron-right::before{\n  content:\"\\f187\";\n}\n\n.bp5-icon-double-chevron-up::before{\n  content:\"\\f188\";\n}\n\n.bp5-icon-doughnut-chart::before{\n  content:\"\\f189\";\n}\n\n.bp5-icon-download::before{\n  content:\"\\f18a\";\n}\n\n.bp5-icon-drag-handle-horizontal::before{\n  content:\"\\f18b\";\n}\n\n.bp5-icon-drag-handle-vertical::before{\n  content:\"\\f18c\";\n}\n\n.bp5-icon-draw::before{\n  content:\"\\f18d\";\n}\n\n.bp5-icon-drawer-left::before{\n  content:\"\\f18f\";\n}\n\n.bp5-icon-drawer-left-filled::before{\n  content:\"\\f18e\";\n}\n\n.bp5-icon-drawer-right::before{\n  content:\"\\f191\";\n}\n\n.bp5-icon-drawer-right-filled::before{\n  content:\"\\f190\";\n}\n\n.bp5-icon-drive-time::before{\n  content:\"\\f192\";\n}\n\n.bp5-icon-duplicate::before{\n  content:\"\\f193\";\n}\n\n.bp5-icon-edit::before{\n  content:\"\\f194\";\n}\n\n.bp5-icon-eject::before{\n  content:\"\\f195\";\n}\n\n.bp5-icon-emoji::before{\n  content:\"\\f196\";\n}\n\n.bp5-icon-endnote::before{\n  content:\"\\f356\";\n}\n\n.bp5-icon-endorsed::before{\n  content:\"\\f197\";\n}\n\n.bp5-icon-envelope::before{\n  content:\"\\f198\";\n}\n\n.bp5-icon-equals::before{\n  content:\"\\f199\";\n}\n\n.bp5-icon-eraser::before{\n  content:\"\\f19a\";\n}\n\n.bp5-icon-error::before{\n  content:\"\\f19b\";\n}\n\n.bp5-icon-euro::before{\n  content:\"\\f19c\";\n}\n\n.bp5-icon-excavator::before{\n  content:\"\\f36d\";\n}\n\n.bp5-icon-exchange::before{\n  content:\"\\f19d\";\n}\n\n.bp5-icon-exclude-row::before{\n  content:\"\\f19e\";\n}\n\n.bp5-icon-expand-all::before{\n  content:\"\\f19f\";\n}\n\n.bp5-icon-explain::before{\n  content:\"\\f34d\";\n}\n\n.bp5-icon-export::before{\n  content:\"\\f1a0\";\n}\n\n.bp5-icon-eye-off::before{\n  content:\"\\f1a1\";\n}\n\n.bp5-icon-eye-on::before{\n  content:\"\\f1a2\";\n}\n\n.bp5-icon-eye-open::before{\n  content:\"\\f1a3\";\n}\n\n.bp5-icon-fast-backward::before{\n  content:\"\\f1a4\";\n}\n\n.bp5-icon-fast-forward::before{\n  content:\"\\f1a5\";\n}\n\n.bp5-icon-feed::before{\n  content:\"\\f1a7\";\n}\n\n.bp5-icon-feed-subscribed::before{\n  content:\"\\f1a6\";\n}\n\n.bp5-icon-fighter-jet::before{\n  content:\"\\f384\";\n}\n\n.bp5-icon-film::before{\n  content:\"\\f1a8\";\n}\n\n.bp5-icon-filter::before{\n  content:\"\\f1ad\";\n}\n\n.bp5-icon-filter-keep::before{\n  content:\"\\f1a9\";\n}\n\n.bp5-icon-filter-list::before{\n  content:\"\\f1aa\";\n}\n\n.bp5-icon-filter-open::before{\n  content:\"\\f1ab\";\n}\n\n.bp5-icon-filter-remove::before{\n  content:\"\\f1ac\";\n}\n\n.bp5-icon-filter-sort-asc::before{\n  content:\"\\f38e\";\n}\n\n.bp5-icon-filter-sort-desc::before{\n  content:\"\\f38f\";\n}\n\n.bp5-icon-flag::before{\n  content:\"\\f1ae\";\n}\n\n.bp5-icon-flame::before{\n  content:\"\\f1af\";\n}\n\n.bp5-icon-flash::before{\n  content:\"\\f1b0\";\n}\n\n.bp5-icon-floating-point::before{\n  content:\"\\f32c\";\n}\n\n.bp5-icon-floppy-disk::before{\n  content:\"\\f1b1\";\n}\n\n.bp5-icon-flow-branch::before{\n  content:\"\\f1b2\";\n}\n\n.bp5-icon-flow-end::before{\n  content:\"\\f1b3\";\n}\n\n.bp5-icon-flow-linear::before{\n  content:\"\\f1b4\";\n}\n\n.bp5-icon-flow-review::before{\n  content:\"\\f1b6\";\n}\n\n.bp5-icon-flow-review-branch::before{\n  content:\"\\f1b5\";\n}\n\n.bp5-icon-flows::before{\n  content:\"\\f1b7\";\n}\n\n.bp5-icon-folder-close::before{\n  content:\"\\f1b8\";\n}\n\n.bp5-icon-folder-new::before{\n  content:\"\\f1b9\";\n}\n\n.bp5-icon-folder-open::before{\n  content:\"\\f1ba\";\n}\n\n.bp5-icon-folder-shared::before{\n  content:\"\\f1bc\";\n}\n\n.bp5-icon-folder-shared-open::before{\n  content:\"\\f1bb\";\n}\n\n.bp5-icon-follower::before{\n  content:\"\\f1bd\";\n}\n\n.bp5-icon-following::before{\n  content:\"\\f1be\";\n}\n\n.bp5-icon-font::before{\n  content:\"\\f1bf\";\n}\n\n.bp5-icon-fork::before{\n  content:\"\\f1c0\";\n}\n\n.bp5-icon-form::before{\n  content:\"\\f1c1\";\n}\n\n.bp5-icon-forward-ten::before{\n  content:\"\\f35d\";\n}\n\n.bp5-icon-fuel::before{\n  content:\"\\f323\";\n}\n\n.bp5-icon-full-circle::before{\n  content:\"\\f1c2\";\n}\n\n.bp5-icon-full-stacked-chart::before{\n  content:\"\\f1c3\";\n}\n\n.bp5-icon-fullscreen::before{\n  content:\"\\f1c4\";\n}\n\n.bp5-icon-function::before{\n  content:\"\\f1c5\";\n}\n\n.bp5-icon-gantt-chart::before{\n  content:\"\\f1c6\";\n}\n\n.bp5-icon-generate::before{\n  content:\"\\f34c\";\n}\n\n.bp5-icon-geofence::before{\n  content:\"\\f1c7\";\n}\n\n.bp5-icon-geolocation::before{\n  content:\"\\f1c8\";\n}\n\n.bp5-icon-geosearch::before{\n  content:\"\\f1c9\";\n}\n\n.bp5-icon-geotime::before{\n  content:\"\\f344\";\n}\n\n.bp5-icon-git-branch::before{\n  content:\"\\f1ca\";\n}\n\n.bp5-icon-git-commit::before{\n  content:\"\\f1cb\";\n}\n\n.bp5-icon-git-merge::before{\n  content:\"\\f1cc\";\n}\n\n.bp5-icon-git-new-branch::before{\n  content:\"\\f1cd\";\n}\n\n.bp5-icon-git-pull::before{\n  content:\"\\f1ce\";\n}\n\n.bp5-icon-git-push::before{\n  content:\"\\f1cf\";\n}\n\n.bp5-icon-git-repo::before{\n  content:\"\\f1d0\";\n}\n\n.bp5-icon-glass::before{\n  content:\"\\f1d1\";\n}\n\n.bp5-icon-globe::before{\n  content:\"\\f1d3\";\n}\n\n.bp5-icon-globe-network::before{\n  content:\"\\f1d2\";\n}\n\n.bp5-icon-globe-network-add::before{\n  content:\"\\f382\";\n}\n\n.bp5-icon-graph::before{\n  content:\"\\f1d5\";\n}\n\n.bp5-icon-graph-remove::before{\n  content:\"\\f1d4\";\n}\n\n.bp5-icon-greater-than::before{\n  content:\"\\f1d7\";\n}\n\n.bp5-icon-greater-than-or-equal-to::before{\n  content:\"\\f1d6\";\n}\n\n.bp5-icon-grid::before{\n  content:\"\\f1d9\";\n}\n\n.bp5-icon-grid-view::before{\n  content:\"\\f1d8\";\n}\n\n.bp5-icon-group-item::before{\n  content:\"\\f34a\";\n}\n\n.bp5-icon-group-objects::before{\n  content:\"\\f1da\";\n}\n\n.bp5-icon-grouped-bar-chart::before{\n  content:\"\\f1db\";\n}\n\n.bp5-icon-hand::before{\n  content:\"\\f1e0\";\n}\n\n.bp5-icon-hand-down::before{\n  content:\"\\f1dc\";\n}\n\n.bp5-icon-hand-left::before{\n  content:\"\\f1dd\";\n}\n\n.bp5-icon-hand-right::before{\n  content:\"\\f1de\";\n}\n\n.bp5-icon-hand-up::before{\n  content:\"\\f1df\";\n}\n\n.bp5-icon-hat::before{\n  content:\"\\f1e1\";\n}\n\n.bp5-icon-header::before{\n  content:\"\\f1e5\";\n}\n\n.bp5-icon-header-one::before{\n  content:\"\\f1e2\";\n}\n\n.bp5-icon-header-three::before{\n  content:\"\\f1e3\";\n}\n\n.bp5-icon-header-two::before{\n  content:\"\\f1e4\";\n}\n\n.bp5-icon-headset::before{\n  content:\"\\f1e6\";\n}\n\n.bp5-icon-heart::before{\n  content:\"\\f1e8\";\n}\n\n.bp5-icon-heart-broken::before{\n  content:\"\\f1e7\";\n}\n\n.bp5-icon-heat-grid::before{\n  content:\"\\f1e9\";\n}\n\n.bp5-icon-heatmap::before{\n  content:\"\\f1ea\";\n}\n\n.bp5-icon-helicopter::before{\n  content:\"\\f1eb\";\n}\n\n.bp5-icon-help::before{\n  content:\"\\f1ec\";\n}\n\n.bp5-icon-helper-management::before{\n  content:\"\\f1ed\";\n}\n\n.bp5-icon-hexagon::before{\n  content:\"\\f374\";\n}\n\n.bp5-icon-high-priority::before{\n  content:\"\\f1ee\";\n}\n\n.bp5-icon-high-voltage-pole::before{\n  content:\"\\f333\";\n}\n\n.bp5-icon-highlight::before{\n  content:\"\\f1ef\";\n}\n\n.bp5-icon-history::before{\n  content:\"\\f1f0\";\n}\n\n.bp5-icon-home::before{\n  content:\"\\f1f1\";\n}\n\n.bp5-icon-horizontal-bar-chart::before{\n  content:\"\\f1f4\";\n}\n\n.bp5-icon-horizontal-bar-chart-asc::before{\n  content:\"\\f1f2\";\n}\n\n.bp5-icon-horizontal-bar-chart-desc::before{\n  content:\"\\f1f3\";\n}\n\n.bp5-icon-horizontal-distribution::before{\n  content:\"\\f1f5\";\n}\n\n.bp5-icon-horizontal-inbetween::before{\n  content:\"\\f329\";\n}\n\n.bp5-icon-hurricane::before{\n  content:\"\\f1f6\";\n}\n\n.bp5-icon-id-number::before{\n  content:\"\\f1f7\";\n}\n\n.bp5-icon-image-rotate-left::before{\n  content:\"\\f1f8\";\n}\n\n.bp5-icon-image-rotate-right::before{\n  content:\"\\f1f9\";\n}\n\n.bp5-icon-import::before{\n  content:\"\\f1fa\";\n}\n\n.bp5-icon-inbox::before{\n  content:\"\\f1ff\";\n}\n\n.bp5-icon-inbox-filtered::before{\n  content:\"\\f1fb\";\n}\n\n.bp5-icon-inbox-geo::before{\n  content:\"\\f1fc\";\n}\n\n.bp5-icon-inbox-search::before{\n  content:\"\\f1fd\";\n}\n\n.bp5-icon-inbox-update::before{\n  content:\"\\f1fe\";\n}\n\n.bp5-icon-info-sign::before{\n  content:\"\\f200\";\n}\n\n.bp5-icon-inheritance::before{\n  content:\"\\f201\";\n}\n\n.bp5-icon-inherited-group::before{\n  content:\"\\f202\";\n}\n\n.bp5-icon-inner-join::before{\n  content:\"\\f203\";\n}\n\n.bp5-icon-input::before{\n  content:\"\\f34b\";\n}\n\n.bp5-icon-insert::before{\n  content:\"\\f204\";\n}\n\n.bp5-icon-intelligence::before{\n  content:\"\\f337\";\n}\n\n.bp5-icon-intersection::before{\n  content:\"\\f205\";\n}\n\n.bp5-icon-ip-address::before{\n  content:\"\\f206\";\n}\n\n.bp5-icon-issue::before{\n  content:\"\\f209\";\n}\n\n.bp5-icon-issue-closed::before{\n  content:\"\\f207\";\n}\n\n.bp5-icon-issue-new::before{\n  content:\"\\f208\";\n}\n\n.bp5-icon-italic::before{\n  content:\"\\f20a\";\n}\n\n.bp5-icon-join-table::before{\n  content:\"\\f20b\";\n}\n\n.bp5-icon-key::before{\n  content:\"\\f215\";\n}\n\n.bp5-icon-key-backspace::before{\n  content:\"\\f20c\";\n}\n\n.bp5-icon-key-command::before{\n  content:\"\\f20d\";\n}\n\n.bp5-icon-key-control::before{\n  content:\"\\f20e\";\n}\n\n.bp5-icon-key-delete::before{\n  content:\"\\f20f\";\n}\n\n.bp5-icon-key-enter::before{\n  content:\"\\f210\";\n}\n\n.bp5-icon-key-escape::before{\n  content:\"\\f211\";\n}\n\n.bp5-icon-key-option::before{\n  content:\"\\f212\";\n}\n\n.bp5-icon-key-shift::before{\n  content:\"\\f213\";\n}\n\n.bp5-icon-key-tab::before{\n  content:\"\\f214\";\n}\n\n.bp5-icon-known-vehicle::before{\n  content:\"\\f216\";\n}\n\n.bp5-icon-lab-test::before{\n  content:\"\\f217\";\n}\n\n.bp5-icon-label::before{\n  content:\"\\f218\";\n}\n\n.bp5-icon-layer::before{\n  content:\"\\f21a\";\n}\n\n.bp5-icon-layer-outline::before{\n  content:\"\\f219\";\n}\n\n.bp5-icon-layers::before{\n  content:\"\\f21b\";\n}\n\n.bp5-icon-layout::before{\n  content:\"\\f225\";\n}\n\n.bp5-icon-layout-auto::before{\n  content:\"\\f21c\";\n}\n\n.bp5-icon-layout-balloon::before{\n  content:\"\\f21d\";\n}\n\n.bp5-icon-layout-bottom-row-three-tiles::before{\n  content:\"\\f364\";\n}\n\n.bp5-icon-layout-bottom-row-two-tiles::before{\n  content:\"\\f363\";\n}\n\n.bp5-icon-layout-circle::before{\n  content:\"\\f21e\";\n}\n\n.bp5-icon-layout-grid::before{\n  content:\"\\f21f\";\n}\n\n.bp5-icon-layout-group-by::before{\n  content:\"\\f220\";\n}\n\n.bp5-icon-layout-hierarchy::before{\n  content:\"\\f221\";\n}\n\n.bp5-icon-layout-left-column-three-tiles::before{\n  content:\"\\f366\";\n}\n\n.bp5-icon-layout-left-column-two-tiles::before{\n  content:\"\\f365\";\n}\n\n.bp5-icon-layout-linear::before{\n  content:\"\\f222\";\n}\n\n.bp5-icon-layout-right-column-three-tiles::before{\n  content:\"\\f368\";\n}\n\n.bp5-icon-layout-right-column-two-tiles::before{\n  content:\"\\f367\";\n}\n\n.bp5-icon-layout-skew-grid::before{\n  content:\"\\f223\";\n}\n\n.bp5-icon-layout-sorted-clusters::before{\n  content:\"\\f224\";\n}\n\n.bp5-icon-layout-three-columns::before{\n  content:\"\\f361\";\n}\n\n.bp5-icon-layout-three-rows::before{\n  content:\"\\f362\";\n}\n\n.bp5-icon-layout-top-row-three-tiles::before{\n  content:\"\\f36a\";\n}\n\n.bp5-icon-layout-top-row-two-tiles::before{\n  content:\"\\f369\";\n}\n\n.bp5-icon-layout-two-columns::before{\n  content:\"\\f35f\";\n}\n\n.bp5-icon-layout-two-rows::before{\n  content:\"\\f360\";\n}\n\n.bp5-icon-learning::before{\n  content:\"\\f226\";\n}\n\n.bp5-icon-left-join::before{\n  content:\"\\f227\";\n}\n\n.bp5-icon-lengthen-text::before{\n  content:\"\\f33e\";\n}\n\n.bp5-icon-less-than::before{\n  content:\"\\f229\";\n}\n\n.bp5-icon-less-than-or-equal-to::before{\n  content:\"\\f228\";\n}\n\n.bp5-icon-lifesaver::before{\n  content:\"\\f22a\";\n}\n\n.bp5-icon-lightbulb::before{\n  content:\"\\f22b\";\n}\n\n.bp5-icon-lightning::before{\n  content:\"\\f22c\";\n}\n\n.bp5-icon-link::before{\n  content:\"\\f22d\";\n}\n\n.bp5-icon-linked-squares::before{\n  content:\"\\f385\";\n}\n\n.bp5-icon-list::before{\n  content:\"\\f230\";\n}\n\n.bp5-icon-list-columns::before{\n  content:\"\\f22e\";\n}\n\n.bp5-icon-list-detail-view::before{\n  content:\"\\f22f\";\n}\n\n.bp5-icon-locate::before{\n  content:\"\\f231\";\n}\n\n.bp5-icon-lock::before{\n  content:\"\\f232\";\n}\n\n.bp5-icon-locomotive::before{\n  content:\"\\f33b\";\n}\n\n.bp5-icon-log-in::before{\n  content:\"\\f233\";\n}\n\n.bp5-icon-log-out::before{\n  content:\"\\f234\";\n}\n\n.bp5-icon-low-voltage-pole::before{\n  content:\"\\f332\";\n}\n\n.bp5-icon-manual::before{\n  content:\"\\f235\";\n}\n\n.bp5-icon-manually-entered-data::before{\n  content:\"\\f236\";\n}\n\n.bp5-icon-many-to-many::before{\n  content:\"\\f237\";\n}\n\n.bp5-icon-many-to-one::before{\n  content:\"\\f238\";\n}\n\n.bp5-icon-map::before{\n  content:\"\\f23b\";\n}\n\n.bp5-icon-map-create::before{\n  content:\"\\f239\";\n}\n\n.bp5-icon-map-marker::before{\n  content:\"\\f23a\";\n}\n\n.bp5-icon-maximize::before{\n  content:\"\\f23c\";\n}\n\n.bp5-icon-media::before{\n  content:\"\\f23d\";\n}\n\n.bp5-icon-menu::before{\n  content:\"\\f240\";\n}\n\n.bp5-icon-menu-closed::before{\n  content:\"\\f23e\";\n}\n\n.bp5-icon-menu-open::before{\n  content:\"\\f23f\";\n}\n\n.bp5-icon-merge-columns::before{\n  content:\"\\f241\";\n}\n\n.bp5-icon-merge-links::before{\n  content:\"\\f242\";\n}\n\n.bp5-icon-microphone::before{\n  content:\"\\f343\";\n}\n\n.bp5-icon-minimize::before{\n  content:\"\\f243\";\n}\n\n.bp5-icon-minus::before{\n  content:\"\\f244\";\n}\n\n.bp5-icon-mobile-phone::before{\n  content:\"\\f245\";\n}\n\n.bp5-icon-mobile-video::before{\n  content:\"\\f246\";\n}\n\n.bp5-icon-modal::before{\n  content:\"\\f248\";\n}\n\n.bp5-icon-modal-filled::before{\n  content:\"\\f247\";\n}\n\n.bp5-icon-model::before{\n  content:\"\\f33d\";\n}\n\n.bp5-icon-moon::before{\n  content:\"\\f249\";\n}\n\n.bp5-icon-more::before{\n  content:\"\\f24a\";\n}\n\n.bp5-icon-mountain::before{\n  content:\"\\f24b\";\n}\n\n.bp5-icon-move::before{\n  content:\"\\f24c\";\n}\n\n.bp5-icon-mugshot::before{\n  content:\"\\f24d\";\n}\n\n.bp5-icon-multi-select::before{\n  content:\"\\f24e\";\n}\n\n.bp5-icon-music::before{\n  content:\"\\f24f\";\n}\n\n.bp5-icon-nest::before{\n  content:\"\\f250\";\n}\n\n.bp5-icon-new-drawing::before{\n  content:\"\\f251\";\n}\n\n.bp5-icon-new-grid-item::before{\n  content:\"\\f252\";\n}\n\n.bp5-icon-new-layer::before{\n  content:\"\\f253\";\n}\n\n.bp5-icon-new-layers::before{\n  content:\"\\f254\";\n}\n\n.bp5-icon-new-link::before{\n  content:\"\\f255\";\n}\n\n.bp5-icon-new-object::before{\n  content:\"\\f256\";\n}\n\n.bp5-icon-new-person::before{\n  content:\"\\f257\";\n}\n\n.bp5-icon-new-prescription::before{\n  content:\"\\f258\";\n}\n\n.bp5-icon-new-shield::before{\n  content:\"\\f349\";\n}\n\n.bp5-icon-new-text-box::before{\n  content:\"\\f259\";\n}\n\n.bp5-icon-ninja::before{\n  content:\"\\f25a\";\n}\n\n.bp5-icon-not-equal-to::before{\n  content:\"\\f25b\";\n}\n\n.bp5-icon-notifications::before{\n  content:\"\\f25e\";\n}\n\n.bp5-icon-notifications-add::before{\n  content:\"\\f398\";\n}\n\n.bp5-icon-notifications-snooze::before{\n  content:\"\\f25c\";\n}\n\n.bp5-icon-notifications-updated::before{\n  content:\"\\f25d\";\n}\n\n.bp5-icon-numbered-list::before{\n  content:\"\\f25f\";\n}\n\n.bp5-icon-numerical::before{\n  content:\"\\f260\";\n}\n\n.bp5-icon-object-view::before{\n  content:\"\\f390\";\n}\n\n.bp5-icon-office::before{\n  content:\"\\f261\";\n}\n\n.bp5-icon-offline::before{\n  content:\"\\f262\";\n}\n\n.bp5-icon-oil-field::before{\n  content:\"\\f263\";\n}\n\n.bp5-icon-one-column::before{\n  content:\"\\f264\";\n}\n\n.bp5-icon-one-to-many::before{\n  content:\"\\f265\";\n}\n\n.bp5-icon-one-to-one::before{\n  content:\"\\f266\";\n}\n\n.bp5-icon-open-application::before{\n  content:\"\\f32b\";\n}\n\n.bp5-icon-outdated::before{\n  content:\"\\f267\";\n}\n\n.bp5-icon-output::before{\n  content:\"\\f370\";\n}\n\n.bp5-icon-package::before{\n  content:\"\\f375\";\n}\n\n.bp5-icon-page-layout::before{\n  content:\"\\f268\";\n}\n\n.bp5-icon-panel::before{\n  content:\"\\f381\";\n}\n\n.bp5-icon-panel-stats::before{\n  content:\"\\f269\";\n}\n\n.bp5-icon-panel-table::before{\n  content:\"\\f26a\";\n}\n\n.bp5-icon-paperclip::before{\n  content:\"\\f26b\";\n}\n\n.bp5-icon-paragraph::before{\n  content:\"\\f26c\";\n}\n\n.bp5-icon-paste-variable::before{\n  content:\"\\f346\";\n}\n\n.bp5-icon-path::before{\n  content:\"\\f26e\";\n}\n\n.bp5-icon-path-search::before{\n  content:\"\\f26d\";\n}\n\n.bp5-icon-pause::before{\n  content:\"\\f26f\";\n}\n\n.bp5-icon-people::before{\n  content:\"\\f270\";\n}\n\n.bp5-icon-percentage::before{\n  content:\"\\f271\";\n}\n\n.bp5-icon-person::before{\n  content:\"\\f272\";\n}\n\n.bp5-icon-phone::before{\n  content:\"\\f273\";\n}\n\n.bp5-icon-phone-call::before{\n  content:\"\\f347\";\n}\n\n.bp5-icon-phone-forward::before{\n  content:\"\\f348\";\n}\n\n.bp5-icon-phone-search::before{\n  content:\"\\f397\";\n}\n\n.bp5-icon-pie-chart::before{\n  content:\"\\f274\";\n}\n\n.bp5-icon-pill::before{\n  content:\"\\f376\";\n}\n\n.bp5-icon-pin::before{\n  content:\"\\f275\";\n}\n\n.bp5-icon-pivot::before{\n  content:\"\\f277\";\n}\n\n.bp5-icon-pivot-table::before{\n  content:\"\\f276\";\n}\n\n.bp5-icon-play::before{\n  content:\"\\f278\";\n}\n\n.bp5-icon-playbook::before{\n  content:\"\\f324\";\n}\n\n.bp5-icon-plus::before{\n  content:\"\\f279\";\n}\n\n.bp5-icon-polygon-filter::before{\n  content:\"\\f27a\";\n}\n\n.bp5-icon-power::before{\n  content:\"\\f27b\";\n}\n\n.bp5-icon-predictive-analysis::before{\n  content:\"\\f27c\";\n}\n\n.bp5-icon-prescription::before{\n  content:\"\\f27d\";\n}\n\n.bp5-icon-presentation::before{\n  content:\"\\f27e\";\n}\n\n.bp5-icon-print::before{\n  content:\"\\f27f\";\n}\n\n.bp5-icon-projects::before{\n  content:\"\\f280\";\n}\n\n.bp5-icon-properties::before{\n  content:\"\\f281\";\n}\n\n.bp5-icon-property::before{\n  content:\"\\f282\";\n}\n\n.bp5-icon-publish-function::before{\n  content:\"\\f283\";\n}\n\n.bp5-icon-pulse::before{\n  content:\"\\f284\";\n}\n\n.bp5-icon-rain::before{\n  content:\"\\f285\";\n}\n\n.bp5-icon-random::before{\n  content:\"\\f286\";\n}\n\n.bp5-icon-range-ring::before{\n  content:\"\\f371\";\n}\n\n.bp5-icon-record::before{\n  content:\"\\f287\";\n}\n\n.bp5-icon-rect-height::before{\n  content:\"\\f325\";\n}\n\n.bp5-icon-rect-width::before{\n  content:\"\\f326\";\n}\n\n.bp5-icon-rectangle::before{\n  content:\"\\f321\";\n}\n\n.bp5-icon-redo::before{\n  content:\"\\f288\";\n}\n\n.bp5-icon-refresh::before{\n  content:\"\\f289\";\n}\n\n.bp5-icon-regex::before{\n  content:\"\\f32f\";\n}\n\n.bp5-icon-regression-chart::before{\n  content:\"\\f28a\";\n}\n\n.bp5-icon-remove::before{\n  content:\"\\f290\";\n}\n\n.bp5-icon-remove-column::before{\n  content:\"\\f28d\";\n}\n\n.bp5-icon-remove-column-left::before{\n  content:\"\\f28b\";\n}\n\n.bp5-icon-remove-column-right::before{\n  content:\"\\f28c\";\n}\n\n.bp5-icon-remove-row-bottom::before{\n  content:\"\\f28e\";\n}\n\n.bp5-icon-remove-row-top::before{\n  content:\"\\f28f\";\n}\n\n.bp5-icon-repeat::before{\n  content:\"\\f291\";\n}\n\n.bp5-icon-reset::before{\n  content:\"\\f292\";\n}\n\n.bp5-icon-resolve::before{\n  content:\"\\f293\";\n}\n\n.bp5-icon-rig::before{\n  content:\"\\f294\";\n}\n\n.bp5-icon-right-join::before{\n  content:\"\\f295\";\n}\n\n.bp5-icon-ring::before{\n  content:\"\\f296\";\n}\n\n.bp5-icon-rocket::before{\n  content:\"\\f298\";\n}\n\n.bp5-icon-rocket-slant::before{\n  content:\"\\f297\";\n}\n\n.bp5-icon-rotate-ccw::before{\n  content:\"\\f389\";\n}\n\n.bp5-icon-rotate-cw::before{\n  content:\"\\f388\";\n}\n\n.bp5-icon-rotate-document::before{\n  content:\"\\f299\";\n}\n\n.bp5-icon-rotate-page::before{\n  content:\"\\f29a\";\n}\n\n.bp5-icon-route::before{\n  content:\"\\f29b\";\n}\n\n.bp5-icon-run-history::before{\n  content:\"\\f393\";\n}\n\n.bp5-icon-satellite::before{\n  content:\"\\f29c\";\n}\n\n.bp5-icon-saved::before{\n  content:\"\\f29d\";\n}\n\n.bp5-icon-scatter-plot::before{\n  content:\"\\f29e\";\n}\n\n.bp5-icon-search::before{\n  content:\"\\f2a2\";\n}\n\n.bp5-icon-search-around::before{\n  content:\"\\f29f\";\n}\n\n.bp5-icon-search-template::before{\n  content:\"\\f2a0\";\n}\n\n.bp5-icon-search-text::before{\n  content:\"\\f2a1\";\n}\n\n.bp5-icon-segmented-control::before{\n  content:\"\\f2a3\";\n}\n\n.bp5-icon-select::before{\n  content:\"\\f2a4\";\n}\n\n.bp5-icon-selection::before{\n  content:\"\\f2a5\";\n}\n\n.bp5-icon-selection-box::before{\n  content:\"\\f399\";\n}\n\n.bp5-icon-selection-box-add::before{\n  content:\"\\f39a\";\n}\n\n.bp5-icon-selection-box-edit::before{\n  content:\"\\f39b\";\n}\n\n.bp5-icon-send-backward::before{\n  content:\"\\f355\";\n}\n\n.bp5-icon-send-message::before{\n  content:\"\\f2a6\";\n}\n\n.bp5-icon-send-to::before{\n  content:\"\\f2a9\";\n}\n\n.bp5-icon-send-to-graph::before{\n  content:\"\\f2a7\";\n}\n\n.bp5-icon-send-to-map::before{\n  content:\"\\f2a8\";\n}\n\n.bp5-icon-sensor::before{\n  content:\"\\f33c\";\n}\n\n.bp5-icon-series-add::before{\n  content:\"\\f2aa\";\n}\n\n.bp5-icon-series-configuration::before{\n  content:\"\\f2ab\";\n}\n\n.bp5-icon-series-derived::before{\n  content:\"\\f2ac\";\n}\n\n.bp5-icon-series-filtered::before{\n  content:\"\\f2ad\";\n}\n\n.bp5-icon-series-search::before{\n  content:\"\\f2ae\";\n}\n\n.bp5-icon-server::before{\n  content:\"\\f378\";\n}\n\n.bp5-icon-server-install::before{\n  content:\"\\f377\";\n}\n\n.bp5-icon-settings::before{\n  content:\"\\f2af\";\n}\n\n.bp5-icon-shapes::before{\n  content:\"\\f2b0\";\n}\n\n.bp5-icon-share::before{\n  content:\"\\f2b1\";\n}\n\n.bp5-icon-shared-filter::before{\n  content:\"\\f2b2\";\n}\n\n.bp5-icon-shield::before{\n  content:\"\\f2b3\";\n}\n\n.bp5-icon-ship::before{\n  content:\"\\f2b4\";\n}\n\n.bp5-icon-shop::before{\n  content:\"\\f2b5\";\n}\n\n.bp5-icon-shopping-cart::before{\n  content:\"\\f2b6\";\n}\n\n.bp5-icon-shorten-text::before{\n  content:\"\\f33f\";\n}\n\n.bp5-icon-signal-search::before{\n  content:\"\\f2b7\";\n}\n\n.bp5-icon-sim-card::before{\n  content:\"\\f2b8\";\n}\n\n.bp5-icon-slash::before{\n  content:\"\\f2b9\";\n}\n\n.bp5-icon-small-cross::before{\n  content:\"\\f2ba\";\n}\n\n.bp5-icon-small-info-sign::before{\n  content:\"\\f334\";\n}\n\n.bp5-icon-small-minus::before{\n  content:\"\\f2bb\";\n}\n\n.bp5-icon-small-plus::before{\n  content:\"\\f2bc\";\n}\n\n.bp5-icon-small-square::before{\n  content:\"\\f2bd\";\n}\n\n.bp5-icon-small-tick::before{\n  content:\"\\f2be\";\n}\n\n.bp5-icon-snowflake::before{\n  content:\"\\f2bf\";\n}\n\n.bp5-icon-soccer-ball::before{\n  content:\"\\f350\";\n}\n\n.bp5-icon-social-media::before{\n  content:\"\\f2c0\";\n}\n\n.bp5-icon-sort::before{\n  content:\"\\f2c7\";\n}\n\n.bp5-icon-sort-alphabetical::before{\n  content:\"\\f2c2\";\n}\n\n.bp5-icon-sort-alphabetical-desc::before{\n  content:\"\\f2c1\";\n}\n\n.bp5-icon-sort-asc::before{\n  content:\"\\f2c3\";\n}\n\n.bp5-icon-sort-desc::before{\n  content:\"\\f2c4\";\n}\n\n.bp5-icon-sort-numerical::before{\n  content:\"\\f2c6\";\n}\n\n.bp5-icon-sort-numerical-desc::before{\n  content:\"\\f2c5\";\n}\n\n.bp5-icon-spell-check::before{\n  content:\"\\f340\";\n}\n\n.bp5-icon-split-columns::before{\n  content:\"\\f2c8\";\n}\n\n.bp5-icon-sports-stadium::before{\n  content:\"\\f351\";\n}\n\n.bp5-icon-square::before{\n  content:\"\\f2c9\";\n}\n\n.bp5-icon-stacked-chart::before{\n  content:\"\\f2ca\";\n}\n\n.bp5-icon-stadium-geometry::before{\n  content:\"\\f2cb\";\n}\n\n.bp5-icon-star::before{\n  content:\"\\f2cd\";\n}\n\n.bp5-icon-star-empty::before{\n  content:\"\\f2cc\";\n}\n\n.bp5-icon-step-backward::before{\n  content:\"\\f2ce\";\n}\n\n.bp5-icon-step-chart::before{\n  content:\"\\f2cf\";\n}\n\n.bp5-icon-step-forward::before{\n  content:\"\\f2d0\";\n}\n\n.bp5-icon-stop::before{\n  content:\"\\f2d1\";\n}\n\n.bp5-icon-stopwatch::before{\n  content:\"\\f2d2\";\n}\n\n.bp5-icon-strikethrough::before{\n  content:\"\\f2d3\";\n}\n\n.bp5-icon-style::before{\n  content:\"\\f2d4\";\n}\n\n.bp5-icon-subscript::before{\n  content:\"\\f339\";\n}\n\n.bp5-icon-subtract-right-join::before{\n  content:\"\\f392\";\n}\n\n.bp5-icon-superscript::before{\n  content:\"\\f33a\";\n}\n\n.bp5-icon-swap-horizontal::before{\n  content:\"\\f2d5\";\n}\n\n.bp5-icon-swap-vertical::before{\n  content:\"\\f2d6\";\n}\n\n.bp5-icon-switch::before{\n  content:\"\\f2d7\";\n}\n\n.bp5-icon-symbol-circle::before{\n  content:\"\\f2d8\";\n}\n\n.bp5-icon-symbol-cross::before{\n  content:\"\\f2d9\";\n}\n\n.bp5-icon-symbol-diamond::before{\n  content:\"\\f2da\";\n}\n\n.bp5-icon-symbol-rectangle::before{\n  content:\"\\f322\";\n}\n\n.bp5-icon-symbol-square::before{\n  content:\"\\f2db\";\n}\n\n.bp5-icon-symbol-triangle-down::before{\n  content:\"\\f2dc\";\n}\n\n.bp5-icon-symbol-triangle-up::before{\n  content:\"\\f2dd\";\n}\n\n.bp5-icon-syringe::before{\n  content:\"\\f2de\";\n}\n\n.bp5-icon-table-sync::before{\n  content:\"\\f36e\";\n}\n\n.bp5-icon-tag::before{\n  content:\"\\f2df\";\n}\n\n.bp5-icon-tag-add::before{\n  content:\"\\f379\";\n}\n\n.bp5-icon-tag-promote::before{\n  content:\"\\f37a\";\n}\n\n.bp5-icon-tag-refresh::before{\n  content:\"\\f37b\";\n}\n\n.bp5-icon-tag-undo::before{\n  content:\"\\f37c\";\n}\n\n.bp5-icon-tags::before{\n  content:\"\\f37d\";\n}\n\n.bp5-icon-take-action::before{\n  content:\"\\f2e0\";\n}\n\n.bp5-icon-tank::before{\n  content:\"\\f2e1\";\n}\n\n.bp5-icon-target::before{\n  content:\"\\f2e2\";\n}\n\n.bp5-icon-taxi::before{\n  content:\"\\f2e3\";\n}\n\n.bp5-icon-team::before{\n  content:\"\\f352\";\n}\n\n.bp5-icon-temperature::before{\n  content:\"\\f2e4\";\n}\n\n.bp5-icon-text-highlight::before{\n  content:\"\\f2e5\";\n}\n\n.bp5-icon-th::before{\n  content:\"\\f2ea\";\n}\n\n.bp5-icon-th-add::before{\n  content:\"\\f38a\";\n}\n\n.bp5-icon-th-derived::before{\n  content:\"\\f2e6\";\n}\n\n.bp5-icon-th-disconnect::before{\n  content:\"\\f2e7\";\n}\n\n.bp5-icon-th-filtered::before{\n  content:\"\\f2e8\";\n}\n\n.bp5-icon-th-list::before{\n  content:\"\\f2e9\";\n}\n\n.bp5-icon-th-list-add::before{\n  content:\"\\f38b\";\n}\n\n.bp5-icon-th-virtual::before{\n  content:\"\\f38c\";\n}\n\n.bp5-icon-th-virtual-add::before{\n  content:\"\\f38d\";\n}\n\n.bp5-icon-third-party::before{\n  content:\"\\f2eb\";\n}\n\n.bp5-icon-thumbs-down::before{\n  content:\"\\f2ec\";\n}\n\n.bp5-icon-thumbs-up::before{\n  content:\"\\f2ed\";\n}\n\n.bp5-icon-tick::before{\n  content:\"\\f2ef\";\n}\n\n.bp5-icon-tick-circle::before{\n  content:\"\\f2ee\";\n}\n\n.bp5-icon-time::before{\n  content:\"\\f2f0\";\n}\n\n.bp5-icon-timeline-area-chart::before{\n  content:\"\\f2f1\";\n}\n\n.bp5-icon-timeline-bar-chart::before{\n  content:\"\\f2f2\";\n}\n\n.bp5-icon-timeline-events::before{\n  content:\"\\f2f3\";\n}\n\n.bp5-icon-timeline-line-chart::before{\n  content:\"\\f2f4\";\n}\n\n.bp5-icon-tint::before{\n  content:\"\\f2f5\";\n}\n\n.bp5-icon-torch::before{\n  content:\"\\f2f6\";\n}\n\n.bp5-icon-tractor::before{\n  content:\"\\f2f7\";\n}\n\n.bp5-icon-train::before{\n  content:\"\\f2f8\";\n}\n\n.bp5-icon-translate::before{\n  content:\"\\f2f9\";\n}\n\n.bp5-icon-trash::before{\n  content:\"\\f2fa\";\n}\n\n.bp5-icon-tree::before{\n  content:\"\\f2fb\";\n}\n\n.bp5-icon-trending-down::before{\n  content:\"\\f2fc\";\n}\n\n.bp5-icon-trending-up::before{\n  content:\"\\f2fd\";\n}\n\n.bp5-icon-trophy::before{\n  content:\"\\f34f\";\n}\n\n.bp5-icon-truck::before{\n  content:\"\\f2fe\";\n}\n\n.bp5-icon-two-columns::before{\n  content:\"\\f2ff\";\n}\n\n.bp5-icon-unarchive::before{\n  content:\"\\f300\";\n}\n\n.bp5-icon-underline::before{\n  content:\"\\f301\";\n}\n\n.bp5-icon-undo::before{\n  content:\"\\f302\";\n}\n\n.bp5-icon-ungroup-objects::before{\n  content:\"\\f303\";\n}\n\n.bp5-icon-unknown-vehicle::before{\n  content:\"\\f304\";\n}\n\n.bp5-icon-unlink::before{\n  content:\"\\f345\";\n}\n\n.bp5-icon-unlock::before{\n  content:\"\\f305\";\n}\n\n.bp5-icon-unpin::before{\n  content:\"\\f306\";\n}\n\n.bp5-icon-unresolve::before{\n  content:\"\\f307\";\n}\n\n.bp5-icon-updated::before{\n  content:\"\\f308\";\n}\n\n.bp5-icon-upload::before{\n  content:\"\\f309\";\n}\n\n.bp5-icon-user::before{\n  content:\"\\f30a\";\n}\n\n.bp5-icon-variable::before{\n  content:\"\\f30b\";\n}\n\n.bp5-icon-vector::before{\n  content:\"\\f35e\";\n}\n\n.bp5-icon-vertical-bar-chart-asc::before{\n  content:\"\\f30c\";\n}\n\n.bp5-icon-vertical-bar-chart-desc::before{\n  content:\"\\f30d\";\n}\n\n.bp5-icon-vertical-distribution::before{\n  content:\"\\f30e\";\n}\n\n.bp5-icon-vertical-inbetween::before{\n  content:\"\\f32a\";\n}\n\n.bp5-icon-video::before{\n  content:\"\\f30f\";\n}\n\n.bp5-icon-virus::before{\n  content:\"\\f310\";\n}\n\n.bp5-icon-volume-down::before{\n  content:\"\\f311\";\n}\n\n.bp5-icon-volume-off::before{\n  content:\"\\f312\";\n}\n\n.bp5-icon-volume-up::before{\n  content:\"\\f313\";\n}\n\n.bp5-icon-walk::before{\n  content:\"\\f314\";\n}\n\n.bp5-icon-warning-sign::before{\n  content:\"\\f315\";\n}\n\n.bp5-icon-waterfall-chart::before{\n  content:\"\\f316\";\n}\n\n.bp5-icon-waves::before{\n  content:\"\\f317\";\n}\n\n.bp5-icon-widget::before{\n  content:\"\\f31b\";\n}\n\n.bp5-icon-widget-button::before{\n  content:\"\\f318\";\n}\n\n.bp5-icon-widget-footer::before{\n  content:\"\\f319\";\n}\n\n.bp5-icon-widget-header::before{\n  content:\"\\f31a\";\n}\n\n.bp5-icon-wind::before{\n  content:\"\\f31c\";\n}\n\n.bp5-icon-wrench::before{\n  content:\"\\f31d\";\n}\n\n.bp5-icon-wrench-redo::before{\n  content:\"\\f37e\";\n}\n\n.bp5-icon-wrench-snooze::before{\n  content:\"\\f37f\";\n}\n\n.bp5-icon-wrench-time::before{\n  content:\"\\f380\";\n}\n\n.bp5-icon-zoom-in::before{\n  content:\"\\f31e\";\n}\n\n.bp5-icon-zoom-out::before{\n  content:\"\\f31f\";\n}\n\n.bp5-icon-zoom-to-fit::before{\n  content:\"\\f320\";\n}\n.bp5-running-text table, table.bp5-html-table{\n  border-spacing:0;\n  font-size:14px;\n}\n.bp5-running-text table th, table.bp5-html-table th,\n.bp5-running-text table td,\ntable.bp5-html-table td{\n  padding:11px;\n  text-align:left;\n  vertical-align:top;\n}\n.bp5-running-text table th, table.bp5-html-table th{\n  color:#1c2127;\n  font-weight:600;\n}\n.bp5-running-text table td, table.bp5-html-table td{\n  color:#1c2127;\n}\n.bp5-running-text table tbody tr:first-child th, table.bp5-html-table tbody tr:first-child th,\n.bp5-running-text table tbody tr:first-child td,\ntable.bp5-html-table tbody tr:first-child td,\n.bp5-running-text table tfoot tr:first-child th,\ntable.bp5-html-table tfoot tr:first-child th,\n.bp5-running-text table tfoot tr:first-child td,\ntable.bp5-html-table tfoot tr:first-child td{\n  box-shadow:inset 0 1px 0 0 rgba(17, 20, 24, 0.15);\n}\n.bp5-dark .bp5-running-text table th, .bp5-running-text .bp5-dark table th, .bp5-dark table.bp5-html-table th{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-running-text table td, .bp5-running-text .bp5-dark table td, .bp5-dark table.bp5-html-table td{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-running-text table tbody tr:first-child th, .bp5-running-text .bp5-dark table tbody tr:first-child th, .bp5-dark table.bp5-html-table tbody tr:first-child th,\n.bp5-dark .bp5-running-text table tbody tr:first-child td,\n.bp5-running-text .bp5-dark table tbody tr:first-child td,\n.bp5-dark table.bp5-html-table tbody tr:first-child td,\n.bp5-dark .bp5-running-text table tfoot tr:first-child th,\n.bp5-running-text .bp5-dark table tfoot tr:first-child th,\n.bp5-dark table.bp5-html-table tfoot tr:first-child th,\n.bp5-dark .bp5-running-text table tfoot tr:first-child td,\n.bp5-running-text .bp5-dark table tfoot tr:first-child td,\n.bp5-dark table.bp5-html-table tfoot tr:first-child td{\n  box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.2);\n}\n\ntable.bp5-html-table.bp5-compact th,\ntable.bp5-html-table.bp5-compact td{\n  padding-bottom:6px;\n  padding-top:6px;\n}\ntable.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n  background:rgba(143, 153, 168, 0.15);\n}\ntable.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n  box-shadow:inset 1px 0 0 0 rgba(17, 20, 24, 0.15);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n    border-left:1px solid buttonborder;\n  }\n}\ntable.bp5-html-table.bp5-html-table-bordered tbody tr td,\ntable.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n  box-shadow:inset 0 1px 0 0 rgba(17, 20, 24, 0.15);\n}\ntable.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\ntable.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n  box-shadow:inset 1px 1px 0 0 rgba(17, 20, 24, 0.15);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n  table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n    border-left:1px solid buttonborder;\n    border-top:1px solid buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-html-table-bordered tbody tr td,\n  table.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n    border-top:1px solid buttonborder;\n  }\n}\ntable.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n  box-shadow:none;\n}\ntable.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:not(:first-child){\n  box-shadow:inset 1px 0 0 0 rgba(17, 20, 24, 0.15);\n}\ntable.bp5-html-table.bp5-interactive tbody tr:hover td{\n  background-color:rgba(143, 153, 168, 0.3);\n  cursor:pointer;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-interactive tbody tr:hover td{\n    background-color:highlight;\n  }\n}\ntable.bp5-html-table.bp5-interactive tbody tr:active td{\n  background-color:rgba(143, 153, 168, 0.35);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-interactive tbody tr:active td{\n    background-color:highlight;\n  }\n}\n.bp5-dark table.bp5-html-table{\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n  background:rgba(95, 107, 124, 0.15);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n  box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td,\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n  box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n  box-shadow:inset 1px 1px 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n  box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:first-child{\n  box-shadow:none;\n}\n.bp5-dark table.bp5-html-table.bp5-interactive tbody tr:hover td{\n  background-color:rgba(95, 107, 124, 0.3);\n  cursor:pointer;\n}\n.bp5-dark table.bp5-html-table.bp5-interactive tbody tr:active td{\n  background-color:rgba(95, 107, 124, 0.4);\n}\n\n.bp5-text-muted{\n  color:#5f6b7c;\n}\n\n.bp5-text-disabled{\n  color:rgba(95, 107, 124, 0.6);\n}\n\n.bp5-running-text hr{\n  border-color:rgba(17, 20, 24, 0.15);\n}\n\na{\n  color:#215db0;\n}\na:hover{\n  color:#215db0;\n}\na .bp5-icon, a .bp5-icon-standard, a .bp5-icon-large{\n  color:inherit;\n}\na code{\n  color:inherit;\n}\n\n.bp5-code,\n.bp5-running-text code{\n  background:rgba(255, 255, 255, 0.7);\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color:#5f6b7c;\n}\na > .bp5-code,\na > .bp5-running-text code{\n  color:#2d72d2;\n}\n\n.bp5-code-block,\n.bp5-running-text pre{\n  background:rgba(255, 255, 255, 0.7);\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n  color:#1c2127;\n}\n.bp5-code-block > code,\n.bp5-running-text pre > code{\n  background:none;\n  box-shadow:none;\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-code-block,\n  .bp5-running-text pre{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n\n.bp5-key,\n.bp5-running-text kbd{\n  background:#ffffff;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  color:#5f6b7c;\n}\n\n.bp5-icon.bp5-intent-primary, .bp5-icon-standard.bp5-intent-primary, .bp5-icon-large.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-icon.bp5-intent-success, .bp5-icon-standard.bp5-intent-success, .bp5-icon-large.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-icon.bp5-intent-warning, .bp5-icon-standard.bp5-intent-warning, .bp5-icon-large.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-icon.bp5-intent-danger, .bp5-icon-standard.bp5-intent-danger, .bp5-icon-large.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-heading{\n  color:#1c2127;\n  font-weight:600;\n  margin:0 0 10px;\n  padding:0;\n}\n.bp5-dark .bp5-heading{\n  color:#f6f7f9;\n}\n.bp5-heading.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-heading.bp5-text-muted{\n  color:#abb3bf;\n}\n\n.bp5-running-text h1, h1.bp5-heading{\n  font-size:36px;\n  line-height:40px;\n}\n\n.bp5-running-text h2, h2.bp5-heading{\n  font-size:28px;\n  line-height:32px;\n}\n\n.bp5-running-text h3, h3.bp5-heading{\n  font-size:22px;\n  line-height:25px;\n}\n\n.bp5-running-text h4, h4.bp5-heading{\n  font-size:18px;\n  line-height:21px;\n}\n\n.bp5-running-text h5, h5.bp5-heading{\n  font-size:16px;\n  line-height:19px;\n}\n\n.bp5-running-text h6, h6.bp5-heading{\n  font-size:14px;\n  line-height:16px;\n}\n.bp5-ui-text{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none;\n}\n\n.bp5-monospace-text{\n  font-family:monospace;\n  text-transform:none;\n}\n\n.bp5-text-overflow-ellipsis{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n}\n.bp5-running-text{\n  font-size:14px;\n  line-height:1.5;\n}\n.bp5-running-text h1{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h1{\n  color:#f6f7f9;\n}\n.bp5-running-text h1.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h1.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h2{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h2{\n  color:#f6f7f9;\n}\n.bp5-running-text h2.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h2.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h3{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h3{\n  color:#f6f7f9;\n}\n.bp5-running-text h3.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h3.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h4{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h4{\n  color:#f6f7f9;\n}\n.bp5-running-text h4.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h4.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h5{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h5{\n  color:#f6f7f9;\n}\n.bp5-running-text h5.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h5.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h6{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h6{\n  color:#f6f7f9;\n}\n.bp5-running-text h6.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h6.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text hr{\n  border:none;\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n  margin:20px 0;\n}\n.bp5-running-text p{\n  margin:0 0 10px;\n  padding:0;\n}\n.bp5-text-large{\n  font-size:16px;\n}\n\n.bp5-text-small{\n  font-size:12px;\n}\na{\n  text-decoration:none;\n}\na:hover{\n  cursor:pointer;\n  text-decoration:underline;\n}\n.bp5-code, .bp5-running-text code{\n  font-family:monospace;\n  text-transform:none;\n  border-radius:2px;\n  font-size:smaller;\n  padding:2px 5px;\n}\n\n.bp5-code-block, .bp5-running-text pre{\n  font-family:monospace;\n  text-transform:none;\n  border-radius:2px;\n  display:block;\n  font-size:13px;\n  line-height:1.4;\n  margin:10px 0;\n  padding:13px 15px 12px;\n  word-break:break-all;\n  word-wrap:break-word;\n}\n.bp5-code-block > code, .bp5-running-text pre > code{\n  font-size:inherit;\n  padding:0;\n}\n\n.bp5-key, .bp5-running-text kbd{\n  align-items:center;\n  border-radius:2px;\n  display:inline-flex;\n  font-family:inherit;\n  font-size:12px;\n  height:24px;\n  justify-content:center;\n  line-height:24px;\n  min-width:24px;\n  padding:2px 4px;\n  vertical-align:middle;\n}\n.bp5-key .bp5-icon, .bp5-running-text kbd .bp5-icon, .bp5-key .bp5-icon-standard, .bp5-running-text kbd .bp5-icon-standard, .bp5-key .bp5-icon-large, .bp5-running-text kbd .bp5-icon-large{\n  margin-right:5px;\n}\n.bp5-blockquote, .bp5-running-text blockquote{\n  border-left:solid 4px rgba(171, 179, 191, 0.5);\n  margin:0 0 10px;\n  padding:0 20px;\n}\n.bp5-dark .bp5-blockquote, .bp5-dark .bp5-running-text blockquote, .bp5-running-text .bp5-dark blockquote{\n  border-color:rgba(115, 128, 145, 0.5);\n}\n.bp5-list, .bp5-running-text ul,\n.bp5-running-text ol{\n  margin:10px 0;\n  padding-left:30px;\n}\n.bp5-list li:not(:last-child), .bp5-running-text ul li:not(:last-child),\n.bp5-running-text ol li:not(:last-child){\n  margin-bottom:5px;\n}\n.bp5-list ol, .bp5-running-text ul ol,\n.bp5-running-text ol ol,\n.bp5-list ul,\n.bp5-running-text ul ul,\n.bp5-running-text ol ul{\n  margin-top:5px;\n}\n\n.bp5-list-unstyled{\n  list-style:none;\n  margin:0;\n  padding:0;\n}\n.bp5-list-unstyled li{\n  padding:0;\n}\n.bp5-rtl{\n  text-align:right;\n}\n.bp5-dark{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-text-disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-running-text hr{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-dark a{\n  color:#8abbff;\n}\n.bp5-dark a:hover{\n  color:#8abbff;\n}\n.bp5-dark a .bp5-icon, .bp5-dark a .bp5-icon-standard, .bp5-dark a .bp5-icon-large{\n  color:inherit;\n}\n.bp5-dark a code{\n  color:inherit;\n}\n.bp5-dark .bp5-code,\n.bp5-dark .bp5-running-text code{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n  color:#abb3bf;\n}\na > .bp5-dark .bp5-code,\na > .bp5-dark .bp5-running-text code{\n  color:inherit;\n}\n.bp5-dark .bp5-code-block,\n.bp5-dark .bp5-running-text pre{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-code-block > code,\n.bp5-dark .bp5-running-text pre > code{\n  background:none;\n  box-shadow:none;\n  color:inherit;\n}\n.bp5-dark .bp5-key,\n.bp5-dark .bp5-running-text kbd{\n  background:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n  color:#abb3bf;\n}\n.bp5-dark .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-icon-large.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-icon.bp5-intent-success, .bp5-dark .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-icon-large.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-icon-large.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-icon-large.bp5-intent-danger{\n  color:#fa999c;\n}\n\n:focus{\n  outline:rgba(33, 93, 176, 0.752) solid 2px;\n  outline-offset:2px;\n  -moz-outline-radius:6px;\n}\n.bp5-dark :focus{\n  outline-color:rgba(138, 187, 255, 0.752);\n}\n\n.bp5-focus-disabled :focus:not(.bp5-focus-style-manager-ignore *){\n  outline:none !important;\n}\n.bp5-focus-disabled :focus:not(.bp5-focus-style-manager-ignore *) ~ .bp5-control-indicator{\n  outline:none !important;\n}\n\n.bp5-dark{\n  color-scheme:dark;\n}\n\n.bp5-alert{\n  max-width:400px;\n  padding:20px;\n}\n\n.bp5-alert-body{\n  display:flex;\n}\n.bp5-alert-body .bp5-icon{\n  font-size:40px;\n  margin-right:20px;\n  margin-top:0;\n}\n\n.bp5-alert-contents{\n  word-break:break-word;\n}\n\n.bp5-alert-footer{\n  display:flex;\n  flex-direction:row-reverse;\n  margin-top:10px;\n}\n.bp5-alert-footer .bp5-button{\n  margin-left:10px;\n}\n.bp5-breadcrumbs{\n  align-items:center;\n  cursor:default;\n  display:flex;\n  flex-wrap:wrap;\n  height:30px;\n  list-style:none;\n  margin:0;\n  padding:0;\n}\n.bp5-breadcrumbs > li{\n  align-items:center;\n  display:flex;\n}\n.bp5-breadcrumbs > li::after{\n  background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 00-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='%235f6b7c'/%3e%3c/svg%3e\");\n  content:\"\";\n  display:block;\n  height:16px;\n  margin:0 5px;\n  width:16px;\n}\n.bp5-breadcrumbs > li:last-of-type::after{\n  display:none;\n}\n\n.bp5-breadcrumb,\n.bp5-breadcrumb-current,\n.bp5-breadcrumbs-collapsed{\n  align-items:center;\n  display:inline-flex;\n  font-size:16px;\n}\n\n.bp5-breadcrumb,\n.bp5-breadcrumbs-collapsed{\n  color:#5f6b7c;\n}\n\n.bp5-breadcrumb:hover{\n  text-decoration:none;\n}\n.bp5-breadcrumb.bp5-disabled{\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-breadcrumb .bp5-icon{\n  margin-right:5px;\n}\n\n.bp5-breadcrumb-current{\n  color:inherit;\n  font-weight:600;\n}\n.bp5-breadcrumb-current .bp5-input{\n  font-size:inherit;\n  font-weight:inherit;\n  vertical-align:baseline;\n}\n\n.bp5-breadcrumbs-collapsed{\n  background:rgba(143, 153, 168, 0.15);\n  border:none;\n  border-radius:2px;\n  cursor:pointer;\n  margin-right:2px;\n  padding:1px 5px;\n  vertical-align:text-bottom;\n}\n.bp5-breadcrumbs-collapsed::before{\n  background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cg fill='%235f6b7c'%3e%3cpath d='M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z'/%3e%3c/g%3e%3c/svg%3e\") center no-repeat;\n  content:\"\";\n  display:block;\n  height:16px;\n  width:16px;\n}\n.bp5-breadcrumbs-collapsed:hover{\n  background:rgba(143, 153, 168, 0.3);\n  color:#1c2127;\n  text-decoration:none;\n}\n\n.bp5-dark .bp5-breadcrumb,\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-breadcrumbs > li::after{\n  background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 00-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='%23abb3bf'/%3e%3c/svg%3e\");\n  color:#abb3bf;\n}\n.bp5-dark .bp5-breadcrumb.bp5-disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-breadcrumb-current{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  background:rgba(143, 153, 168, 0.2);\n}\n.bp5-dark .bp5-breadcrumbs-collapsed::before{\n  background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cg fill='%23abb3bf'%3e%3cpath d='M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z'/%3e%3c/g%3e%3c/svg%3e\") center no-repeat;\n}\n.bp5-dark .bp5-breadcrumbs-collapsed:hover{\n  background:rgba(143, 153, 168, 0.3);\n  color:#f6f7f9;\n}\n.bp5-button{\n  display:inline-flex;\n  flex-direction:row;\n  align-items:center;\n  border:none;\n  border-radius:2px;\n  cursor:pointer;\n  font-size:14px;\n  justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  min-height:30px;\n  min-width:30px;\n}\n.bp5-button > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-button > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-button::before,\n.bp5-button > *{\n  margin-right:7px;\n}\n.bp5-button:empty::before,\n.bp5-button > :last-child{\n  margin-right:0;\n}\n.bp5-button:empty{\n  padding:0 !important;\n}\n.bp5-button:disabled, .bp5-button.bp5-disabled{\n  cursor:not-allowed;\n}\n.bp5-button.bp5-fill{\n  display:flex;\n  width:100%;\n}\n.bp5-button.bp5-align-right, .bp5-align-right .bp5-button{\n  text-align:right;\n}\n.bp5-button.bp5-align-left, .bp5-align-left .bp5-button{\n  text-align:left;\n}\n.bp5-button:not([class*=bp5-intent-]){\n  background-color:#f6f7f9;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#1c2127;\n}\n.bp5-button:not([class*=bp5-intent-]):hover{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button:not([class*=bp5-intent-]):active, .bp5-button:not([class*=bp5-intent-]).bp5-active{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button:not([class*=bp5-intent-]):active, .bp5-button:not([class*=bp5-intent-]).bp5-active{\n    background:highlight;\n  }\n}\n.bp5-button:not([class*=bp5-intent-]):disabled, .bp5-button:not([class*=bp5-intent-]).bp5-disabled{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  outline:none;\n}\n.bp5-button:not([class*=bp5-intent-]):disabled.bp5-active, .bp5-button:not([class*=bp5-intent-]):disabled.bp5-active:hover, .bp5-button:not([class*=bp5-intent-]).bp5-disabled.bp5-active, .bp5-button:not([class*=bp5-intent-]).bp5-disabled.bp5-active:hover{\n  background:rgba(211, 216, 222, 0.7);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button:not([class*=bp5-intent-]){\n    border:1px solid buttonborder;\n  }\n}\n.bp5-button.bp5-intent-primary{\n  background-color:#2d72d2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-primary:hover, .bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-primary:hover{\n  background-color:#215db0;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n  background-color:#184a90;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-primary:disabled, .bp5-button.bp5-intent-primary.bp5-disabled{\n  background-color:rgba(45, 114, 210, 0.5);\n  border-color:transparent;\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-primary:disabled, .bp5-button.bp5-intent-primary.bp5-disabled{\n    border-color:graytext;\n    color:graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-primary{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-button.bp5-intent-success{\n  background-color:#238551;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-success:hover, .bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-success:hover{\n  background-color:#1c6e42;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n  background-color:#165a36;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-success:disabled, .bp5-button.bp5-intent-success.bp5-disabled{\n  background-color:rgba(35, 133, 81, 0.5);\n  border-color:transparent;\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-success:disabled, .bp5-button.bp5-intent-success.bp5-disabled{\n    border-color:graytext;\n    color:graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-success{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-button.bp5-intent-warning{\n  background-color:#c87619;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-warning:hover, .bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-warning:hover{\n  background-color:#935610;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n  background-color:#77450d;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-warning:disabled, .bp5-button.bp5-intent-warning.bp5-disabled{\n  background-color:rgba(200, 118, 25, 0.5);\n  border-color:transparent;\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-warning:disabled, .bp5-button.bp5-intent-warning.bp5-disabled{\n    border-color:graytext;\n    color:graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-warning{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-button.bp5-intent-danger{\n  background-color:#cd4246;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-danger:hover, .bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-danger:hover{\n  background-color:#ac2f33;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n  background-color:#8e292c;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-danger:disabled, .bp5-button.bp5-intent-danger.bp5-disabled{\n  background-color:rgba(205, 66, 70, 0.5);\n  border-color:transparent;\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-danger:disabled, .bp5-button.bp5-intent-danger.bp5-disabled{\n    border-color:graytext;\n    color:graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-danger{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-button[class*=bp5-intent-] .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ffffff;\n}\n.bp5-button.bp5-large, .bp5-large .bp5-button{\n  min-height:40px;\n  min-width:40px;\n  font-size:16px;\n  padding:5px 15px;\n}\n.bp5-button.bp5-large::before,\n.bp5-button.bp5-large > *, .bp5-large .bp5-button::before,\n.bp5-large .bp5-button > *{\n  margin-right:10px;\n}\n.bp5-button.bp5-large:empty::before,\n.bp5-button.bp5-large > :last-child, .bp5-large .bp5-button:empty::before,\n.bp5-large .bp5-button > :last-child{\n  margin-right:0;\n}\n.bp5-button.bp5-small, .bp5-small .bp5-button{\n  min-height:24px;\n  min-width:24px;\n  padding:0 7px;\n}\n.bp5-button.bp5-loading{\n  position:relative;\n}\n.bp5-button.bp5-loading[class*=bp5-icon-]::before{\n  visibility:hidden;\n}\n.bp5-button.bp5-loading .bp5-button-spinner{\n  margin:0;\n  position:absolute;\n}\n.bp5-button.bp5-loading > :not(.bp5-button-spinner){\n  visibility:hidden;\n}\n.bp5-button[class*=bp5-icon-]::before{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  color:#5f6b7c;\n}\n.bp5-button .bp5-icon, .bp5-button .bp5-icon-standard, .bp5-button .bp5-icon-large{\n  color:#5f6b7c;\n}\n.bp5-button .bp5-icon.bp5-align-right, .bp5-button .bp5-icon-standard.bp5-align-right, .bp5-button .bp5-icon-large.bp5-align-right{\n  margin-left:7px;\n}\n.bp5-button .bp5-icon:first-child:last-child,\n.bp5-button .bp5-spinner + .bp5-icon:last-child{\n  margin:0 -7px;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]){\n  background-color:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):hover, .bp5-dark .bp5-button:not([class*=bp5-intent-]):active, .bp5-dark .bp5-button:not([class*=bp5-intent-]).bp5-active{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):hover{\n  background-color:#252a31;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):active, .bp5-dark .bp5-button:not([class*=bp5-intent-]).bp5-active{\n  background-color:#1c2127;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):disabled, .bp5-dark .bp5-button:not([class*=bp5-intent-]).bp5-disabled{\n  background-color:rgba(47, 52, 60, 0.15);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):disabled.bp5-active, .bp5-dark .bp5-button:not([class*=bp5-intent-]).bp5-disabled.bp5-active{\n  background:rgba(47, 52, 60, 0.7);\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]) .bp5-button-spinner .bp5-spinner-head{\n  background:rgba(17, 20, 24, 0.5);\n  stroke:#8f99a8;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-])[class*=bp5-icon-]::before{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]) .bp5-icon:not([class*=bp5-intent-]), .bp5-dark .bp5-button:not([class*=bp5-intent-]) .bp5-icon-standard:not([class*=bp5-intent-]), .bp5-dark .bp5-button:not([class*=bp5-intent-]) .bp5-icon-large:not([class*=bp5-intent-]){\n  color:#abb3bf;\n}\n.bp5-dark .bp5-button[class*=bp5-intent-]{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-button[class*=bp5-intent-]:hover{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-button[class*=bp5-intent-]:active, .bp5-dark .bp5-button[class*=bp5-intent-].bp5-active{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-button[class*=bp5-intent-]:disabled, .bp5-dark .bp5-button[class*=bp5-intent-].bp5-disabled{\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-button[class*=bp5-intent-] .bp5-button-spinner .bp5-spinner-head{\n  stroke:#8f99a8;\n}\n.bp5-button:disabled::before,\n.bp5-button:disabled .bp5-icon, .bp5-button:disabled .bp5-icon-standard, .bp5-button:disabled .bp5-icon-large, .bp5-button.bp5-disabled::before,\n.bp5-button.bp5-disabled .bp5-icon, .bp5-button.bp5-disabled .bp5-icon-standard, .bp5-button.bp5-disabled .bp5-icon-large, .bp5-button[class*=bp5-intent-]::before,\n.bp5-button[class*=bp5-intent-] .bp5-icon, .bp5-button[class*=bp5-intent-] .bp5-icon-standard, .bp5-button[class*=bp5-intent-] .bp5-icon-large{\n  color:inherit !important;\n}\n.bp5-button.bp5-minimal{\n  background:none;\n  box-shadow:none;\n}\n.bp5-button.bp5-minimal:hover{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  color:#1c2127;\n  text-decoration:none;\n}\n.bp5-button.bp5-minimal:active, .bp5-button.bp5-minimal.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:none;\n  color:#1c2127;\n}\n.bp5-button.bp5-minimal:disabled, .bp5-button.bp5-minimal:disabled:hover, .bp5-button.bp5-minimal.bp5-disabled, .bp5-button.bp5-minimal.bp5-disabled:hover{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-button.bp5-minimal:disabled.bp5-active, .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-minimal{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button.bp5-minimal:hover, .bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button.bp5-minimal:hover{\n  background:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-minimal:disabled, .bp5-dark .bp5-button.bp5-minimal:disabled:hover, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-button.bp5-minimal:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:hover, .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#215db0;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.15);\n  color:#215db0;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(33, 93, 176, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n  stroke:#215db0;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:hover, .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#1c6e42;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.15);\n  color:#1c6e42;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(28, 110, 66, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n  stroke:#1c6e42;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:hover, .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#935610;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.15);\n  color:#935610;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(147, 86, 16, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n  stroke:#935610;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:hover, .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ac2f33;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.15);\n  color:#ac2f33;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(172, 47, 51, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ac2f33;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-outlined{\n  background:none;\n  box-shadow:none;\n  border:1px solid rgba(28, 33, 39, 0.2);\n  box-sizing:border-box;\n}\n.bp5-button.bp5-outlined:hover{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  color:#1c2127;\n  text-decoration:none;\n}\n.bp5-button.bp5-outlined:active, .bp5-button.bp5-outlined.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:none;\n  color:#1c2127;\n}\n.bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined.bp5-disabled:hover{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-button.bp5-outlined:disabled.bp5-active, .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-outlined{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button.bp5-outlined:hover, .bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button.bp5-outlined:hover{\n  background:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-button.bp5-outlined:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:hover, .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#215db0;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.15);\n  color:#215db0;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(33, 93, 176, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n  stroke:#215db0;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:hover, .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#1c6e42;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.15);\n  color:#1c6e42;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(28, 110, 66, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n  stroke:#1c6e42;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:hover, .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#935610;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.15);\n  color:#935610;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(147, 86, 16, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n  stroke:#935610;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:hover, .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ac2f33;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.15);\n  color:#ac2f33;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(172, 47, 51, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ac2f33;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled:hover{\n  border-color:rgba(95, 107, 124, 0.1);\n}\n.bp5-dark .bp5-button.bp5-outlined{\n  border-color:rgba(255, 255, 255, 0.4);\n}\n.bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary{\n  border-color:rgba(33, 93, 176, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n  border-color:rgba(33, 93, 176, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n  border-color:rgba(138, 187, 255, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n  border-color:rgba(138, 187, 255, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-success{\n  border-color:rgba(28, 110, 66, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n  border-color:rgba(28, 110, 66, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n  border-color:rgba(114, 202, 155, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n  border-color:rgba(114, 202, 155, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning{\n  border-color:rgba(147, 86, 16, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n  border-color:rgba(147, 86, 16, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n  border-color:rgba(251, 179, 96, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n  border-color:rgba(251, 179, 96, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger{\n  border-color:rgba(172, 47, 51, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n  border-color:rgba(172, 47, 51, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n  border-color:rgba(250, 153, 156, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n  border-color:rgba(250, 153, 156, 0.2);\n}\n.bp5-button.bp5-intent-warning{\n  background:#fbb360;\n  color:#1c2127;\n}\n.bp5-button.bp5-intent-warning:not(.bp5-disabled).bp5-icon > svg{\n  fill:rgba(28, 33, 39, 0.7);\n}\n.bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined):hover{\n  background:#ec9a3c;\n  color:#1c2127;\n}\n.bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined):active, .bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined).bp5-active{\n  background:#c87619;\n  color:#1c2127;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined):active, .bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined).bp5-active{\n    background:highlight;\n  }\n}\n.bp5-button.bp5-intent-warning:disabled, .bp5-button.bp5-intent-warning.bp5-disabled{\n  background:rgba(200, 118, 25, 0.5);\n  color:rgba(28, 33, 39, 0.35);\n}\n.bp5-dark .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-intent-warning.bp5-disabled{\n  color:rgba(28, 33, 39, 0.6);\n}\n.bp5-button.bp5-intent-warning.bp5-minimal, .bp5-button.bp5-intent-warning.bp5-outlined{\n  background:none;\n}\n.bp5-dark .bp5-button.bp5-intent-warning.bp5-minimal:not(.bp5-disabled).bp5-icon > svg, .bp5-dark .bp5-button.bp5-intent-warning.bp5-outlined:not(.bp5-disabled).bp5-icon > svg{\n  fill:#fbb360;\n}\n\na.bp5-button{\n  text-align:center;\n  text-decoration:none;\n  transition:none;\n}\na.bp5-button, a.bp5-button:hover, a.bp5-button:active{\n  color:#1c2127;\n}\na.bp5-button.bp5-disabled{\n  color:rgba(95, 107, 124, 0.6);\n}\n\n.bp5-button-text{\n  flex:0 1 auto;\n}\n\n.bp5-button.bp5-align-left .bp5-button-text, .bp5-button.bp5-align-right .bp5-button-text,\n.bp5-button-group.bp5-align-left .bp5-button-text,\n.bp5-button-group.bp5-align-right .bp5-button-text{\n  flex:1 1 auto;\n}\n.bp5-button-group{\n  display:inline-flex;\n}\n.bp5-button-group .bp5-button{\n  flex:0 0 auto;\n  position:relative;\n  z-index:4;\n}\n.bp5-button-group .bp5-button:focus{\n  z-index:5;\n}\n.bp5-button-group .bp5-button:hover{\n  z-index:6;\n}\n.bp5-button-group .bp5-button:active, .bp5-button-group .bp5-button.bp5-active{\n  z-index:7;\n}\n.bp5-button-group .bp5-button:disabled, .bp5-button-group .bp5-button.bp5-disabled{\n  z-index:3;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]{\n  z-index:9;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]:focus{\n  z-index:10;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]:hover{\n  z-index:11;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]:active, .bp5-button-group .bp5-button[class*=bp5-intent-].bp5-active{\n  z-index:12;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]:disabled, .bp5-button-group .bp5-button[class*=bp5-intent-].bp5-disabled{\n  z-index:8;\n}\n.bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:first-child) .bp5-button,\n.bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:first-child), .bp5-button-group.bp5-outlined > .bp5-popover-wrapper:not(:first-child) .bp5-button,\n.bp5-button-group.bp5-outlined > .bp5-button:not(:first-child){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n.bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:last-child), .bp5-button-group.bp5-outlined > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n.bp5-button-group.bp5-outlined > .bp5-button:not(:last-child){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n}\n.bp5-button-group:not(.bp5-minimal):not(.bp5-outlined) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n.bp5-button-group:not(.bp5-minimal):not(.bp5-outlined) > .bp5-button:not(:last-child){\n  margin-right:-1px;\n}\n.bp5-button-group.bp5-minimal .bp5-button, .bp5-button-group.bp5-outlined .bp5-button{\n  background:none;\n  box-shadow:none;\n}\n.bp5-button-group.bp5-minimal .bp5-button:hover, .bp5-button-group.bp5-outlined .bp5-button:hover{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  color:#1c2127;\n  text-decoration:none;\n}\n.bp5-button-group.bp5-minimal .bp5-button:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-active, .bp5-button-group.bp5-outlined .bp5-button:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:none;\n  color:#1c2127;\n}\n.bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover, .bp5-button-group.bp5-outlined .bp5-button:disabled, .bp5-button-group.bp5-outlined .bp5-button:disabled:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled:hover{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active, .bp5-button-group.bp5-outlined .bp5-button:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button:disabled:hover.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:hover{\n  background:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:disabled:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled:hover{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:disabled:hover.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#215db0;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.15);\n  color:#215db0;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(33, 93, 176, 0.5);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n  stroke:#215db0;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#1c6e42;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.15);\n  color:#1c6e42;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(28, 110, 66, 0.5);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success .bp5-button-spinner .bp5-spinner-head, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n  stroke:#1c6e42;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#935610;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.15);\n  color:#935610;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(147, 86, 16, 0.5);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n  stroke:#935610;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ac2f33;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.15);\n  color:#ac2f33;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(172, 47, 51, 0.5);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ac2f33;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button-group.bp5-minimal:not(:first-child), .bp5-button-group.bp5-outlined:not(:first-child){\n    border-bottom-left-radius:0;\n    border-left:none;\n    border-top-left-radius:0;\n  }\n  .bp5-button-group.bp5-minimal:not(:last-child), .bp5-button-group.bp5-outlined:not(:last-child){\n    border-bottom-right-radius:0;\n    border-top-right-radius:0;\n    margin-right:-1px;\n  }\n}\n.bp5-button-group.bp5-outlined > .bp5-button{\n  border:1px solid rgba(28, 33, 39, 0.2);\n  box-sizing:border-box;\n}\n.bp5-button-group.bp5-outlined > .bp5-button:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-disabled, .bp5-button-group.bp5-outlined > .bp5-button:disabled:hover, .bp5-button-group.bp5-outlined > .bp5-button.bp5-disabled:hover{\n  border-color:rgba(95, 107, 124, 0.1);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button{\n  border-color:rgba(255, 255, 255, 0.4);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button:disabled:hover, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-disabled:hover{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary{\n  border-color:rgba(33, 93, 176, 0.6);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary.bp5-disabled{\n  border-color:rgba(33, 93, 176, 0.2);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary{\n  border-color:rgba(138, 187, 255, 0.6);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary.bp5-disabled{\n  border-color:rgba(138, 187, 255, 0.2);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success{\n  border-color:rgba(28, 110, 66, 0.6);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success.bp5-disabled{\n  border-color:rgba(28, 110, 66, 0.2);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success{\n  border-color:rgba(114, 202, 155, 0.6);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success.bp5-disabled{\n  border-color:rgba(114, 202, 155, 0.2);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning{\n  border-color:rgba(147, 86, 16, 0.6);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning.bp5-disabled{\n  border-color:rgba(147, 86, 16, 0.2);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning{\n  border-color:rgba(251, 179, 96, 0.6);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning.bp5-disabled{\n  border-color:rgba(251, 179, 96, 0.2);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger{\n  border-color:rgba(172, 47, 51, 0.6);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger.bp5-disabled{\n  border-color:rgba(172, 47, 51, 0.2);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger{\n  border-color:rgba(250, 153, 156, 0.6);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger.bp5-disabled{\n  border-color:rgba(250, 153, 156, 0.2);\n}\n.bp5-button-group.bp5-outlined:not(.bp5-vertical) > .bp5-button:not(:last-child){\n  border-right:none;\n}\n.bp5-button-group .bp5-popover-wrapper,\n.bp5-button-group .bp5-popover-target{\n  display:flex;\n  flex:1 1 auto;\n}\n.bp5-button-group.bp5-fill{\n  display:flex;\n  width:100%;\n}\n.bp5-button-group .bp5-button.bp5-fill, .bp5-button-group.bp5-fill .bp5-button:not(.bp5-fixed){\n  flex:1 1 auto;\n}\n.bp5-button-group.bp5-vertical{\n  align-items:stretch;\n  flex-direction:column;\n  vertical-align:top;\n}\n.bp5-button-group.bp5-vertical.bp5-fill{\n  height:100%;\n  width:unset;\n}\n.bp5-button-group.bp5-vertical .bp5-button{\n  margin-right:0 !important;\n  width:100%;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:first-child .bp5-button,\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:first-child, .bp5-button-group.bp5-vertical.bp5-outlined > .bp5-popover-wrapper:first-child .bp5-button,\n.bp5-button-group.bp5-vertical.bp5-outlined > .bp5-button:first-child{\n  border-radius:2px 2px 0 0;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:last-child .bp5-button,\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:last-child, .bp5-button-group.bp5-vertical.bp5-outlined > .bp5-popover-wrapper:last-child .bp5-button,\n.bp5-button-group.bp5-vertical.bp5-outlined > .bp5-button:last-child{\n  border-radius:0 0 2px 2px;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal):not(.bp5-outlined) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n.bp5-button-group.bp5-vertical:not(.bp5-minimal):not(.bp5-outlined) > .bp5-button:not(:last-child){\n  margin-bottom:-1px;\n}\n.bp5-button-group.bp5-vertical.bp5-outlined > .bp5-button:not(:last-child){\n  border-bottom:none;\n}\n.bp5-button-group.bp5-align-left .bp5-button{\n  text-align:left;\n}\n.bp5-callout{\n  font-size:14px;\n  line-height:1.5;\n  border-radius:2px;\n  padding:15px;\n  position:relative;\n  width:100%;\n}\n.bp5-callout:not(.bp5-minimal){\n  background-color:rgba(143, 153, 168, 0.15);\n}\n.bp5-callout[class*=bp5-icon-]{\n  padding-left:38px;\n}\n.bp5-callout[class*=bp5-icon-]::before{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  color:#5f6b7c;\n  left:15px;\n  position:absolute;\n  top:17px;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-callout-icon{\n  padding-left:38px;\n}\n.bp5-callout.bp5-callout-icon > .bp5-icon:first-child{\n  color:#5f6b7c;\n  left:15px;\n  position:absolute;\n  top:17px;\n}\n.bp5-callout .bp5-heading{\n  line-height:16px;\n  margin-bottom:0;\n  margin-top:2px;\n}\n.bp5-callout.bp5-callout-has-body-content .bp5-heading{\n  margin-bottom:5px;\n}\n.bp5-callout.bp5-compact{\n  padding:10px;\n}\n.bp5-callout.bp5-compact.bp5-callout-icon{\n  padding-left:33px;\n}\n.bp5-callout.bp5-compact.bp5-callout-icon > .bp5-icon:first-child{\n  left:10px;\n  top:12px;\n}\n.bp5-dark .bp5-callout:not(.bp5-minimal){\n  background-color:rgba(143, 153, 168, 0.2);\n}\n.bp5-dark .bp5-callout[class*=bp5-icon-]::before, .bp5-dark .bp5-callout.bp5-callout-icon > .bp5-icon:first-child{\n  color:#abb3bf;\n}\n.bp5-callout.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-callout.bp5-intent-primary:not(.bp5-minimal){\n  background-color:rgba(45, 114, 210, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout.bp5-intent-primary{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-primary[class*=bp5-icon-]::before,\n.bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-primary .bp5-heading{\n  color:#215db0;\n}\n.bp5-dark .bp5-callout.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-callout.bp5-intent-primary:not(.bp5-minimal){\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-dark .bp5-callout.bp5-intent-primary[class*=bp5-icon-]::before,\n.bp5-dark .bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-primary .bp5-heading{\n  color:#8abbff;\n}\n.bp5-callout.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-callout.bp5-intent-success:not(.bp5-minimal){\n  background-color:rgba(35, 133, 81, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout.bp5-intent-success{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-success[class*=bp5-icon-]::before,\n.bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-success .bp5-heading{\n  color:#1c6e42;\n}\n.bp5-dark .bp5-callout.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-callout.bp5-intent-success:not(.bp5-minimal){\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-dark .bp5-callout.bp5-intent-success[class*=bp5-icon-]::before,\n.bp5-dark .bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-success .bp5-heading{\n  color:#72ca9b;\n}\n.bp5-callout.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-callout.bp5-intent-warning:not(.bp5-minimal){\n  background-color:rgba(200, 118, 25, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout.bp5-intent-warning{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-warning[class*=bp5-icon-]::before,\n.bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-warning .bp5-heading{\n  color:#935610;\n}\n.bp5-dark .bp5-callout.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-callout.bp5-intent-warning:not(.bp5-minimal){\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-dark .bp5-callout.bp5-intent-warning[class*=bp5-icon-]::before,\n.bp5-dark .bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-warning .bp5-heading{\n  color:#fbb360;\n}\n.bp5-callout.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-callout.bp5-intent-danger:not(.bp5-minimal){\n  background-color:rgba(205, 66, 70, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout.bp5-intent-danger{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-danger[class*=bp5-icon-]::before,\n.bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-danger .bp5-heading{\n  color:#ac2f33;\n}\n.bp5-dark .bp5-callout.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-callout.bp5-intent-danger:not(.bp5-minimal){\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-dark .bp5-callout.bp5-intent-danger[class*=bp5-icon-]::before,\n.bp5-dark .bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-danger .bp5-heading{\n  color:#fa999c;\n}\n.bp5-callout.bp5-intent-primary a{\n  text-decoration:underline;\n}\n.bp5-callout.bp5-intent-primary a:hover{\n  color:#184a90;\n}\n.bp5-dark .bp5-callout.bp5-intent-primary a:hover{\n  color:#99c4ff;\n}\n.bp5-running-text .bp5-callout{\n  margin:20px 0;\n}\n.bp5-card{\n  background-color:#ffffff;\n  border-radius:2px;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.15);\n  padding:20px;\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-card.bp5-dark, .bp5-dark .bp5-card{\n  background-color:#252a31;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-card{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n\n.bp5-elevation-0{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-elevation-0.bp5-dark, .bp5-dark .bp5-elevation-0{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-0{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-1{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-1.bp5-dark, .bp5-dark .bp5-elevation-1{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-1{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-2{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2), 0 2px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-2.bp5-dark, .bp5-dark .bp5-elevation-2{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px rgba(17, 20, 24, 0.4), 0 2px 6px rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-2{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-3{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-3.bp5-dark, .bp5-dark .bp5-elevation-3{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-3{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-4{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 4px 8px rgba(17, 20, 24, 0.2), 0 18px 46px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-4.bp5-dark, .bp5-dark .bp5-elevation-4{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 4px 8px rgba(17, 20, 24, 0.4), 0 18px 46px 6px rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-4{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-card.bp5-compact{\n  padding:15px;\n}\n\n.bp5-card.bp5-interactive:hover{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  cursor:pointer;\n}\n.bp5-card.bp5-interactive:hover.bp5-dark, .bp5-dark .bp5-card.bp5-interactive:hover{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-card.bp5-interactive.bp5-selected{\n  box-shadow:0 0 0 3px rgba(76, 144, 240, 0.2), 0 0 0 1px #4c90f0;\n}\n.bp5-card.bp5-interactive.bp5-selected.bp5-dark, .bp5-dark .bp5-card.bp5-interactive.bp5-selected{\n  box-shadow:0 0 0 3px rgba(138, 187, 255, 0.4), 0 0 0 1px #8abbff;\n}\n.bp5-card.bp5-interactive:active{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  transition-duration:0;\n}\n.bp5-card.bp5-interactive:active.bp5-dark, .bp5-dark .bp5-card.bp5-interactive:active{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n\n.bp5-card-list{\n  overflow:auto;\n  padding:0;\n  width:100%;\n}\n.bp5-card-list > .bp5-card{\n  align-items:center;\n  border-radius:0;\n  box-shadow:none;\n  display:flex;\n  min-height:51px;\n  padding:10px 20px;\n}\n.bp5-card-list > .bp5-card.bp5-interactive:hover, .bp5-card-list > .bp5-card.bp5-interactive:active{\n  background-color:#f6f7f9;\n  box-shadow:none;\n}\n.bp5-dark .bp5-card-list > .bp5-card.bp5-interactive:hover, .bp5-dark .bp5-card-list > .bp5-card.bp5-interactive:active{\n  background-color:#2f343c;\n}\n.bp5-card-list > .bp5-card.bp5-selected{\n  background-color:#edeff2;\n  box-shadow:none;\n}\n.bp5-dark .bp5-card-list > .bp5-card.bp5-selected{\n  background-color:#383e47;\n  box-shadow:none;\n}\n.bp5-card-list > .bp5-card:not(:last-child){\n  border-bottom:1px solid rgba(17, 20, 24, 0.1);\n}\n.bp5-dark .bp5-card-list > .bp5-card:not(:last-child){\n  border-color:rgba(255, 255, 255, 0.1);\n}\n.bp5-card-list.bp5-compact{\n  padding:0;\n}\n.bp5-card-list.bp5-compact > .bp5-card{\n  min-height:45px;\n  padding:7px 15px;\n}\n.bp5-dark .bp5-card-list{\n  padding:1px;\n}\n.bp5-card-list:not(.bp5-card-list-bordered){\n  border-radius:0;\n  box-shadow:none;\n}\n.bp5-dark .bp5-card-list:not(.bp5-card-list-bordered){\n  margin:1px;\n  width:calc(100% - 2px);\n}\n\n.bp5-collapse{\n  height:0;\n  overflow-y:hidden;\n  transition:height 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-collapse .bp5-collapse-body{\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-collapse .bp5-collapse-body[aria-hidden=true]{\n  display:none;\n}\n\n.bp5-context-menu-virtual-target{\n  position:fixed;\n}\n\n.bp5-card.bp5-control-card, .bp5-card-list > .bp5-card.bp5-control-card{\n  min-height:auto;\n  padding:0;\n}\n\n.bp5-control-card .bp5-control.bp5-control.bp5-control{\n  align-items:flex-start;\n  display:flex;\n  gap:10px;\n  margin:0;\n  padding:20px;\n  width:100%;\n}\n.bp5-control-card .bp5-control.bp5-control.bp5-control.bp5-align-left{\n  flex-direction:row;\n  justify-content:flex-start;\n}\n.bp5-control-card .bp5-control.bp5-control.bp5-control.bp5-align-right{\n  flex-direction:row-reverse;\n  justify-content:space-between;\n}\n.bp5-card-list .bp5-control-card .bp5-control.bp5-control.bp5-control{\n  padding:20px;\n}\n.bp5-card-list.bp5-compact .bp5-control-card .bp5-control.bp5-control.bp5-control{\n  padding:15px;\n}\n.bp5-control-card .bp5-control.bp5-control.bp5-control .bp5-control-indicator{\n  margin:0;\n}\n.bp5-control-card.bp5-compact .bp5-control.bp5-control.bp5-control{\n  padding:15px;\n}\n\n.bp5-divider{\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n  border-right:1px solid rgba(17, 20, 24, 0.15);\n  margin:5px;\n}\n.bp5-dark .bp5-divider{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-dialog-container{\n  opacity:1;\n  transform:scale(1);\n  align-items:center;\n  display:flex;\n  justify-content:center;\n  min-height:100%;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  width:100%;\n}\n.bp5-dialog-container.bp5-overlay-enter > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear > .bp5-dialog{\n  opacity:0;\n  transform:scale(0.5);\n}\n.bp5-dialog-container.bp5-overlay-enter-active > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear-active > .bp5-dialog{\n  opacity:1;\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:opacity, transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-dialog-container.bp5-overlay-exit > .bp5-dialog{\n  opacity:1;\n  transform:scale(1);\n}\n.bp5-dialog-container.bp5-overlay-exit-active > .bp5-dialog{\n  opacity:0;\n  transform:scale(0.5);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:opacity, transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n\n.bp5-dialog{\n  background:#f6f7f9;\n  border-radius:4px;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  display:flex;\n  flex-direction:column;\n  margin:30px 0;\n  pointer-events:all;\n  -webkit-user-select:text;\n     -moz-user-select:text;\n      -ms-user-select:text;\n          user-select:text;\n  width:500px;\n}\n.bp5-dialog:focus{\n  outline:0;\n}\n.bp5-dialog.bp5-dark, .bp5-dark .bp5-dialog{\n  background:#1c2127;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n  color:#f6f7f9;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dialog{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-dialog-header{\n  align-items:center;\n  background:#ffffff;\n  border-radius:4px 4px 0 0;\n  box-shadow:0 1px 0 rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex:0 0 auto;\n  min-height:40px;\n  padding:5px;\n  padding-left:15px;\n  z-index:0;\n}\n.bp5-dialog-header .bp5-icon-large,\n.bp5-dialog-header .bp5-icon{\n  flex:0 0 auto;\n  margin-left:-3px;\n  margin-right:7.5px;\n}\n.bp5-dialog-header .bp5-icon-large:not([class*=bp5-intent]),\n.bp5-dialog-header .bp5-icon:not([class*=bp5-intent]){\n  color:#5f6b7c;\n}\n.bp5-dialog-header .bp5-heading{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  flex:1 1 auto;\n  line-height:inherit;\n  margin:0;\n}\n.bp5-dialog-header .bp5-heading:last-child{\n  margin-right:15px;\n}\n.bp5-dark .bp5-dialog-header{\n  background:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-dialog-header :not([class*=bp5-intent]).bp5-icon-large, .bp5-dark .bp5-dialog-header :not([class*=bp5-intent]).bp5-icon{\n  color:#abb3bf;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dialog-header{\n    border-bottom:1px solid buttonborder;\n  }\n}\n\n.bp5-dialog-body{\n  flex:1 1 auto;\n  margin:15px;\n}\n\n.bp5-dialog-body-scroll-container{\n  margin:0;\n  max-height:70vh;\n  overflow:auto;\n  padding:15px;\n}\n\n.bp5-dialog-footer{\n  flex:0 0 auto;\n  margin:15px;\n}\n\n.bp5-dialog-footer-fixed{\n  align-items:center;\n  background-color:#ffffff;\n  border-radius:0 0 4px 4px;\n  border-top:1px solid rgba(17, 20, 24, 0.15);\n  display:flex;\n  gap:15px;\n  justify-content:space-between;\n  margin:0;\n  padding:10px 10px 10px 15px;\n}\n.bp5-dark .bp5-dialog-footer-fixed{\n  background:#383e47;\n  border-top:1px solid rgba(255, 255, 255, 0.2);\n}\n\n.bp5-dialog-footer-main-section{\n  flex:1 1 auto;\n}\n\n.bp5-dialog-footer-actions{\n  display:flex;\n  justify-content:flex-end;\n}\n.bp5-dialog-footer-actions .bp5-button{\n  margin-left:10px;\n}\n\n.bp5-multistep-dialog-panels{\n  display:flex;\n}\n.bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:first-child{\n  border-radius:4px 0 0 0;\n}\n.bp5-multistep-dialog-panels:first-child .bp5-multistep-dialog-right-panel{\n  border-top-right-radius:4px;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels{\n  flex-direction:column;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:first-child{\n  border-radius:4px 0 0 0;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:last-child{\n  border-radius:0 4px 0 0;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-multistep-dialog-left-panel{\n  flex-direction:row;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-dialog-step-container{\n  flex-grow:1;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-dialog-step-container:not(:first-child){\n  border-left:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-dark .bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-dialog-step-container{\n  border-color:rgba(17, 20, 24, 0.4);\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-multistep-dialog-right-panel{\n  border-left:none;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-multistep-dialog-right-panel,\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-multistep-dialog-footer{\n  border-radius:0 0 4px 4px;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels{\n  flex-direction:row-reverse;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels:first-child .bp5-multistep-dialog-right-panel{\n  border-radius:4px 0 0 4px;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:first-child{\n  border-radius:0 4px 0 0;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels .bp5-multistep-dialog-left-panel{\n  border-radius:0 0 4px;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels .bp5-multistep-dialog-right-panel{\n  border-left:none;\n  border-radius:4px 0 0 4px;\n  border-right:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-dark .bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels .bp5-multistep-dialog-right-panel{\n  border-color:rgba(17, 20, 24, 0.4);\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels .bp5-dialog-footer{\n  border-bottom-left-radius:0;\n}\n\n.bp5-multistep-dialog-left-panel{\n  display:flex;\n  flex:1;\n  flex-direction:column;\n}\n.bp5-dark .bp5-multistep-dialog-left-panel{\n  background:#252a31;\n  border-bottom:1px solid rgba(255, 255, 255, 0.2);\n  border-bottom-left-radius:4px;\n  border-left:1px solid rgba(255, 255, 255, 0.2);\n}\n\n.bp5-multistep-dialog-right-panel{\n  background-color:#f6f7f9;\n  border-left:1px solid rgba(17, 20, 24, 0.15);\n  border-radius:0 0 4px;\n  flex:3;\n  min-width:0;\n}\n.bp5-dark .bp5-multistep-dialog-right-panel{\n  background-color:#2f343c;\n  border-bottom:1px solid rgba(255, 255, 255, 0.2);\n  border-bottom-right-radius:4px;\n  border-left:1px solid rgba(255, 255, 255, 0.2);\n  border-right:1px solid rgba(255, 255, 255, 0.2);\n}\n\n.bp5-dialog-step-container{\n  background-color:#f6f7f9;\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-dark .bp5-dialog-step-container{\n  background:#2f343c;\n  border-bottom:1px solid rgba(255, 255, 255, 0.2);\n}\n.bp5-dialog-step-container.bp5-dialog-step-viewed{\n  background-color:#ffffff;\n}\n.bp5-dark .bp5-dialog-step-container.bp5-dialog-step-viewed{\n  background:#383e47;\n}\n\n.bp5-dialog-step{\n  align-items:center;\n  border-radius:4px;\n  cursor:not-allowed;\n  display:flex;\n  margin:4px;\n  padding:6px 14px;\n}\n.bp5-dark .bp5-dialog-step{\n  background:#2f343c;\n}\n.bp5-dialog-step-viewed .bp5-dialog-step{\n  background-color:#ffffff;\n  cursor:pointer;\n}\n.bp5-dark .bp5-dialog-step-viewed .bp5-dialog-step{\n  background:#383e47;\n}\n.bp5-dialog-step:hover{\n  background-color:#f6f7f9;\n}\n.bp5-dark .bp5-dialog-step:hover{\n  background:#2f343c;\n}\n\n.bp5-dialog-step-icon{\n  align-items:center;\n  background-color:rgba(95, 107, 124, 0.6);\n  border-radius:50%;\n  color:#ffffff;\n  display:flex;\n  height:25px;\n  justify-content:center;\n  width:25px;\n}\n.bp5-dark .bp5-dialog-step-icon{\n  background-color:rgba(171, 179, 191, 0.6);\n}\n.bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-icon{\n  background-color:#2d72d2;\n}\n.bp5-dialog-step-viewed .bp5-dialog-step-icon{\n  background-color:#8f99a8;\n}\n\n.bp5-dialog-step-title{\n  color:rgba(95, 107, 124, 0.6);\n  flex:1;\n  padding-left:10px;\n}\n.bp5-dark .bp5-dialog-step-title{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-title{\n  color:#2d72d2;\n}\n.bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n  color:#1c2127;\n}\n.bp5-dark .bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n  color:#f6f7f9;\n}\n\n.bp5-drawer{\n  background:#ffffff;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 4px 8px rgba(17, 20, 24, 0.2), 0 18px 46px 6px rgba(17, 20, 24, 0.2);\n  display:flex;\n  flex-direction:column;\n  margin:0;\n  padding:0;\n}\n.bp5-drawer:focus{\n  outline:0;\n}\n.bp5-drawer.bp5-position-top{\n  height:50%;\n  left:0;\n  right:0;\n  top:0;\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-enter, .bp5-drawer.bp5-position-top.bp5-overlay-appear{\n  transform:translateY(-100%);\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-enter-active, .bp5-drawer.bp5-position-top.bp5-overlay-appear-active{\n  transform:translateY(0);\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-exit{\n  transform:translateY(0);\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-exit-active{\n  transform:translateY(-100%);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer.bp5-position-top{\n    border-bottom:1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-position-bottom{\n  bottom:0;\n  height:50%;\n  left:0;\n  right:0;\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-enter, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear{\n  transform:translateY(100%);\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-enter-active, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear-active{\n  transform:translateY(0);\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-exit{\n  transform:translateY(0);\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-exit-active{\n  transform:translateY(100%);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer.bp5-position-bottom{\n    border-top:1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-position-left{\n  bottom:0;\n  left:0;\n  top:0;\n  width:50%;\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-enter, .bp5-drawer.bp5-position-left.bp5-overlay-appear{\n  transform:translateX(-100%);\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-enter-active, .bp5-drawer.bp5-position-left.bp5-overlay-appear-active{\n  transform:translateX(0);\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-exit{\n  transform:translateX(0);\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-exit-active{\n  transform:translateX(-100%);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer.bp5-position-left{\n    border-right:1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-position-right{\n  bottom:0;\n  right:0;\n  top:0;\n  width:50%;\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-enter, .bp5-drawer.bp5-position-right.bp5-overlay-appear{\n  transform:translateX(100%);\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-enter-active, .bp5-drawer.bp5-position-right.bp5-overlay-appear-active{\n  transform:translateX(0);\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-exit{\n  transform:translateX(0);\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-exit-active{\n  transform:translateX(100%);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer.bp5-position-right{\n    border-left:1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-dark, .bp5-dark .bp5-drawer{\n  background:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n  color:#f6f7f9;\n}\n\n.bp5-drawer-header{\n  align-items:center;\n  border-radius:0;\n  box-shadow:0 1px 0 rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex:0 0 auto;\n  min-height:40px;\n  padding:5px;\n  padding-left:20px;\n  position:relative;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer-header{\n    border-bottom:1px solid buttonborder;\n  }\n}\n.bp5-drawer-header .bp5-icon-large,\n.bp5-drawer-header .bp5-icon{\n  color:#5f6b7c;\n  flex:0 0 auto;\n  margin-right:10px;\n}\n.bp5-drawer-header .bp5-heading{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  flex:1 1 auto;\n  line-height:inherit;\n  margin:0;\n}\n.bp5-drawer-header .bp5-heading:last-child{\n  margin-right:20px;\n}\n.bp5-dark .bp5-drawer-header{\n  box-shadow:0 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-drawer-header .bp5-icon-large,\n.bp5-dark .bp5-drawer-header .bp5-icon{\n  color:#abb3bf;\n}\n\n.bp5-drawer-body{\n  flex:1 1 auto;\n  line-height:18px;\n  overflow:auto;\n}\n\n.bp5-drawer-footer{\n  box-shadow:inset 0 1px 0 rgba(17, 20, 24, 0.15);\n  flex:0 0 auto;\n  padding:10px 20px;\n  position:relative;\n}\n.bp5-dark .bp5-drawer-footer{\n  box-shadow:inset 0 1px 0 rgba(17, 20, 24, 0.4);\n}\n\n.bp5-editable-text{\n  cursor:text;\n  display:inline-block;\n  max-width:100%;\n  position:relative;\n  vertical-align:top;\n  white-space:nowrap;\n}\n.bp5-editable-text::before{\n  inset:-2px;\n  position:absolute;\n  border-radius:2px;\n  content:\"\";\n  transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-editable-text:hover::before{\n  box-shadow:0 0 0 0 rgba(33, 93, 176, 0), 0 0 0 0 rgba(33, 93, 176, 0), inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-editable-text.bp5-editable-text-editing::before{\n  background-color:#ffffff;\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-editable-text:not(.bp5-disabled)::before{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input::-moz-placeholder{\n  color:#2d72d2;\n}\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input:-ms-input-placeholder{\n  color:#2d72d2;\n}\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input::placeholder{\n  color:#2d72d2;\n}\n.bp5-editable-text.bp5-intent-primary:hover::before{\n  box-shadow:0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0), inset 0 0 0 1px rgba(45, 114, 210, 0.4);\n}\n.bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(45, 114, 210, 0.752), 0 0 0 1px rgba(45, 114, 210, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input::-moz-placeholder{\n  color:#238551;\n}\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input:-ms-input-placeholder{\n  color:#238551;\n}\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input::placeholder{\n  color:#238551;\n}\n.bp5-editable-text.bp5-intent-success:hover::before{\n  box-shadow:0 0 0 0 rgba(35, 133, 81, 0), 0 0 0 0 rgba(35, 133, 81, 0), inset 0 0 0 1px rgba(35, 133, 81, 0.4);\n}\n.bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(35, 133, 81, 0.752), 0 0 0 1px rgba(35, 133, 81, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input::-moz-placeholder{\n  color:#c87619;\n}\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input:-ms-input-placeholder{\n  color:#c87619;\n}\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input::placeholder{\n  color:#c87619;\n}\n.bp5-editable-text.bp5-intent-warning:hover::before{\n  box-shadow:0 0 0 0 rgba(200, 118, 25, 0), 0 0 0 0 rgba(200, 118, 25, 0), inset 0 0 0 1px rgba(200, 118, 25, 0.4);\n}\n.bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(200, 118, 25, 0.752), 0 0 0 1px rgba(200, 118, 25, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input::-moz-placeholder{\n  color:#cd4246;\n}\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input:-ms-input-placeholder{\n  color:#cd4246;\n}\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input::placeholder{\n  color:#cd4246;\n}\n.bp5-editable-text.bp5-intent-danger:hover::before{\n  box-shadow:0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0), inset 0 0 0 1px rgba(205, 66, 70, 0.4);\n}\n.bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(205, 66, 70, 0.752), 0 0 0 1px rgba(205, 66, 70, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-editable-text:hover::before{\n  box-shadow:0 0 0 0 rgba(138, 187, 255, 0), 0 0 0 0 rgba(138, 187, 255, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-editable-text.bp5-editable-text-editing::before{\n  background-color:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-editable-text.bp5-disabled::before{\n  box-shadow:none;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input::-moz-placeholder{\n  color:#8abbff;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input:-ms-input-placeholder{\n  color:#8abbff;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input,\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input::placeholder{\n  color:#8abbff;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary:hover::before{\n  box-shadow:0 0 0 0 rgba(138, 187, 255, 0), 0 0 0 0 rgba(138, 187, 255, 0), inset 0 0 0 1px rgba(138, 187, 255, 0.4);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-input::-moz-placeholder{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-input:-ms-input-placeholder{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-input,\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-input::placeholder{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success:hover::before{\n  box-shadow:0 0 0 0 rgba(114, 202, 155, 0), 0 0 0 0 rgba(114, 202, 155, 0), inset 0 0 0 1px rgba(114, 202, 155, 0.4);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(114, 202, 155, 0.752), 0 0 0 1px rgba(114, 202, 155, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input::-moz-placeholder{\n  color:#fbb360;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input:-ms-input-placeholder{\n  color:#fbb360;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input,\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input::placeholder{\n  color:#fbb360;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning:hover::before{\n  box-shadow:0 0 0 0 rgba(251, 179, 96, 0), 0 0 0 0 rgba(251, 179, 96, 0), inset 0 0 0 1px rgba(251, 179, 96, 0.4);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(251, 179, 96, 0.752), 0 0 0 1px rgba(251, 179, 96, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input::-moz-placeholder{\n  color:#fa999c;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input:-ms-input-placeholder{\n  color:#fa999c;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input,\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input::placeholder{\n  color:#fa999c;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger:hover::before{\n  box-shadow:0 0 0 0 rgba(250, 153, 156, 0), 0 0 0 0 rgba(250, 153, 156, 0), inset 0 0 0 1px rgba(250, 153, 156, 0.4);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(250, 153, 156, 0.752), 0 0 0 1px rgba(250, 153, 156, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-editable-text.bp5-disabled::before{\n  box-shadow:none !important;\n}\n\n.bp5-editable-text-input,\n.bp5-editable-text-content{\n  color:inherit;\n  display:inherit;\n  font:inherit;\n  letter-spacing:inherit;\n  max-width:inherit;\n  min-width:inherit;\n  position:relative;\n  resize:none;\n  text-transform:inherit;\n  vertical-align:top;\n}\n\n.bp5-editable-text-input{\n  background:none;\n  border:none;\n  box-shadow:none;\n  padding:0;\n  white-space:pre-wrap;\n  width:100%;\n}\n.bp5-editable-text-input::-moz-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-editable-text-input:-ms-input-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-editable-text-input::placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-editable-text-input:focus{\n  outline:none;\n}\n.bp5-editable-text-input::-ms-clear{\n  display:none;\n}\n\n.bp5-editable-text-content{\n  overflow:hidden;\n  padding-right:2px;\n  text-overflow:ellipsis;\n  white-space:pre;\n}\n.bp5-editable-text-editing > .bp5-editable-text-content{\n  left:0;\n  position:absolute;\n  visibility:hidden;\n}\n.bp5-editable-text-placeholder > .bp5-editable-text-content{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-editable-text-placeholder > .bp5-editable-text-content{\n  color:#abb3bf;\n}\n\n.bp5-editable-text.bp5-multiline{\n  display:block;\n}\n.bp5-editable-text.bp5-multiline .bp5-editable-text-content{\n  overflow:auto;\n  white-space:pre-wrap;\n  word-wrap:break-word;\n}\n\n.bp5-entity-title{\n  align-items:center;\n  display:flex;\n  gap:7px;\n  min-width:0;\n}\n.bp5-entity-title.bp5-fill{\n  width:100%;\n}\n.bp5-entity-title-icon-container.bp5-entity-title-has-subtitle{\n  align-self:flex-start;\n}\n.bp5-entity-title-icon-container:not(.bp5-entity-title-has-subtitle){\n  align-items:center;\n  display:flex;\n}\n.bp5-entity-title-text{\n  display:flex;\n  flex-direction:column;\n}\n.bp5-fill .bp5-entity-title-text{\n  flex-grow:1;\n}\n.bp5-entity-title-title-and-tags{\n  align-items:center;\n  display:flex;\n  flex-direction:row;\n  gap:5px;\n}\n.bp5-entity-title-tags-container{\n  display:flex;\n  gap:2px;\n  margin-left:5px;\n}\n.bp5-entity-title-title{\n  margin-bottom:0;\n  min-width:0;\n  overflow-wrap:break-word;\n}\n.bp5-fill .bp5-entity-title-title{\n  flex-grow:1;\n}\n.bp5-entity-title-subtitle{\n  font-size:12px;\n  margin-top:2px;\n}\n.bp5-entity-title-ellipsize, .bp5-entity-title-ellipsize .bp5-entity-title-text{\n  overflow:hidden;\n}\n.bp5-entity-title-heading-h1 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:40px;\n}\n.bp5-entity-title-heading-h2 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:32px;\n}\n.bp5-entity-title-heading-h3 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:25px;\n}\n.bp5-entity-title-heading-h4 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:21px;\n}\n.bp5-entity-title-heading-h5 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:19px;\n}\n.bp5-entity-title-heading-h6 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:16px;\n}\n.bp5-entity-title-heading-h1, .bp5-entity-title-heading-h2, .bp5-entity-title-heading-h3{\n  gap:15px;\n}\n.bp5-entity-title-heading-h1 .bp5-entity-title-status-tag, .bp5-entity-title-heading-h2 .bp5-entity-title-status-tag, .bp5-entity-title-heading-h3 .bp5-entity-title-status-tag{\n  margin-left:10px;\n}\n.bp5-entity-title-heading-h1 .bp5-entity-title-subtitle, .bp5-entity-title-heading-h2 .bp5-entity-title-subtitle, .bp5-entity-title-heading-h3 .bp5-entity-title-subtitle{\n  font-size:14px;\n}\n.bp5-entity-title-heading-h4, .bp5-entity-title-heading-h5, .bp5-entity-title-heading-h6{\n  gap:10px;\n}\n.bp5-entity-title-heading-h4 .bp5-entity-title-subtitle, .bp5-entity-title-heading-h5 .bp5-entity-title-subtitle, .bp5-entity-title-heading-h6 .bp5-entity-title-subtitle{\n  font-size:12px;\n}\n.bp5-control-group{\n  transform:translateZ(0);\n  display:flex;\n  flex-direction:row;\n  align-items:stretch;\n}\n.bp5-control-group > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-control-group > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-control-group .bp5-button,\n.bp5-control-group .bp5-html-select,\n.bp5-control-group .bp5-input,\n.bp5-control-group .bp5-select{\n  position:relative;\n}\n.bp5-control-group .bp5-input{\n  z-index:2;\n}\n.bp5-control-group .bp5-input:focus{\n  z-index:14;\n}\n.bp5-control-group .bp5-input[class*=bp5-intent]{\n  z-index:13;\n}\n.bp5-control-group .bp5-input[class*=bp5-intent]:focus{\n  z-index:15;\n}\n.bp5-control-group .bp5-input[readonly], .bp5-control-group .bp5-input:disabled, .bp5-control-group .bp5-input.bp5-disabled{\n  z-index:1;\n}\n.bp5-control-group .bp5-input-group[class*=bp5-intent] .bp5-input{\n  z-index:13;\n}\n.bp5-control-group .bp5-input-group[class*=bp5-intent] .bp5-input:focus{\n  z-index:15;\n}\n.bp5-control-group .bp5-button,\n.bp5-control-group .bp5-html-select select,\n.bp5-control-group .bp5-select select{\n  transform:translateZ(0);\n  z-index:4;\n}\n.bp5-control-group .bp5-button:focus,\n.bp5-control-group .bp5-html-select select:focus,\n.bp5-control-group .bp5-select select:focus{\n  z-index:5;\n}\n.bp5-control-group .bp5-button:hover,\n.bp5-control-group .bp5-html-select select:hover,\n.bp5-control-group .bp5-select select:hover{\n  z-index:6;\n}\n.bp5-control-group .bp5-button:active,\n.bp5-control-group .bp5-html-select select:active,\n.bp5-control-group .bp5-select select:active{\n  z-index:7;\n}\n.bp5-control-group .bp5-button[readonly], .bp5-control-group .bp5-button:disabled, .bp5-control-group .bp5-button.bp5-disabled,\n.bp5-control-group .bp5-html-select select[readonly],\n.bp5-control-group .bp5-html-select select:disabled,\n.bp5-control-group .bp5-html-select select.bp5-disabled,\n.bp5-control-group .bp5-select select[readonly],\n.bp5-control-group .bp5-select select:disabled,\n.bp5-control-group .bp5-select select.bp5-disabled{\n  z-index:3;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent],\n.bp5-control-group .bp5-html-select select[class*=bp5-intent],\n.bp5-control-group .bp5-select select[class*=bp5-intent]{\n  z-index:9;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent]:focus,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent]:focus,\n.bp5-control-group .bp5-select select[class*=bp5-intent]:focus{\n  z-index:10;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent]:hover,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent]:hover,\n.bp5-control-group .bp5-select select[class*=bp5-intent]:hover{\n  z-index:11;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent]:active,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent]:active,\n.bp5-control-group .bp5-select select[class*=bp5-intent]:active{\n  z-index:12;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent][readonly], .bp5-control-group .bp5-button[class*=bp5-intent]:disabled, .bp5-control-group .bp5-button[class*=bp5-intent].bp5-disabled,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent][readonly],\n.bp5-control-group .bp5-html-select select[class*=bp5-intent]:disabled,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent].bp5-disabled,\n.bp5-control-group .bp5-select select[class*=bp5-intent][readonly],\n.bp5-control-group .bp5-select select[class*=bp5-intent]:disabled,\n.bp5-control-group .bp5-select select[class*=bp5-intent].bp5-disabled{\n  z-index:8;\n}\n.bp5-control-group .bp5-input-group > .bp5-icon,\n.bp5-control-group .bp5-input-group > .bp5-button,\n.bp5-control-group .bp5-input-group > .bp5-input-left-container,\n.bp5-control-group .bp5-input-group > .bp5-input-action{\n  z-index:16;\n}\n.bp5-control-group .bp5-select::after,\n.bp5-control-group .bp5-html-select::after,\n.bp5-control-group .bp5-select > .bp5-icon,\n.bp5-control-group .bp5-html-select > .bp5-icon{\n  z-index:17;\n}\n.bp5-control-group .bp5-html-select:focus-within,\n.bp5-control-group .bp5-select:focus-within{\n  z-index:5;\n}\n.bp5-control-group:not(.bp5-vertical) > :not(:last-child){\n  margin-right:2px;\n}\n.bp5-control-group .bp5-numeric-input:not(:first-child) .bp5-input-group{\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-control-group.bp5-fill{\n  width:100%;\n}\n.bp5-control-group > .bp5-fill{\n  flex:1 1 auto;\n}\n.bp5-control-group.bp5-fill > *:not(.bp5-fixed){\n  flex:1 1 auto;\n}\n.bp5-control-group.bp5-vertical{\n  flex-direction:column;\n}\n.bp5-control-group.bp5-vertical > :not(:last-child){\n  margin-bottom:2px;\n}\n\n.bp5-control{\n  cursor:pointer;\n  display:block;\n  margin-bottom:10px;\n  position:relative;\n  text-transform:none;\n}\n.bp5-control input:checked ~ .bp5-control-indicator{\n  background-color:#2d72d2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control input:checked ~ .bp5-control-indicator{\n    background-color:highlight;\n    border:1px solid highlight;\n  }\n}\n.bp5-control:hover input:checked ~ .bp5-control-indicator{\n  background-color:#215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control:hover input:checked ~ .bp5-control-indicator{\n    background-color:highlight;\n  }\n}\n.bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n  background:#184a90;\n}\n.bp5-control input:disabled:checked ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n    background-color:graytext;\n    border-color:graytext;\n  }\n}\n.bp5-dark .bp5-control input:checked ~ .bp5-control-indicator{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-control input:checked ~ .bp5-control-indicator{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-dark .bp5-control:hover input:checked ~ .bp5-control-indicator{\n  background-color:#215db0;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark .bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n  background-color:#184a90;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n.bp5-control:not(.bp5-align-right){\n  padding-inline-start:26px;\n}\n.bp5-control:not(.bp5-align-right) .bp5-control-indicator{\n  margin-inline-start:-26px;\n}\n.bp5-control.bp5-align-right{\n  padding-right:26px;\n}\n.bp5-control.bp5-align-right .bp5-control-indicator{\n  margin-right:-26px;\n}\n[dir=rtl] .bp5-control.bp5-align-left{\n  padding-inline-start:0;\n  padding-left:26px;\n}\n[dir=rtl] .bp5-control.bp5-align-left .bp5-control-indicator{\n  margin-left:-26px;\n}\n.bp5-control.bp5-disabled{\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-control.bp5-inline{\n  display:inline-block;\n  margin-inline-end:20px;\n}\n.bp5-control input{\n  left:0;\n  opacity:0;\n  position:absolute;\n  top:0;\n  z-index:-1;\n}\n.bp5-control .bp5-control-indicator{\n  background-clip:padding-box;\n  background-color:transparent;\n  border:none;\n  box-shadow:inset 0 0 0 1px #738091;\n  cursor:pointer;\n  display:inline-block;\n  font-size:16px;\n  height:1em;\n  margin-inline-end:10px;\n  margin-top:-3px;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  vertical-align:middle;\n  width:1em;\n}\n.bp5-control .bp5-control-indicator::before{\n  content:\"\";\n  display:block;\n  height:1em;\n  width:1em;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control .bp5-control-indicator{\n    border:1px solid buttonborder;\n  }\n  .bp5-control .bp5-control-indicator::before{\n    margin-left:-1px;\n    margin-top:-1px;\n  }\n}\n.bp5-control:hover .bp5-control-indicator{\n  background-color:rgba(143, 153, 168, 0.15);\n}\n.bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:inset 0 0 0 1px #738091;\n}\n.bp5-control input:disabled ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  cursor:not-allowed;\n}\n.bp5-control input:focus ~ .bp5-control-indicator{\n  outline:rgba(33, 93, 176, 0.752) solid 2px;\n  outline-offset:2px;\n  -moz-outline-radius:6px;\n}\n.bp5-dark .bp5-control input:focus ~ .bp5-control-indicator{\n  outline-color:rgba(138, 187, 255, 0.752);\n}\n.bp5-control.bp5-align-right .bp5-control-indicator{\n  float:right;\n  margin-left:10px;\n  margin-top:1px;\n}\n[dir=rtl] .bp5-control.bp5-align-left .bp5-control-indicator{\n  float:left;\n  margin-top:1px;\n}\n.bp5-control.bp5-large{\n  font-size:16px;\n}\n.bp5-control.bp5-large:not(.bp5-align-right){\n  padding-inline-start:30px;\n}\n.bp5-control.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n  margin-inline-start:-30px;\n}\n.bp5-control.bp5-large.bp5-align-right{\n  padding-right:30px;\n}\n.bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n  margin-right:-30px;\n}\n[dir=rtl] .bp5-control.bp5-large.bp5-align-left{\n  padding-inline-start:0;\n  padding-left:30px;\n}\n[dir=rtl] .bp5-control.bp5-large.bp5-align-left .bp5-control-indicator{\n  margin-left:-30px;\n}\n.bp5-control.bp5-large .bp5-control-indicator{\n  font-size:20px;\n}\n.bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n  margin-top:0;\n}\n[dir=rtl] .bp5-control.bp5-large.bp5-align-left .bp5-control-indicator{\n  margin-top:0;\n}\n.bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n  background-color:#2d72d2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    background-color:highlight;\n    border:1px solid highlight;\n  }\n}\n.bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n  background-color:#215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n    background-color:highlight;\n  }\n}\n.bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n  background:#184a90;\n}\n.bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n    background-color:graytext;\n    border-color:graytext;\n  }\n}\n.bp5-dark .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-dark .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n  background-color:#215db0;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark .bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n  background-color:#184a90;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n.bp5-control.bp5-checkbox .bp5-control-indicator{\n  border-radius:2px;\n}\n.bp5-control.bp5-checkbox input:checked ~ .bp5-control-indicator::before{\n  background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 5c-.28 0-.53.11-.71.29L7 9.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29s.53-.11.71-.29l5-5A1.003 1.003 0 0012 5z' fill='white'/%3e%3c/svg%3e\");\n}\n.bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator::before{\n  background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 7H5c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1z' fill='white'/%3e%3c/svg%3e\");\n}\n.bp5-control.bp5-checkbox input:disabled ~ .bp5-control-indicator::before{\n  opacity:0.5;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-checkbox input:checked:not(:disabled) ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 5c-.28 0-.53.11-.71.29L7 9.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29s.53-.11.71-.29l5-5A1.003 1.003 0 0012 5z' fill='%23111418'/%3e%3c/svg%3e\");\n  }\n  .bp5-control.bp5-checkbox input:indeterminate:not(:disabled) ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 7H5c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1z' fill='%23111418'/%3e%3c/svg%3e\");\n  }\n  .bp5-control.bp5-checkbox input:disabled ~ .bp5-control-indicator{\n    border-color:graytext;\n  }\n}\n.bp5-control.bp5-radio .bp5-control-indicator{\n  border-radius:50%;\n}\n.bp5-control.bp5-radio input:checked ~ .bp5-control-indicator::before{\n  background-image:radial-gradient(#ffffff, #ffffff 28%, transparent 32%);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-radio input:checked ~ .bp5-control-indicator::before{\n    background:highlight;\n    height:12px;\n    margin-left:1px;\n    margin-top:1px;\n    width:12px;\n  }\n}\n.bp5-control.bp5-radio input:checked:disabled ~ .bp5-control-indicator::before{\n  opacity:0.5;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-radio input:checked:disabled ~ .bp5-control-indicator::before{\n    background:graytext;\n  }\n}\n.bp5-control.bp5-radio input:focus ~ .bp5-control-indicator{\n  -moz-outline-radius:16px;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-radio input:disabled ~ .bp5-control-indicator{\n    border-color:graytext;\n  }\n}\n.bp5-control.bp5-switch input ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.3);\n  color:#1c2127;\n}\n.bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.4);\n}\n.bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.5);\n}\n.bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.15);\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n  background:rgba(255, 255, 255, 0.8);\n  box-shadow:none;\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n  background:#2d72d2;\n  color:#ffffff;\n}\n.bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n  background:#215db0;\n}\n.bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n  background:#184a90;\n}\n.bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  color:rgba(255, 255, 255, 0.6);\n}\n.bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n  background:rgba(255, 255, 255, 0.5);\n  box-shadow:none;\n}\n.bp5-control.bp5-switch:not(.bp5-align-right){\n  padding-inline-start:38px;\n}\n.bp5-control.bp5-switch:not(.bp5-align-right) .bp5-control-indicator{\n  margin-inline-start:-38px;\n}\n.bp5-control.bp5-switch.bp5-align-right{\n  padding-right:38px;\n}\n.bp5-control.bp5-switch.bp5-align-right .bp5-control-indicator{\n  margin-right:-38px;\n}\n[dir=rtl] .bp5-control.bp5-switch.bp5-align-left{\n  padding-inline-start:0;\n  padding-left:38px;\n}\n[dir=rtl] .bp5-control.bp5-switch.bp5-align-left .bp5-control-indicator{\n  margin-left:-38px;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:highlight;\n    border:1px solid buttonborder;\n  }\n  .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background-color:graytext;\n  }\n  .bp5-control.bp5-switch input:not(:checked):disabled ~ .bp5-control-indicator{\n    border-color:graytext;\n  }\n  .bp5-control.bp5-switch input:not(:checked):disabled ~ .bp5-control-indicator::before{\n    border-color:graytext;\n  }\n  .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:highlight;\n  }\n}\n.bp5-control.bp5-switch .bp5-control-indicator{\n  border:none;\n  border-radius:1.75em;\n  box-shadow:none !important;\n  min-width:1.75em;\n  transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width:auto;\n}\n.bp5-control.bp5-switch .bp5-control-indicator::before{\n  background:#ffffff;\n  border-radius:50%;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.5);\n  height:calc(1em - 4px);\n  left:0;\n  margin:2px;\n  position:absolute;\n  transition:left 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width:calc(1em - 4px);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-switch .bp5-control-indicator::before{\n    border:1px solid buttonborder;\n    margin-top:1px;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-switch .bp5-control-indicator{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n  left:calc(100% - 1em);\n}\n.bp5-control.bp5-switch.bp5-large:not(.bp5-align-right){\n  padding-inline-start:45px;\n}\n.bp5-control.bp5-switch.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n  margin-inline-start:-45px;\n}\n.bp5-control.bp5-switch.bp5-large.bp5-align-right{\n  padding-right:45px;\n}\n.bp5-control.bp5-switch.bp5-large.bp5-align-right .bp5-control-indicator{\n  margin-right:-45px;\n}\n[dir=rtl] .bp5-control.bp5-switch.bp5-large.bp5-align-left{\n  padding-inline-start:0;\n  padding-left:45px;\n}\n[dir=rtl] .bp5-control.bp5-switch.bp5-large.bp5-align-left .bp5-control-indicator{\n  margin-left:-45px;\n}\n.bp5-dark .bp5-control.bp5-switch input ~ .bp5-control-indicator{\n  background:rgba(17, 20, 24, 0.5);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n  background:rgba(17, 20, 24, 0.8);\n}\n.bp5-dark .bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n  background:rgba(17, 20, 24, 0.9);\n}\n.bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.15);\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n  background:rgba(171, 179, 191, 0.5);\n  box-shadow:none;\n}\n.bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n  background:#2d72d2;\n  color:#ffffff;\n}\n.bp5-dark .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n  background:#215db0;\n}\n.bp5-dark .bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n  background:#184a90;\n}\n.bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n  background:rgba(255, 255, 255, 0.3);\n  box-shadow:none;\n}\n.bp5-dark .bp5-control.bp5-switch .bp5-control-indicator::before{\n  background:#abb3bf;\n}\n.bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n  background:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:highlight;\n    border:1px solid buttonborder;\n  }\n  .bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background-color:graytext;\n  }\n  .bp5-dark .bp5-control.bp5-switch input:not(:checked):disabled ~ .bp5-control-indicator{\n    border-color:graytext;\n  }\n  .bp5-dark .bp5-control.bp5-switch input:not(:checked):disabled ~ .bp5-control-indicator::before{\n    border-color:graytext;\n  }\n  .bp5-dark .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:highlight;\n  }\n}\n.bp5-control.bp5-switch .bp5-switch-inner-text{\n  font-size:0.7em;\n  text-align:center;\n}\n.bp5-control.bp5-switch .bp5-control-indicator-child:first-child{\n  line-height:0;\n  margin-left:0.5em;\n  margin-right:1.2em;\n  visibility:hidden;\n}\n.bp5-control.bp5-switch .bp5-control-indicator-child:last-child{\n  line-height:1em;\n  margin-left:1.2em;\n  margin-right:0.5em;\n  visibility:visible;\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:first-child{\n  line-height:1em;\n  visibility:visible;\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:last-child{\n  line-height:0;\n  visibility:hidden;\n}\n.bp5-dark .bp5-control{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-control.bp5-disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-control .bp5-control-indicator{\n  background-color:transparent;\n  box-shadow:inset 0 0 0 1px #8f99a8;\n}\n.bp5-dark .bp5-control:hover .bp5-control-indicator{\n  background-color:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:inset 0 0 0 1px #8f99a8;\n}\n.bp5-dark .bp5-control input:disabled ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-control.bp5-checkbox input:disabled:checked ~ .bp5-control-indicator, .bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n}\n.bp5-file-input{\n  cursor:pointer;\n  display:inline-block;\n  height:30px;\n  position:relative;\n}\n.bp5-file-input input{\n  margin:0;\n  min-width:200px;\n  opacity:0;\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n  background:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  resize:none;\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input::-moz-placeholder, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::-moz-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input:-ms-input-placeholder, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input:-ms-input-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input::placeholder, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input::after, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  outline:none;\n}\n.bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input, .bp5-dark .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n  background:rgba(64, 72, 84, 0.5);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input::after, .bp5-dark .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n  background-color:rgba(47, 52, 60, 0.15);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-file-input input:focus + .bp5-file-upload-input{\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-file-input input:focus + .bp5-file-upload-input{\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n  color:#1c2127;\n}\n.bp5-dark .bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n  color:#f6f7f9;\n}\n.bp5-file-input.bp5-fill{\n  width:100%;\n}\n.bp5-file-input.bp5-large, .bp5-large .bp5-file-input{\n  height:40px;\n}\n.bp5-file-input.bp5-small, .bp5-small .bp5-file-input{\n  height:24px;\n}\n.bp5-file-input .bp5-file-upload-input-custom-text::after{\n  content:attr(bp5-button-text);\n}\n\n.bp5-file-upload-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:2px;\n  box-shadow:0 0 0 0 rgba(33, 93, 176, 0), 0 0 0 0 rgba(33, 93, 176, 0), inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n  color:#1c2127;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  color:rgba(95, 107, 124, 0.6);\n  left:0;\n  padding-right:80px;\n  position:absolute;\n  right:0;\n  top:0;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-file-upload-input::-moz-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-file-upload-input:-ms-input-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-file-upload-input::placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-file-upload-input:focus, .bp5-file-upload-input.bp5-active{\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-file-upload-input[type=search], .bp5-file-upload-input.bp5-round{\n  border-radius:30px;\n  box-sizing:border-box;\n  padding-left:10px;\n}\n.bp5-file-upload-input[readonly]{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-file-upload-input:disabled, .bp5-file-upload-input.bp5-disabled{\n  background:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  resize:none;\n}\n.bp5-file-upload-input:disabled::-moz-placeholder, .bp5-file-upload-input.bp5-disabled::-moz-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-upload-input:disabled:-ms-input-placeholder, .bp5-file-upload-input.bp5-disabled:-ms-input-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-upload-input:disabled::placeholder, .bp5-file-upload-input.bp5-disabled::placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-file-upload-input{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-file-upload-input::after{\n  background-color:#f6f7f9;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#1c2127;\n  min-height:24px;\n  min-width:24px;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  border-radius:2px;\n  content:\"Browse\";\n  line-height:24px;\n  margin:3px;\n  position:absolute;\n  right:0;\n  text-align:center;\n  top:0;\n  width:70px;\n}\n.bp5-file-upload-input:hover::after{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-file-upload-input:active::after{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-file-upload-input:active::after{\n    background:highlight;\n  }\n}\n.bp5-large .bp5-file-upload-input{\n  font-size:16px;\n  height:40px;\n  line-height:40px;\n  padding-right:95px;\n}\n.bp5-large .bp5-file-upload-input[type=search], .bp5-large .bp5-file-upload-input.bp5-round{\n  padding:0 15px;\n}\n.bp5-large .bp5-file-upload-input::after{\n  min-height:30px;\n  min-width:30px;\n  line-height:30px;\n  margin:5px;\n  width:85px;\n}\n.bp5-small .bp5-file-upload-input{\n  font-size:12px;\n  height:24px;\n  line-height:24px;\n  padding-left:8px;\n  padding-right:8px;\n  padding-right:65px;\n}\n.bp5-small .bp5-file-upload-input[type=search], .bp5-small .bp5-file-upload-input.bp5-round{\n  padding:0 12px;\n}\n.bp5-small .bp5-file-upload-input::after{\n  min-height:20px;\n  min-width:20px;\n  line-height:20px;\n  margin:2px;\n  width:55px;\n}\n.bp5-dark .bp5-file-upload-input{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:0 0 0 0 rgba(138, 187, 255, 0), 0 0 0 0 rgba(138, 187, 255, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n  color:#f6f7f9;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-file-upload-input::-moz-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-file-upload-input:-ms-input-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-file-upload-input::placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-file-upload-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752);\n}\n.bp5-dark .bp5-file-upload-input[readonly]{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-file-upload-input:disabled, .bp5-dark .bp5-file-upload-input.bp5-disabled{\n  background:rgba(64, 72, 84, 0.5);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-file-upload-input::after{\n  background-color:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-file-upload-input:hover::after{\n  background-color:#252a31;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-file-upload-input:active::after{\n  background-color:#1c2127;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-file-upload-input::after{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n}\n.bp5-form-group{\n  display:flex;\n  flex-direction:column;\n  margin:0 0 15px;\n}\n.bp5-form-group label.bp5-label{\n  margin-bottom:5px;\n}\n.bp5-form-group .bp5-control{\n  margin-top:7px;\n}\n.bp5-form-group .bp5-form-group-sub-label,\n.bp5-form-group .bp5-form-helper-text{\n  color:#5f6b7c;\n  font-size:12px;\n}\n.bp5-form-group .bp5-form-group-sub-label{\n  margin-bottom:5px;\n}\n.bp5-form-group .bp5-form-helper-text{\n  margin-top:5px;\n}\n.bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n  color:#215db0;\n}\n.bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n  color:#1c6e42;\n}\n.bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n  color:#935610;\n}\n.bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n  color:#ac2f33;\n}\n.bp5-form-group.bp5-fill{\n  width:100%;\n}\n.bp5-form-group.bp5-inline{\n  align-items:flex-start;\n  flex-direction:row;\n}\n.bp5-form-group.bp5-inline.bp5-large label.bp5-label{\n  line-height:40px;\n  margin:0 10px 0 0;\n}\n.bp5-form-group.bp5-inline label.bp5-label{\n  line-height:30px;\n  margin:0 10px 0 0;\n}\n.bp5-form-group.bp5-disabled .bp5-label,\n.bp5-form-group.bp5-disabled .bp5-text-muted,\n.bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n.bp5-form-group.bp5-disabled .bp5-form-helper-text{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-dark .bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n  color:#8abbff;\n}\n.bp5-dark .bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n  color:#fbb360;\n}\n.bp5-dark .bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n  color:#fa999c;\n}\n.bp5-dark .bp5-form-group .bp5-form-group-sub-label,\n.bp5-dark .bp5-form-group .bp5-form-helper-text{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-label,\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-text-muted,\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-form-helper-text{\n  color:rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-input-group{\n  display:block;\n  position:relative;\n}\n.bp5-input-group .bp5-input{\n  position:relative;\n  width:100%;\n}\n.bp5-input-group .bp5-input:not(:first-child){\n  padding-left:30px;\n}\n.bp5-input-group .bp5-input:not(:last-child){\n  padding-right:30px;\n}\n.bp5-input-group .bp5-input-action,\n.bp5-input-group > .bp5-input-left-container,\n.bp5-input-group > .bp5-button,\n.bp5-input-group > .bp5-icon{\n  position:absolute;\n  top:0;\n}\n.bp5-input-group .bp5-input-action:first-child,\n.bp5-input-group > .bp5-input-left-container:first-child,\n.bp5-input-group > .bp5-button:first-child,\n.bp5-input-group > .bp5-icon:first-child{\n  left:0;\n}\n.bp5-input-group .bp5-input-action:last-child,\n.bp5-input-group > .bp5-input-left-container:last-child,\n.bp5-input-group > .bp5-button:last-child,\n.bp5-input-group > .bp5-icon:last-child{\n  right:0;\n}\n.bp5-input-group .bp5-button{\n  min-height:24px;\n  min-width:24px;\n  margin:3px;\n  padding:0 7px;\n}\n.bp5-input-group .bp5-button:empty{\n  padding:0;\n}\n.bp5-input-group > .bp5-input-left-container,\n.bp5-input-group > .bp5-icon{\n  z-index:1;\n}\n.bp5-input-group > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group > .bp5-icon{\n  color:#5f6b7c;\n}\n.bp5-input-group > .bp5-input-left-container > .bp5-icon:empty,\n.bp5-input-group > .bp5-icon:empty{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n}\n.bp5-input-group > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group > .bp5-icon,\n.bp5-input-group .bp5-input-action > .bp5-spinner{\n  margin:7px;\n}\n.bp5-input-group .bp5-tag{\n  margin:5px;\n}\n.bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus),\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus),\n.bp5-dark .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n  color:#abb3bf;\n}\n.bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large{\n  color:#5f6b7c;\n}\n.bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-large,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-standard,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-large{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-input-group.bp5-disabled{\n  cursor:not-allowed;\n}\n.bp5-input-group.bp5-disabled .bp5-icon{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-input-group.bp5-large .bp5-button{\n  min-height:30px;\n  min-width:30px;\n  margin:5px;\n}\n.bp5-input-group.bp5-large > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group.bp5-large > .bp5-icon,\n.bp5-input-group.bp5-large .bp5-input-action > .bp5-spinner{\n  margin:12px;\n}\n.bp5-input-group.bp5-large .bp5-input{\n  font-size:16px;\n  height:40px;\n  line-height:40px;\n}\n.bp5-input-group.bp5-large .bp5-input[type=search], .bp5-input-group.bp5-large .bp5-input.bp5-round{\n  padding:0 15px;\n}\n.bp5-input-group.bp5-large .bp5-input:not(:first-child){\n  padding-left:40px;\n}\n.bp5-input-group.bp5-large .bp5-input:not(:last-child){\n  padding-right:40px;\n}\n.bp5-input-group.bp5-small .bp5-button{\n  min-height:20px;\n  min-width:20px;\n  margin:2px;\n}\n.bp5-input-group.bp5-small .bp5-tag{\n  min-height:20px;\n  min-width:20px;\n  margin:2px;\n}\n.bp5-input-group.bp5-small > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group.bp5-small > .bp5-icon,\n.bp5-input-group.bp5-small .bp5-input-action > .bp5-spinner{\n  margin:4px;\n}\n.bp5-input-group.bp5-small .bp5-input{\n  font-size:12px;\n  height:24px;\n  line-height:24px;\n  padding-left:8px;\n  padding-right:8px;\n}\n.bp5-input-group.bp5-small .bp5-input[type=search], .bp5-input-group.bp5-small .bp5-input.bp5-round{\n  padding:0 12px;\n}\n.bp5-input-group.bp5-small .bp5-input:not(:first-child){\n  padding-left:24px;\n}\n.bp5-input-group.bp5-small .bp5-input:not(:last-child){\n  padding-right:24px;\n}\n.bp5-input-group.bp5-fill{\n  flex:1 1 auto;\n  width:100%;\n}\n.bp5-input-group.bp5-round .bp5-button,\n.bp5-input-group.bp5-round .bp5-input,\n.bp5-input-group.bp5-round .bp5-tag{\n  border-radius:30px;\n}\n.bp5-dark .bp5-input-group .bp5-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-input-group.bp5-disabled .bp5-icon{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-input-group.bp5-intent-primary .bp5-input{\n  box-shadow:0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0), inset 0 0 0 1px #2d72d2, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-primary .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(45, 114, 210, 0.752), 0 0 0 2px rgba(45, 114, 210, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-primary .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #2d72d2;\n}\n.bp5-input-group.bp5-intent-primary .bp5-input:disabled, .bp5-input-group.bp5-intent-primary .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input{\n  box-shadow:0 0 0 0 rgba(76, 144, 240, 0), 0 0 0 0 rgba(76, 144, 240, 0), inset 0 0 0 1px #4c90f0, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(76, 144, 240, 0.752), 0 0 0 2px rgba(76, 144, 240, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #4c90f0;\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input:disabled, .bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input-group.bp5-intent-primary > .bp5-icon{\n  color:#215db0;\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary > .bp5-icon{\n  color:#8abbff;\n}\n.bp5-input-group.bp5-intent-success .bp5-input{\n  box-shadow:0 0 0 0 rgba(35, 133, 81, 0), 0 0 0 0 rgba(35, 133, 81, 0), inset 0 0 0 1px #238551, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-success .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(35, 133, 81, 0.752), 0 0 0 2px rgba(35, 133, 81, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-success .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #238551;\n}\n.bp5-input-group.bp5-intent-success .bp5-input:disabled, .bp5-input-group.bp5-intent-success .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input{\n  box-shadow:0 0 0 0 rgba(50, 164, 103, 0), 0 0 0 0 rgba(50, 164, 103, 0), inset 0 0 0 1px #32a467, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(50, 164, 103, 0.752), 0 0 0 2px rgba(50, 164, 103, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #32a467;\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input:disabled, .bp5-dark .bp5-input-group.bp5-intent-success .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input-group.bp5-intent-success > .bp5-icon{\n  color:#1c6e42;\n}\n.bp5-dark .bp5-input-group.bp5-intent-success > .bp5-icon{\n  color:#72ca9b;\n}\n.bp5-input-group.bp5-intent-warning .bp5-input{\n  box-shadow:0 0 0 0 rgba(200, 118, 25, 0), 0 0 0 0 rgba(200, 118, 25, 0), inset 0 0 0 1px #c87619, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-warning .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(200, 118, 25, 0.752), 0 0 0 2px rgba(200, 118, 25, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-warning .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #c87619;\n}\n.bp5-input-group.bp5-intent-warning .bp5-input:disabled, .bp5-input-group.bp5-intent-warning .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input{\n  box-shadow:0 0 0 0 rgba(236, 154, 60, 0), 0 0 0 0 rgba(236, 154, 60, 0), inset 0 0 0 1px #ec9a3c, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(236, 154, 60, 0.752), 0 0 0 2px rgba(236, 154, 60, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #ec9a3c;\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input:disabled, .bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input-group.bp5-intent-warning > .bp5-icon{\n  color:#935610;\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning > .bp5-icon{\n  color:#fbb360;\n}\n.bp5-input-group.bp5-intent-danger .bp5-input{\n  box-shadow:0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0), inset 0 0 0 1px #cd4246, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-danger .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(205, 66, 70, 0.752), 0 0 0 2px rgba(205, 66, 70, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-danger .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #cd4246;\n}\n.bp5-input-group.bp5-intent-danger .bp5-input:disabled, .bp5-input-group.bp5-intent-danger .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input{\n  box-shadow:0 0 0 0 rgba(231, 106, 110, 0), 0 0 0 0 rgba(231, 106, 110, 0), inset 0 0 0 1px #e76a6e, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(231, 106, 110, 0.752), 0 0 0 2px rgba(231, 106, 110, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #e76a6e;\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input:disabled, .bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input-group.bp5-intent-danger > .bp5-icon{\n  color:#ac2f33;\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger > .bp5-icon{\n  color:#fa999c;\n}\n.bp5-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:2px;\n  box-shadow:0 0 0 0 rgba(33, 93, 176, 0), 0 0 0 0 rgba(33, 93, 176, 0), inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n  color:#1c2127;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle;\n}\n.bp5-input::-moz-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input:-ms-input-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input::placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input:focus, .bp5-input.bp5-active{\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input[type=search], .bp5-input.bp5-round{\n  border-radius:30px;\n  box-sizing:border-box;\n  padding-left:10px;\n}\n.bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-input:disabled, .bp5-input.bp5-disabled{\n  background:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  resize:none;\n}\n.bp5-input:disabled::-moz-placeholder, .bp5-input.bp5-disabled::-moz-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-input:disabled:-ms-input-placeholder, .bp5-input.bp5-disabled:-ms-input-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-input:disabled::placeholder, .bp5-input.bp5-disabled::placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-input{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-input.bp5-large{\n  font-size:16px;\n  height:40px;\n  line-height:40px;\n}\n.bp5-input.bp5-large[type=search], .bp5-input.bp5-large.bp5-round{\n  padding:0 15px;\n}\n.bp5-input.bp5-small{\n  font-size:12px;\n  height:24px;\n  line-height:24px;\n  padding-left:8px;\n  padding-right:8px;\n}\n.bp5-input.bp5-small[type=search], .bp5-input.bp5-small.bp5-round{\n  padding:0 12px;\n}\n.bp5-input.bp5-fill{\n  flex:1 1 auto;\n  width:100%;\n}\n.bp5-dark .bp5-input{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:0 0 0 0 rgba(138, 187, 255, 0), 0 0 0 0 rgba(138, 187, 255, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-input::-moz-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-input:-ms-input-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-input::placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752);\n}\n.bp5-dark .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-input:disabled, .bp5-dark .bp5-input.bp5-disabled{\n  background:rgba(64, 72, 84, 0.5);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-input.bp5-intent-primary{\n  box-shadow:0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0), inset 0 0 0 1px #2d72d2, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-primary:focus{\n  box-shadow:inset 0 0 0 1px rgba(45, 114, 210, 0.752), 0 0 0 2px rgba(45, 114, 210, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-primary[readonly]{\n  box-shadow:inset 0 0 0 1px #2d72d2;\n}\n.bp5-input.bp5-intent-primary:disabled, .bp5-input.bp5-intent-primary.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input.bp5-intent-primary{\n  box-shadow:0 0 0 0 rgba(76, 144, 240, 0), 0 0 0 0 rgba(76, 144, 240, 0), inset 0 0 0 1px #4c90f0, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-primary:focus{\n  box-shadow:inset 0 0 0 1px rgba(76, 144, 240, 0.752), 0 0 0 2px rgba(76, 144, 240, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-primary[readonly]{\n  box-shadow:inset 0 0 0 1px #4c90f0;\n}\n.bp5-dark .bp5-input.bp5-intent-primary:disabled, .bp5-dark .bp5-input.bp5-intent-primary.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input.bp5-intent-success{\n  box-shadow:0 0 0 0 rgba(35, 133, 81, 0), 0 0 0 0 rgba(35, 133, 81, 0), inset 0 0 0 1px #238551, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-success:focus{\n  box-shadow:inset 0 0 0 1px rgba(35, 133, 81, 0.752), 0 0 0 2px rgba(35, 133, 81, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-success[readonly]{\n  box-shadow:inset 0 0 0 1px #238551;\n}\n.bp5-input.bp5-intent-success:disabled, .bp5-input.bp5-intent-success.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input.bp5-intent-success{\n  box-shadow:0 0 0 0 rgba(50, 164, 103, 0), 0 0 0 0 rgba(50, 164, 103, 0), inset 0 0 0 1px #32a467, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-success:focus{\n  box-shadow:inset 0 0 0 1px rgba(50, 164, 103, 0.752), 0 0 0 2px rgba(50, 164, 103, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-success[readonly]{\n  box-shadow:inset 0 0 0 1px #32a467;\n}\n.bp5-dark .bp5-input.bp5-intent-success:disabled, .bp5-dark .bp5-input.bp5-intent-success.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input.bp5-intent-warning{\n  box-shadow:0 0 0 0 rgba(200, 118, 25, 0), 0 0 0 0 rgba(200, 118, 25, 0), inset 0 0 0 1px #c87619, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-warning:focus{\n  box-shadow:inset 0 0 0 1px rgba(200, 118, 25, 0.752), 0 0 0 2px rgba(200, 118, 25, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-warning[readonly]{\n  box-shadow:inset 0 0 0 1px #c87619;\n}\n.bp5-input.bp5-intent-warning:disabled, .bp5-input.bp5-intent-warning.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input.bp5-intent-warning{\n  box-shadow:0 0 0 0 rgba(236, 154, 60, 0), 0 0 0 0 rgba(236, 154, 60, 0), inset 0 0 0 1px #ec9a3c, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-warning:focus{\n  box-shadow:inset 0 0 0 1px rgba(236, 154, 60, 0.752), 0 0 0 2px rgba(236, 154, 60, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-warning[readonly]{\n  box-shadow:inset 0 0 0 1px #ec9a3c;\n}\n.bp5-dark .bp5-input.bp5-intent-warning:disabled, .bp5-dark .bp5-input.bp5-intent-warning.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input.bp5-intent-danger{\n  box-shadow:0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0), inset 0 0 0 1px #cd4246, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-danger:focus{\n  box-shadow:inset 0 0 0 1px rgba(205, 66, 70, 0.752), 0 0 0 2px rgba(205, 66, 70, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-danger[readonly]{\n  box-shadow:inset 0 0 0 1px #cd4246;\n}\n.bp5-input.bp5-intent-danger:disabled, .bp5-input.bp5-intent-danger.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input.bp5-intent-danger{\n  box-shadow:0 0 0 0 rgba(231, 106, 110, 0), 0 0 0 0 rgba(231, 106, 110, 0), inset 0 0 0 1px #e76a6e, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-danger:focus{\n  box-shadow:inset 0 0 0 1px rgba(231, 106, 110, 0.752), 0 0 0 2px rgba(231, 106, 110, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-danger[readonly]{\n  box-shadow:inset 0 0 0 1px #e76a6e;\n}\n.bp5-dark .bp5-input.bp5-intent-danger:disabled, .bp5-dark .bp5-input.bp5-intent-danger.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input::-ms-clear{\n  display:none;\n}\n\n@supports (-webkit-touch-callout: none){\n  input.bp5-input:disabled, input.bp5-input.bp5-disabled{\n    opacity:1;\n    -webkit-text-fill-color:rgba(95, 107, 124, 0.6);\n  }\n  .bp5-dark input.bp5-input:disabled, .bp5-dark input.bp5-input.bp5-disabled{\n    -webkit-text-fill-color:rgba(171, 179, 191, 0.6);\n  }\n}\ntextarea.bp5-input{\n  max-width:100%;\n  padding:10px;\n}\ntextarea.bp5-input, textarea.bp5-input.bp5-large, textarea.bp5-input.bp5-small{\n  height:auto;\n  line-height:inherit;\n}\ntextarea.bp5-input.bp5-small{\n  padding:8px;\n}\n\n.bp5-text-area.bp5-text-area-auto-resize{\n  resize:horizontal;\n}\nlabel.bp5-label{\n  display:block;\n  margin-bottom:15px;\n  margin-top:0;\n}\nlabel.bp5-label .bp5-html-select,\nlabel.bp5-label .bp5-input,\nlabel.bp5-label .bp5-select,\nlabel.bp5-label .bp5-slider,\nlabel.bp5-label .bp5-popover-wrapper{\n  display:block;\n  margin-top:5px;\n  text-transform:none;\n}\nlabel.bp5-label .bp5-button-group{\n  margin-top:5px;\n}\nlabel.bp5-label .bp5-select select,\nlabel.bp5-label .bp5-html-select select{\n  font-weight:400;\n  vertical-align:top;\n  width:100%;\n}\nlabel.bp5-label .bp5-control-group{\n  margin-top:5px;\n}\nlabel.bp5-label .bp5-control-group > .bp5-button-group,\nlabel.bp5-label .bp5-control-group > .bp5-html-select,\nlabel.bp5-label .bp5-control-group > .bp5-input,\nlabel.bp5-label .bp5-control-group > .bp5-select,\nlabel.bp5-label .bp5-control-group > .bp5-slider,\nlabel.bp5-label .bp5-control-group > .bp5-popover-wrapper{\n  margin-top:0;\n}\nlabel.bp5-label.bp5-disabled,\nlabel.bp5-label.bp5-disabled .bp5-text-muted{\n  color:rgba(95, 107, 124, 0.6);\n}\nlabel.bp5-label.bp5-inline{\n  line-height:30px;\n}\nlabel.bp5-label.bp5-inline .bp5-html-select,\nlabel.bp5-label.bp5-inline .bp5-input,\nlabel.bp5-label.bp5-inline .bp5-input-group,\nlabel.bp5-label.bp5-inline .bp5-select,\nlabel.bp5-label.bp5-inline .bp5-popover-wrapper{\n  display:inline-block;\n  margin:0 0 0 5px;\n  vertical-align:top;\n}\nlabel.bp5-label.bp5-inline .bp5-button-group{\n  margin:0 0 0 5px;\n}\nlabel.bp5-label.bp5-inline .bp5-input-group .bp5-input{\n  margin-left:0;\n}\nlabel.bp5-label.bp5-inline.bp5-large{\n  line-height:40px;\n}\nlabel.bp5-label.bp5-inline .bp5-control-group{\n  margin:0 0 0 5px;\n}\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-button-group,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-html-select,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-input,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-select,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-slider,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-popover-wrapper{\n  margin:0;\n}\nlabel.bp5-label:not(.bp5-inline) .bp5-popover-target{\n  display:block;\n}\n.bp5-dark label.bp5-label{\n  color:#f6f7f9;\n}\n.bp5-dark label.bp5-label.bp5-disabled,\n.bp5-dark label.bp5-label.bp5-disabled .bp5-text-muted{\n  color:rgba(171, 179, 191, 0.6);\n}\n\n.bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button{\n  flex:1 1 11px;\n  min-height:0;\n  padding:0;\n  width:24px;\n}\n.bp5-numeric-input.bp5-large .bp5-button-group.bp5-vertical > .bp5-button{\n  width:40px;\n}\n.bp5-numeric-input.bp5-small .bp5-button-group.bp5-vertical > .bp5-button{\n  width:24px;\n}\n\nform{\n  display:block;\n}\n\n.bp5-html-select select,\n.bp5-select select{\n  display:inline-flex;\n  flex-direction:row;\n  align-items:center;\n  border:none;\n  border-radius:2px;\n  cursor:pointer;\n  font-size:14px;\n  justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  background-color:#f6f7f9;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#1c2127;\n  -moz-appearance:none;\n  -webkit-appearance:none;\n  border-radius:2px;\n  height:30px;\n  padding:0 30px 0 10px;\n  width:100%;\n}\n.bp5-html-select select > *,\n.bp5-select select > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-html-select select > .bp5-fill,\n.bp5-select select > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-html-select select::before,\n.bp5-select select::before,\n.bp5-html-select select > *,\n.bp5-select select > *{\n  margin-right:7px;\n}\n.bp5-html-select select:empty::before,\n.bp5-select select:empty::before,\n.bp5-html-select select > :last-child,\n.bp5-select select > :last-child{\n  margin-right:0;\n}\n.bp5-html-select select:hover,\n.bp5-select select:hover{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-html-select select:active,\n.bp5-select select:active, .bp5-html-select select.bp5-active,\n.bp5-select select.bp5-active{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-html-select select:active,\n  .bp5-select select:active, .bp5-html-select select.bp5-active,\n  .bp5-select select.bp5-active{\n    background:highlight;\n  }\n}\n.bp5-html-select select:disabled,\n.bp5-select select:disabled, .bp5-html-select select.bp5-disabled,\n.bp5-select select.bp5-disabled{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  outline:none;\n}\n.bp5-html-select select:disabled.bp5-active,\n.bp5-select select:disabled.bp5-active, .bp5-html-select select.bp5-disabled.bp5-active,\n.bp5-select select.bp5-disabled.bp5-active{\n  background:rgba(211, 216, 222, 0.7);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-html-select select,\n  .bp5-select select{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-html-select.bp5-minimal select,\n.bp5-select.bp5-minimal select{\n  background:none;\n  box-shadow:none;\n}\n.bp5-html-select.bp5-minimal select:hover,\n.bp5-select.bp5-minimal select:hover{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  color:#1c2127;\n  text-decoration:none;\n}\n.bp5-html-select.bp5-minimal select:active,\n.bp5-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal select.bp5-active,\n.bp5-select.bp5-minimal select.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:none;\n  color:#1c2127;\n}\n.bp5-html-select.bp5-minimal select:disabled,\n.bp5-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal select.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-disabled{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-html-select.bp5-minimal select:disabled.bp5-active,\n.bp5-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-disabled.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select, .bp5-html-select.bp5-minimal .bp5-dark select,\n.bp5-dark .bp5-select.bp5-minimal select,\n.bp5-select.bp5-minimal .bp5-dark select{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n.bp5-dark .bp5-select.bp5-minimal select:hover,\n.bp5-select.bp5-minimal .bp5-dark select:hover, .bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n.bp5-dark .bp5-select.bp5-minimal select:active,\n.bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n.bp5-dark .bp5-select.bp5-minimal select:hover,\n.bp5-select.bp5-minimal .bp5-dark select:hover{\n  background:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n.bp5-dark .bp5-select.bp5-minimal select:active,\n.bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal .bp5-dark select:disabled,\n.bp5-dark .bp5-select.bp5-minimal select:disabled,\n.bp5-select.bp5-minimal .bp5-dark select:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-disabled{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary,\n.bp5-select.bp5-minimal select.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#215db0;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-select.bp5-minimal select.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.15);\n  color:#215db0;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(33, 93, 176, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head,\n.bp5-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n  stroke:#215db0;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success,\n.bp5-select.bp5-minimal select.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#1c6e42;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-select.bp5-minimal select.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.15);\n  color:#1c6e42;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(28, 110, 66, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head,\n.bp5-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n  stroke:#1c6e42;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning,\n.bp5-select.bp5-minimal select.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#935610;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-select.bp5-minimal select.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.15);\n  color:#935610;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(147, 86, 16, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head,\n.bp5-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n  stroke:#935610;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger,\n.bp5-select.bp5-minimal select.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ac2f33;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-select.bp5-minimal select.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.15);\n  color:#ac2f33;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(172, 47, 51, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head,\n.bp5-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ac2f33;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n\n.bp5-html-select.bp5-large select,\n.bp5-select.bp5-large select{\n  font-size:16px;\n  height:40px;\n  padding-right:35px;\n}\n\n.bp5-dark .bp5-html-select select,\n.bp5-dark .bp5-select select{\n  background-color:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-html-select select:hover,\n.bp5-dark .bp5-select select:hover, .bp5-dark .bp5-html-select select:active,\n.bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active,\n.bp5-dark .bp5-select select.bp5-active{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-html-select select:hover,\n.bp5-dark .bp5-select select:hover{\n  background-color:#252a31;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-html-select select:active,\n.bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active,\n.bp5-dark .bp5-select select.bp5-active{\n  background-color:#1c2127;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-html-select select:disabled,\n.bp5-dark .bp5-select select:disabled, .bp5-dark .bp5-html-select select.bp5-disabled,\n.bp5-dark .bp5-select select.bp5-disabled{\n  background-color:rgba(47, 52, 60, 0.15);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-html-select select:disabled.bp5-active,\n.bp5-dark .bp5-select select:disabled.bp5-active, .bp5-dark .bp5-html-select select.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select select.bp5-disabled.bp5-active{\n  background:rgba(47, 52, 60, 0.7);\n}\n.bp5-dark .bp5-html-select select .bp5-button-spinner .bp5-spinner-head,\n.bp5-dark .bp5-select select .bp5-button-spinner .bp5-spinner-head{\n  background:rgba(17, 20, 24, 0.5);\n  stroke:#8f99a8;\n}\n\n.bp5-html-select select:disabled,\n.bp5-select select:disabled{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n\n.bp5-html-select .bp5-icon,\n.bp5-select .bp5-icon, .bp5-select::after{\n  color:#5f6b7c;\n  pointer-events:none;\n  position:absolute;\n  right:10px;\n  top:7px;\n}\n.bp5-html-select .bp5-disabled.bp5-icon,\n.bp5-select .bp5-disabled.bp5-icon, .bp5-disabled.bp5-select::after{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-html-select,\n.bp5-select{\n  display:inline-block;\n  letter-spacing:normal;\n  position:relative;\n  vertical-align:middle;\n}\n.bp5-html-select .bp5-icon,\n.bp5-select .bp5-icon{\n  color:#5f6b7c;\n}\n.bp5-html-select .bp5-icon:hover,\n.bp5-select .bp5-icon:hover{\n  color:#1c2127;\n}\n.bp5-dark .bp5-html-select .bp5-icon,\n.bp5-dark .bp5-select .bp5-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-html-select .bp5-icon:hover,\n.bp5-dark .bp5-select .bp5-icon:hover{\n  color:#f6f7f9;\n}\n.bp5-html-select.bp5-large::after,\n.bp5-html-select.bp5-large .bp5-icon,\n.bp5-select.bp5-large::after,\n.bp5-select.bp5-large .bp5-icon{\n  right:12px;\n  top:12px;\n}\n.bp5-html-select.bp5-fill,\n.bp5-html-select.bp5-fill select,\n.bp5-select.bp5-fill,\n.bp5-select.bp5-fill select{\n  width:100%;\n}\n.bp5-dark .bp5-html-select option,\n.bp5-dark .bp5-select option{\n  background-color:#2f343c;\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-html-select option:disabled,\n.bp5-dark .bp5-select option:disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-html-select::after,\n.bp5-dark .bp5-select::after{\n  color:#abb3bf;\n}\n\n.bp5-select::after{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  content:\"\\f184\";\n}\n\n.bp5-key-combo{\n  align-items:center;\n}\n.bp5-key-combo:not(.bp5-minimal){\n  display:flex;\n  flex-direction:row;\n}\n.bp5-key-combo:not(.bp5-minimal) > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-key-combo:not(.bp5-minimal) > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-key-combo:not(.bp5-minimal)::before,\n.bp5-key-combo:not(.bp5-minimal) > *{\n  margin-right:5px;\n}\n.bp5-key-combo:not(.bp5-minimal):empty::before,\n.bp5-key-combo:not(.bp5-minimal) > :last-child{\n  margin-right:0;\n}\n.bp5-key-combo.bp5-minimal{\n  display:flex;\n  flex-direction:row;\n}\n.bp5-key-combo.bp5-minimal > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-key-combo.bp5-minimal > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n\n.bp5-hotkey-dialog{\n  padding-bottom:0;\n  top:40px;\n}\n.bp5-hotkey-dialog .bp5-dialog-body{\n  margin:0;\n  padding:0;\n}\n.bp5-hotkey-dialog .bp5-hotkey-label{\n  flex-grow:1;\n}\n\n.bp5-hotkey-column{\n  margin:auto;\n  padding:30px;\n}\n.bp5-hotkey-column .bp5-heading{\n  margin-bottom:20px;\n}\n.bp5-hotkey-column .bp5-heading:not(:first-child){\n  margin-top:40px;\n}\n\n.bp5-hotkey{\n  align-items:center;\n  display:flex;\n  justify-content:space-between;\n  margin-left:0;\n  margin-right:0;\n}\n.bp5-hotkey:not(:last-child){\n  margin-bottom:10px;\n}\n\n.bp5-submenu > .bp5-popover-wrapper{\n  display:block;\n}\n.bp5-submenu .bp5-popover-target{\n  display:block;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item:not([class*=bp5-intent-]){\n  background-color:rgba(143, 153, 168, 0.15);\n  color:inherit;\n  cursor:pointer;\n  text-decoration:none;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item:not([class*=bp5-intent-]){\n  color:inherit;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item:not([class*=bp5-intent-]) .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item:not([class*=bp5-intent-]) .bp5-submenu-icon{\n  color:#abb3bf;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-], .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active{\n  background-color:rgba(45, 114, 210, 0.1);\n  color:#215db0;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-label, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-label, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-selected-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-selected-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-selected-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-submenu-icon{\n  color:#215db0;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.1);\n  color:#1c6e42;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.1);\n  color:#935610;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.1);\n  color:#ac2f33;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger .bp5-submenu-icon{\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-], .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active{\n    background-color:highlight;\n  }\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-], .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-submenu-icon{\n  color:#8abbff;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger .bp5-submenu-icon{\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-], .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active{\n    background-color:highlight;\n  }\n}\n.bp5-submenu.bp5-popover{\n  box-shadow:none;\n  padding:0 5px;\n}\n.bp5-submenu.bp5-popover > .bp5-popover-content{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-submenu.bp5-popover, .bp5-submenu.bp5-popover.bp5-dark{\n  box-shadow:none;\n}\n.bp5-dark .bp5-submenu.bp5-popover > .bp5-popover-content, .bp5-submenu.bp5-popover.bp5-dark > .bp5-popover-content{\n  box-shadow:0 0 0 1px hsl(215, 3%, 38%), inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-menu{\n  background:#ffffff;\n  border-radius:2px;\n  color:#1c2127;\n  list-style:none;\n  margin:0;\n  min-width:180px;\n  padding:5px;\n  text-align:left;\n}\n\n.bp5-menu-divider{\n  border-top:1px solid rgba(17, 20, 24, 0.15);\n  display:block;\n  margin:5px -5px;\n}\n.bp5-dark .bp5-menu-divider{\n  border-top-color:rgba(255, 255, 255, 0.2);\n}\n\n.bp5-menu-item{\n  display:flex;\n  flex-direction:row;\n  align-items:flex-start;\n  border-radius:2px;\n  color:inherit;\n  line-height:20px;\n  padding:5px 7px;\n  text-decoration:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-menu-item > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-menu-item > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-menu-item::before,\n.bp5-menu-item > *{\n  margin-right:7px;\n}\n.bp5-menu-item:empty::before,\n.bp5-menu-item > :last-child{\n  margin-right:0;\n}\n.bp5-menu-item > .bp5-fill{\n  word-break:break-word;\n}\n.bp5-menu-item .bp5-menu-item-icon{\n  display:flex;\n  flex-direction:column;\n  height:20px;\n  justify-content:center;\n}\n.bp5-menu-item .bp5-menu-item-label{\n  color:#5f6b7c;\n}\n.bp5-menu-item::before,\n.bp5-menu-item .bp5-menu-item-icon,\n.bp5-menu-item .bp5-menu-item-selected-icon,\n.bp5-menu-item .bp5-submenu-icon{\n  color:#5f6b7c;\n}\n.bp5-menu-item::before,\n.bp5-menu-item .bp5-submenu-icon{\n  margin-top:2px;\n}\n.bp5-menu-item:hover{\n  background-color:rgba(143, 153, 168, 0.15);\n  color:inherit;\n  cursor:pointer;\n  text-decoration:none;\n}\n.bp5-menu-item:active{\n  background-color:rgba(143, 153, 168, 0.3);\n}\n.bp5-menu-item:active .bp5-menu-item-label{\n  color:#1c2127;\n}\n.bp5-menu-item.bp5-active{\n  background-color:rgba(45, 114, 210, 0.1);\n  color:#215db0;\n}\n.bp5-menu-item.bp5-active .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-active::before,\n.bp5-menu-item.bp5-active .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-active .bp5-submenu-icon{\n  color:#215db0;\n}\n.bp5-menu-item.bp5-active.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.1);\n  color:#1c6e42;\n}\n.bp5-menu-item.bp5-active.bp5-intent-success::before,\n.bp5-menu-item.bp5-active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active.bp5-intent-success .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-menu-item.bp5-active.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.1);\n  color:#935610;\n}\n.bp5-menu-item.bp5-active.bp5-intent-warning::before,\n.bp5-menu-item.bp5-active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active.bp5-intent-warning .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-menu-item.bp5-active.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.1);\n  color:#ac2f33;\n}\n.bp5-menu-item.bp5-active.bp5-intent-danger::before,\n.bp5-menu-item.bp5-active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active.bp5-intent-danger .bp5-submenu-icon{\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-menu-item.bp5-active{\n    background-color:highlight;\n  }\n}\n.bp5-menu-item.bp5-menu-item-is-selectable{\n  padding-left:20px;\n}\n.bp5-menu-item.bp5-menu-item-is-selectable.bp5-selected{\n  padding-left:0;\n}\n.bp5-menu-item.bp5-menu-item-is-selectable .bp5-menu-item-selected-icon{\n  align-self:center;\n  margin:0 2px;\n}\n.bp5-menu-item.bp5-disabled{\n  background-color:inherit !important;\n  color:rgba(95, 107, 124, 0.6) !important;\n  cursor:not-allowed !important;\n  outline:none !important;\n}\n.bp5-menu-item.bp5-disabled::before,\n.bp5-menu-item.bp5-disabled .bp5-menu-item-icon,\n.bp5-menu-item.bp5-disabled .bp5-submenu-icon{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-menu-item.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-menu-item.bp5-intent-primary::before,\n.bp5-menu-item.bp5-intent-primary .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-primary .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-primary .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-intent-primary:hover{\n  background-color:rgba(45, 114, 210, 0.1);\n}\n.bp5-menu-item.bp5-intent-primary:active, .bp5-menu-item.bp5-intent-primary.bp5-active{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#184a90;\n}\n.bp5-menu-item.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-menu-item.bp5-intent-success::before,\n.bp5-menu-item.bp5-intent-success .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-success .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-success .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-intent-success:hover{\n  background-color:rgba(35, 133, 81, 0.1);\n}\n.bp5-menu-item.bp5-intent-success:active, .bp5-menu-item.bp5-intent-success.bp5-active{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#165a36;\n}\n.bp5-menu-item.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-menu-item.bp5-intent-warning::before,\n.bp5-menu-item.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-warning .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-warning .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-intent-warning:hover{\n  background-color:rgba(200, 118, 25, 0.1);\n}\n.bp5-menu-item.bp5-intent-warning:active, .bp5-menu-item.bp5-intent-warning.bp5-active{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#77450d;\n}\n.bp5-menu-item.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-menu-item.bp5-intent-danger::before,\n.bp5-menu-item.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-danger .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-danger .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-intent-danger:hover{\n  background-color:rgba(205, 66, 70, 0.1);\n}\n.bp5-menu-item.bp5-intent-danger:active, .bp5-menu-item.bp5-intent-danger.bp5-active{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#8e292c;\n}\n.bp5-menu-item::before{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  margin-right:7px;\n}\n.bp5-large .bp5-menu-item{\n  font-size:16px;\n  line-height:22px;\n  padding-bottom:9px;\n  padding-top:9px;\n}\n.bp5-large .bp5-menu-item .bp5-menu-item-icon{\n  height:22px;\n}\n.bp5-large .bp5-menu-item::before,\n.bp5-large .bp5-menu-item .bp5-submenu-icon{\n  margin-top:3px;\n}\n.bp5-large .bp5-menu-item::before{\n  font-family:\"blueprint-icons-20\", sans-serif;\n  font-size:20px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:20px;\n  line-height:1;\n  width:20px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  margin-right:10px;\n}\n.bp5-small .bp5-menu-item{\n  padding-bottom:2px;\n  padding-top:2px;\n}\n\nbutton.bp5-menu-item{\n  background:none;\n  border:none;\n  text-align:left;\n  width:100%;\n}\n.bp5-menu-header{\n  border-top:1px solid rgba(17, 20, 24, 0.15);\n  display:block;\n  margin:5px -5px;\n  cursor:default;\n  padding-left:2px;\n}\n.bp5-dark .bp5-menu-header{\n  border-top-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-menu-header:first-of-type{\n  border-top:none;\n}\n.bp5-menu-header > h6{\n  color:#1c2127;\n  font-weight:600;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  line-height:17px;\n  margin:0;\n  padding:10px 7px 0 6px;\n}\n.bp5-dark .bp5-menu-header > h6{\n  color:#f6f7f9;\n}\n.bp5-menu-header > h6.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-menu-header > h6.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-menu-header:first-of-type > h6{\n  padding-top:0;\n}\n.bp5-large .bp5-menu-header > h6{\n  font-size:18px;\n  padding-bottom:5px;\n  padding-top:15px;\n}\n.bp5-large .bp5-menu-header:first-of-type > h6{\n  padding-top:0;\n}\n\n.bp5-dark .bp5-menu{\n  background:#2f343c;\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-menu-item{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item .bp5-menu-item-label{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-menu-item::before,\n.bp5-dark .bp5-menu-item .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item .bp5-submenu-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-menu-item:hover{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item:hover .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item:hover .bp5-submenu-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-menu-item:active{\n  background-color:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-menu-item:active .bp5-menu-item-label{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-menu-item.bp5-active{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-menu-item.bp5-active::before,\n.bp5-dark .bp5-menu-item.bp5-active .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-active .bp5-submenu-icon{\n  color:#8abbff;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success::before,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning::before,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger::before,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger .bp5-submenu-icon{\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-menu-item.bp5-active{\n    background-color:highlight;\n  }\n}\n.bp5-dark .bp5-menu-item.bp5-disabled{\n  color:rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-dark .bp5-menu-item.bp5-disabled::before,\n.bp5-dark .bp5-menu-item.bp5-disabled .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-disabled .bp5-submenu-icon{\n  color:rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-dark .bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n  color:rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary::before,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary:hover{\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary:active, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active{\n  background-color:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success::before,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success:hover{\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success:active, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active{\n  background-color:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning::before,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning:hover{\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning:active, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active{\n  background-color:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger::before,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger:hover{\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger:active, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active{\n  background-color:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-menu-divider,\n.bp5-dark .bp5-menu-header{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-menu-header > h6{\n  color:#f6f7f9;\n}\n\n.bp5-label .bp5-menu{\n  margin-top:5px;\n}\n.bp5-navbar{\n  background-color:#ffffff;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  height:50px;\n  padding:0 15px;\n  position:relative;\n  width:100%;\n  z-index:10;\n}\n.bp5-navbar.bp5-dark, .bp5-dark .bp5-navbar{\n  background-color:#252a31;\n}\n.bp5-navbar.bp5-dark{\n  box-shadow:inset inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-navbar{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-navbar.bp5-fixed-top{\n  left:0;\n  position:fixed;\n  right:0;\n  top:0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-navbar{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-navbar-heading{\n  font-size:16px;\n  margin-right:15px;\n}\n\n.bp5-navbar-group{\n  align-items:center;\n  display:flex;\n  height:50px;\n}\n.bp5-navbar-group.bp5-align-left{\n  float:left;\n}\n.bp5-navbar-group.bp5-align-right{\n  float:right;\n}\n\n.bp5-navbar-divider{\n  border-left:1px solid rgba(17, 20, 24, 0.15);\n  height:20px;\n  margin:0 10px;\n}\n.bp5-dark .bp5-navbar-divider{\n  border-left-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-non-ideal-state{\n  display:flex;\n  flex-direction:column;\n  align-items:center;\n  color:#5f6b7c;\n  height:100%;\n  justify-content:center;\n  text-align:center;\n  width:100%;\n}\n.bp5-non-ideal-state > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-non-ideal-state > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-non-ideal-state::before,\n.bp5-non-ideal-state > *{\n  margin-bottom:20px;\n}\n.bp5-non-ideal-state:empty::before,\n.bp5-non-ideal-state > :last-child{\n  margin-bottom:0;\n}\n.bp5-non-ideal-state > *{\n  max-width:400px;\n}\n.bp5-non-ideal-state .bp5-heading{\n  color:#5f6b7c;\n  line-height:20px;\n  margin-bottom:10px;\n}\n.bp5-non-ideal-state .bp5-heading:only-child{\n  margin-bottom:0;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal{\n  display:flex;\n  flex-direction:row;\n  text-align:left;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal::before,\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > *{\n  margin-right:20px;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal:empty::before,\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > :last-child{\n  margin-right:0;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > *{\n  margin-bottom:0;\n}\n.bp5-dark .bp5-non-ideal-state{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-non-ideal-state .bp5-heading{\n  color:#abb3bf;\n}\n\n.bp5-non-ideal-state-visual{\n  color:#8f99a8;\n}\n\n.bp5-overflow-list{\n  display:flex;\n  flex-wrap:nowrap;\n  min-width:0;\n}\n\n.bp5-overflow-list-spacer{\n  flex-shrink:1;\n  width:1px;\n}\n\nbody.bp5-overlay-open{\n  overflow:hidden;\n}\n\n.bp5-overlay{\n  inset:0;\n  position:static;\n  z-index:20;\n}\n.bp5-overlay:not(.bp5-overlay-open){\n  pointer-events:none;\n}\n.bp5-overlay.bp5-overlay-container{\n  overflow:hidden;\n  position:fixed;\n}\n.bp5-overlay.bp5-overlay-container.bp5-overlay-inline{\n  position:absolute;\n}\n.bp5-overlay.bp5-overlay-scroll-container{\n  overflow:auto;\n  position:fixed;\n}\n.bp5-overlay.bp5-overlay-scroll-container.bp5-overlay-inline{\n  position:absolute;\n}\n.bp5-overlay.bp5-overlay-inline{\n  display:inline;\n  overflow:visible;\n}\n\n.bp5-overlay-content{\n  position:fixed;\n  z-index:20;\n}\n.bp5-overlay-inline .bp5-overlay-content, .bp5-overlay-scroll-container .bp5-overlay-content{\n  position:absolute;\n}\n\n.bp5-overlay-backdrop{\n  inset:0;\n  position:fixed;\n  opacity:1;\n  background-color:rgba(17, 20, 24, 0.7);\n  overflow:auto;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  z-index:20;\n}\n.bp5-overlay-backdrop.bp5-overlay-enter, .bp5-overlay-backdrop.bp5-overlay-appear{\n  opacity:0;\n}\n.bp5-overlay-backdrop.bp5-overlay-enter-active, .bp5-overlay-backdrop.bp5-overlay-appear-active{\n  opacity:1;\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:opacity;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-overlay-backdrop.bp5-overlay-exit{\n  opacity:1;\n}\n.bp5-overlay-backdrop.bp5-overlay-exit-active{\n  opacity:0;\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:opacity;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-overlay-backdrop:focus{\n  outline:none;\n}\n.bp5-overlay-inline .bp5-overlay-backdrop{\n  position:absolute;\n}\n\n.bp5-panel-stack{\n  overflow:hidden;\n  position:relative;\n}\n\n.bp5-panel-stack-header{\n  align-items:center;\n  box-shadow:0 1px rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex-shrink:0;\n  height:30px;\n  z-index:1;\n}\n.bp5-dark .bp5-panel-stack-header{\n  box-shadow:0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-panel-stack-header > span{\n  align-items:stretch;\n  display:flex;\n  flex:1;\n}\n.bp5-panel-stack-header .bp5-heading{\n  margin:0 5px;\n}\n\n.bp5-button.bp5-panel-stack-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap;\n}\n.bp5-button.bp5-panel-stack-header-back .bp5-icon{\n  margin:0 2px;\n}\n\n.bp5-panel-stack-view{\n  inset:0;\n  position:absolute;\n  background-color:#ffffff;\n  border-right:1px solid rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1;\n}\n.bp5-dark .bp5-panel-stack-view{\n  background-color:#252a31;\n}\n.bp5-panel-stack-view:nth-last-child(n+4){\n  display:none;\n}\n\n.bp5-panel-stack-push .bp5-panel-stack-enter, .bp5-panel-stack-push .bp5-panel-stack-appear{\n  transform:translateX(100%);\n  opacity:0;\n}\n.bp5-panel-stack-push .bp5-panel-stack-enter-active, .bp5-panel-stack-push .bp5-panel-stack-appear-active{\n  transform:translate(0%);\n  opacity:1;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n.bp5-panel-stack-push .bp5-panel-stack-exit{\n  transform:translate(0%);\n  opacity:1;\n}\n.bp5-panel-stack-push .bp5-panel-stack-exit-active{\n  transform:translateX(-50%);\n  opacity:0;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n\n.bp5-panel-stack-pop .bp5-panel-stack-enter, .bp5-panel-stack-pop .bp5-panel-stack-appear{\n  transform:translateX(-50%);\n  opacity:0;\n}\n.bp5-panel-stack-pop .bp5-panel-stack-enter-active, .bp5-panel-stack-pop .bp5-panel-stack-appear-active{\n  transform:translate(0%);\n  opacity:1;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n.bp5-panel-stack-pop .bp5-panel-stack-exit{\n  transform:translate(0%);\n  opacity:1;\n}\n.bp5-panel-stack-pop .bp5-panel-stack-exit-active{\n  transform:translateX(100%);\n  opacity:0;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n\n.bp5-panel-stack2{\n  overflow:hidden;\n  position:relative;\n}\n\n.bp5-panel-stack2-header{\n  align-items:center;\n  box-shadow:0 1px rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex-shrink:0;\n  height:30px;\n  z-index:1;\n}\n.bp5-dark .bp5-panel-stack2-header{\n  box-shadow:0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-panel-stack2-header > span{\n  align-items:stretch;\n  display:flex;\n  flex:1;\n}\n.bp5-panel-stack2-header .bp5-heading{\n  margin:0 5px;\n}\n\n.bp5-button.bp5-panel-stack2-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap;\n}\n.bp5-button.bp5-panel-stack2-header-back .bp5-icon{\n  margin:0 2px;\n}\n\n.bp5-panel-stack2-view{\n  inset:0;\n  position:absolute;\n  background-color:#ffffff;\n  border-right:1px solid rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1;\n}\n.bp5-dark .bp5-panel-stack2-view{\n  background-color:#252a31;\n}\n.bp5-panel-stack2-view:nth-last-child(n+4){\n  display:none;\n}\n\n.bp5-panel-stack2-push .bp5-panel-stack2-enter, .bp5-panel-stack2-push .bp5-panel-stack2-appear{\n  transform:translateX(100%);\n  opacity:0;\n}\n.bp5-panel-stack2-push .bp5-panel-stack2-enter-active, .bp5-panel-stack2-push .bp5-panel-stack2-appear-active{\n  transform:translate(0%);\n  opacity:1;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n.bp5-panel-stack2-push .bp5-panel-stack2-exit{\n  transform:translate(0%);\n  opacity:1;\n}\n.bp5-panel-stack2-push .bp5-panel-stack2-exit-active{\n  transform:translateX(-50%);\n  opacity:0;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter, .bp5-panel-stack2-pop .bp5-panel-stack2-appear{\n  transform:translateX(-50%);\n  opacity:0;\n}\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter-active, .bp5-panel-stack2-pop .bp5-panel-stack2-appear-active{\n  transform:translate(0%);\n  opacity:1;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit{\n  transform:translate(0%);\n  opacity:1;\n}\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit-active{\n  transform:translateX(100%);\n  opacity:0;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n\n.bp5-button-group:not(.bp5-minimal) > .bp5-popover-target:not(:first-child) .bp5-button{\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-button-group:not(.bp5-minimal) > .bp5-popover-target:not(:last-child) .bp5-button{\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n  margin-right:-1px;\n}\n.bp5-button-group .bp5-popover-target{\n  display:flex;\n  flex:1 1 auto;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-target:first-child .bp5-button{\n  border-radius:2px 2px 0 0;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-target:last-child .bp5-button{\n  border-radius:0 0 2px 2px;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-target:not(:last-child) .bp5-button{\n  margin-bottom:-1px;\n}\n\n.bp5-control-group .bp5-popover-target{\n  border-radius:inherit;\n}\n\nlabel.bp5-label .bp5-popover-target{\n  display:block;\n  margin-top:5px;\n  text-transform:none;\n}\n\n.bp5-submenu .bp5-popover-target{\n  display:block;\n}\n.bp5-submenu.bp5-popover{\n  box-shadow:none;\n  padding:0 5px;\n}\n.bp5-submenu.bp5-popover > .bp5-popover-content{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-submenu.bp5-popover, .bp5-submenu.bp5-popover.bp5-dark{\n  box-shadow:none;\n}\n.bp5-dark .bp5-submenu.bp5-popover > .bp5-popover-content, .bp5-submenu.bp5-popover.bp5-dark > .bp5-popover-content{\n  box-shadow:0 0 0 1px hsl(215, 3%, 38%), inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n\n.bp5-tree-node-secondary-label .bp5-popover-target{\n  align-items:center;\n  display:flex;\n}\n\n.bp5-popover{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  transform:scale(1);\n  border-radius:2px;\n  display:inline-block;\n  z-index:20;\n}\n.bp5-popover .bp5-popover-arrow{\n  height:30px;\n  position:absolute;\n  width:30px;\n}\n.bp5-popover .bp5-popover-arrow::before{\n  height:20px;\n  margin:5px;\n  width:20px;\n}\n.bp5-popover .bp5-popover-content{\n  background:#ffffff;\n}\n.bp5-popover .bp5-popover-content,\n.bp5-popover .bp5-heading{\n  color:inherit;\n}\n.bp5-popover .bp5-popover-arrow::before{\n  box-shadow:1px 1px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-popover .bp5-popover-arrow-border{\n  fill:#111418;\n  fill-opacity:0.1;\n}\n.bp5-popover .bp5-popover-arrow-fill{\n  fill:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-popover-enter > .bp5-popover, .bp5-popover-appear > .bp5-popover{\n  transform:scale(0.3);\n}\n.bp5-popover-enter-active > .bp5-popover, .bp5-popover-appear-active > .bp5-popover{\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-popover-exit > .bp5-popover{\n  transform:scale(1);\n}\n.bp5-popover-exit-active > .bp5-popover{\n  transform:scale(0.3);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-popover .bp5-popover-content{\n  border-radius:2px;\n  position:relative;\n}\n.bp5-popover.bp5-popover-content-sizing .bp5-popover-content{\n  max-width:350px;\n  padding:20px;\n}\n.bp5-popover-target + .bp5-overlay .bp5-popover.bp5-popover-content-sizing{\n  width:350px;\n}\n.bp5-popover.bp5-minimal{\n  margin:0 !important;\n}\n.bp5-popover.bp5-minimal .bp5-popover-arrow{\n  display:none;\n}\n.bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n}\n.bp5-popover-enter > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear > .bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n}\n.bp5-popover-enter-active > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear-active > .bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-exit > .bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n}\n.bp5-popover-exit-active > .bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover.bp5-popover-match-target-width{\n  width:100%;\n}\n.bp5-popover.bp5-dark, .bp5-dark .bp5-popover{\n  box-shadow:0 0 0 1px hsl(215, 3%, 38%), inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-popover.bp5-dark .bp5-popover-content, .bp5-dark .bp5-popover .bp5-popover-content{\n  background:#2f343c;\n}\n.bp5-popover.bp5-dark .bp5-popover-content,\n.bp5-popover.bp5-dark .bp5-heading, .bp5-dark .bp5-popover .bp5-popover-content,\n.bp5-dark .bp5-popover .bp5-heading{\n  color:inherit;\n}\n.bp5-popover.bp5-dark .bp5-popover-arrow::before, .bp5-dark .bp5-popover .bp5-popover-arrow::before{\n  box-shadow:0 0 0 1px #777a7e, 1px 1px 6px rgba(17, 20, 24, 0.4);\n}\n.bp5-popover.bp5-dark .bp5-popover-arrow-border, .bp5-dark .bp5-popover .bp5-popover-arrow-border{\n  fill:#111418;\n  fill-opacity:0.2;\n}\n.bp5-popover.bp5-dark .bp5-popover-arrow-fill, .bp5-dark .bp5-popover .bp5-popover-arrow-fill{\n  fill:#2f343c;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover.bp5-dark .bp5-popover-arrow-fill, .bp5-dark .bp5-popover .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover.bp5-dark, .bp5-dark .bp5-popover{\n    border:1px solid buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n\n.bp5-popover-arrow::before{\n  border-radius:1px;\n  content:\"\";\n  display:block;\n  position:absolute;\n  transform:rotate(45deg);\n}\n\n.bp5-overlay-backdrop.bp5-popover-backdrop{\n  background:rgba(255, 255, 255, 0);\n}\n\n.bp5-popover-transition-container{\n  opacity:1;\n  display:flex;\n  z-index:20;\n}\n.bp5-popover-transition-container.bp5-popover-enter, .bp5-popover-transition-container.bp5-popover-appear{\n  opacity:0;\n}\n.bp5-popover-transition-container.bp5-popover-enter-active, .bp5-popover-transition-container.bp5-popover-appear-active{\n  opacity:1;\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:opacity;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-transition-container.bp5-popover-exit{\n  opacity:1;\n}\n.bp5-popover-transition-container.bp5-popover-exit-active{\n  opacity:0;\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:opacity;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-transition-container:focus{\n  outline:none;\n}\n.bp5-popover-transition-container.bp5-popover-leave .bp5-popover-content{\n  pointer-events:none;\n}\n\nspan.bp5-popover-target{\n  display:inline-block;\n}\n\n.bp5-popover-target.bp5-fill{\n  width:100%;\n}\n\n.bp5-portal{\n  left:0;\n  position:absolute;\n  right:0;\n  top:0;\n}\n@keyframes linear-progress-bar-stripes{\n  from{\n    background-position:0 0;\n  }\n  to{\n    background-position:30px 0;\n  }\n}\n.bp5-progress-bar{\n  background:rgba(95, 107, 124, 0.2);\n  border-radius:40px;\n  display:block;\n  height:8px;\n  overflow:hidden;\n  position:relative;\n  width:100%;\n}\n.bp5-progress-bar .bp5-progress-meter{\n  background:linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%);\n  background-color:rgba(95, 107, 124, 0.8);\n  background-size:30px 30px;\n  border-radius:40px;\n  height:100%;\n  position:absolute;\n  transition:width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width:100%;\n}\n.bp5-progress-bar:not(.bp5-no-animation):not(.bp5-no-stripes) .bp5-progress-meter{\n  animation:linear-progress-bar-stripes 300ms linear infinite reverse;\n}\n.bp5-progress-bar.bp5-no-stripes .bp5-progress-meter{\n  background-image:none;\n}\n\n.bp5-dark .bp5-progress-bar{\n  background:rgba(17, 20, 24, 0.5);\n}\n.bp5-dark .bp5-progress-bar .bp5-progress-meter{\n  background-color:#8f99a8;\n}\n\n.bp5-progress-bar.bp5-intent-primary .bp5-progress-meter{\n  background-color:#2d72d2;\n}\n\n.bp5-progress-bar.bp5-intent-success .bp5-progress-meter{\n  background-color:#238551;\n}\n\n.bp5-progress-bar.bp5-intent-warning .bp5-progress-meter{\n  background-color:#c87619;\n}\n\n.bp5-progress-bar.bp5-intent-danger .bp5-progress-meter{\n  background-color:#cd4246;\n}\n\n.bp5-section{\n  overflow:hidden;\n  width:100%;\n}\n.bp5-section, .bp5-section.bp5-compact{\n  padding:0;\n}\n.bp5-section-header{\n  align-items:center;\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n  display:flex;\n  gap:20px;\n  justify-content:space-between;\n  min-height:50px;\n  padding:0 20px;\n  position:relative;\n  width:100%;\n}\n.bp5-section-header.bp5-dark, .bp5-dark .bp5-section-header{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-section-header-left{\n  align-items:center;\n  display:flex;\n  gap:10px;\n  padding:10px 0;\n}\n.bp5-section-header-title{\n  margin-bottom:0;\n}\n.bp5-section-header-sub-title{\n  margin-top:2px;\n}\n.bp5-section-header-right{\n  align-items:center;\n  display:flex;\n  gap:10px;\n  margin-left:auto;\n}\n.bp5-section-header-divider{\n  align-self:stretch;\n  margin:15px 0;\n}\n.bp5-section-header.bp5-interactive{\n  cursor:pointer;\n}\n.bp5-section-header.bp5-interactive:hover, .bp5-section-header.bp5-interactive:active{\n  background:#f6f7f9;\n}\n.bp5-section-header.bp5-interactive:hover.bp5-dark, .bp5-dark .bp5-section-header.bp5-interactive:hover, .bp5-section-header.bp5-interactive:active.bp5-dark, .bp5-dark .bp5-section-header.bp5-interactive:active{\n  background:#383e47;\n}\n.bp5-section-card.bp5-padded{\n  padding:20px;\n}\n.bp5-section-card:not(:last-child){\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-section-card:not(:last-child).bp5-dark, .bp5-dark .bp5-section-card:not(:last-child){\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-section.bp5-section-collapsed .bp5-section-header{\n  border:none;\n}\n.bp5-section.bp5-compact .bp5-section-header{\n  min-height:40px;\n  padding:0 15px;\n}\n.bp5-section.bp5-compact .bp5-section-header-left{\n  padding:7px 0;\n}\n.bp5-section.bp5-compact .bp5-section-card.bp5-padded{\n  padding:15px;\n}\n\n.bp5-segmented-control{\n  background-color:#f6f7f9;\n  border-radius:2px;\n  display:flex;\n  gap:3px;\n  padding:3px;\n}\n.bp5-segmented-control.bp5-inline{\n  display:inline-flex;\n}\n.bp5-segmented-control.bp5-fill{\n  width:100%;\n}\n.bp5-segmented-control.bp5-fill > .bp5-button{\n  flex-grow:1;\n}\n.bp5-segmented-control > .bp5-button:not(.bp5-minimal):not(.bp5-intent-primary){\n  background-color:#ffffff;\n}\n.bp5-dark .bp5-segmented-control > .bp5-button:not(.bp5-minimal):not(.bp5-intent-primary){\n  background-color:#404854;\n}\n.bp5-segmented-control > .bp5-button.bp5-minimal{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-segmented-control > .bp5-button.bp5-minimal{\n  color:#abb3bf;\n}\n.bp5-segmented-control > .bp5-button.bp5-minimal:disabled{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-dark .bp5-segmented-control > .bp5-button.bp5-minimal:disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-segmented-control{\n  background-color:#252a31;\n}\n@keyframes skeleton-glow{\n  from{\n    background:rgba(211, 216, 222, 0.2);\n    border-color:rgba(211, 216, 222, 0.2);\n  }\n  to{\n    background:rgba(95, 107, 124, 0.2);\n    border-color:rgba(95, 107, 124, 0.2);\n  }\n}\n.bp5-skeleton{\n  animation:1000ms linear infinite alternate skeleton-glow;\n  background:rgba(211, 216, 222, 0.2);\n  background-clip:padding-box !important;\n  border-color:rgba(211, 216, 222, 0.2) !important;\n  border-radius:2px;\n  box-shadow:none !important;\n  color:transparent !important;\n  cursor:default;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-skeleton::before, .bp5-skeleton::after,\n.bp5-skeleton *{\n  visibility:hidden !important;\n}\n.bp5-tooltip{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  transform:scale(1);\n  color:#f6f7f9;\n}\n.bp5-tooltip .bp5-popover-arrow{\n  height:22px;\n  position:absolute;\n  width:22px;\n}\n.bp5-tooltip .bp5-popover-arrow::before{\n  height:14px;\n  margin:4px;\n  width:14px;\n}\n.bp5-tooltip .bp5-popover-content{\n  background:#404854;\n}\n.bp5-tooltip .bp5-popover-content,\n.bp5-tooltip .bp5-heading{\n  color:#f6f7f9;\n}\n.bp5-tooltip .bp5-popover-arrow::before{\n  box-shadow:1px 1px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-tooltip .bp5-popover-arrow-border{\n  fill:#111418;\n  fill-opacity:0.1;\n}\n.bp5-tooltip .bp5-popover-arrow-fill{\n  fill:#404854;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-popover-enter > .bp5-tooltip, .bp5-popover-appear > .bp5-tooltip{\n  transform:scale(0.8);\n}\n.bp5-popover-enter-active > .bp5-tooltip, .bp5-popover-appear-active > .bp5-tooltip{\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-exit > .bp5-tooltip{\n  transform:scale(1);\n}\n.bp5-popover-exit-active > .bp5-tooltip{\n  transform:scale(0.8);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tooltip .bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-tooltip .bp5-text-disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-tooltip .bp5-running-text hr{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-tooltip a{\n  color:#8abbff;\n}\n.bp5-tooltip a:hover{\n  color:#8abbff;\n}\n.bp5-tooltip a .bp5-icon, .bp5-tooltip a .bp5-icon-standard, .bp5-tooltip a .bp5-icon-large{\n  color:inherit;\n}\n.bp5-tooltip a code{\n  color:inherit;\n}\n.bp5-tooltip .bp5-code,\n.bp5-tooltip .bp5-running-text code{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n  color:#abb3bf;\n}\na > .bp5-tooltip .bp5-code,\na > .bp5-tooltip .bp5-running-text code{\n  color:inherit;\n}\n.bp5-tooltip .bp5-code-block,\n.bp5-tooltip .bp5-running-text pre{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n  color:#f6f7f9;\n}\n.bp5-tooltip .bp5-code-block > code,\n.bp5-tooltip .bp5-running-text pre > code{\n  background:none;\n  box-shadow:none;\n  color:inherit;\n}\n.bp5-tooltip .bp5-key,\n.bp5-tooltip .bp5-running-text kbd{\n  background:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n  color:#abb3bf;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-primary, .bp5-tooltip .bp5-icon-standard.bp5-intent-primary, .bp5-tooltip .bp5-icon-large.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-success, .bp5-tooltip .bp5-icon-standard.bp5-intent-success, .bp5-tooltip .bp5-icon-large.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-warning, .bp5-tooltip .bp5-icon-standard.bp5-intent-warning, .bp5-tooltip .bp5-icon-large.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-danger, .bp5-tooltip .bp5-icon-standard.bp5-intent-danger, .bp5-tooltip .bp5-icon-large.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-tooltip .bp5-popover-content{\n  padding:10px 12px;\n}\n.bp5-tooltip.bp5-compact .bp5-popover-content{\n  line-height:1rem;\n  padding:5px 7px;\n}\n.bp5-tooltip.bp5-compact .bp5-code{\n  vertical-align:text-bottom;\n}\n.bp5-tooltip.bp5-popover-placement-top .bp5-popover-arrow{\n  transform:translateY(-3px);\n}\n.bp5-tooltip.bp5-popover-placement-left .bp5-popover-arrow{\n  transform:translateX(-3px);\n}\n.bp5-tooltip.bp5-popover-placement-bottom .bp5-popover-arrow{\n  transform:translateY(3px);\n}\n.bp5-tooltip.bp5-popover-placement-right .bp5-popover-arrow{\n  transform:translateX(3px);\n}\n.bp5-tooltip.bp5-dark, .bp5-dark .bp5-tooltip{\n  box-shadow:0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-tooltip.bp5-dark .bp5-popover-content, .bp5-dark .bp5-tooltip .bp5-popover-content{\n  background:#e5e8eb;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-content,\n.bp5-tooltip.bp5-dark .bp5-heading, .bp5-dark .bp5-tooltip .bp5-popover-content,\n.bp5-dark .bp5-tooltip .bp5-heading{\n  color:#404854;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow::before, .bp5-dark .bp5-tooltip .bp5-popover-arrow::before{\n  box-shadow:1px 1px 6px rgba(17, 20, 24, 0.4);\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow-border, .bp5-dark .bp5-tooltip .bp5-popover-arrow-border{\n  fill:#111418;\n  fill-opacity:0.2;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow-fill, .bp5-dark .bp5-tooltip .bp5-popover-arrow-fill{\n  fill:#e5e8eb;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-dark .bp5-popover-arrow-fill, .bp5-dark .bp5-tooltip .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-dark, .bp5-dark .bp5-tooltip{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-tooltip.bp5-dark .bp5-text-muted, .bp5-dark .bp5-tooltip .bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-tooltip.bp5-dark .bp5-text-disabled, .bp5-dark .bp5-tooltip .bp5-text-disabled{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-tooltip.bp5-dark .bp5-running-text hr, .bp5-dark .bp5-tooltip .bp5-running-text hr{\n  border-color:rgba(17, 20, 24, 0.15);\n}\n.bp5-tooltip.bp5-dark a, .bp5-dark .bp5-tooltip a{\n  color:#215db0;\n}\n.bp5-tooltip.bp5-dark a:hover, .bp5-dark .bp5-tooltip a:hover{\n  color:#215db0;\n}\n.bp5-tooltip.bp5-dark a .bp5-icon, .bp5-tooltip.bp5-dark a .bp5-icon-standard, .bp5-tooltip.bp5-dark a .bp5-icon-large, .bp5-dark .bp5-tooltip a .bp5-icon, .bp5-dark .bp5-tooltip a .bp5-icon-standard, .bp5-dark .bp5-tooltip a .bp5-icon-large{\n  color:inherit;\n}\n.bp5-tooltip.bp5-dark a code, .bp5-dark .bp5-tooltip a code{\n  color:inherit;\n}\n.bp5-tooltip.bp5-dark .bp5-code,\n.bp5-tooltip.bp5-dark .bp5-running-text code, .bp5-dark .bp5-tooltip .bp5-code,\n.bp5-dark .bp5-tooltip .bp5-running-text code{\n  background:rgba(255, 255, 255, 0.7);\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color:#5f6b7c;\n}\na > .bp5-tooltip.bp5-dark .bp5-code,\na > .bp5-tooltip.bp5-dark .bp5-running-text code, a > .bp5-dark .bp5-tooltip .bp5-code,\na > .bp5-dark .bp5-tooltip .bp5-running-text code{\n  color:#2d72d2;\n}\n.bp5-tooltip.bp5-dark .bp5-code-block,\n.bp5-tooltip.bp5-dark .bp5-running-text pre, .bp5-dark .bp5-tooltip .bp5-code-block,\n.bp5-dark .bp5-tooltip .bp5-running-text pre{\n  background:rgba(255, 255, 255, 0.7);\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n  color:#1c2127;\n}\n.bp5-tooltip.bp5-dark .bp5-code-block > code,\n.bp5-tooltip.bp5-dark .bp5-running-text pre > code, .bp5-dark .bp5-tooltip .bp5-code-block > code,\n.bp5-dark .bp5-tooltip .bp5-running-text pre > code{\n  background:none;\n  box-shadow:none;\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-dark .bp5-code-block,\n  .bp5-tooltip.bp5-dark .bp5-running-text pre, .bp5-dark .bp5-tooltip .bp5-code-block,\n  .bp5-dark .bp5-tooltip .bp5-running-text pre{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-tooltip.bp5-dark .bp5-key,\n.bp5-tooltip.bp5-dark .bp5-running-text kbd, .bp5-dark .bp5-tooltip .bp5-key,\n.bp5-dark .bp5-tooltip .bp5-running-text kbd{\n  background:#ffffff;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  color:#5f6b7c;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-primary, .bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-primary, .bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-primary, .bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-success, .bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-success, .bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-success, .bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-success, .bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-warning, .bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-warning, .bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-warning, .bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-danger, .bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-danger, .bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-danger, .bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-tooltip.bp5-intent-primary .bp5-popover-content{\n  background:#2d72d2;\n  color:#ffffff;\n}\n.bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill{\n  fill:#2d72d2;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-success .bp5-popover-content{\n  background:#238551;\n  color:#ffffff;\n}\n.bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill{\n  fill:#238551;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-warning .bp5-popover-content{\n  background:#c87619;\n  color:#ffffff;\n}\n.bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill{\n  fill:#c87619;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-danger .bp5-popover-content{\n  background:#cd4246;\n  color:#ffffff;\n}\n.bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill{\n  fill:#cd4246;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n\n.bp5-tooltip-indicator{\n  border-bottom:dotted 1px;\n  cursor:help;\n}\n\n.bp5-slider{\n  height:40px;\n  min-width:150px;\n  width:100%;\n  cursor:default;\n  outline:none;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-slider:hover{\n  cursor:pointer;\n}\n.bp5-slider:active{\n  cursor:grabbing;\n}\n.bp5-slider.bp5-disabled{\n  cursor:not-allowed;\n  opacity:0.5;\n}\n.bp5-slider.bp5-slider-unlabeled{\n  height:16px;\n}\n\n.bp5-slider-track,\n.bp5-slider-progress{\n  height:6px;\n  left:0;\n  right:0;\n  top:5px;\n  position:absolute;\n}\n\n.bp5-slider-track{\n  border-radius:2px;\n  overflow:hidden;\n}\n\n.bp5-slider-progress{\n  background:rgba(95, 107, 124, 0.2);\n}\n.bp5-dark .bp5-slider-progress{\n  background:rgba(17, 20, 24, 0.5);\n}\n.bp5-slider-progress.bp5-intent-primary{\n  background-color:#2d72d2;\n}\n.bp5-slider-progress.bp5-intent-success{\n  background-color:#238551;\n}\n.bp5-slider-progress.bp5-intent-warning{\n  background-color:#c87619;\n}\n.bp5-slider-progress.bp5-intent-danger{\n  background-color:#cd4246;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-slider-progress{\n    background:ButtonText;\n  }\n}\n\n.bp5-slider-handle{\n  background-color:#f6f7f9;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#1c2127;\n  border-radius:2px;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.5), 0 1px 1px rgba(17, 20, 24, 0.5);\n  cursor:pointer;\n  height:16px;\n  left:0;\n  position:absolute;\n  top:0;\n  width:16px;\n}\n.bp5-slider-handle:hover{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-slider-handle:active, .bp5-slider-handle.bp5-active{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-slider-handle:active, .bp5-slider-handle.bp5-active{\n    background:highlight;\n  }\n}\n.bp5-slider-handle:disabled, .bp5-slider-handle.bp5-disabled{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  outline:none;\n}\n.bp5-slider-handle:disabled.bp5-active, .bp5-slider-handle:disabled.bp5-active:hover, .bp5-slider-handle.bp5-disabled.bp5-active, .bp5-slider-handle.bp5-disabled.bp5-active:hover{\n  background:rgba(211, 216, 222, 0.7);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-slider-handle{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-slider-handle:focus{\n  z-index:1;\n}\n.bp5-slider-handle:hover{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.5), 0 1px 2px rgba(17, 20, 24, 0.6);\n  cursor:grab;\n  z-index:2;\n}\n.bp5-slider-handle.bp5-active{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n  box-shadow:inset 0 1px 1px rgba(17, 20, 24, 0.1), 0 0 0 1px rgba(17, 20, 24, 0.5), 0 1px 2px rgba(17, 20, 24, 0.2);\n  cursor:grabbing;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-slider-handle.bp5-active{\n    background:highlight;\n  }\n}\n.bp5-disabled .bp5-slider-handle{\n  background:#c5cbd3;\n  box-shadow:none;\n  pointer-events:none;\n}\n.bp5-dark .bp5-slider-handle{\n  background-color:#abb3bf;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-slider-handle:hover{\n  background-color:#8f99a8;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-slider-handle.bp5-active{\n  background-color:#738091;\n  box-shadow:inset 0 1px 1px rgba(17, 20, 24, 0.1), inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-disabled .bp5-slider-handle{\n  background:#5f6b7c;\n  border-color:#5f6b7c;\n  box-shadow:none;\n}\n.bp5-slider-handle .bp5-slider-label{\n  background:#404854;\n  border-radius:2px;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  color:#f6f7f9;\n  margin-left:8px;\n}\n.bp5-dark .bp5-slider-handle .bp5-slider-label{\n  background:#e5e8eb;\n  box-shadow:0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n  color:#404854;\n}\n.bp5-disabled .bp5-slider-handle .bp5-slider-label{\n  box-shadow:none;\n}\n.bp5-slider-handle.bp5-start, .bp5-slider-handle.bp5-end{\n  width:8px;\n}\n.bp5-slider-handle.bp5-start{\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n}\n.bp5-slider-handle.bp5-end{\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n  margin-left:8px;\n}\n.bp5-slider-handle.bp5-end .bp5-slider-label{\n  margin-left:0;\n}\n\n.bp5-slider-label{\n  transform:translate(-50%, 20px);\n  display:inline-block;\n  font-size:12px;\n  line-height:1;\n  padding:2px 5px;\n  position:absolute;\n  vertical-align:top;\n}\n\n.bp5-slider.bp5-vertical{\n  height:150px;\n  min-width:40px;\n  width:40px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-track,\n.bp5-slider.bp5-vertical .bp5-slider-progress{\n  bottom:0;\n  height:auto;\n  left:5px;\n  top:0;\n  width:6px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-progress{\n  top:auto;\n}\n.bp5-slider.bp5-vertical .bp5-slider-label{\n  transform:translate(20px, 50%);\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle{\n  top:auto;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle .bp5-slider-label{\n  margin-left:0;\n  margin-top:-8px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end, .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n  height:8px;\n  margin-left:0;\n  width:16px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n  border-bottom-right-radius:2px;\n  border-top-left-radius:0;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start .bp5-slider-label{\n  transform:translate(20px);\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end{\n  border-bottom-left-radius:0;\n  border-bottom-right-radius:0;\n  border-top-left-radius:2px;\n  margin-bottom:8px;\n}\n\n@keyframes pt-spinner-animation{\n  from{\n    transform:rotate(0deg);\n  }\n  to{\n    transform:rotate(360deg);\n  }\n}\n.bp5-spinner{\n  align-items:center;\n  display:flex;\n  justify-content:center;\n  overflow:visible;\n  vertical-align:middle;\n}\n.bp5-spinner svg{\n  display:block;\n}\n.bp5-spinner path{\n  fill-opacity:0;\n}\n.bp5-spinner .bp5-spinner-head{\n  stroke:rgba(95, 107, 124, 0.8);\n  stroke-linecap:round;\n  transform-origin:center;\n  transition:stroke-dashoffset 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-spinner .bp5-spinner-track{\n  stroke:rgba(95, 107, 124, 0.2);\n}\n\n.bp5-spinner-animation{\n  animation:pt-spinner-animation 500ms linear infinite;\n}\n.bp5-no-spin > .bp5-spinner-animation{\n  animation:none;\n}\n\n.bp5-dark .bp5-spinner .bp5-spinner-head{\n  stroke:#8f99a8;\n}\n.bp5-dark .bp5-spinner .bp5-spinner-track{\n  stroke:rgba(17, 20, 24, 0.5);\n}\n\n.bp5-spinner.bp5-intent-primary .bp5-spinner-head{\n  stroke:#2d72d2;\n}\n\n.bp5-spinner.bp5-intent-success .bp5-spinner-head{\n  stroke:#238551;\n}\n\n.bp5-spinner.bp5-intent-warning .bp5-spinner-head{\n  stroke:#c87619;\n}\n\n.bp5-spinner.bp5-intent-danger .bp5-spinner-head{\n  stroke:#cd4246;\n}\n.bp5-tabs:not(.bp5-vertical).bp5-fill{\n  height:100%;\n}\n.bp5-tabs:not(.bp5-vertical).bp5-fill .bp5-tab-list{\n  height:100%;\n}\n\n.bp5-tabs.bp5-vertical{\n  display:flex;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list{\n  align-items:flex-start;\n  flex-direction:column;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab{\n  align-items:center;\n  border-radius:2px;\n  display:flex;\n  padding:0 10px;\n  width:100%;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab[aria-selected=true]{\n  background-color:rgba(45, 114, 210, 0.2);\n  box-shadow:none;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab[aria-selected=true]{\n    background-color:highlight;\n    color:#111418;\n  }\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n  background-color:rgba(45, 114, 210, 0.2);\n  border-radius:2px;\n  height:auto;\n  inset:0;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-panel{\n  margin-top:0;\n  padding-left:20px;\n}\n\n.bp5-tab-list{\n  align-items:flex-end;\n  border:none;\n  -moz-column-gap:20px;\n       column-gap:20px;\n  display:flex;\n  flex:0 0 auto;\n  list-style:none;\n  margin:0;\n  padding:0;\n  position:relative;\n}\n.bp5-tab-list .bp5-tabs:not(.bp5-vertical) > .bp5-flex-expander{\n  margin-right:-20px;\n}\n\n.bp5-tab{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  align-items:center;\n  align-self:stretch;\n  color:#1c2127;\n  cursor:pointer;\n  display:flex;\n  flex:0 0 auto;\n  font-size:14px;\n  line-height:30px;\n  max-width:100%;\n  position:relative;\n  vertical-align:top;\n}\n.bp5-tab a{\n  color:inherit;\n  display:block;\n  text-decoration:none;\n}\n.bp5-tab-indicator-wrapper ~ .bp5-tab{\n  background-color:transparent !important;\n  box-shadow:none !important;\n}\n.bp5-tab[aria-disabled=true]{\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tab[aria-disabled=true]{\n    color:graytext;\n  }\n}\n.bp5-tab[aria-selected=true]{\n  border-radius:0;\n  box-shadow:inset 0 -3px 0 #215db0;\n}\n.bp5-tab[aria-selected=true], .bp5-tab:not([aria-disabled=true]):hover{\n  color:#215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tab[aria-selected=true], .bp5-tab:not([aria-disabled=true]):hover{\n    color:highlight;\n  }\n}\n.bp5-tab:focus{\n  -moz-outline-radius:0;\n}\n.bp5-large > .bp5-tab{\n  font-size:16px;\n  line-height:40px;\n}\n\n.bp5-tab-panel{\n  margin-top:20px;\n}\n.bp5-tab-panel[aria-hidden=true]{\n  display:none;\n}\n\n.bp5-tab-icon{\n  margin-right:7px;\n}\n\n.bp5-tab-tag{\n  margin-left:7px;\n}\n\n.bp5-tab-indicator-wrapper{\n  left:0;\n  pointer-events:none;\n  position:absolute;\n  top:0;\n  transform:translateX(0), translateY(0);\n  transition:height, transform, width;\n  transition-duration:200ms;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tab-indicator-wrapper .bp5-tab-indicator{\n  background-color:#215db0;\n  bottom:0;\n  height:3px;\n  left:0;\n  position:absolute;\n  right:0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n    background-color:highlight;\n  }\n}\n.bp5-tab-indicator-wrapper.bp5-no-animation{\n  transition:none;\n}\n.bp5-tabs:not(.bp5-vertical) .bp5-tab-indicator-wrapper{\n  height:100%;\n}\n\n.bp5-dark .bp5-tab{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-tab[aria-disabled=true]{\n  color:rgba(171, 179, 191, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-tab[aria-disabled=true]{\n    color:graytext;\n  }\n}\n.bp5-dark .bp5-tab[aria-selected=true]{\n  box-shadow:inset 0 -3px 0 #8abbff;\n}\n.bp5-dark .bp5-tab[aria-selected=true], .bp5-dark .bp5-tab:not([aria-disabled=true]):hover{\n  color:#8abbff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-tab[aria-selected=true], .bp5-dark .bp5-tab:not([aria-disabled=true]):hover{\n    color:highlight;\n  }\n}\n.bp5-dark .bp5-tab-indicator{\n  background-color:#8abbff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-tab-indicator{\n    background-color:highlight;\n  }\n}\n\n.bp5-flex-expander{\n  flex:1 1;\n}\n.bp5-tag{\n  display:inline-flex;\n  flex-direction:row;\n  align-items:center;\n  background-color:#5f6b7c;\n  border:none;\n  border-radius:2px;\n  box-shadow:none;\n  color:#ffffff;\n  font-size:12px;\n  line-height:16px;\n  max-width:100%;\n  min-height:20px;\n  min-width:20px;\n  padding:2px 6px;\n  position:relative;\n}\n.bp5-tag > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-tag > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-tag::before,\n.bp5-tag > *{\n  margin-right:4px;\n}\n.bp5-tag:empty::before,\n.bp5-tag > :last-child{\n  margin-right:0;\n}\n.bp5-tag:focus{\n  outline:rgba(33, 93, 176, 0.752) solid 2px;\n  outline-offset:0;\n  -moz-outline-radius:6px;\n}\n.bp5-dark .bp5-tag:focus{\n  outline-color:rgba(138, 187, 255, 0.752);\n}\n.bp5-tag.bp5-interactive{\n  cursor:pointer;\n}\n.bp5-tag.bp5-interactive:hover{\n  background:#404854;\n}\n.bp5-tag.bp5-interactive:active, .bp5-tag.bp5-interactive.bp5-active{\n  background:#383e47;\n}\n.bp5-tag.bp5-round{\n  border-radius:30px;\n  padding-left:8px;\n  padding-right:8px;\n}\n.bp5-tag > .bp5-icon, .bp5-tag .bp5-icon-standard, .bp5-tag .bp5-icon-large{\n  fill:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tag{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-tag.bp5-large, .bp5-large .bp5-tag{\n  font-size:14px;\n  line-height:20px;\n  min-height:30px;\n  min-width:30px;\n  padding:5px 10px;\n}\n.bp5-tag.bp5-large::before,\n.bp5-tag.bp5-large > *, .bp5-large .bp5-tag::before,\n.bp5-large .bp5-tag > *{\n  margin-right:7px;\n}\n.bp5-tag.bp5-large:empty::before,\n.bp5-tag.bp5-large > :last-child, .bp5-large .bp5-tag:empty::before,\n.bp5-large .bp5-tag > :last-child{\n  margin-right:0;\n}\n.bp5-tag.bp5-large.bp5-round, .bp5-large .bp5-tag.bp5-round{\n  padding-left:12px;\n  padding-right:12px;\n}\n.bp5-tag.bp5-intent-primary{\n  background:#2d72d2;\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-primary.bp5-interactive:hover{\n  background-color:#215db0;\n}\n.bp5-tag.bp5-intent-primary.bp5-interactive:active, .bp5-tag.bp5-intent-primary.bp5-interactive.bp5-active{\n  background-color:#184a90;\n}\n.bp5-tag.bp5-intent-primary .bp5-tag-remove{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-tag.bp5-intent-primary .bp5-tag-remove:hover, .bp5-tag.bp5-intent-primary .bp5-tag-remove:active{\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-success{\n  background:#238551;\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-success.bp5-interactive:hover{\n  background-color:#1c6e42;\n}\n.bp5-tag.bp5-intent-success.bp5-interactive:active, .bp5-tag.bp5-intent-success.bp5-interactive.bp5-active{\n  background-color:#165a36;\n}\n.bp5-tag.bp5-intent-success .bp5-tag-remove{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-tag.bp5-intent-success .bp5-tag-remove:hover, .bp5-tag.bp5-intent-success .bp5-tag-remove:active{\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-warning{\n  background:#fbb360;\n  color:#1c2127;\n}\n.bp5-tag.bp5-intent-warning.bp5-interactive:hover{\n  background-color:#ec9a3c;\n}\n.bp5-tag.bp5-intent-warning.bp5-interactive:active, .bp5-tag.bp5-intent-warning.bp5-interactive.bp5-active{\n  background-color:#c87619;\n}\n.bp5-tag.bp5-intent-warning .bp5-tag-remove{\n  color:rgba(28, 33, 39, 0.7);\n}\n.bp5-tag.bp5-intent-warning .bp5-tag-remove:hover, .bp5-tag.bp5-intent-warning .bp5-tag-remove:active{\n  color:#1c2127;\n}\n.bp5-tag.bp5-intent-danger{\n  background:#cd4246;\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-danger.bp5-interactive:hover{\n  background-color:#ac2f33;\n}\n.bp5-tag.bp5-intent-danger.bp5-interactive:active, .bp5-tag.bp5-intent-danger.bp5-interactive.bp5-active{\n  background-color:#8e292c;\n}\n.bp5-tag.bp5-intent-danger .bp5-tag-remove{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-tag.bp5-intent-danger .bp5-tag-remove:hover, .bp5-tag.bp5-intent-danger .bp5-tag-remove:active{\n  color:#ffffff;\n}\n.bp5-tag.bp5-fill{\n  display:flex;\n  width:100%;\n}\n.bp5-tag.bp5-minimal > .bp5-icon, .bp5-tag.bp5-minimal .bp5-icon-standard, .bp5-tag.bp5-minimal .bp5-icon-large{\n  fill:#5f6b7c;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]){\n  background-color:rgba(143, 153, 168, 0.15);\n  color:#1c2127;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive{\n  cursor:pointer;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover{\n  background-color:rgba(143, 153, 168, 0.3);\n  color:#111418;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active, .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active{\n  background-color:rgba(143, 153, 168, 0.35);\n  color:#111418;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove{\n  color:#5f6b7c;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove:hover, .bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove:active{\n  color:#404854;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]){\n  background-color:rgba(143, 153, 168, 0.15);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive{\n  cursor:pointer;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover{\n  background-color:rgba(143, 153, 168, 0.3);\n  color:#ffffff;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active{\n  background-color:rgba(143, 153, 168, 0.35);\n  color:#ffffff;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove:active{\n  color:#d3d8de;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary{\n  background-color:rgba(45, 114, 210, 0.1);\n  color:#215db0;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-large{\n  fill:#215db0;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#184a90;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active, .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active{\n  background-color:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove{\n  color:#215db0;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:hover, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:active{\n  color:#184a90;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.1);\n  color:#1c6e42;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-large{\n  fill:#1c6e42;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#165a36;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active, .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active{\n  background-color:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove{\n  color:#1c6e42;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:hover, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:active{\n  color:#165a36;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.1);\n  color:#935610;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-large{\n  fill:#935610;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#77450d;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active, .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active{\n  background-color:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove{\n  color:#935610;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:hover, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:active{\n  color:#77450d;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.1);\n  color:#ac2f33;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-large{\n  fill:#ac2f33;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#8e292c;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active, .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active{\n  background-color:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove{\n  color:#ac2f33;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:hover, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:active{\n  color:#8e292c;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n  background-color:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active{\n  background-color:rgba(45, 114, 210, 0.35);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove{\n  color:#8abbff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:active{\n  color:#99c4ff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n  background-color:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active{\n  background-color:rgba(35, 133, 81, 0.35);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:active{\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n  background-color:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active{\n  background-color:rgba(200, 118, 25, 0.35);\n  color:#f5c186;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove{\n  color:#fbb360;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:active{\n  color:#f5c186;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n  background-color:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active{\n  background-color:rgba(205, 66, 70, 0.35);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove{\n  color:#fa999c;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:active{\n  color:#ffa1a4;\n}\n\n.bp5-tag-remove{\n  background:none;\n  border:none;\n  color:rgba(255, 255, 255, 0.7);\n  cursor:pointer;\n  display:flex;\n  margin-bottom:-2px;\n  margin-right:-6px !important;\n  margin-top:-2px;\n  padding:2px;\n  padding-left:0;\n}\n.bp5-tag-remove:hover{\n  background:none;\n  color:inherit;\n  text-decoration:none;\n}\n.bp5-tag-remove .bp5-icon:first-child{\n  color:inherit;\n}\n.bp5-tag-remove:hover, .bp5-tag-remove:active{\n  color:#ffffff;\n}\n.bp5-tag-remove:empty::before{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  content:\"\\f2ba\";\n}\n.bp5-large .bp5-tag-remove{\n  margin-right:-10px !important;\n  padding:0 5px 0 0;\n}\n.bp5-large .bp5-tag-remove:empty::before{\n  font-family:\"blueprint-icons-20\", sans-serif;\n  font-size:20px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:20px;\n  line-height:1;\n  width:20px;\n}\n.bp5-compound-tag{\n  background:none;\n  padding:0;\n}\n.bp5-compound-tag .bp5-compound-tag-left{\n  background-color:#404854;\n}\n.bp5-compound-tag .bp5-compound-tag-right{\n  background-color:#5f6b7c;\n}\n.bp5-compound-tag.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#383e47;\n}\n.bp5-compound-tag.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#404854;\n}\n.bp5-compound-tag.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#2f343c;\n}\n.bp5-compound-tag.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#383e47;\n}\n.bp5-compound-tag .bp5-compound-tag-left,\n.bp5-compound-tag .bp5-compound-tag-right{\n  align-items:center;\n  display:inline-flex;\n  padding:2px 4px;\n}\n.bp5-compound-tag .bp5-compound-tag-left{\n  border-bottom-left-radius:2px;\n  border-top-left-radius:2px;\n  margin-right:0;\n}\n.bp5-compound-tag .bp5-compound-tag-left > .bp5-icon, .bp5-compound-tag .bp5-compound-tag-left .bp5-icon-standard, .bp5-compound-tag .bp5-compound-tag-left .bp5-icon-large{\n  margin-right:4px;\n}\n.bp5-compound-tag .bp5-compound-tag-right{\n  border-bottom-right-radius:2px;\n  border-top-right-radius:2px;\n  flex-grow:1;\n  padding:2px 4px;\n}\n.bp5-compound-tag .bp5-compound-tag-right > .bp5-icon, .bp5-compound-tag .bp5-compound-tag-right .bp5-icon-standard, .bp5-compound-tag .bp5-compound-tag-right .bp5-icon-large{\n  margin-left:4px;\n}\n.bp5-compound-tag .bp5-compound-tag-right .bp5-compound-tag-right-content{\n  flex-grow:1;\n}\n.bp5-compound-tag .bp5-compound-tag-right .bp5-tag-remove{\n  margin-left:2px;\n  margin-right:-4px !important;\n}\n.bp5-compound-tag.bp5-round{\n  padding:0;\n}\n.bp5-compound-tag.bp5-round .bp5-compound-tag-left{\n  border-bottom-left-radius:20px;\n  border-top-left-radius:20px;\n  padding-left:8px;\n}\n.bp5-compound-tag.bp5-round .bp5-compound-tag-right{\n  border-bottom-right-radius:20px;\n  border-top-right-radius:20px;\n  padding-right:8px;\n}\n.bp5-compound-tag.bp5-large{\n  padding:0;\n}\n.bp5-compound-tag.bp5-large .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-large .bp5-compound-tag-right{\n  padding:5px 8px;\n}\n.bp5-compound-tag.bp5-large .bp5-compound-tag-left > .bp5-icon, .bp5-compound-tag.bp5-large .bp5-compound-tag-left .bp5-icon-standard, .bp5-compound-tag.bp5-large .bp5-compound-tag-left .bp5-icon-large{\n  margin-right:7px;\n}\n.bp5-compound-tag.bp5-large .bp5-compound-tag-right > .bp5-icon, .bp5-compound-tag.bp5-large .bp5-compound-tag-right .bp5-icon-standard, .bp5-compound-tag.bp5-large .bp5-compound-tag-right .bp5-icon-large{\n  margin-left:7px;\n}\n.bp5-compound-tag.bp5-large .bp5-tag-remove{\n  margin-left:7px;\n  margin-right:-10px !important;\n}\n.bp5-compound-tag.bp5-large.bp5-round{\n  padding:0;\n}\n.bp5-compound-tag.bp5-large.bp5-round .bp5-compound-tag-left{\n  border-bottom-left-radius:30px;\n  border-top-left-radius:30px;\n  padding-left:12px;\n}\n.bp5-compound-tag.bp5-large.bp5-round .bp5-compound-tag-right{\n  border-bottom-right-radius:30px;\n  border-top-right-radius:30px;\n  padding-right:12px;\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]){\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.2);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.1);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.3);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.2);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.4);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary{\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success{\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning{\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger{\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]){\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary{\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success{\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning{\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger{\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.35);\n}\n.bp5-compound-tag.bp5-intent-primary{\n  background:none;\n}\n.bp5-compound-tag.bp5-intent-primary .bp5-compound-tag-left{\n  background-color:#215db0;\n}\n.bp5-compound-tag.bp5-intent-primary .bp5-compound-tag-right{\n  background-color:#2d72d2;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#184a90;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#215db0;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#11376b;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#184a90;\n}\n.bp5-compound-tag.bp5-intent-success{\n  background:none;\n}\n.bp5-compound-tag.bp5-intent-success .bp5-compound-tag-left{\n  background-color:#1c6e42;\n}\n.bp5-compound-tag.bp5-intent-success .bp5-compound-tag-right{\n  background-color:#238551;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#165a36;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#1c6e42;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#0f3e25;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#165a36;\n}\n.bp5-compound-tag.bp5-intent-warning{\n  background:none;\n}\n.bp5-compound-tag.bp5-intent-warning .bp5-compound-tag-left{\n  background-color:#ec9a3c;\n}\n.bp5-compound-tag.bp5-intent-warning .bp5-compound-tag-right{\n  background-color:#fbb360;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#c87619;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#ec9a3c;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#935610;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#c87619;\n}\n.bp5-compound-tag.bp5-intent-danger{\n  background:none;\n}\n.bp5-compound-tag.bp5-intent-danger .bp5-compound-tag-left{\n  background-color:#ac2f33;\n}\n.bp5-compound-tag.bp5-intent-danger .bp5-compound-tag-right{\n  background-color:#cd4246;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#8e292c;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#ac2f33;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#782326;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#8e292c;\n}\n\n.bp5-tag-input{\n  display:flex;\n  flex-direction:row;\n  align-items:flex-start;\n  cursor:text;\n  height:auto;\n  line-height:inherit;\n  min-height:30px;\n  padding-left:5px;\n  padding-right:0;\n}\n.bp5-tag-input > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-tag-input > .bp5-tag-input-values{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-tag-input .bp5-tag-input-icon{\n  color:#5f6b7c;\n  margin-left:2px;\n  margin-right:7px;\n  margin-top:7px;\n}\n.bp5-tag-input .bp5-tag-input-values{\n  display:flex;\n  flex-direction:row;\n  align-items:center;\n  align-self:stretch;\n  flex-wrap:wrap;\n  margin-right:7px;\n  margin-top:5px;\n  min-width:0;\n  position:relative;\n}\n.bp5-tag-input .bp5-tag-input-values > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-tag-input .bp5-tag-input-values > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-tag-input .bp5-tag-input-values::before,\n.bp5-tag-input .bp5-tag-input-values > *{\n  margin-right:5px;\n}\n.bp5-tag-input .bp5-tag-input-values:empty::before,\n.bp5-tag-input .bp5-tag-input-values > :last-child{\n  margin-right:0;\n}\n.bp5-tag-input .bp5-tag-input-values:first-child .bp5-tag ~ .bp5-input-ghost{\n  padding-left:0;\n}\n.bp5-tag-input .bp5-tag-input-values:first-child .bp5-input-ghost{\n  padding-left:5px;\n}\n.bp5-tag-input .bp5-tag-input-values > *{\n  margin-bottom:5px;\n}\n.bp5-tag-input .bp5-tag{\n  overflow-wrap:break-word;\n}\n.bp5-tag-input .bp5-tag.bp5-active{\n  outline:rgba(33, 93, 176, 0.752) solid 2px;\n  outline-offset:0;\n  -moz-outline-radius:6px;\n}\n.bp5-dark .bp5-tag-input .bp5-tag.bp5-active{\n  outline-color:rgba(138, 187, 255, 0.752);\n}\n.bp5-tag-input .bp5-input-ghost{\n  flex:1 1 auto;\n  line-height:20px;\n  width:80px;\n}\n.bp5-tag-input .bp5-input-ghost:disabled, .bp5-tag-input .bp5-input-ghost.bp5-disabled{\n  cursor:not-allowed;\n}\n.bp5-tag-input .bp5-button,\n.bp5-tag-input .bp5-spinner{\n  margin:3px;\n  margin-left:0;\n}\n.bp5-tag-input .bp5-button{\n  min-height:24px;\n  min-width:24px;\n  padding:0 7px;\n}\n.bp5-tag-input.bp5-large{\n  height:auto;\n  min-height:40px;\n}\n.bp5-tag-input.bp5-large::before,\n.bp5-tag-input.bp5-large > *{\n  margin-right:10px;\n}\n.bp5-tag-input.bp5-large:empty::before,\n.bp5-tag-input.bp5-large > :last-child{\n  margin-right:0;\n}\n.bp5-tag-input.bp5-large .bp5-tag-input-icon{\n  margin-left:5px;\n  margin-top:10px;\n}\n.bp5-tag-input.bp5-large .bp5-input-ghost{\n  line-height:30px;\n}\n.bp5-tag-input.bp5-large .bp5-button{\n  min-height:30px;\n  min-width:30px;\n  padding:5px 10px;\n  margin:5px;\n  margin-left:0;\n}\n.bp5-tag-input.bp5-large .bp5-spinner{\n  margin:8px;\n  margin-left:0;\n}\n.bp5-tag-input.bp5-active{\n  background-color:#ffffff;\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-primary{\n  box-shadow:inset 0 0 0 1px rgba(45, 114, 210, 0.752), 0 0 0 1px rgba(45, 114, 210, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-success{\n  box-shadow:inset 0 0 0 1px rgba(35, 133, 81, 0.752), 0 0 0 1px rgba(35, 133, 81, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-warning{\n  box-shadow:inset 0 0 0 1px rgba(200, 118, 25, 0.752), 0 0 0 1px rgba(200, 118, 25, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-danger{\n  box-shadow:inset 0 0 0 1px rgba(205, 66, 70, 0.752), 0 0 0 1px rgba(205, 66, 70, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-tag-input .bp5-tag-input-icon, .bp5-tag-input.bp5-dark .bp5-tag-input-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost, .bp5-tag-input.bp5-dark .bp5-input-ghost{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost::-moz-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-moz-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost:-ms-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost:-ms-input-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost::placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag-input.bp5-active, .bp5-tag-input.bp5-dark.bp5-active{\n  background-color:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-primary, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-primary{\n  box-shadow:inset 0 0 0 1px rgba(76, 144, 240, 0.752), 0 0 0 1px rgba(76, 144, 240, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-success, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-success{\n  box-shadow:inset 0 0 0 1px rgba(50, 164, 103, 0.752), 0 0 0 1px rgba(50, 164, 103, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-warning, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-warning{\n  box-shadow:inset 0 0 0 1px rgba(236, 154, 60, 0.752), 0 0 0 1px rgba(236, 154, 60, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-danger, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-danger{\n  box-shadow:inset 0 0 0 1px rgba(231, 106, 110, 0.752), 0 0 0 1px rgba(231, 106, 110, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n\n.bp5-input-ghost{\n  background:none;\n  border:none;\n  box-shadow:none;\n  padding:0;\n}\n.bp5-input-ghost::-moz-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input-ghost:-ms-input-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input-ghost::placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input-ghost:focus{\n  outline:none !important;\n}\n\n.bp5-resizable-input-span{\n  max-height:0;\n  max-width:100%;\n  min-width:80px;\n  opacity:0;\n  overflow:hidden;\n  position:absolute;\n  white-space:nowrap;\n  z-index:-1;\n}\n\n.bp5-toast{\n  align-items:flex-start;\n  background-color:#ffffff;\n  border-radius:2px;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  display:flex;\n  margin:20px 0 0;\n  max-width:min(500px, 100%);\n  min-width:min(300px, 100%);\n  pointer-events:all;\n  position:relative !important;\n}\n.bp5-toast.bp5-toast-enter, .bp5-toast.bp5-toast-appear{\n  transform:translateY(-40px);\n}\n.bp5-toast.bp5-toast-enter-active, .bp5-toast.bp5-toast-appear-active{\n  transform:translateY(0);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-toast.bp5-toast-enter ~ .bp5-toast, .bp5-toast.bp5-toast-appear ~ .bp5-toast{\n  transform:translateY(-40px);\n}\n.bp5-toast.bp5-toast-enter-active ~ .bp5-toast, .bp5-toast.bp5-toast-appear-active ~ .bp5-toast{\n  transform:translateY(0);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-toast.bp5-toast-exit{\n  opacity:1;\n  filter:blur(0);\n}\n.bp5-toast.bp5-toast-exit-active{\n  opacity:0;\n  filter:blur(10px);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:opacity, filter;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-toast.bp5-toast-exit ~ .bp5-toast{\n  transform:translateY(0);\n}\n.bp5-toast.bp5-toast-exit-active ~ .bp5-toast{\n  transform:translateY(-40px);\n  transition-delay:50ms;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-toast .bp5-button-group{\n  flex:0 0 auto;\n  padding:5px;\n  padding-left:0;\n}\n.bp5-toast > .bp5-icon{\n  color:#5f6b7c;\n  margin:12px;\n  margin-right:0;\n}\n.bp5-toast.bp5-dark, .bp5-dark .bp5-toast{\n  background-color:#383e47;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-toast.bp5-dark > .bp5-icon, .bp5-dark .bp5-toast > .bp5-icon{\n  color:#abb3bf;\n}\n.bp5-toast.bp5-dark .bp5-button .bp5-icon, .bp5-dark .bp5-toast .bp5-button .bp5-icon{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast[class*=bp5-intent-] a{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast[class*=bp5-intent-] a:hover{\n  color:#ffffff;\n}\n.bp5-toast[class*=bp5-intent-] > .bp5-icon{\n  color:#ffffff;\n}\n.bp5-toast.bp5-intent-primary{\n  background-color:#2d72d2;\n  color:#ffffff;\n}\n.bp5-toast.bp5-intent-primary .bp5-icon:first-child{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast.bp5-intent-primary .bp5-button{\n  background-color:#2d72d2 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-primary .bp5-button:hover{\n  background-color:#215db0 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-primary .bp5-button:active{\n  background-color:#184a90 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-primary .bp5-button:focus{\n  outline-color:rgba(255, 255, 255, 0.75);\n}\n.bp5-toast.bp5-intent-primary .bp5-button:last-child > .bp5-icon-cross{\n  color:rgba(255, 255, 255, 0.7) !important;\n}\n.bp5-toast.bp5-intent-success{\n  background-color:#238551;\n  color:#ffffff;\n}\n.bp5-toast.bp5-intent-success .bp5-icon:first-child{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast.bp5-intent-success .bp5-button{\n  background-color:#238551 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-success .bp5-button:hover{\n  background-color:#1c6e42 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-success .bp5-button:active{\n  background-color:#165a36 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-success .bp5-button:focus{\n  outline-color:rgba(255, 255, 255, 0.75);\n}\n.bp5-toast.bp5-intent-success .bp5-button:last-child > .bp5-icon-cross{\n  color:rgba(255, 255, 255, 0.7) !important;\n}\n.bp5-toast.bp5-intent-warning{\n  background-color:#fbb360;\n  color:#1c2127;\n}\n.bp5-toast.bp5-intent-warning .bp5-icon:first-child{\n  color:rgba(28, 33, 39, 0.7);\n}\n.bp5-toast.bp5-intent-warning .bp5-button{\n  background-color:#fbb360 !important;\n  color:#1c2127 !important;\n}\n.bp5-toast.bp5-intent-warning .bp5-button:hover{\n  background-color:#ec9a3c !important;\n  color:#1c2127 !important;\n}\n.bp5-toast.bp5-intent-warning .bp5-button:active{\n  background-color:#c87619 !important;\n  color:#1c2127 !important;\n}\n.bp5-toast.bp5-intent-warning .bp5-button:focus{\n  outline-color:rgba(28, 33, 39, 0.75);\n}\n.bp5-toast.bp5-intent-warning .bp5-button:last-child > .bp5-icon-cross{\n  color:rgba(28, 33, 39, 0.7) !important;\n}\n.bp5-toast.bp5-intent-danger{\n  background-color:#cd4246;\n  color:#ffffff;\n}\n.bp5-toast.bp5-intent-danger .bp5-icon:first-child{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast.bp5-intent-danger .bp5-button{\n  background-color:#cd4246 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-danger .bp5-button:hover{\n  background-color:#ac2f33 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-danger .bp5-button:active{\n  background-color:#8e292c !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-danger .bp5-button:focus{\n  outline-color:rgba(255, 255, 255, 0.75);\n}\n.bp5-toast.bp5-intent-danger .bp5-button:last-child > .bp5-icon-cross{\n  color:rgba(255, 255, 255, 0.7) !important;\n}\n\n.bp5-toast-message{\n  flex:1 1 auto;\n  padding:11px;\n  word-break:break-word;\n}\n\n.bp5-toast-container{\n  align-items:center;\n  display:flex !important;\n  flex-direction:column;\n  left:0;\n  overflow:hidden;\n  padding:0 20px 20px;\n  pointer-events:none;\n  right:0;\n  z-index:40;\n}\n.bp5-toast-container.bp5-toast-container-in-portal{\n  position:fixed;\n}\n.bp5-toast-container.bp5-toast-container-inline{\n  position:absolute;\n}\n.bp5-toast-container.bp5-toast-container-top{\n  top:0;\n}\n.bp5-toast-container.bp5-toast-container-bottom{\n  bottom:0;\n  flex-direction:column-reverse;\n  top:auto;\n}\n.bp5-toast-container.bp5-toast-container-left{\n  align-items:flex-start;\n}\n.bp5-toast-container.bp5-toast-container-right{\n  align-items:flex-end;\n}\n\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active), .bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active) ~ .bp5-toast, .bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active), .bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active) ~ .bp5-toast, .bp5-toast-container-bottom .bp5-toast.bp5-toast-exit-active ~ .bp5-toast, .bp5-toast-container-bottom .bp5-toast.bp5-toast-leave-active ~ .bp5-toast{\n  transform:translateY(60px);\n}\n.bp5-tree .bp5-icon, .bp5-tree .bp5-icon-standard, .bp5-tree .bp5-icon-large{\n  color:#5f6b7c;\n}\n.bp5-tree .bp5-icon.bp5-intent-primary, .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-tree .bp5-icon-large.bp5-intent-primary{\n  color:#2d72d2;\n}\n.bp5-tree .bp5-icon.bp5-intent-success, .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-tree .bp5-icon-large.bp5-intent-success{\n  color:#238551;\n}\n.bp5-tree .bp5-icon.bp5-intent-warning, .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-tree .bp5-icon-large.bp5-intent-warning{\n  color:#c87619;\n}\n.bp5-tree .bp5-icon.bp5-intent-danger, .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-tree .bp5-icon-large.bp5-intent-danger{\n  color:#cd4246;\n}\n\n.bp5-tree-node-list{\n  list-style:none;\n  margin:0;\n  padding-left:0;\n}\n\n.bp5-tree-root{\n  background-color:transparent;\n  cursor:default;\n  padding-left:0;\n  position:relative;\n}\n\n.bp5-tree-node-content-0{\n  padding-left:0px;\n}\n\n.bp5-tree-node-content-1{\n  padding-left:23px;\n}\n\n.bp5-tree-node-content-2{\n  padding-left:46px;\n}\n\n.bp5-tree-node-content-3{\n  padding-left:69px;\n}\n\n.bp5-tree-node-content-4{\n  padding-left:92px;\n}\n\n.bp5-tree-node-content-5{\n  padding-left:115px;\n}\n\n.bp5-tree-node-content-6{\n  padding-left:138px;\n}\n\n.bp5-tree-node-content-7{\n  padding-left:161px;\n}\n\n.bp5-tree-node-content-8{\n  padding-left:184px;\n}\n\n.bp5-tree-node-content-9{\n  padding-left:207px;\n}\n\n.bp5-tree-node-content-10{\n  padding-left:230px;\n}\n\n.bp5-tree-node-content-11{\n  padding-left:253px;\n}\n\n.bp5-tree-node-content-12{\n  padding-left:276px;\n}\n\n.bp5-tree-node-content-13{\n  padding-left:299px;\n}\n\n.bp5-tree-node-content-14{\n  padding-left:322px;\n}\n\n.bp5-tree-node-content-15{\n  padding-left:345px;\n}\n\n.bp5-tree-node-content-16{\n  padding-left:368px;\n}\n\n.bp5-tree-node-content-17{\n  padding-left:391px;\n}\n\n.bp5-tree-node-content-18{\n  padding-left:414px;\n}\n\n.bp5-tree-node-content-19{\n  padding-left:437px;\n}\n\n.bp5-tree-node-content-20{\n  padding-left:460px;\n}\n\n.bp5-tree-node-content{\n  align-items:center;\n  background:none;\n  display:flex;\n  height:30px;\n  padding-right:5px;\n  width:100%;\n}\n.bp5-tree-node-content:hover{\n  background-color:rgba(143, 153, 168, 0.15);\n}\n.bp5-tree-node-content:active{\n  background-color:rgba(143, 153, 168, 0.3);\n}\n\n.bp5-tree-node-caret,\n.bp5-tree-node-caret-none{\n  min-width:30px;\n}\n\n.bp5-tree-node-caret{\n  color:#5f6b7c;\n  cursor:pointer;\n  padding:7px;\n  transform:rotate(0deg);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tree-node-caret:hover{\n  color:#1c2127;\n}\n.bp5-dark .bp5-tree-node-caret{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tree-node-caret:hover{\n  color:#f6f7f9;\n}\n.bp5-tree-node-caret:hover{\n  color:#1c2127;\n}\n.bp5-tree-node-caret.bp5-tree-node-caret-open{\n  transform:rotate(90deg);\n}\n.bp5-tree-node-caret.bp5-icon-standard::before{\n  content:\"\\f14d\";\n}\n\n.bp5-tree-node-icon{\n  margin-right:7px;\n  position:relative;\n}\n\n.bp5-tree-node-label{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  flex:1 1 auto;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-tree-node-label span{\n  display:inline;\n}\n\n.bp5-tree-node-secondary-label{\n  padding:0 5px;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-tree-node-secondary-label .bp5-popover-wrapper,\n.bp5-tree-node-secondary-label .bp5-popover-target{\n  align-items:center;\n  display:flex;\n}\n\n.bp5-tree-node.bp5-disabled .bp5-tree-node-content{\n  background-color:inherit;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-tree-node.bp5-disabled .bp5-tree-node-caret,\n.bp5-tree-node.bp5-disabled .bp5-tree-node-icon{\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#2d72d2;\n}\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content,\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-standard, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-large{\n  color:#ffffff;\n}\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret::before{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret:hover::before{\n  color:#ffffff;\n}\n\n.bp5-tree.bp5-compact .bp5-tree-node-content{\n  height:24px;\n}\n.bp5-tree.bp5-compact .bp5-tree-node-caret{\n  margin-right:3px;\n  min-width:24px;\n  padding:4px;\n}\n\n.bp5-dark .bp5-tree-node-content:hover{\n  background-color:rgba(95, 107, 124, 0.3);\n}\n.bp5-dark .bp5-tree .bp5-icon, .bp5-dark .bp5-tree .bp5-icon-standard, .bp5-dark .bp5-tree .bp5-icon-large{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-tree-node:not(.bp5-disabled) .bp5-tree-node-caret:hover{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#2d72d2;\n}\n.bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon, .bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-standard, .bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-large{\n  color:#ffffff;\n}"
  },
  {
    "path": "www/dist/static/global.css",
    "content": "@font-face {\n  font-family: \"Roboto Mono\";\n  src: url(\"RobotoMono-Regular.ttf\") format(\"truetype\");\n  font-weight: 400;\n}\n\n@font-face {\n  font-family: \"Roboto Mono\";\n  src: url(\"RobotoMono-Medium.ttf\") format(\"truetype\");\n  font-weight: 500;\n}\n\n*, *:before, *:after {\n  -webkit-box-sizing: border-box;\n  -webkit-appearance: none;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n}\n\nhtml {\n  scrollbar-color: #5f6b7c #ffffff;\n}\nhtml.dark3-scroll {\n  scrollbar-color: #555b63 #30404d;\n}\nhtml.dark5-scroll {\n  scrollbar-color: #555b63 #2f343c;\n}\n\nbody {\n  font-family: Roboto, Noto, sans-serif;\n  min-width: 1100px;\n}\n\nbody {\n  background-color: #f6f7f9;\n}\nbody.bp5-dark {\n  background-color: #252a31;\n}\n\ncode {\n  font-family: Consolas, Menlo, 'Roboto Mono', 'DejaVu Sans Mono';\n  font-weight: 400;\n  white-space: pre-wrap;\n  word-wrap: break-word;\n  overflow-wrap: break-word;\n}\n\nh2 {\n  font-size: 28px;\n}\n\nh5 {\n  font-size: 16px;\n}\n\n.no-select {\n  user-select: none;\n}\n\n.logo-dark {\n  display: none;\n}\n\n.hljs {\n  border-radius: 3px;\n}\n\ncode.intent-primary {\n  background-color: #ecf8ff;\n}\n.bp5-dark code.intent-primary {\n  background-color: #183345;\n}\n\ncode.intent-secondary {\n  background-color: #fff1e7;\n}\n.bp5-dark code.intent-secondary {\n  background-color: #3a322c\n}\n\n.bp5-dark .logo-light {\n  display: none;\n}\n\n.bp5-dark .logo-dark {\n  display: block;\n}\n\n.bp5-button:disabled, .bp5-button.bp5-disabled {\n  cursor: default !important;\n}\n\n.bp5-input:disabled, .bp5-input.bp5-disabled {\n  cursor: default !important;\n}\n\n.bp5-select select:disabled, .bp5-select select.bp5-disabled {\n  cursor: default !important;\n}\n\n.bp5-text-intent-primary {\n  color: #106ba3;\n}\n.bp5-dark .bp5-text-intent-primary {\n  color: #2b95d6;\n}\n\n.bp5-text-intent-success {\n  color: #0d8050;\n}\n.bp5-dark .bp5-text-intent-success {\n  color: #15b371;\n}\n\n.bp5-text-intent-warning {\n  color: #bf7326;\n}\n.bp5-dark .bp5-text-intent-warning {\n  color: #f29d49;\n}\n\n.bp5-text-intent-danger {\n  color: #c23030;\n}\n.bp5-dark .bp5-text-intent-danger {\n  color: #f55656;\n}\n\n.bp5-border {\n  border-width: 1px;\n  border-color: rgba(16, 22, 26, 0.15);\n}\n\n.bp5-dark .bp5-border {\n  border-color: rgba(255, 255, 255, 0.15);\n}\n\n.bp5-button.bp5-button-empty[class*=\"bp5-icon-\"] {\n  padding: 0;\n}\n\n.bp5-button.bp5-button-empty[class*=\"bp5-icon-\"]::before {\n  margin-right: 0;\n}\n\n.bp5-row .bp5-cell span {\n  vertical-align: middle;\n}\n\n.bp5-row .bp5-cell {\n  border-top: 1px solid rgba(16, 22, 26, 0.15);\n  border-bottom: 1px solid rgba(16, 22, 26, 0.15);\n}\n\n.bp5-row .bp5-cell:first-child {\n  border-top-left-radius: 3px;\n  border-bottom-left-radius: 3px;\n  border-left: 1px solid rgba(16, 22, 26, 0.15);\n}\n\n.bp5-row .bp5-cell:last-child {\n  border-top-right-radius: 3px;\n  border-bottom-right-radius: 3px;\n  border-right: 1px solid rgba(16, 22, 26, 0.15);\n}\n\n.bp5-dark .bp5-row .bp5-cell {\n  border-top: 1px solid rgba(16, 22, 26, 0.5);\n  border-bottom: 1px solid rgba(16, 22, 26, 0.5);\n}\n\n.bp5-dark .bp5-row .bp5-cell:first-child {\n  border-left: 1px solid rgba(16, 22, 26, 0.5);\n}\n\n.bp5-dark .bp5-row .bp5-cell:last-child {\n  border-right: 1px solid rgba(16, 22, 26, 0.5);\n}\n\n.bp5-progress-bar .bp5-loading-bar {\n  animation: bp5-loading-bar-frames 2s linear infinite;\n}\n\nlabel.bp5-label .bp5-button-group {\n  margin-top: 5px !important;\n}\n\n.bp5-select-popover .bp5-menu {\n  max-height: 400px;\n  overflow-y: auto;\n}\n\n@keyframes bp5-loading-bar-frames {\n  from {\n    left: -200px;\n    width: 30%;\n  }\n  50% {\n    width: 30%;\n  }\n  70% {\n    width: 70%;\n  }\n  80% {\n    left: 50%;\n  }\n  95% {\n    left: 120%;\n  }\n  to {\n    left: 100%;\n  }\n}\n\n.bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.06)\n}\n\n.bp5-dark .bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.13)\n}\n\n.bp5-toast-container.bp5-toast-container-bottom {\n  position: fixed !important;\n}\n.bp5-toast-container.bp5-toast-container-inline {\n  position: fixed !important;\n}\n\n.bp3-theme.bp5-dark .bp5-datepicker {\n  background: #30404d !important;\n}\n\n#chartjs-tooltip {\n   background-color: #fcfcfc;\n }\n\n.bp5-dark #chartjs-tooltip {\n  background-color: #232f38;\n}\n\n#chartjs-tooltip {\n  padding: 0 3px 3px 3px;\n}\n\n#chartjs-tooltip th {\n  font-size: 12px;\n}\n\n#chartjs-tooltip td {\n  font-size: 10px;\n  padding: 4px 4px 2px 6px;\n  position: relative;\n}\n\n#chartjs-tooltip td.line-box {\n  padding: 4px 4px 2px 17px;\n}\n\n#chartjs-tooltip td span {\n  width: 10px;\n  height: 10px;\n  border-width: 2px;\n  border-style: solid;\n  top: 4px;\n  left: 3px;\n  position: absolute;\n}\n\n/*******************************\n          Flex Layout\n*******************************/\n.layout.horizontal,\n.layout.horizontal-reverse,\n.layout.vertical,\n.layout.vertical-reverse {\n  display: -ms-flexbox;\n  display: -webkit-flex;\n  display: flex;\n}\n.layout.inline {\n  display: -ms-inline-flexbox;\n  display: -webkit-inline-flex;\n  display: inline-flex;\n}\n.layout.horizontal {\n  -ms-flex-direction: row;\n  -webkit-flex-direction: row;\n  flex-direction: row;\n}\n.layout.horizontal-reverse {\n  -ms-flex-direction: row-reverse;\n  -webkit-flex-direction: row-reverse;\n  flex-direction: row-reverse;\n}\n.layout.vertical {\n  -ms-flex-direction: column;\n  -webkit-flex-direction: column;\n  flex-direction: column;\n}\n.layout.vertical-reverse {\n  -ms-flex-direction: column-reverse;\n  -webkit-flex-direction: column-reverse;\n  flex-direction: column-reverse;\n}\n.layout.wrap {\n  -ms-flex-wrap: wrap;\n  -webkit-flex-wrap: wrap;\n  flex-wrap: wrap;\n}\n.layout.no-wrap {\n  -ms-flex-wrap: nowrap;\n  -webkit-flex-wrap: nowrap;\n  flex-wrap: nowrap;\n}\n.layout.wrap-reverse {\n  -ms-flex-wrap: wrap-reverse;\n  -webkit-flex-wrap: wrap-reverse;\n  flex-wrap: wrap-reverse;\n}\n.flex-auto {\n  -ms-flex: 1 1 auto;\n  -webkit-flex: 1 1 auto;\n  flex: 1 1 auto;\n}\n.flex-none {\n  -ms-flex: none;\n  -webkit-flex: none;\n  flex: none;\n}\n.flex,\n.flex-1 {\n  -ms-flex: 1;\n  -webkit-flex: 1;\n  flex: 1;\n}\n.flex-2 {\n  -ms-flex: 2;\n  -webkit-flex: 2;\n  flex: 2;\n}\n.flex-3 {\n  -ms-flex: 3;\n  -webkit-flex: 3;\n  flex: 3;\n}\n.flex-4 {\n  -ms-flex: 4;\n  -webkit-flex: 4;\n  flex: 4;\n}\n.flex-5 {\n  -ms-flex: 5;\n  -webkit-flex: 5;\n  flex: 5;\n}\n.flex-6 {\n  -ms-flex: 6;\n  -webkit-flex: 6;\n  flex: 6;\n}\n.flex-7 {\n  -ms-flex: 7;\n  -webkit-flex: 7;\n  flex: 7;\n}\n.flex-8 {\n  -ms-flex: 8;\n  -webkit-flex: 8;\n  flex: 8;\n}\n.flex-9 {\n  -ms-flex: 9;\n  -webkit-flex: 9;\n  flex: 9;\n}\n.flex-10 {\n  -ms-flex: 10;\n  -webkit-flex: 10;\n  flex: 10;\n}\n.flex-11 {\n  -ms-flex: 11;\n  -webkit-flex: 11;\n  flex: 11;\n}\n.flex-12 {\n  -ms-flex: 12;\n  -webkit-flex: 12;\n  flex: 12;\n}\n.layout.start {\n  -ms-flex-align: start;\n  -webkit-align-items: flex-start;\n  align-items: flex-start;\n}\n.layout.center,\n.layout.center-center {\n  -ms-flex-align: center;\n  -webkit-align-items: center;\n  align-items: center;\n}\n.layout.end {\n  -ms-flex-align: end;\n  -webkit-align-items: flex-end;\n  align-items: flex-end;\n}\n.layout.start-justified {\n  -ms-flex-pack: start;\n  -webkit-justify-content: flex-start;\n  justify-content: flex-start;\n}\n.layout.center-justified,\n.layout.center-center {\n  -ms-flex-pack: center;\n  -webkit-justify-content: center;\n  justify-content: center;\n}\n.layout.end-justified {\n  -ms-flex-pack: end;\n  -webkit-justify-content: flex-end;\n  justify-content: flex-end;\n}\n.layout.around-justified {\n  -ms-flex-pack: around;\n  -webkit-justify-content: space-around;\n  justify-content: space-around;\n}\n.layout.justified {\n  -ms-flex-pack: justify;\n  -webkit-justify-content: space-between;\n  justify-content: space-between;\n}\n.self-start {\n  -ms-align-self: flex-start;\n  -webkit-align-self: flex-start;\n  align-self: flex-start;\n}\n.self-center {\n  -ms-align-self: center;\n  -webkit-align-self: center;\n  align-self: center;\n}\n.self-end {\n  -ms-align-self: flex-end;\n  -webkit-align-self: flex-end;\n  align-self: flex-end;\n}\n.self-stretch {\n  -ms-align-self: stretch;\n  -webkit-align-self: stretch;\n  align-self: stretch;\n}\n.block {\n  display: block;\n}\n[hidden] {\n  display: none !important;\n}\n.invisible {\n  visibility: hidden !important;\n}\n.relative {\n  position: relative;\n}\n.fit {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n}\nbody.fullbleed {\n  margin: 0;\n  height: 100vh;\n}\n.scroll {\n  -webkit-overflow-scrolling: touch;\n  overflow: auto;\n}\n.fixed-bottom,\n.fixed-left,\n.fixed-right,\n.fixed-top {\n  position: fixed;\n}\n.fixed-top {\n  top: 0;\n  left: 0;\n  right: 0;\n}\n.fixed-right {\n  top: 0;\n  right: 0;\n  bottom: 0;\n}\n.fixed-bottom {\n  right: 0;\n  bottom: 0;\n  left: 0;\n}\n.fixed-left {\n  top: 0;\n  bottom: 0;\n  left: 0;\n}\n\n/* FIXES */\n\nlabel.bp5-label .bp5-button-group {\n  margin-top: 5px;\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-card {\n  background-color:#2f343c;\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-navbar {\n  background-color: rgb(47, 52, 60);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *) {\n  background-color: rgb(56, 62, 71);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *):hover {\n  background-color: rgb(47, 52, 60);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button.bp5-minimal:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *) {\n  background: none;\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button.bp5-minimal:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *):hover {\n  background: rgba(143, 153, 168, 0.15);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-select select:not(.bp5-datepicker *) {\n  background-color: rgb(56, 62, 71);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-select select:not(.bp5-datepicker *):hover {\n  background-color: rgb(47, 52, 60);\n}\n\n.bp5-card .bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.04);\n}\n\n.bp5-dark .bp5-card .bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.13);\n}\n\n.monaco-editor .codicon.codicon-diff-insert {\n  width: 5px !important;\n  background-color: #2ea043;\n  left: unset !important;\n  right: 0 !important;\n}\n.monaco-editor .codicon.codicon-diff-insert:before {\n  content: \"\" !important;\n}\n.monaco-editor .codicon.codicon-diff-remove {\n  width: 5px !important;\n  background-color: #f85149;\n  left: unset !important;\n  right: 0 !important;\n}\n.monaco-editor .codicon.codicon-diff-remove:before {\n  content: \"\" !important;\n}\n\n/***********************************************************************\nhighlight.js\n***********************************************************************/\npre code.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 1em\n}\ncode.hljs {\n  padding: 3px 5px\n}\n/*\n\nAtom One Light by Daniel Gamage\nOriginal One Light Syntax theme from https://github.com/atom/one-light-syntax\n\nbase:    #fafafa\nmono-1:  #383a42\nmono-2:  #686b77\nmono-3:  #a0a1a7\nhue-1:   #0184bb\nhue-2:   #4078f2\nhue-3:   #a626a4\nhue-4:   #50a14f\nhue-5:   #e45649\nhue-5-2: #c91243\nhue-6:   #986801\nhue-6-2: #c18401\n\n*/\n.hljs {\n  color: #383a42;\n  background: #fafafa\n}\n.hljs-comment,\n.hljs-quote {\n  color: #a0a1a7;\n  font-style: italic\n}\n.hljs-doctag,\n.hljs-keyword,\n.hljs-formula {\n  color: #a626a4\n}\n.hljs-section,\n.hljs-name,\n.hljs-selector-tag,\n.hljs-deletion,\n.hljs-subst {\n  color: #e45649\n}\n.hljs-literal {\n  color: #0184bb\n}\n.hljs-string,\n.hljs-regexp,\n.hljs-addition,\n.hljs-attribute,\n.hljs-meta .hljs-string {\n  color: #50a14f\n}\n.hljs-attr,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-type,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-number {\n  color: #986801\n}\n.hljs-symbol,\n.hljs-bullet,\n.hljs-link,\n.hljs-meta,\n.hljs-selector-id,\n.hljs-title {\n  color: #4078f2\n}\n.hljs-built_in,\n.hljs-title.class_,\n.hljs-class .hljs-title {\n  color: #c18401\n}\n.hljs-emphasis {\n  font-style: italic\n}\n.hljs-strong {\n  font-weight: bold\n}\n.hljs-link {\n  text-decoration: underline\n}\n/*\n\nAtom One Dark by Daniel Gamage\nOriginal One Dark Syntax theme from https://github.com/atom/one-dark-syntax\n\nbase:    #282c34\nmono-1:  #abb2bf\nmono-2:  #818896\nmono-3:  #5c6370\nhue-1:   #56b6c2\nhue-2:   #61aeee\nhue-3:   #c678dd\nhue-4:   #98c379\nhue-5:   #e06c75\nhue-5-2: #be5046\nhue-6:   #d19a66\nhue-6-2: #e6c07b\n\n*/\n.bp5-dark .hljs {\n  color: #abb2bf;\n  background: #282c34\n}\n.bp5-dark .hljs-comment,\n.bp5-dark .hljs-quote {\n  color: #5c6370;\n  font-style: italic\n}\n.bp5-dark .hljs-doctag,\n.bp5-dark .hljs-keyword,\n.bp5-dark .hljs-formula {\n  color: #c678dd\n}\n.bp5-dark .hljs-section,\n.bp5-dark .hljs-name,\n.bp5-dark .hljs-selector-tag,\n.bp5-dark .hljs-deletion,\n.bp5-dark .hljs-subst {\n  color: #e06c75\n}\n.bp5-dark .hljs-literal {\n  color: #56b6c2\n}\n.bp5-dark .hljs-string,\n.bp5-dark .hljs-regexp,\n.bp5-dark .hljs-addition,\n.bp5-dark .hljs-attribute,\n.bp5-dark .hljs-meta .hljs-string {\n  color: #98c379\n}\n.bp5-dark .hljs-attr,\n.bp5-dark .hljs-variable,\n.bp5-dark .hljs-template-variable,\n.bp5-dark .hljs-type,\n.bp5-dark .hljs-selector-class,\n.bp5-dark .hljs-selector-attr,\n.bp5-dark .hljs-selector-pseudo,\n.bp5-dark .hljs-number {\n  color: #d19a66\n}\n.bp5-dark .hljs-symbol,\n.bp5-dark .hljs-bullet,\n.bp5-dark .hljs-link,\n.bp5-dark .hljs-meta,\n.bp5-dark .hljs-selector-id,\n.bp5-dark .hljs-title {\n  color: #61aeee\n}\n.bp5-dark .hljs-built_in,\n.bp5-dark .hljs-title.class_,\n.bp5-dark .hljs-class .hljs-title {\n  color: #e6c07b\n}\n.bp5-dark .hljs-emphasis {\n  font-style: italic\n}\n.bp5-dark .hljs-strong {\n  font-weight: bold\n}\n.bp5-dark .hljs-link {\n  text-decoration: underline\n}\n\n/***********************************************************************\nbp5 datepicker\n***********************************************************************/\n.bp5-datepicker input[type=number]::-webkit-inner-spin-button,\n.bp5-datepicker input[type=number]::-webkit-outer-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\n.bp5-datepicker{\n  background:#ffffff;\n  border-radius:3px;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  padding:5px;\n  position:relative;\n  -webkit-user-select:none;\n  -moz-user-select:none;\n  -ms-user-select:none;\n  user-select:none; }\n.bp5-datepicker .DayPicker{\n  display:inline-block;\n  min-width:210px;\n  position:relative;\n  vertical-align:top; }\n.bp5-datepicker .DayPicker:focus{\n  outline:none; }\n.bp5-datepicker .bp5-datepicker-day-wrapper{\n  border-radius:3px;\n  padding:7px; }\n.bp5-datepicker .DayPicker-Month{\n  border-collapse:collapse;\n  border-spacing:0;\n  display:inline-table;\n  margin:0 5px 5px;\n  -webkit-user-select:none;\n  -moz-user-select:none;\n  -ms-user-select:none;\n  user-select:none; }\n.bp5-datepicker .DayPicker-Month + .bp5-datepicker .DayPicker-Month{\n  margin-left:10px; }\n.bp5-datepicker .DayPicker-Caption{\n  display:table-caption; }\n.bp5-datepicker .DayPicker-Weekdays{\n  display:table-header-group; }\n.bp5-datepicker .DayPicker-WeekdaysRow{\n  display:table-row; }\n.bp5-datepicker .DayPicker-Weekday{\n  display:table-cell;\n  height:30px;\n  line-height:1;\n  text-align:center;\n  vertical-align:middle;\n  width:30px;\n  font-weight:600;\n  padding-top:5px; }\n.bp5-datepicker .DayPicker-Weekday abbr[title]{\n  text-decoration:none; }\n.bp5-datepicker .DayPicker-Body{\n  display:table-row-group; }\n.bp5-datepicker .DayPicker-Week{\n  display:table-row; }\n.bp5-datepicker .DayPicker-WeekNumber{\n  display:table-cell;\n  height:30px;\n  line-height:1;\n  text-align:center;\n  vertical-align:middle;\n  width:30px;\n  color:rgba(92, 112, 128, 0.6);\n  font-size:14px; }\n.bp5-datepicker .DayPicker-Day{\n  display:table-cell;\n  height:30px;\n  line-height:1;\n  text-align:center;\n  vertical-align:middle;\n  width:30px;\n  border-radius:3px;\n  cursor:pointer; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--outside{\n  color:rgba(92, 112, 128, 0.6); }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--isToday .bp5-datepicker-day-wrapper{\n  border:1px solid rgba(16, 22, 26, 0.15); }\n.bp5-datepicker .DayPicker-Day:hover, .bp5-datepicker .DayPicker-Day:focus{\n  background:#d8e1e8;\n  color:#182026; }\n.bp5-datepicker .DayPicker-Day:active{\n  background:#ced9e0; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--selected{\n  background-color:#137cbd;\n  border-radius:3px;\n  color:#ffffff; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--selected:hover{\n  background-color:#106ba3; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--disabled{\n  background:none;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-datepicker-navbar{\n  -webkit-box-align:center;\n  -ms-flex-align:center;\n  align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:30px;\n  left:0;\n  position:absolute;\n  right:0;\n  top:0; }\n.bp5-datepicker-navbar > .DayPicker-NavButton--prev{\n  margin-right:auto; }\n.bp5-datepicker-navbar > .DayPicker-NavButton--next{\n  margin-left:auto; }\n\n.bp5-datepicker-caption{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n  -ms-flex-direction:row;\n  flex-direction:row;\n  -webkit-box-pack:justify;\n  -ms-flex-pack:justify;\n  justify-content:space-between;\n  margin:0 25px 5px; }\n.bp5-datepicker-caption > *{\n  -webkit-box-flex:0;\n  -ms-flex-positive:0;\n  flex-grow:0;\n  -ms-flex-negative:0;\n  flex-shrink:0; }\n.bp5-datepicker-caption > :first-child{\n  -webkit-box-flex:1;\n  -ms-flex-positive:1;\n  flex-grow:1;\n  -ms-flex-negative:1;\n  flex-shrink:1; }\n.bp5-datepicker-caption select{\n  font-weight:600;\n  padding-left:5px;\n  padding-right:16px; }\n.bp5-datepicker-caption select + .bp5-icon{\n  right:2px; }\n.bp5-datepicker-caption + .bp5-divider{\n  margin:0; }\n\n.bp5-datepicker-month-select{\n  -ms-flex-negative:1;\n  flex-shrink:1; }\n\n.bp5-datepicker-year-select{\n  -ms-flex-negative:1;\n  flex-shrink:1;\n  min-width:60px; }\n\n.bp5-datepicker-caption-measure{\n  font-weight:600;\n  padding-left:5px; }\n\n.bp5-datepicker-footer{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:justify;\n  -ms-flex-pack:justify;\n  justify-content:space-between; }\n\n.bp5-dark .bp5-datepicker{\n  background:#30404d; }\n.bp5-dark .bp5-datepicker .DayPicker-WeekNumber{\n  color:rgba(167, 182, 194, 0.6); }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--outside{\n  color:rgba(167, 182, 194, 0.6); }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--isToday .bp5-datepicker-day-wrapper{\n  border:1px solid rgba(255, 255, 255, 0.15); }\n.bp5-dark .bp5-datepicker .DayPicker-Day:hover, .bp5-dark .bp5-datepicker .DayPicker-Day:focus{\n  background:#5c7080;\n  color:#ffffff; }\n.bp5-dark .bp5-datepicker .DayPicker-Day:active{\n  background:#738694; }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--selected{\n  background-color:#137cbd; }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--selected:hover{\n  background-color:#2b95d6; }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--disabled{\n  background:none;\n  color:rgba(167, 182, 194, 0.6); }\n.bp5-dark .bp5-datepicker .bp5-datepicker-footer{\n  border-top-color:rgba(16, 22, 26, 0.4); }\n\n.bp5-datepicker-timepicker-wrapper{\n  -webkit-box-align:center;\n  -ms-flex-align:center;\n  align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n  -ms-flex-direction:column;\n  flex-direction:column; }\n.bp5-daterangepicker{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  white-space:nowrap; }\n.bp5-daterangepicker .DayPicker-NavButton--interactionDisabled{\n  display:none; }\n.bp5-daterangepicker .bp5-daterangepicker-timepickers{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-pack:distribute;\n  justify-content:space-around; }\n.bp5-daterangepicker.bp5-daterangepicker-contiguous .DayPicker{\n  min-width:220px; }\n.bp5-daterangepicker.bp5-daterangepicker-single-month .DayPicker{\n  min-width:210px; }\n.bp5-daterangepicker .DayPicker-Day{ }\n.bp5-daterangepicker .DayPicker-Day--outside{\n  visibility:hidden; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range{\n  border-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range:not(.DayPicker-Day--selected):not(.DayPicker-Day--selected-range):not(.DayPicker-Day--selected-range-start):not(.DayPicker-Day--selected-range-end){\n  background-color:#ebf1f5; }\n.bp5-daterangepicker .DayPicker-Day--selected-range{\n  background-color:#ebf1f5;\n  border-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--selected-range:hover{\n  background-color:#ced9e0; }\n.bp5-daterangepicker .DayPicker-Day--selected-range-start:not(.DayPicker-Day--selected-range-end):not(.DayPicker-Day--hovered-range-end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--selected-range-end:not(.DayPicker-Day--selected-range-start):not(.DayPicker-Day--hovered-range-start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range-start:not(.DayPicker-Day--hovered-range-end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range-end:not(.DayPicker-Day--hovered-range-start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0; }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--hovered-range{ }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--hovered-range:not(.DayPicker-Day--selected):not(.DayPicker-Day--selected-range):not(.DayPicker-Day--selected-range-start):not(.DayPicker-Day--selected-range-end){\n  background-color:#394b59; }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--selected-range{\n  background-color:#394b59; }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--selected-range:hover{\n  background-color:#5c7080; }\n\n.bp5-menu.bp5-daterangepicker-shortcuts{\n  min-width:120px;\n  padding:0; }\n.bp5-timepicker{\n  white-space:nowrap; }\n.bp5-timepicker .bp5-timepicker-arrow-row{\n  padding:0 1px; }\n.bp5-timepicker .bp5-timepicker-arrow-button{\n  color:#5c7080;\n  display:inline-block;\n  padding:4px 0;\n  text-align:center;\n  width:33px; }\n.bp5-timepicker .bp5-timepicker-arrow-button:hover{\n  color:#182026; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-arrow-button{\n  color:#a7b6c2; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-arrow-button:hover{\n  color:#f5f8fa; }\n.bp5-timepicker .bp5-timepicker-arrow-button + .bp5-timepicker-arrow-button{\n  margin-left:11px; }\n.bp5-timepicker .bp5-timepicker-arrow-button:hover{\n  cursor:pointer; }\n.bp5-timepicker .bp5-timepicker-input-row{\n  background:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  display:inline-block;\n  height:30px;\n  line-height:28px;\n  padding:0 1px;\n  vertical-align:middle; }\n.bp5-timepicker .bp5-timepicker-divider-text{\n  color:#5c7080;\n  display:inline-block;\n  font-size:16px;\n  text-align:center;\n  width:11px; }\n.bp5-timepicker .bp5-timepicker-input{\n  background:transparent;\n  border:0;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0);\n  box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0);\n  color:#182026;\n  height:28px;\n  outline:0;\n  padding:0;\n  text-align:center;\n  -webkit-transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width:33px; }\n.bp5-timepicker .bp5-timepicker-input:focus{\n  -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3);\n  box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary{\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:focus{\n  -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n  box-shadow:inset 0 0 0 1px #137cbd; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary{\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:focus{\n  -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n  box-shadow:inset 0 0 0 1px #137cbd; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success{\n  -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success:focus{\n  -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n  box-shadow:inset 0 0 0 1px #0f9960; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-success.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success{\n  -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success:focus{\n  -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n  box-shadow:inset 0 0 0 1px #0f9960; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning{\n  -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:focus{\n  -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n  box-shadow:inset 0 0 0 1px #d9822b; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning{\n  -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:focus{\n  -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n  box-shadow:inset 0 0 0 1px #d9822b; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger{\n  -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:focus{\n  -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #db3737;\n  box-shadow:inset 0 0 0 1px #db3737; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger{\n  -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:focus{\n  -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #db3737;\n  box-shadow:inset 0 0 0 1px #db3737; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-ampm-select{\n  margin-left:5px; }\n.bp5-timepicker.bp5-disabled .bp5-timepicker-input-row{\n  background:rgba(206, 217, 224, 0.5);\n  -webkit-box-shadow:none;\n  box-shadow:none;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed;\n  resize:none; }\n.bp5-timepicker.bp5-disabled .bp5-timepicker-input,\n.bp5-timepicker.bp5-disabled .bp5-timepicker-divider-text{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n.bp5-timepicker.bp5-disabled .bp5-timepicker-arrow-button,\n.bp5-timepicker.bp5-disabled .bp5-timepicker-arrow-button:hover{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-dark .bp5-timepicker .bp5-timepicker-input-row{\n  background:rgba(16, 22, 26, 0.3);\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-dark .bp5-timepicker .bp5-timepicker-divider-text{\n  color:#a7b6c2; }\n\n.bp5-dark .bp5-timepicker .bp5-timepicker-input{\n  color:#f5f8fa; }\n\n.bp5-datepicker .bp5-timepicker{\n  margin-bottom:10px;\n  margin-top:5px; }\n.bp5-datepicker .bp5-timepicker:last-child{\n  margin-bottom:5px; }\n.bp5-datetimepicker{\n  background-color:#ffffff;\n  border-radius:3px;\n  padding:10px;\n  text-align:center; }\n.bp5-dark .bp5-datetimepicker{\n  background:#30404d; }\n.bp5-dark .bp5-datetimepicker .bp5-datepicker{\n  border-bottom:1px solid rgba(16, 22, 26, 0.4); }\n.bp5-datetimepicker .bp5-datepicker{\n  border-bottom:1px solid rgba(16, 22, 26, 0.15);\n  padding:0 0 10px; }\n.bp5-datetimepicker .bp5-timepicker{\n  margin-top:10px; }\n\n.bp5-dateinput-popover{\n  padding:0; }\n/*# sourceMappingURL=blueprint-datetime.css.map */\n\n.bp5-dark .almalinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20id%3D%27Layer_1%27%20x%3D%270%27%20y%3D%270%27%20version%3D%271.1%27%20viewBox%3D%270%200%2061.028%2059.731%27%3E%3Cstyle%20id%3D%27style2%27%20type%3D%27text%2Fcss%27%3E.st1%7Bfill%3A%2386da2f%7D.st2%7Bfill%3A%2324c2ff%7D.st3%7Bfill%3A%23ffcb12%7D.st4%7Bfill%3A%230069da%7D.st5%7Bfill%3A%23ff4649%7D%3C%2Fstyle%3E%3Cpath%20id%3D%27path22%27%20d%3D%27M56.114%2033.731c2.6-.2%204.7%201.5%204.9%204.1.2%202.7-1.7%204.9-4.3%205.1-2.5.2-4.7-1.7-4.9-4.2-.2-2.7%201.6-4.7%204.3-5z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path24%27%20d%3D%27M24.514%2055.031c0-2.6%202-4.6%204.4-4.6%202.4%200%204.7%202.2%204.7%204.7%200%202.4-2%204.5-4.3%204.6-2.9%200-4.8-1.8-4.8-4.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path26%27%20d%3D%27M31.614%2025.831c-.4.2-.6-.1-.7-.4-3.7-6.9-2.6-15.6%203.9-20.8%201.7-1.4%204.9-1.7%206.3-.3.6.5.7%201.1.8%201.8.2%201.5.5%203%201.5%204.2%201.1%201.3%202.5%201.8%204.1%201.7%201.4%200%202.8-.2%203.7%201.4.5.9.3%204.4-.5%205.1-.4.3-.7.1-1%200-2.3-.9-4.7-.9-7.1-.5-.8.1-1.2-.1-1.2-1-.1-1.5-.4-2.9-1.2-4.2-1.5-2.7-4.3-2.8-6.1-.3-1.5%202-1.9%204.4-2.3%206.8-.4%202.1-.3%204.3-.2%206.5%200%200-.1%200%200%200z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path28%27%20d%3D%27M34.114%2027.331c-.2-.3-.1-.6.2-.8%205.7-5.2%2014.2-6.2%2020.8-1.1%201.7%201.4%202.8%204.3%201.9%206-.4.7-.9%201-1.5%201.2-1.4.6-2.7%201.2-3.6%202.5-.9%201.3-1.1%202.8-.7%204.4.3%201.3.8%202.7-.5%203.9-.7.7-4.1%201.3-5%20.7-.4-.3-.3-.6-.2-1%20.3-2.5-.3-4.8-1.2-7-.3-.8-.2-1.2.6-1.4%201.4-.4%202.7-1.1%203.7-2.1%202.2-2.1%201.7-4.8-1.2-6-2.3-1-4.7-.8-7-.6-2.2.1-4.3.7-6.3%201.3z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path30%27%20d%3D%27M32.814%2029.931c.3-.3.5-.2.8%200%206.6%204%2010%2011.9%207%2019.6-.8%202-3.4%204-5.3%203.5-.8-.2-1.2-.6-1.6-1.1-.9-1.2-1.9-2.3-3.4-2.8-1.6-.5-3-.2-4.4.6-1.2.7-2.4%201.6-3.9.7-.9-.5-2.4-3.6-2.1-4.6.2-.4.6-.4%201-.4%202.5-.4%204.5-1.6%206.4-3.2.6-.5%201.1-.5%201.6.2.8%201.2%201.8%202.2%203.1%202.9%202.6%201.5%205.1.2%205.4-2.8.3-2.5-.6-4.7-1.4-6.9-.9-2-2-3.9-3.2-5.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path32%27%20d%3D%27M29.614%2030.531c-.4%202-1.3%203.9-2.5%205.6-3.6%205.4-8.8%207.6-15.2%207-2.3-.2-4.2-2.1-4.4-4-.1-.8.1-1.4.6-2%20.7-.9%201.3-1.7%201.6-2.8.6-2.2-.2-4-1.8-5.6-2.2-2.2-1.9-4.2.7-5.8.3-.2.7-.4%201.1-.6.6-.3%201.1-.3%201.3.4.9%202.3%202.7%204%204.7%205.4.7.6.7%201%20.1%201.7-1.2%201.3-1.9%202.9-2%204.7-.2%202.2%201.1%203.6%203.3%203.6%201.4%200%202.7-.5%203.9-1.1%203.1-1.6%205.5-3.9%207.8-6.3.3-.1.4-.3.8-.2z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path34%27%20d%3D%27M13.214%209.531c.2%200%20.7.1%201.2.2%203.7.7%206-.6%207.2-4.1.8-2.3%202.5-3%204.7-1.8.1%200%20.1.1.2.1%202.3%201.3%202.3%201.5.9%203.5-1.2%201.6-1.8%203.4-2.1%205.3-.2%201.1-.6%201.3-1.6.9-1.6-.6-3.3-.6-5%200-1.9.6-2.7%202.3-2.1%204.2.8%202.5%203%203.6%204.9%204.9%201.9%201.3%204.1%202%206.2%202.9.3.1.8.1.7.6-.1.3-.5.3-.9.3-4.5.2-8.8-.5-12.3-3.5-3.3-2.7-5.7-6-5.3-10.6.3-1.5%201.4-2.6%203.3-2.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path36%27%20d%3D%27M5.014%2037.631c-2.4.3-4.8-1.7-5-4.2-.2-2.4%201.8-4.8%204.1-5%202.6-.3%205%201.5%205.2%203.9.1%202.3-1.4%205.1-4.3%205.3z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path38%27%20d%3D%27M47.014%202.031c2.5-.2%204.9%201.8%205.1%204.3.2%202.4-1.8%204.7-4.2%204.9-2.6.2-4.9-1.7-5.1-4.2-.2-2.5%201.6-4.8%204.2-5z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path40%27%20d%3D%27M20.914%203.931c.3%202.6-1.5%204.8-4.2%205.2-2.3.3-4.7-1.6-5-3.8-.3-2.9%201.3-5%204-5.3%202.5-.3%204.9%201.6%205.2%203.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.almalinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20id%3D%27Layer_1%27%20x%3D%270%27%20y%3D%270%27%20version%3D%271.1%27%20viewBox%3D%270%200%2061.028%2059.731%27%3E%3Cstyle%20id%3D%27style2%27%20type%3D%27text%2Fcss%27%3E.st1%7Bfill%3A%2386da2f%7D.st2%7Bfill%3A%2324c2ff%7D.st3%7Bfill%3A%23ffcb12%7D.st4%7Bfill%3A%230069da%7D.st5%7Bfill%3A%23ff4649%7D%3C%2Fstyle%3E%3Cpath%20id%3D%27path22%27%20d%3D%27M56.114%2033.731c2.6-.2%204.7%201.5%204.9%204.1.2%202.7-1.7%204.9-4.3%205.1-2.5.2-4.7-1.7-4.9-4.2-.2-2.7%201.6-4.7%204.3-5z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path24%27%20d%3D%27M24.514%2055.031c0-2.6%202-4.6%204.4-4.6%202.4%200%204.7%202.2%204.7%204.7%200%202.4-2%204.5-4.3%204.6-2.9%200-4.8-1.8-4.8-4.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path26%27%20d%3D%27M31.614%2025.831c-.4.2-.6-.1-.7-.4-3.7-6.9-2.6-15.6%203.9-20.8%201.7-1.4%204.9-1.7%206.3-.3.6.5.7%201.1.8%201.8.2%201.5.5%203%201.5%204.2%201.1%201.3%202.5%201.8%204.1%201.7%201.4%200%202.8-.2%203.7%201.4.5.9.3%204.4-.5%205.1-.4.3-.7.1-1%200-2.3-.9-4.7-.9-7.1-.5-.8.1-1.2-.1-1.2-1-.1-1.5-.4-2.9-1.2-4.2-1.5-2.7-4.3-2.8-6.1-.3-1.5%202-1.9%204.4-2.3%206.8-.4%202.1-.3%204.3-.2%206.5%200%200-.1%200%200%200z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path28%27%20d%3D%27M34.114%2027.331c-.2-.3-.1-.6.2-.8%205.7-5.2%2014.2-6.2%2020.8-1.1%201.7%201.4%202.8%204.3%201.9%206-.4.7-.9%201-1.5%201.2-1.4.6-2.7%201.2-3.6%202.5-.9%201.3-1.1%202.8-.7%204.4.3%201.3.8%202.7-.5%203.9-.7.7-4.1%201.3-5%20.7-.4-.3-.3-.6-.2-1%20.3-2.5-.3-4.8-1.2-7-.3-.8-.2-1.2.6-1.4%201.4-.4%202.7-1.1%203.7-2.1%202.2-2.1%201.7-4.8-1.2-6-2.3-1-4.7-.8-7-.6-2.2.1-4.3.7-6.3%201.3z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path30%27%20d%3D%27M32.814%2029.931c.3-.3.5-.2.8%200%206.6%204%2010%2011.9%207%2019.6-.8%202-3.4%204-5.3%203.5-.8-.2-1.2-.6-1.6-1.1-.9-1.2-1.9-2.3-3.4-2.8-1.6-.5-3-.2-4.4.6-1.2.7-2.4%201.6-3.9.7-.9-.5-2.4-3.6-2.1-4.6.2-.4.6-.4%201-.4%202.5-.4%204.5-1.6%206.4-3.2.6-.5%201.1-.5%201.6.2.8%201.2%201.8%202.2%203.1%202.9%202.6%201.5%205.1.2%205.4-2.8.3-2.5-.6-4.7-1.4-6.9-.9-2-2-3.9-3.2-5.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path32%27%20d%3D%27M29.614%2030.531c-.4%202-1.3%203.9-2.5%205.6-3.6%205.4-8.8%207.6-15.2%207-2.3-.2-4.2-2.1-4.4-4-.1-.8.1-1.4.6-2%20.7-.9%201.3-1.7%201.6-2.8.6-2.2-.2-4-1.8-5.6-2.2-2.2-1.9-4.2.7-5.8.3-.2.7-.4%201.1-.6.6-.3%201.1-.3%201.3.4.9%202.3%202.7%204%204.7%205.4.7.6.7%201%20.1%201.7-1.2%201.3-1.9%202.9-2%204.7-.2%202.2%201.1%203.6%203.3%203.6%201.4%200%202.7-.5%203.9-1.1%203.1-1.6%205.5-3.9%207.8-6.3.3-.1.4-.3.8-.2z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path34%27%20d%3D%27M13.214%209.531c.2%200%20.7.1%201.2.2%203.7.7%206-.6%207.2-4.1.8-2.3%202.5-3%204.7-1.8.1%200%20.1.1.2.1%202.3%201.3%202.3%201.5.9%203.5-1.2%201.6-1.8%203.4-2.1%205.3-.2%201.1-.6%201.3-1.6.9-1.6-.6-3.3-.6-5%200-1.9.6-2.7%202.3-2.1%204.2.8%202.5%203%203.6%204.9%204.9%201.9%201.3%204.1%202%206.2%202.9.3.1.8.1.7.6-.1.3-.5.3-.9.3-4.5.2-8.8-.5-12.3-3.5-3.3-2.7-5.7-6-5.3-10.6.3-1.5%201.4-2.6%203.3-2.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path36%27%20d%3D%27M5.014%2037.631c-2.4.3-4.8-1.7-5-4.2-.2-2.4%201.8-4.8%204.1-5%202.6-.3%205%201.5%205.2%203.9.1%202.3-1.4%205.1-4.3%205.3z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path38%27%20d%3D%27M47.014%202.031c2.5-.2%204.9%201.8%205.1%204.3.2%202.4-1.8%204.7-4.2%204.9-2.6.2-4.9-1.7-5.1-4.2-.2-2.5%201.6-4.8%204.2-5z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path40%27%20d%3D%27M20.914%203.931c.3%202.6-1.5%204.8-4.2%205.2-2.3.3-4.7-1.6-5-3.8-.3-2.9%201.3-5%204-5.3%202.5-.3%204.9%201.6%205.2%203.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .alpinelinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20width%3D%27186.125%27%20height%3D%27161.25%27%3E%3Cpath%20d%3D%27M0%200v15.7L-11.3%204.4a33.27%2033.27%200%200%201%203.44-2.11c1.08-.567%202.09-1%203.03-1.34.941-.334%201.81-.562%202.62-.71C-1.406.093-.67.027%200%20.018m57.8%201.3c.02-.017.13-.11.333-.239a5.89%205.89%200%200%201%20.898-.462%208.098%208.098%200%200%201%201.48-.448c.596-.122%201.29-.202%202.1-.202.671%200%201.41.059%202.22.2.812.142%201.69.367%202.64.699a23.4%2023.4%200%200%201%203.07%201.34c1.09.572%202.26%201.28%203.5%202.14l-8.57%208.4-28.4%2028.5-12.5-12.5-16.8%2017.3-42.2-41.7c1.24-.86%202.4-1.56%203.49-2.14%201.09-.571%202.12-1.01%203.07-1.34a16.71%2016.71%200%200%201%202.64-.698%2012.85%2012.85%200%200%201%202.22-.201c.804%200%201.5.08%202.1.202a7.78%207.78%200%200%201%201.48.449c.396.164.693.331.897.461.204.13.314.223.334.24l19.1%2019.1%206.81%206.53%2018.9-18.9%206.59-6.78c.02-.017.13-.11.334-.239a5.87%205.87%200%200%201%20.897-.462c.396-.164.89-.327%201.48-.448.596-.122%201.3-.202%202.1-.202.67%200%201.41.059%202.22.2.811.142%201.69.367%202.64.699.952.333%201.98.773%203.07%201.34%201.09.572%202.26%201.28%203.5%202.14l-15.1%2014.8%202.82%202.82%2013.1-13.1%207.64-7.57m-.163%2079%2037.2-64.5-37.2-64.5h-74.5l-37.2%2064.5%2037.2%2064.5z%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%2067.45%20100.309%29%27%2F%3E%3Cpath%20d%3D%27m0%200-9.91%209.9.705.709L.775.739z%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%20111.2%2076.559%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.alpinelinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20width%3D%27186.125%27%20height%3D%27161.25%27%3E%3Cpath%20d%3D%27M0%200v15.7L-11.3%204.4a33.27%2033.27%200%200%201%203.44-2.11c1.08-.567%202.09-1%203.03-1.34.941-.334%201.81-.562%202.62-.71C-1.406.093-.67.027%200%20.018m57.8%201.3c.02-.017.13-.11.333-.239a5.89%205.89%200%200%201%20.898-.462%208.098%208.098%200%200%201%201.48-.448c.596-.122%201.29-.202%202.1-.202.671%200%201.41.059%202.22.2.812.142%201.69.367%202.64.699a23.4%2023.4%200%200%201%203.07%201.34c1.09.572%202.26%201.28%203.5%202.14l-8.57%208.4-28.4%2028.5-12.5-12.5-16.8%2017.3-42.2-41.7c1.24-.86%202.4-1.56%203.49-2.14%201.09-.571%202.12-1.01%203.07-1.34a16.71%2016.71%200%200%201%202.64-.698%2012.85%2012.85%200%200%201%202.22-.201c.804%200%201.5.08%202.1.202a7.78%207.78%200%200%201%201.48.449c.396.164.693.331.897.461.204.13.314.223.334.24l19.1%2019.1%206.81%206.53%2018.9-18.9%206.59-6.78c.02-.017.13-.11.334-.239a5.87%205.87%200%200%201%20.897-.462c.396-.164.89-.327%201.48-.448.596-.122%201.3-.202%202.1-.202.67%200%201.41.059%202.22.2.811.142%201.69.367%202.64.699.952.333%201.98.773%203.07%201.34%201.09.572%202.26%201.28%203.5%202.14l-15.1%2014.8%202.82%202.82%2013.1-13.1%207.64-7.57m-.163%2079%2037.2-64.5-37.2-64.5h-74.5l-37.2%2064.5%2037.2%2064.5z%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%2067.45%20100.309%29%27%2F%3E%3Cpath%20d%3D%27m0%200-9.91%209.9.705.709L.775.739z%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%20111.2%2076.559%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .archlinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%22256%22%20height%3D%22256%22%3E%3Cpath%20d%3D%22M128%200c-11.395%2027.937-18.313%2046.237-31%2073.344%207.779%208.245%2017.338%2017.812%2032.844%2028.656-16.67-6.86-28.034-13.729-36.531-20.875C77.076%20115.005%2051.622%20163.246%200%20256c40.563-23.418%2072.008-37.862%20101.313-43.375-1.254-5.404-1.924-11.278-1.876-17.375l.032-1.281c.644-25.994%2014.17-45.982%2030.187-44.625s28.488%2023.537%2027.844%2049.531c-.121%204.896-.69%209.6-1.656%2013.969C184.834%20218.517%20215.982%20232.897%20256%20256a14843%2014843%200%200%201-21.687-40.094c-10.597-8.212-21.644-18.897-44.188-30.469%2015.496%204.027%2026.602%208.683%2035.25%2013.875C156.98%2071.973%20151.455%2055.04%20128%200%22%20style%3D%22fill%3A%23fff%3Bfill-opacity%3A1%22/%3E%3Cpath%20d%3D%22M818.226%20548.553c-41.181-55.895-50.727-100.945-53.145-111.7%2021.968%2050.668%2021.818%2051.29%2053.145%20111.7%22%20style%3D%22fill%3A%23fff%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3Cpath%20d%3D%22M765.098%20436.435c-1.056%202.597-2.086%205.117-3.061%207.515-1.082%202.655-2.11%205.19-3.132%207.677a1118%201118%200%200%201-3.038%207.306%20752%20752%200%200%201-3.038%207.143c-1.028%202.382-2.062%204.789-3.155%207.236a619%20619%200%200%201-3.432%207.538%20686%20686%200%200%201-3.804%208.095c-.186.39-.413.834-.603%201.229%205.755%206.096%2012.841%2013.15%2024.284%2021.152-12.34-5.078-20.766-10.157-27.067-15.446-.327.667-.614%201.264-.951%201.948-.444.9-.976%201.923-1.438%202.853-.81%201.66-1.656%203.365-2.528%205.125-.335.667-.63%201.243-.974%201.925-5.508%2011.053-12.34%2024.283-21.13%2040.728%2024.096-13.576%2050.086-33.163%2097.297-16.305a1624%201624%200%200%201-6.587-12.64%201193%201193%200%200%201-5.706-11.156%20865%20865%200%200%201-4.917-9.927%20671%20671%200%200%201-4.267-8.906%20563%20563%200%200%201-3.758-8.165%20521%20521%200%200%201-3.363-7.584%20548%20548%200%200%201-3.084-7.306%20692%20692%200%200%201-2.946-7.236c-.348-.866-.695-1.769-1.044-2.644-2.663-6.253-5.398-12.732-8.558-20.155%22%20style%3D%22fill%3A%23fff%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3C/svg%3E\");\n}\n\n.archlinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%22256%22%20height%3D%22256%22%3E%3Cpath%20d%3D%22M128%200c-11.395%2027.937-18.313%2046.237-31%2073.344%207.779%208.245%2017.338%2017.812%2032.844%2028.656-16.67-6.86-28.034-13.729-36.531-20.875C77.076%20115.005%2051.622%20163.246%200%20256c40.563-23.418%2072.008-37.862%20101.313-43.375-1.254-5.404-1.924-11.278-1.876-17.375l.032-1.281c.644-25.994%2014.17-45.982%2030.187-44.625s28.488%2023.537%2027.844%2049.531c-.121%204.896-.69%209.6-1.656%2013.969C184.834%20218.517%20215.982%20232.897%20256%20256a14843%2014843%200%200%201-21.687-40.094c-10.597-8.212-21.644-18.897-44.188-30.469%2015.496%204.027%2026.602%208.683%2035.25%2013.875C156.98%2071.973%20151.455%2055.04%20128%200%22%20style%3D%22fill%3A%23000%3Bfill-opacity%3A1%22/%3E%3Cpath%20d%3D%22M818.226%20548.553c-41.181-55.895-50.727-100.945-53.145-111.7%2021.968%2050.668%2021.818%2051.29%2053.145%20111.7%22%20style%3D%22fill%3A%23000%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3Cpath%20d%3D%22M765.098%20436.435c-1.056%202.597-2.086%205.117-3.061%207.515-1.082%202.655-2.11%205.19-3.132%207.677a1118%201118%200%200%201-3.038%207.306%20752%20752%200%200%201-3.038%207.143c-1.028%202.382-2.062%204.789-3.155%207.236a619%20619%200%200%201-3.432%207.538%20686%20686%200%200%201-3.804%208.095c-.186.39-.413.834-.603%201.229%205.755%206.096%2012.841%2013.15%2024.284%2021.152-12.34-5.078-20.766-10.157-27.067-15.446-.327.667-.614%201.264-.951%201.948-.444.9-.976%201.923-1.438%202.853-.81%201.66-1.656%203.365-2.528%205.125-.335.667-.63%201.243-.974%201.925-5.508%2011.053-12.34%2024.283-21.13%2040.728%2024.096-13.576%2050.086-33.163%2097.297-16.305a1624%201624%200%200%201-6.587-12.64%201193%201193%200%200%201-5.706-11.156%20865%20865%200%200%201-4.917-9.927%20671%20671%200%200%201-4.267-8.906%20563%20563%200%200%201-3.758-8.165%20521%20521%200%200%201-3.363-7.584%20548%20548%200%200%201-3.084-7.306%20692%20692%200%200%201-2.946-7.236c-.348-.866-.695-1.769-1.044-2.644-2.663-6.253-5.398-12.732-8.558-20.155%22%20style%3D%22fill%3A%23000%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3C/svg%3E\");\n}\n\n.bp5-dark .fedora-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27512.041%27%20height%3D%27502.439%27%20viewBox%3D%270%200%20135.478%20132.937%27%3E%3Cpath%20d%3D%27M67.775%200C30.366%200%20.079%2029.732.012%2066.439H0v51.43h.012c.018%208.325%206.903%2015.068%2015.393%2015.068H67.78c37.415-.024%2067.697-29.752%2067.697-66.439C135.478%2029.792%20105.157.06%2067.715.06zm13.771%2027.338-.007.02c11.376%200%2022.116%208.543%2022.116%2020.328%200%201.094.007%202.187-.176%203.427-.315%203.135-3.236%205.388-6.417%204.946-3.181-.447-5.34-3.41-4.752-6.509.054-.354.074-.91.074-1.865%200-6.683-5.576-9.264-10.846-9.264-5.27%200-10.019%204.346-10.026%209.264.091%205.687%200%2011.33%200%2017.007l9.78-.071c7.637-.155%207.723%2011.123.089%2011.07l-9.867.072c-.024%204.574.036%203.747.012%206.05%200%200%20.083%205.595-.087%209.834-1.18%2012.454-11.99%2022.407-24.98%2022.407-13.772%200-25.114-11.038-25.114-24.577.413-13.923%2011.733-24.875%2025.993-24.75l7.954-.058v11.05l-7.954.073h-.042c-7.835.227-14.552%205.447-14.677%2013.678%200%207.498%206.176%2013.506%2013.843%2013.506%207.657%200%2013.784-5.464%2013.784-13.493l-.012-41.855c.005-.777.03-1.394.117-2.251%201.292-10.238%2010.627-18.04%2021.195-18.04z%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.fedora-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27512.041%27%20height%3D%27502.439%27%20viewBox%3D%270%200%20135.478%20132.937%27%3E%3Cpath%20d%3D%27M67.775%200C30.366%200%20.079%2029.732.012%2066.439H0v51.43h.012c.018%208.325%206.903%2015.068%2015.393%2015.068H67.78c37.415-.024%2067.697-29.752%2067.697-66.439C135.478%2029.792%20105.157.06%2067.715.06zm13.771%2027.338-.007.02c11.376%200%2022.116%208.543%2022.116%2020.328%200%201.094.007%202.187-.176%203.427-.315%203.135-3.236%205.388-6.417%204.946-3.181-.447-5.34-3.41-4.752-6.509.054-.354.074-.91.074-1.865%200-6.683-5.576-9.264-10.846-9.264-5.27%200-10.019%204.346-10.026%209.264.091%205.687%200%2011.33%200%2017.007l9.78-.071c7.637-.155%207.723%2011.123.089%2011.07l-9.867.072c-.024%204.574.036%203.747.012%206.05%200%200%20.083%205.595-.087%209.834-1.18%2012.454-11.99%2022.407-24.98%2022.407-13.772%200-25.114-11.038-25.114-24.577.413-13.923%2011.733-24.875%2025.993-24.75l7.954-.058v11.05l-7.954.073h-.042c-7.835.227-14.552%205.447-14.677%2013.678%200%207.498%206.176%2013.506%2013.843%2013.506%207.657%200%2013.784-5.464%2013.784-13.493l-.012-41.855c.005-.777.03-1.394.117-2.251%201.292-10.238%2010.627-18.04%2021.195-18.04z%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .freebsd-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272500%27%20height%3D%272461%27%20preserveAspectRatio%3D%27xMidYMid%27%20viewBox%3D%270%200%20256%20252%27%3E%3Cg%20fill%3D%27%23B5010F%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%3E%3Cpath%20d%3D%27M252.723%205.11c13.508%2013.5-23.939%2072.848-30.27%2079.182-6.33%206.321-22.409.505-35.91-13-13.508-13.5-19.327-29.583-12.996-35.914%206.327-6.333%2065.671-43.777%2079.176-30.269M63.305%2019.394c-20.622-11.7-49.966-24.716-59.3-15.38-9.458%209.454%204.034%2039.458%2015.858%2060.117a126.812%20126.812%200%200%201%2043.442-44.737%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20d%3D%27M232.123%2079.636c1.899%206.44%201.558%2011.76-1.522%2014.834-7.193%207.196-26.624-.464-44.14-17.134a89.383%2089.383%200%200%201-3.627-3.428c-6.334-6.336-11.262-13.08-14.414-19.291-6.135-11.006-7.67-20.726-3.033-25.364%202.527-2.524%206.57-3.212%2011.502-2.325%203.216-2.034%207.013-4.3%2011.176-6.621-16.929-8.83-36.176-13.817-56.593-13.817C63.753%206.49%208.854%2061.38%208.854%20129.105c0%2067.713%2054.9%20122.61%20122.618%20122.61%2067.72%200%20122.616-54.897%20122.616-122.61%200-21.87-5.74-42.377-15.767-60.156-2.167%203.955-4.274%207.578-6.198%2010.687%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\");\n}\n\n.freebsd-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272500%27%20height%3D%272461%27%20preserveAspectRatio%3D%27xMidYMid%27%20viewBox%3D%270%200%20256%20252%27%3E%3Cg%20fill%3D%27%23B5010F%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%3E%3Cpath%20d%3D%27M252.723%205.11c13.508%2013.5-23.939%2072.848-30.27%2079.182-6.33%206.321-22.409.505-35.91-13-13.508-13.5-19.327-29.583-12.996-35.914%206.327-6.333%2065.671-43.777%2079.176-30.269M63.305%2019.394c-20.622-11.7-49.966-24.716-59.3-15.38-9.458%209.454%204.034%2039.458%2015.858%2060.117a126.812%20126.812%200%200%201%2043.442-44.737%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20d%3D%27M232.123%2079.636c1.899%206.44%201.558%2011.76-1.522%2014.834-7.193%207.196-26.624-.464-44.14-17.134a89.383%2089.383%200%200%201-3.627-3.428c-6.334-6.336-11.262-13.08-14.414-19.291-6.135-11.006-7.67-20.726-3.033-25.364%202.527-2.524%206.57-3.212%2011.502-2.325%203.216-2.034%207.013-4.3%2011.176-6.621-16.929-8.83-36.176-13.817-56.593-13.817C63.753%206.49%208.854%2061.38%208.854%20129.105c0%2067.713%2054.9%20122.61%20122.618%20122.61%2067.72%200%20122.616-54.897%20122.616-122.61%200-21.87-5.74-42.377-15.767-60.156-2.167%203.955-4.274%207.578-6.198%2010.687%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .oraclelinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2732%27%20height%3D%2720%27%3E%3Cpath%20fill%3D%27none%27%20stroke%3D%27%23C74634%27%20stroke-width%3D%274%27%20d%3D%27M10%202a8%208%200%201%200%200%2016h12a8%208%200%201%200%200-16z%27%20style%3D%27stroke%3A%23fff%3Bstroke-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.oraclelinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2732%27%20height%3D%2720%27%3E%3Cpath%20fill%3D%27none%27%20stroke%3D%27%23C74634%27%20stroke-width%3D%274%27%20d%3D%27M10%202a8%208%200%201%200%200%2016h12a8%208%200%201%200%200-16z%27%20style%3D%27stroke%3A%23000%3Bstroke-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .rockylinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%274096%27%20height%3D%274096%27%20fill%3D%27none%27%20viewBox%3D%270%200%20192%20192%27%3E%3Cpath%20fill%3D%27%2310B981%27%20fill-rule%3D%27evenodd%27%20d%3D%27M186.658%20127.658C190.119%20117.746%20192%20107.093%20192%2096c0-53.02-42.981-96-96-96C42.98%200%200%2042.98%200%2096c0%2026.234%2010.523%2050.011%2027.578%2067.338l97.38-97.38L149%2090l37.658%2037.658Zm-17.536%2030.547-44.164-44.163-69.16%2069.16C68.027%20188.849%2081.645%20192%2096%20192c29.288%200%2055.514-13.116%2073.122-33.795Z%27%20clip-rule%3D%27evenodd%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.rockylinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%274096%27%20height%3D%274096%27%20fill%3D%27none%27%20viewBox%3D%270%200%20192%20192%27%3E%3Cpath%20fill%3D%27%2310B981%27%20fill-rule%3D%27evenodd%27%20d%3D%27M186.658%20127.658C190.119%20117.746%20192%20107.093%20192%2096c0-53.02-42.981-96-96-96C42.98%200%200%2042.98%200%2096c0%2026.234%2010.523%2050.011%2027.578%2067.338l97.38-97.38L149%2090l37.658%2037.658Zm-17.536%2030.547-44.164-44.163-69.16%2069.16C68.027%20188.849%2081.645%20192%2096%20192c29.288%200%2055.514-13.116%2073.122-33.795Z%27%20clip-rule%3D%27evenodd%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .ubuntu-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20id%3D%27Layer_1%27%20data-name%3D%27Layer%201%27%20version%3D%271.1%27%20viewBox%3D%270%200%20767.407%20738.314%27%3E%3Cdefs%20id%3D%27defs1%27%3E%3Cstyle%20id%3D%27style1%27%3E.cls-1%7Bfill%3A%23e9500e%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ccircle%20id%3D%27circle1%27%20cx%3D%27109.027%27%20cy%3D%27348.094%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Ccircle%20id%3D%27circle2%27%20cx%3D%27563.084%27%20cy%3D%27109.027%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path2%27%20d%3D%27M492.482%201336.666a265.525%20265.525%200%200%201-181.071-138.098%20156.948%20156.948%200%200%201-93.22%2011.458A354.935%20354.935%200%200%200%20473.73%201424.19a359.208%20359.208%200%200%200%2077.42%207.967%20156.003%20156.003%200%200%201-31.923-91.154%20271.194%20271.194%200%200%201-26.744-4.337Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Ccircle%20id%3D%27circle3%27%20cx%3D%27538.52%27%20cy%3D%27629.287%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path3%27%20d%3D%27M827.798%201297.22a356.7%20356.7%200%200%200%2017.875-418.518%20157.144%20157.144%200%200%201-61.31%2071.803%20267.293%20267.293%200%200%201-8.737%20265.488%20156.347%20156.347%200%200%201%2052.172%2081.228Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Cpath%20id%3D%27path4%27%20d%3D%27M238.176%20899.72q4.15-.229%208.306-.226a157.346%20157.346%200%200%201%2082.682%2023.545%20265.907%20265.907%200%200%201%20214.108-114.51%20158.088%20158.088%200%200%201%2033.076-88.42c-137.103-10.857-270.324%2059.892-338.172%20179.61Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.ubuntu-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20id%3D%27Layer_1%27%20data-name%3D%27Layer%201%27%20version%3D%271.1%27%20viewBox%3D%270%200%20767.407%20738.314%27%3E%3Cdefs%20id%3D%27defs1%27%3E%3Cstyle%20id%3D%27style1%27%3E.cls-1%7Bfill%3A%23e9500e%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ccircle%20id%3D%27circle1%27%20cx%3D%27109.027%27%20cy%3D%27348.094%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Ccircle%20id%3D%27circle2%27%20cx%3D%27563.084%27%20cy%3D%27109.027%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path2%27%20d%3D%27M492.482%201336.666a265.525%20265.525%200%200%201-181.071-138.098%20156.948%20156.948%200%200%201-93.22%2011.458A354.935%20354.935%200%200%200%20473.73%201424.19a359.208%20359.208%200%200%200%2077.42%207.967%20156.003%20156.003%200%200%201-31.923-91.154%20271.194%20271.194%200%200%201-26.744-4.337Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Ccircle%20id%3D%27circle3%27%20cx%3D%27538.52%27%20cy%3D%27629.287%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path3%27%20d%3D%27M827.798%201297.22a356.7%20356.7%200%200%200%2017.875-418.518%20157.144%20157.144%200%200%201-61.31%2071.803%20267.293%20267.293%200%200%201-8.737%20265.488%20156.347%20156.347%200%200%201%2052.172%2081.228Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Cpath%20id%3D%27path4%27%20d%3D%27M238.176%20899.72q4.15-.229%208.306-.226a157.346%20157.346%200%200%201%2082.682%2023.545%20265.907%20265.907%200%200%201%20214.108-114.51%20158.088%20158.088%200%200%201%2033.076-88.42c-137.103-10.857-270.324%2059.892-338.172%20179.61Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n"
  },
  {
    "path": "www/dist/static/normalize.css",
    "content": "/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n   ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n  line-height: 1.15; /* 1 */\n  -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n   ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n  margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n  display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n/* Grouping content\n   ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n  box-sizing: content-box; /* 1 */\n  height: 0; /* 1 */\n  overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n  font-family: monospace, monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n  background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n  border-bottom: none; /* 1 */\n  text-decoration: underline; /* 2 */\n  text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n  font-family: monospace, monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n  font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\n/* Embedded content\n   ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n  border-style: none;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: inherit; /* 1 */\n  font-size: 100%; /* 1 */\n  line-height: 1.15; /* 1 */\n  margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n  overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n  text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n  -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n  border-style: none;\n  padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n  outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n  padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n *    `fieldset` elements in all browsers.\n */\n\nlegend {\n  box-sizing: border-box; /* 1 */\n  color: inherit; /* 2 */\n  display: table; /* 1 */\n  max-width: 100%; /* 1 */\n  padding: 0; /* 3 */\n  white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n  vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n  overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n  box-sizing: border-box; /* 1 */\n  padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n  -webkit-appearance: textfield; /* 1 */\n  outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n  -webkit-appearance: button; /* 1 */\n  font: inherit; /* 2 */\n}\n\n/* Interactive\n   ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n  display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n  display: list-item;\n}\n\n/* Misc\n   ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n  display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n  display: none;\n}\n"
  },
  {
    "path": "www/dist/uindex.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/normalize.css\"/>\n    <link id=\"blueprint3-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint3.css\" disabled/>\n    <link id=\"blueprint5-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint5.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/global.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-datetime2.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-icons.css\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"toaster\"></div>\n    <script>\n      window.user = true;\n    </script>\n    <script src=\"static/app.e2e283.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/dist-dev/index.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud Dev</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/normalize.css\"/>\n    <link id=\"blueprint3-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint3.css\" disabled/>\n    <link id=\"blueprint5-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint5.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/global.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-datetime2.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-icons.css\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"toaster\"></div>\n    <script src=\"static/app.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/dist-dev/login.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\"\n      content=\"user-scalable=no, width=device-width, initial-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"apple-touch-icon\" href=\"/logo.png\">\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <style>\n      progress,sub,sup{vertical-align:baseline}button,hr,input{overflow:visible}[type=checkbox],[type=radio],legend{box-sizing:border-box;padding:0}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}button,input,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{color:inherit;display:table;max-width:100%;white-space:normal}textarea{overflow:auto}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}\n\n      @charset \"UTF-8\";h1,h2,h3{line-height:.8em}h4,h5,h6{line-height:.9em}.pt-ui-text,body{letter-spacing:0;text-transform:none}a,a:hover{color:#106ba3}.pt-breadcrumbs>li::after,span.pt-icon-large,span.pt-icon-standard,span.pt-icon::before{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-button.pt-loading>:not(.pt-button-spinner),.pt-button.pt-loading[class*=pt-icon-]::before{visibility:hidden}.pt-monospace-text,.pt-ui-text,body,code,pre{text-transform:none}.pt-select::after,.pt-skeleton,.pt-tab-indicator-wrapper,.pt-transition-container.pt-popover-leave .pt-popover-content{pointer-events:none}html{-moz-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-moz-box-sizing:inherit;box-sizing:inherit}@font-face{font-family:Icons16;font-style:normal;font-weight:400;src:url(icons-16.eot?#iefix) format(\"embedded-opentype\"),url(icons-16.woff) format(\"woff\"),url(icons-16.ttf) format(\"truetype\")}@font-face{font-family:Icons20;font-style:normal;font-weight:400;src:url(icons-20.eot?#iefix) format(\"embedded-opentype\"),url(icons-20.woff) format(\"woff\"),url(icons-20.ttf) format(\"truetype\")}hr{margin:20px 0;border:none;border-bottom:1px solid rgba(16,22,26,.15)}code,pre{border-radius:3px}.pt-dark hr{border-color:rgba(255,255,255,.15)}::-moz-selection{background:rgba(125,188,255,.6)}::selection{background:rgba(125,188,255,.6)}body{line-height:1.28581;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,\"Open Sans\",\"Helvetica Neue\",Icons16,sans-serif;font-size:14px;font-weight:400;color:#182026}h1,h2,h3,h4,h5,h6{color:#182026;font-weight:600;margin:0 0 10px;padding:0}small{font-size:12px}.pt-monospace-text{font-family:monospace;font-size:smaller}h1{font-size:40px}h2{font-size:27px}h3{font-size:24px}h4{font-size:20px}h5{font-size:17px}h6{font-size:15px}.pt-ui-text{line-height:1.28581;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,\"Open Sans\",\"Helvetica Neue\",Icons16,sans-serif;font-size:14px;font-weight:400}.pt-ui-text-large{line-height:1.25;font-size:16px}.pt-running-text{line-height:1.5;font-size:16px}.pt-running-text h1,.pt-running-text h2,.pt-running-text h3,.pt-running-text h4,.pt-running-text h5,.pt-running-text h6{margin-top:40px;margin-bottom:20px}blockquote,p{margin:0 0 10px}ol,pre,ul{margin:10px 0}.pt-running-text-small{line-height:1.5;font-size:14px}p{padding:0}a{text-decoration:none}a:hover{cursor:pointer;text-decoration:underline}.pt-dark a code,a .pt-icon,a .pt-icon-large,a .pt-icon-standard,a code{color:inherit}.pt-dark a,.pt-dark a:hover{color:#48aff0}.pt-dark a .pt-icon,.pt-dark a .pt-icon-large,.pt-dark a .pt-icon-standard,.pt-dark a:hover .pt-icon,.pt-dark a:hover .pt-icon-large,.pt-dark a:hover .pt-icon-standard{color:inherit}code,pre{font-family:monospace;font-size:smaller}code{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2);background:rgba(255,255,255,.7);padding:2px 5px;color:#5c7080}.pt-dark code{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#bfccd6}pre{display:block;box-shadow:inset 0 0 0 1px rgba(16,22,26,.15);background:rgba(255,255,255,.7);padding:13px 15px 12px;line-height:1.4;color:#182026;font-size:13px;word-break:break-all;word-wrap:break-word}.pt-dialog-header h5,.pt-menu-header>h6,.pt-menu-item,.pt-tab,.pt-text-overflow-ellipsis,.pt-tree-node-label{text-overflow:ellipsis;word-wrap:normal}pre>code{border-radius:0;box-shadow:none;background:0 0;padding:0;white-space:pre-wrap;color:inherit;font-size:inherit}.pt-dialog-header h5,.pt-editable-text,.pt-menu-header>h6,.pt-menu-item,.pt-popover-target .pt-tooltip,.pt-tab,.pt-tree-node-label{white-space:nowrap}.pt-dark pre{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#f5f8fa}.pt-dark pre>code{box-shadow:none;background:0 0}blockquote{line-height:1.5;font-size:16px;border-left:solid 4px rgba(167,182,194,.5);padding:0 20px}span.pt-icon,span.pt-icon-large,span.pt-icon-standard{line-height:1;font-weight:400;font-style:normal}blockquote p:last-child{margin-bottom:0}.pt-dark blockquote{border-color:rgba(115,134,148,.5)}ol,ul{padding-left:40px}.pt-list li:not(:last-child),.pt-running-text ol li:not(:last-child),.pt-running-text ul li:not(:last-child){padding-bottom:5px}.pt-list li :last-child,.pt-running-text ol li :last-child,.pt-running-text ul li :last-child{margin-bottom:0}.pt-list ol,.pt-list ul,.pt-running-text ol ol,.pt-running-text ol ul,.pt-running-text ul ol,.pt-running-text ul ul{margin-top:5px}.pt-list-unstyled{margin:0;padding:0;list-style:none}.pt-list-unstyled li{padding:0}.pt-text-muted{color:#5c7080}.pt-dark .pt-text-muted{color:#bfccd6}.pt-text-overflow-ellipsis{overflow:hidden;white-space:nowrap}.pt-rtl{text-align:right}.pt-dark,.pt-dark h1,.pt-dark h2,.pt-dark h3,.pt-dark h4,.pt-dark h5,.pt-dark h6{color:#f5f8fa}.pt-icon,.pt-icon-large,.pt-icon-standard{display:inline-block}.pt-icon-large.pt-intent-primary,.pt-icon-standard.pt-intent-primary,.pt-icon.pt-intent-primary{color:#137cbd}.pt-icon-large.pt-intent-success,.pt-icon-standard.pt-intent-success,.pt-icon.pt-intent-success{color:#0f9960}.pt-icon-large.pt-intent-warning,.pt-icon-standard.pt-intent-warning,.pt-icon.pt-intent-warning{color:#d9822b}.pt-icon-large.pt-intent-danger,.pt-icon-standard.pt-intent-danger,.pt-icon.pt-intent-danger{color:#db3737}span.pt-icon-standard{font-family:Icons16,sans-serif;font-size:16px}span.pt-icon-large{font-family:Icons20,sans-serif;font-size:20px}span.pt-icon{font-family:Icons20;font-size:inherit}.pt-icon-blank::before{content:\"\"}.pt-icon-style::before{content:\"\"}.pt-icon-align-left::before{content:\"\"}.pt-icon-align-center::before{content:\"\"}.pt-icon-align-right::before{content:\"\"}.pt-icon-align-justify::before{content:\"\"}.pt-icon-bold::before{content:\"\"}.pt-icon-italic::before{content:\"\"}.pt-icon-underline::before{content:\"⎁\"}.pt-icon-search-around::before{content:\"\"}.pt-icon-graph-remove::before{content:\"\"}.pt-icon-group-objects::before{content:\"\"}.pt-icon-merge-links::before{content:\"\"}.pt-icon-layout::before{content:\"\"}.pt-icon-layout-auto::before{content:\"\"}.pt-icon-layout-circle::before{content:\"\"}.pt-icon-layout-hierarchy::before{content:\"\"}.pt-icon-layout-grid::before{content:\"\"}.pt-icon-layout-group-by::before{content:\"\"}.pt-icon-layout-skew-grid::before{content:\"\"}.pt-icon-geosearch::before{content:\"\"}.pt-icon-heatmap::before{content:\"\"}.pt-icon-drive-time::before{content:\"\"}.pt-icon-select::before{content:\"\"}.pt-icon-predictive-analysis::before{content:\"\"}.pt-icon-layers::before{content:\"\"}.pt-icon-locate::before{content:\"\"}.pt-icon-bookmark::before{content:\"\"}.pt-icon-citation::before{content:\"\"}.pt-icon-tag::before{content:\"\"}.pt-icon-clipboard::before{content:\"\"}.pt-icon-selection::before{content:\"⦿\"}.pt-icon-timeline-events::before{content:\"\"}.pt-icon-timeline-line-chart::before{content:\"\"}.pt-icon-timeline-bar-chart::before{content:\"\"}.pt-icon-applications::before{content:\"\"}.pt-icon-projects::before{content:\"\"}.pt-icon-changes::before{content:\"\"}.pt-icon-notifications::before{content:\"\"}.pt-icon-lock::before{content:\"\"}.pt-icon-unlock::before{content:\"\"}.pt-icon-user::before{content:\"\"}.pt-icon-search-template::before{content:\"\"}.pt-icon-inbox::before{content:\"\"}.pt-icon-more::before{content:\"\"}.pt-icon-help::before{content:\"?\"}.pt-icon-calendar::before{content:\"\"}.pt-icon-media::before{content:\"\"}.pt-icon-link::before{content:\"\"}.pt-icon-share::before{content:\"\"}.pt-icon-download::before{content:\"\"}.pt-icon-document::before{content:\"\"}.pt-icon-properties::before{content:\"\"}.pt-icon-import::before{content:\"\"}.pt-icon-export::before{content:\"\"}.pt-icon-minimize::before{content:\"\"}.pt-icon-maximize::before{content:\"\"}.pt-icon-tick::before{content:\"✓\"}.pt-icon-cross::before{content:\"✗\"}.pt-icon-plus::before{content:\"+\"}.pt-icon-minus::before{content:\"−\"}.pt-icon-arrow-left::before{content:\"←\"}.pt-icon-arrow-right::before{content:\"→\"}.pt-icon-exchange::before{content:\"\"}.pt-icon-comparison::before{content:\"\"}.pt-icon-list::before{content:\"☰\"}.pt-icon-filter::before{content:\"\"}.pt-icon-confirm::before{content:\"\"}.pt-icon-fork::before{content:\"\"}.pt-icon-trash::before{content:\"\"}.pt-icon-person::before{content:\"\"}.pt-icon-people::before{content:\"\"}.pt-icon-add::before{content:\"\"}.pt-icon-remove::before{content:\"\"}.pt-icon-geolocation::before{content:\"\"}.pt-icon-zoom-in::before{content:\"\"}.pt-icon-zoom-out::before{content:\"\"}.pt-icon-refresh::before{content:\"\"}.pt-icon-delete::before{content:\"\"}.pt-icon-cog::before{content:\"\"}.pt-icon-flag::before{content:\"⚑\"}.pt-icon-pin::before{content:\"\"}.pt-icon-warning-sign::before{content:\"\"}.pt-icon-error::before{content:\"\"}.pt-icon-info-sign::before{content:\"ℹ\"}.pt-icon-credit-card::before{content:\"\"}.pt-icon-edit::before{content:\"✎\"}.pt-icon-history::before{content:\"\"}.pt-icon-search::before{content:\"\"}.pt-icon-log-out::before{content:\"\"}.pt-icon-star::before{content:\"★\"}.pt-icon-star-empty::before{content:\"☆\"}.pt-icon-sort-alphabetical::before{content:\"\"}.pt-icon-sort-numerical::before{content:\"\"}.pt-icon-sort::before{content:\"\"}.pt-icon-folder-open::before{content:\"\"}.pt-icon-folder-close::before{content:\"\"}.pt-icon-folder-shared::before{content:\"\"}.pt-icon-caret-up::before{content:\"⌃\"}.pt-icon-caret-right::before{content:\"〉\"}.pt-icon-caret-down::before{content:\"⌄\"}.pt-icon-caret-left::before{content:\"〈\"}.pt-icon-menu-open::before{content:\"\"}.pt-icon-menu-closed::before{content:\"\"}.pt-icon-feed::before{content:\"\"}.pt-icon-two-columns::before{content:\"\"}.pt-icon-one-column::before{content:\"\"}.pt-icon-dot::before{content:\"•\"}.pt-icon-property::before{content:\"\"}.pt-icon-time::before{content:\"⏲\"}.pt-icon-disable::before{content:\"\"}.pt-icon-unpin::before{content:\"\"}.pt-icon-flows::before{content:\"\"}.pt-icon-new-text-box::before{content:\"\"}.pt-icon-new-link::before{content:\"\"}.pt-icon-new-object::before{content:\"\"}.pt-icon-path-search::before{content:\"\"}.pt-icon-automatic-updates::before{content:\"\"}.pt-icon-page-layout::before{content:\"\"}.pt-icon-code::before{content:\"\"}.pt-icon-map::before{content:\"\"}.pt-icon-search-text::before{content:\"\"}.pt-icon-envelope::before{content:\"✉\"}.pt-icon-paperclip::before{content:\"\"}.pt-icon-label::before{content:\"\"}.pt-icon-globe::before{content:\"\"}.pt-icon-home::before{content:\"⌂\"}.pt-icon-th::before{content:\"\"}.pt-icon-th-list::before{content:\"\"}.pt-icon-th-derived::before{content:\"\"}.pt-icon-circle::before{content:\"\"}.pt-icon-draw::before{content:\"\"}.pt-icon-insert::before{content:\"\"}.pt-icon-helper-management::before{content:\"\"}.pt-icon-send-to::before{content:\"\"}.pt-icon-eye-open::before{content:\"\"}.pt-icon-folder-shared-open::before{content:\"\"}.pt-icon-social-media::before{content:\"\"}.pt-icon-arrow-up::before{content:\"↑\"}.pt-icon-arrow-down::before{content:\"↓\"}.pt-icon-arrows-horizontal::before{content:\"↔\"}.pt-icon-arrows-vertical::before{content:\"↕\"}.pt-icon-resolve::before{content:\"\"}.pt-icon-graph::before{content:\"\"}.pt-icon-briefcase::before{content:\"\"}.pt-icon-dollar::before{content:\"$\"}.pt-icon-ninja::before{content:\"\"}.pt-icon-delta::before{content:\"Δ\"}.pt-icon-barcode::before{content:\"\"}.pt-icon-torch::before{content:\"\"}.pt-icon-widget::before{content:\"\"}.pt-icon-unresolve::before{content:\"\"}.pt-icon-offline::before{content:\"\"}.pt-icon-zoom-to-fit::before{content:\"\"}.pt-icon-add-to-artifact::before{content:\"\"}.pt-icon-map-marker::before{content:\"\"}.pt-icon-chart::before{content:\"\"}.pt-icon-control::before{content:\"\"}.pt-icon-multi-select::before{content:\"\"}.pt-icon-direction-left::before{content:\"\"}.pt-icon-direction-right::before{content:\"\"}.pt-icon-database::before{content:\"\"}.pt-icon-pie-chart::before{content:\"\"}.pt-icon-full-circle::before{content:\"\"}.pt-icon-square::before{content:\"\"}.pt-icon-print::before{content:\"⎙\"}.pt-icon-presentation::before{content:\"\"}.pt-icon-ungroup-objects::before{content:\"\"}.pt-icon-chat::before{content:\"\"}.pt-icon-comment::before{content:\"\"}.pt-icon-circle-arrow-right::before{content:\"\"}.pt-icon-circle-arrow-left::before{content:\"\"}.pt-icon-circle-arrow-up::before{content:\"\"}.pt-icon-circle-arrow-down::before{content:\"\"}.pt-icon-upload::before{content:\"\"}.pt-icon-asterisk::before{content:\"*\"}.pt-icon-cloud::before{content:\"☁\"}.pt-icon-cloud-download::before{content:\"\"}.pt-icon-cloud-upload::before{content:\"\"}.pt-icon-repeat::before{content:\"\"}.pt-icon-move::before{content:\"\"}.pt-icon-chevron-left::before{content:\"\"}.pt-icon-chevron-right::before{content:\"\"}.pt-icon-chevron-up::before{content:\"\"}.pt-icon-chevron-down::before{content:\"\"}.pt-icon-random::before{content:\"\"}.pt-icon-fullscreen::before{content:\"\"}.pt-icon-log-in::before{content:\"\"}.pt-icon-heart::before{content:\"♥\"}.pt-icon-office::before{content:\"\"}.pt-icon-duplicate::before{content:\"\"}.pt-icon-ban-circle::before{content:\"\"}.pt-icon-camera::before{content:\"\"}.pt-icon-mobile-video::before{content:\"\"}.pt-icon-video::before{content:\"\"}.pt-icon-film::before{content:\"\"}.pt-icon-settings::before{content:\"\"}.pt-icon-volume-off::before{content:\"\"}.pt-icon-volume-down::before{content:\"\"}.pt-icon-volume-up::before{content:\"\"}.pt-icon-music::before{content:\"\"}.pt-icon-step-backward::before{content:\"\"}.pt-icon-fast-backward::before{content:\"\"}.pt-icon-pause::before{content:\"\"}.pt-icon-stop::before{content:\"\"}.pt-icon-play::before{content:\"\"}.pt-icon-fast-forward::before{content:\"\"}.pt-icon-step-forward::before{content:\"\"}.pt-icon-eject::before{content:\"⏏\"}.pt-icon-record::before{content:\"\"}.pt-icon-desktop::before{content:\"\"}.pt-icon-phone::before{content:\"☎\"}.pt-icon-lightbulb::before{content:\"\"}.pt-icon-glass::before{content:\"\"}.pt-icon-tint::before{content:\"\"}.pt-icon-flash::before{content:\"\"}.pt-icon-font::before{content:\"\"}.pt-icon-header::before{content:\"\"}.pt-icon-saved::before{content:\"\"}.pt-icon-floppy-disk::before{content:\"\"}.pt-icon-book::before{content:\"\"}.pt-icon-hand-right::before{content:\"\"}.pt-icon-hand-up::before{content:\"\"}.pt-icon-hand-down::before{content:\"\"}.pt-icon-hand-left::before{content:\"\"}.pt-icon-thumbs-up::before{content:\"\"}.pt-icon-thumbs-down::before{content:\"\"}.pt-icon-box::before{content:\"\"}.pt-icon-compressed::before{content:\"\"}.pt-icon-shopping-cart::before{content:\"\"}.pt-icon-shop::before{content:\"\"}.pt-icon-layout-linear::before{content:\"\"}.pt-icon-undo::before{content:\"⎌\"}.pt-icon-redo::before{content:\"\"}.pt-icon-code-block::before{content:\"\"}.pt-icon-double-caret-vertical::before{content:\"\"}.pt-icon-double-caret-horizontal::before{content:\"\"}.pt-icon-sort-alphabetical-desc::before{content:\"\"}.pt-icon-sort-numerical-desc::before{content:\"\"}.pt-icon-take-action::before{content:\"\"}.pt-icon-contrast::before{content:\"\"}.pt-icon-eye-off::before{content:\"\"}.pt-icon-timeline-area-chart::before{content:\"\"}.pt-icon-doughnut-chart::before{content:\"\"}.pt-icon-layer::before{content:\"\"}.pt-icon-grid::before{content:\"\"}.pt-icon-polygon-filter::before{content:\"\"}.pt-icon-add-to-folder::before{content:\"\"}.pt-icon-layout-balloon::before{content:\"\"}.pt-icon-layout-sorted-clusters::before{content:\"\"}.pt-icon-sort-asc::before{content:\"\"}.pt-icon-sort-desc::before{content:\"\"}.pt-icon-small-cross::before{content:\"\"}.pt-icon-small-tick::before{content:\"\"}.pt-icon-power::before{content:\"\"}.pt-icon-column-layout::before{content:\"\"}.pt-icon-arrow-top-left::before{content:\"↖\"}.pt-icon-arrow-top-right::before{content:\"↗\"}.pt-icon-arrow-bottom-right::before{content:\"↘\"}.pt-icon-arrow-bottom-left::before{content:\"↙\"}.pt-icon-mugshot::before{content:\"\"}.pt-icon-headset::before{content:\"\"}.pt-icon-text-highlight::before{content:\"\"}.pt-icon-hand::before{content:\"\"}.pt-icon-chevron-backward::before{content:\"\"}.pt-icon-chevron-forward::before{content:\"\"}.pt-icon-rotate-document::before{content:\"\"}.pt-icon-rotate-page::before{content:\"\"}.pt-icon-badge::before{content:\"\"}.pt-icon-grid-view::before{content:\"\"}.pt-icon-function::before{content:\"\"}.pt-icon-waterfall-chart::before{content:\"\"}.pt-icon-stacked-chart::before{content:\"\"}.pt-icon-pulse::before{content:\"\"}.pt-icon-new-person::before{content:\"\"}.pt-icon-exclude-row::before{content:\"\"}.pt-icon-pivot-table::before{content:\"\"}.pt-icon-segmented-control::before{content:\"\"}.pt-icon-highlight::before{content:\"\"}.pt-icon-filter-list::before{content:\"\"}.pt-icon-cut::before{content:\"\"}.pt-icon-annotation::before{content:\"\"}.pt-icon-pivot::before{content:\"\"}.pt-icon-ring::before{content:\"\"}.pt-icon-heat-grid::before{content:\"\"}.pt-icon-gantt-chart::before{content:\"\"}.pt-icon-variable::before{content:\"\"}.pt-icon-manual::before{content:\"\"}.pt-icon-add-row-top::before{content:\"\"}.pt-icon-add-row-bottom::before{content:\"\"}.pt-icon-add-column-left::before{content:\"\"}.pt-icon-add-column-right::before{content:\"\"}.pt-icon-remove-row-top::before{content:\"\"}.pt-icon-remove-row-bottom::before{content:\"\"}.pt-icon-remove-column-left::before{content:\"\"}.pt-icon-remove-column-right::before{content:\"\"}.pt-icon-double-chevron-left::before{content:\"\"}.pt-icon-double-chevron-right::before{content:\"\"}.pt-icon-double-chevron-up::before{content:\"\"}.pt-icon-double-chevron-down::before{content:\"\"}.pt-icon-key-control::before{content:\"\"}.pt-icon-key-command::before{content:\"\"}.pt-icon-key-shift::before{content:\"\"}.pt-icon-key-backspace::before{content:\"\"}.pt-icon-key-delete::before{content:\"\"}.pt-icon-key-escape::before{content:\"\"}.pt-icon-key-enter::before{content:\"\"}.pt-icon-calculator::before{content:\"\"}.pt-icon-horizontal-bar-chart::before{content:\"\"}.pt-icon-small-plus::before{content:\"\"}.pt-icon-small-minus::before{content:\"\"}.pt-icon-step-chart::before{content:\"\"}.pt-icon-euro::before{content:\"€\"}.pt-icon-drag-handle-vertical::before{content:\"\"}.pt-icon-drag-handle-horizontal::before{content:\"\"}.pt-icon-mobile-phone::before{content:\"\"}.pt-icon-sim-card::before{content:\"\"}.pt-icon-trending-up::before{content:\"\"}.pt-icon-trending-down::before{content:\"\"}.pt-icon-curved-range-chart::before{content:\"\"}.pt-icon-vertical-bar-chart-desc::before{content:\"\"}.pt-icon-horizontal-bar-chart-desc::before{content:\"\"}.pt-icon-document-open::before{content:\"\"}.pt-icon-document-share::before{content:\"\"}.pt-icon-horizontal-distribution::before{content:\"\"}.pt-icon-vertical-distribution::before{content:\"\"}.pt-icon-alignment-left::before{content:\"\"}.pt-icon-alignment-vertical-center::before{content:\"\"}.pt-icon-alignment-right::before{content:\"\"}.pt-icon-alignment-top::before{content:\"\"}.pt-icon-alignment-horizontal-center::before{content:\"\"}.pt-icon-alignment-bottom::before{content:\"\"}.pt-icon-git-pull::before{content:\"\"}.pt-icon-git-merge::before{content:\"\"}.pt-icon-git-branch::before{content:\"\"}.pt-icon-git-commit::before{content:\"\"}.pt-icon-git-push::before{content:\"\"}.pt-icon-build::before{content:\"\"}.pt-icon-symbol-circle::before{content:\"\"}.pt-icon-symbol-square::before{content:\"\"}.pt-icon-symbol-diamond::before{content:\"\"}.pt-icon-symbol-cross::before{content:\"\"}.pt-icon-symbol-triangle-up::before{content:\"\"}.pt-icon-symbol-triangle-down::before{content:\"\"}.pt-icon-wrench::before{content:\"\"}.pt-icon-application::before{content:\"\"}.pt-icon-send-to-graph::before{content:\"\"}.pt-icon-send-to-map::before{content:\"\"}.pt-icon-join-table::before{content:\"\"}.pt-icon-derive-column::before{content:\"\"}.pt-icon-image-rotate-left::before{content:\"\"}.pt-icon-image-rotate-right::before{content:\"\"}.pt-icon-known-vehicle::before{content:\"\"}.pt-icon-unknown-vehicle::before{content:\"\"}.pt-icon-scatter-plot::before{content:\"\"}.pt-icon-oil-field::before{content:\"\"}.pt-icon-rig::before{content:\"\"}.pt-icon-map-create::before{content:\"\"}.pt-icon-key-option::before{content:\"\"}.pt-icon-list-detail-view::before{content:\"\"}.pt-icon-swap-vertical::before{content:\"\"}.pt-icon-swap-horizontal::before{content:\"\"}.pt-icon-numbered-list::before{content:\"\"}.pt-icon-new-grid-item::before{content:\"\"}.pt-icon-git-repo::before{content:\"\"}.pt-icon-git-new-branch::before{content:\"\"}.pt-icon-manually-entered-data::before{content:\"\"}.pt-icon-airplane::before{content:\"\"}.pt-icon-merge-columns::before{content:\"\"}.pt-icon-split-columns::before{content:\"\"}.pt-icon-dashboard::before{content:\"\"}.pt-icon-publish-function::before{content:\"\"}.pt-icon-path::before{content:\"\"}.pt-icon-moon::before{content:\"\"}.pt-icon-remove-column::before{content:\"\"}.pt-icon-numerical::before{content:\"\"}.pt-icon-key-tab::before{content:\"\"}.pt-icon-regression-chart::before{content:\"\"}.pt-icon-translate::before{content:\"\"}.pt-icon-eye-on::before{content:\"\"}.pt-icon-vertical-bar-chart-asc::before{content:\"\"}.pt-icon-horizontal-bar-chart-asc::before{content:\"\"}.pt-icon-grouped-bar-chart::before{content:\"\"}.pt-icon-full-stacked-chart::before{content:\"\"}.pt-icon-endorsed::before{content:\"\"}.pt-icon-follower::before{content:\"\"}.pt-icon-following::before{content:\"\"}.pt-icon-menu::before{content:\"\"}.pt-icon-collapse-all::before{content:\"\"}.pt-icon-expand-all::before{content:\"\"}.pt-icon-intersection::before{content:\"\"}.pt-icon-blocked-person::before{content:\"\"}.pt-icon-slash::before{content:\"\"}.pt-icon-percentage::before{content:\"\"}.pt-icon-satellite::before{content:\"\"}.pt-icon-paragraph::before{content:\"\"}.pt-icon-banl-account::before{content:\"\"}.pt-icon-cell-tower::before{content:\"\"}.pt-icon-id-number::before{content:\"\"}.pt-icon-ip-address::before{content:\"\"}:focus{outline:rgba(19,124,189,.5) auto 2px;outline-offset:2px;-moz-outline-radius:6px}.pt-focus-disabled :focus,.pt-focus-disabled :focus~.pt-control-indicator{outline:0!important}.pt-alert{max-width:400px;padding:20px}.pt-alert-body{display:-webkit-flex;display:flex}.pt-alert-body .pt-icon{margin-right:20px;font-size:40px}.pt-alert-footer{display:-webkit-flex;display:flex;-webkit-flex-direction:row-reverse;flex-direction:row-reverse;margin-top:10px}.pt-alert-footer .pt-button{margin-left:10px}.pt-breadcrumbs{display:inline-block;margin:0;cursor:default;padding:0;list-style:none;vertical-align:top;line-height:30px}.pt-breadcrumbs>li{float:left}.pt-breadcrumbs>li::after{line-height:1;font-family:Icons16,sans-serif;font-size:16px;font-weight:400;font-style:normal;padding:0 5px;color:#5c7080;content:\"\"}.pt-breadcrumbs>li:last-child::after{display:none}.pt-breadcrumb,.pt-breadcrumb-current,.pt-breadcrumbs-collapsed{display:inline-block;line-height:19px;font-size:16px}.pt-breadcrumb,.pt-breadcrumbs-collapsed{color:#5c7080}.pt-breadcrumb:hover{text-decoration:none}.pt-breadcrumb.pt-disabled{cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-breadcrumb-current{color:inherit;font-weight:600}.pt-breadcrumb-current .pt-input{vertical-align:baseline;font-size:inherit;font-weight:inherit}.pt-breadcrumbs-collapsed::before,.pt-button[class*=pt-icon-]::before{font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-breadcrumbs-collapsed{margin-right:2px;border:none;border-radius:3px;background:#ced9e0;cursor:pointer;padding:0 5px}.pt-breadcrumbs-collapsed::before{font-family:Icons20,sans-serif;font-size:20px;line-height:19px;content:\"\"}.pt-breadcrumbs-collapsed:hover{background:#bfccd6;text-decoration:none;color:#182026}.pt-dark .pt-breadcrumb,.pt-dark .pt-breadcrumbs-collapsed,.pt-dark .pt-breadcrumbs>li::after{color:#bfccd6}.pt-dark .pt-breadcrumb.pt-disabled{color:rgba(191,204,214,.5)}.pt-dark .pt-breadcrumb-current{color:#f5f8fa}.pt-dark .pt-breadcrumbs-collapsed{background:rgba(16,22,26,.4)}.pt-dark .pt-breadcrumbs-collapsed:hover{background:rgba(16,22,26,.6);color:#f5f8fa}.pt-button{display:inline-block;border:none;border-radius:3px;cursor:pointer;padding:0 10px;vertical-align:middle;font-size:14px;min-width:30px;min-height:30px;line-height:30px}.pt-button.pt-disabled,.pt-button:disabled{cursor:not-allowed}.pt-button.pt-fill{width:100%}.pt-button:not([class*=pt-intent-]){background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);color:#182026}.pt-button:not([class*=pt-intent-]):hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);background-clip:padding-box}.pt-button:not([class*=pt-intent-]).pt-active,.pt-button:not([class*=pt-intent-]):active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-button:not([class*=pt-intent-]).pt-disabled,.pt-button:not([class*=pt-intent-]):disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-button.pt-intent-primary,.pt-button.pt-intent-primary:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button:not([class*=pt-intent-]).pt-disabled.pt-active,.pt-button:not([class*=pt-intent-]):disabled.pt-active{background:rgba(206,217,224,.7)}.pt-button.pt-intent-primary{background:#137cbd;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #137cbd;color:#fff}.pt-button.pt-intent-primary.pt-active,.pt-button.pt-intent-primary:active,.pt-button.pt-intent-primary:hover{color:#fff}.pt-button.pt-intent-primary:hover{background:#106ba3;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #106ba3}.pt-button.pt-intent-primary.pt-active,.pt-button.pt-intent-primary:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#0e5a8a;background-image:none}.pt-button.pt-intent-primary.pt-disabled,.pt-button.pt-intent-primary:disabled{border-color:transparent;box-shadow:none;background-color:rgba(19,124,189,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button.pt-intent-success,.pt-button.pt-intent-success:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button.pt-intent-success{background:#0f9960;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #0f9960;color:#fff}.pt-button.pt-intent-success.pt-active,.pt-button.pt-intent-success:active,.pt-button.pt-intent-success:hover{color:#fff}.pt-button.pt-intent-success:hover{background:#0d8050;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #0d8050}.pt-button.pt-intent-success.pt-active,.pt-button.pt-intent-success:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#0a6640;background-image:none}.pt-button.pt-intent-success.pt-disabled,.pt-button.pt-intent-success:disabled{border-color:transparent;box-shadow:none;background-color:rgba(15,153,96,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button.pt-intent-warning,.pt-button.pt-intent-warning:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button.pt-intent-warning{background:#d9822b;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #d9822b;color:#fff}.pt-button.pt-intent-warning.pt-active,.pt-button.pt-intent-warning:active,.pt-button.pt-intent-warning:hover{color:#fff}.pt-button.pt-intent-warning:hover{background:#bf7326;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #bf7326}.pt-button.pt-intent-warning.pt-active,.pt-button.pt-intent-warning:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#a66321;background-image:none}.pt-button.pt-intent-warning.pt-disabled,.pt-button.pt-intent-warning:disabled{border-color:transparent;box-shadow:none;background-color:rgba(217,130,43,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button.pt-intent-danger,.pt-button.pt-intent-danger:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button.pt-intent-danger{background:#db3737;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #db3737;color:#fff}.pt-button.pt-intent-danger.pt-active,.pt-button.pt-intent-danger:active,.pt-button.pt-intent-danger:hover{color:#fff}.pt-button.pt-intent-danger:hover{background:#c23030;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #c23030}.pt-button.pt-intent-danger.pt-active,.pt-button.pt-intent-danger:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#a82a2a;background-image:none}.pt-button.pt-intent-danger.pt-disabled,.pt-button.pt-intent-danger:disabled{border-color:transparent;box-shadow:none;background-color:rgba(219,55,55,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button[class*=pt-intent-] .pt-button-spinner .pt-spinner-head{stroke:#fff}.pt-button.pt-loading{position:relative}.pt-button.pt-loading .pt-button-spinner{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.pt-button[class*=pt-icon-]::before{line-height:1;font-family:Icons16,sans-serif;font-size:16px;margin-right:7px;color:#5c7080}.pt-button .pt-icon,.pt-button .pt-icon-large,.pt-button .pt-icon-standard{margin-right:7px;color:#5c7080}.pt-button .pt-icon-large.pt-align-right,.pt-button .pt-icon-standard.pt-align-right,.pt-button .pt-icon.pt-align-right{margin-right:0;margin-left:7px}.pt-dark .pt-button:not([class*=pt-intent-]){background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-button:not([class*=pt-intent-]).pt-active,.pt-dark .pt-button:not([class*=pt-intent-]):active,.pt-dark .pt-button:not([class*=pt-intent-]):hover{color:#f5f8fa}.pt-dark .pt-button:not([class*=pt-intent-]):hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-button:not([class*=pt-intent-]).pt-active,.pt-dark .pt-button:not([class*=pt-intent-]):active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-button:not([class*=pt-intent-]).pt-disabled,.pt-dark .pt-button:not([class*=pt-intent-]):disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-button:not([class*=pt-intent-]).pt-disabled.pt-active,.pt-dark .pt-button:not([class*=pt-intent-]):disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-button:not([class*=pt-intent-]) .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-dark .pt-button:not([class*=pt-intent-]) .pt-icon,.pt-dark .pt-button:not([class*=pt-intent-]) .pt-icon-large,.pt-dark .pt-button:not([class*=pt-intent-]) .pt-icon-standard,.pt-dark .pt-button:not([class*=pt-intent-])[class*=pt-icon-]::before{color:#bfccd6}.pt-dark .pt-button[class*=pt-intent-],.pt-dark .pt-button[class*=pt-intent-]:hover{box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-button[class*=pt-intent-].pt-active,.pt-dark .pt-button[class*=pt-intent-]:active{box-shadow:0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2)}.pt-dark .pt-button[class*=pt-intent-].pt-disabled,.pt-dark .pt-button[class*=pt-intent-]:disabled{box-shadow:none;background-image:none;color:rgba(255,255,255,.3)}.pt-dark .pt-button[class*=pt-intent-] .pt-button-spinner .pt-spinner-head{stroke:#8a9ba8}.pt-button.pt-disabled .pt-icon,.pt-button.pt-disabled .pt-icon-large,.pt-button.pt-disabled .pt-icon-standard,.pt-button.pt-disabled::before,.pt-button:disabled .pt-icon,.pt-button:disabled .pt-icon-large,.pt-button:disabled .pt-icon-standard,.pt-button:disabled::before,.pt-button[class*=pt-intent-] .pt-icon,.pt-button[class*=pt-intent-] .pt-icon-large,.pt-button[class*=pt-intent-] .pt-icon-standard,.pt-button[class*=pt-intent-]::before{color:inherit!important}.pt-button.pt-minimal{box-shadow:none;background:0 0}.pt-button.pt-minimal:focus,.pt-button.pt-minimal:hover{box-shadow:none;background:rgba(167,182,194,.3);text-decoration:none;color:#182026}.pt-button.pt-minimal.pt-active,.pt-button.pt-minimal:active{box-shadow:none;background:rgba(115,134,148,.3);color:#182026}.pt-button.pt-minimal.pt-disabled,.pt-button.pt-minimal.pt-disabled:hover,.pt-button.pt-minimal:disabled,.pt-button.pt-minimal:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-dark .pt-button.pt-minimal{box-shadow:none;background:0 0;color:inherit}.pt-dark .pt-button.pt-minimal.pt-active,.pt-dark .pt-button.pt-minimal:active,.pt-dark .pt-button.pt-minimal:hover{box-shadow:none}.pt-dark .pt-button.pt-minimal:hover{background:rgba(138,155,168,.15)}.pt-dark .pt-button.pt-minimal.pt-active,.pt-dark .pt-button.pt-minimal:active{background:rgba(138,155,168,.3);color:#f5f8fa}.pt-dark .pt-button.pt-minimal.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-disabled:hover,.pt-dark .pt-button.pt-minimal:disabled,.pt-dark .pt-button.pt-minimal:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(191,204,214,.5)}.pt-button.pt-minimal.pt-intent-primary{color:#106ba3}.pt-button.pt-minimal.pt-intent-primary.pt-active,.pt-button.pt-minimal.pt-intent-primary:active,.pt-button.pt-minimal.pt-intent-primary:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-primary:focus,.pt-button.pt-minimal.pt-intent-primary:hover{background:rgba(19,124,189,.15);color:#106ba3}.pt-button.pt-minimal.pt-intent-primary.pt-active,.pt-button.pt-minimal.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#106ba3}.pt-button.pt-minimal.pt-intent-primary.pt-disabled,.pt-button.pt-minimal.pt-intent-primary:disabled{background:0 0;color:rgba(16,107,163,.5)}.pt-button.pt-minimal.pt-intent-primary .pt-button-spinner .pt-spinner-head{stroke:#106ba3}.pt-dark .pt-button.pt-minimal.pt-intent-primary{color:#48aff0}.pt-dark .pt-button.pt-minimal.pt-intent-primary:focus,.pt-dark .pt-button.pt-minimal.pt-intent-primary:hover{background:rgba(19,124,189,.2);color:#48aff0}.pt-dark .pt-button.pt-minimal.pt-intent-primary.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#48aff0}.pt-dark .pt-button.pt-minimal.pt-intent-primary.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-primary:disabled{background:0 0;color:rgba(72,175,240,.5)}.pt-button.pt-minimal.pt-intent-success{color:#0d8050}.pt-button.pt-minimal.pt-intent-success.pt-active,.pt-button.pt-minimal.pt-intent-success:active,.pt-button.pt-minimal.pt-intent-success:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-success:focus,.pt-button.pt-minimal.pt-intent-success:hover{background:rgba(15,153,96,.15);color:#0d8050}.pt-button.pt-minimal.pt-intent-success.pt-active,.pt-button.pt-minimal.pt-intent-success:active{background:rgba(15,153,96,.3);color:#0d8050}.pt-button.pt-minimal.pt-intent-success.pt-disabled,.pt-button.pt-minimal.pt-intent-success:disabled{background:0 0;color:rgba(13,128,80,.5)}.pt-button.pt-minimal.pt-intent-success .pt-button-spinner .pt-spinner-head{stroke:#0d8050}.pt-dark .pt-button.pt-minimal.pt-intent-success{color:#3dcc91}.pt-dark .pt-button.pt-minimal.pt-intent-success:focus,.pt-dark .pt-button.pt-minimal.pt-intent-success:hover{background:rgba(15,153,96,.2);color:#3dcc91}.pt-dark .pt-button.pt-minimal.pt-intent-success.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-success:active{background:rgba(15,153,96,.3);color:#3dcc91}.pt-dark .pt-button.pt-minimal.pt-intent-success.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-success:disabled{background:0 0;color:rgba(61,204,145,.5)}.pt-button.pt-minimal.pt-intent-warning{color:#bf7326}.pt-button.pt-minimal.pt-intent-warning.pt-active,.pt-button.pt-minimal.pt-intent-warning:active,.pt-button.pt-minimal.pt-intent-warning:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-warning:focus,.pt-button.pt-minimal.pt-intent-warning:hover{background:rgba(217,130,43,.15);color:#bf7326}.pt-button.pt-minimal.pt-intent-warning.pt-active,.pt-button.pt-minimal.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#bf7326}.pt-button.pt-minimal.pt-intent-warning.pt-disabled,.pt-button.pt-minimal.pt-intent-warning:disabled{background:0 0;color:rgba(191,115,38,.5)}.pt-button.pt-minimal.pt-intent-warning .pt-button-spinner .pt-spinner-head{stroke:#bf7326}.pt-dark .pt-button.pt-minimal.pt-intent-warning{color:#ffb366}.pt-dark .pt-button.pt-minimal.pt-intent-warning:focus,.pt-dark .pt-button.pt-minimal.pt-intent-warning:hover{background:rgba(217,130,43,.2);color:#ffb366}.pt-dark .pt-button.pt-minimal.pt-intent-warning.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#ffb366}.pt-dark .pt-button.pt-minimal.pt-intent-warning.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-warning:disabled{background:0 0;color:rgba(255,179,102,.5)}.pt-button.pt-minimal.pt-intent-danger{color:#c23030}.pt-button.pt-minimal.pt-intent-danger.pt-active,.pt-button.pt-minimal.pt-intent-danger:active,.pt-button.pt-minimal.pt-intent-danger:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-danger:focus,.pt-button.pt-minimal.pt-intent-danger:hover{background:rgba(219,55,55,.15);color:#c23030}.pt-button.pt-minimal.pt-intent-danger.pt-active,.pt-button.pt-minimal.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#c23030}.pt-button.pt-minimal.pt-intent-danger.pt-disabled,.pt-button.pt-minimal.pt-intent-danger:disabled{background:0 0;color:rgba(194,48,48,.5)}.pt-button.pt-minimal.pt-intent-danger .pt-button-spinner .pt-spinner-head{stroke:#c23030}.pt-dark .pt-button.pt-minimal.pt-intent-danger{color:#ff7373}.pt-dark .pt-button.pt-minimal.pt-intent-danger:focus,.pt-dark .pt-button.pt-minimal.pt-intent-danger:hover{background:rgba(219,55,55,.2);color:#ff7373}.pt-dark .pt-button.pt-minimal.pt-intent-danger.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#ff7373}.pt-dark .pt-button.pt-minimal.pt-intent-danger.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-danger:disabled{background:0 0;color:rgba(255,115,115,.5)}.pt-button.pt-large,.pt-large .pt-button{min-width:40px;min-height:40px;line-height:40px;padding:0 15px;font-size:16px}.pt-button.pt-large .pt-icon,.pt-button.pt-large .pt-icon-large,.pt-button.pt-large .pt-icon-standard,.pt-button.pt-large[class*=pt-icon-]::before,.pt-large .pt-button .pt-icon,.pt-large .pt-button .pt-icon-large,.pt-large .pt-button .pt-icon-standard,.pt-large .pt-button[class*=pt-icon-]::before{margin-right:10px}.pt-button.pt-large .pt-icon-large.pt-align-right,.pt-button.pt-large .pt-icon-standard.pt-align-right,.pt-button.pt-large .pt-icon.pt-align-right,.pt-large .pt-button .pt-icon-large.pt-align-right,.pt-large .pt-button .pt-icon-standard.pt-align-right,.pt-large .pt-button .pt-icon.pt-align-right{margin-right:0;margin-left:10px}.pt-button.pt-small,.pt-small .pt-button{min-width:24px;min-height:24px;line-height:24px;padding:0 7px;font-size:12px}.pt-button.pt-small .pt-icon,.pt-button.pt-small .pt-icon-large,.pt-button.pt-small .pt-icon-standard,.pt-button.pt-small[class*=pt-icon-]::before,.pt-small .pt-button .pt-icon,.pt-small .pt-button .pt-icon-large,.pt-small .pt-button .pt-icon-standard,.pt-small .pt-button[class*=pt-icon-]::before{display:none}.pt-button[class*=pt-icon-]:empty{padding:0}.pt-button[class*=pt-icon-]:empty::before{margin-right:0}a.pt-button{text-align:center;text-decoration:none;transition:none}a.pt-button,a.pt-button:active,a.pt-button:hover{color:#182026}a.pt-button.pt-disabled{color:rgba(92,112,128,.5)}.pt-button-group{display:-webkit-inline-flex;display:inline-flex}.pt-button-group .pt-button{-webkit-flex:0 0 auto;flex:0 0 auto;position:relative;z-index:4}.pt-button-group .pt-button:focus{z-index:5}.pt-button-group .pt-button:hover{z-index:6}.pt-button-group .pt-button.pt-active,.pt-button-group .pt-button:active{z-index:7}.pt-button-group .pt-button.pt-disabled,.pt-button-group .pt-button:disabled{z-index:3}.pt-button-group .pt-button[class*=pt-intent-]{z-index:9}.pt-button-group .pt-button[class*=pt-intent-]:focus{z-index:10}.pt-button-group .pt-button[class*=pt-intent-]:hover{z-index:11}.pt-button-group .pt-button[class*=pt-intent-].pt-active,.pt-button-group .pt-button[class*=pt-intent-]:active{z-index:12}.pt-button-group .pt-button[class*=pt-intent-].pt-disabled,.pt-button-group .pt-button[class*=pt-intent-]:disabled{z-index:8}.pt-button-group:not(.pt-minimal)>.pt-button:not(:first-child),.pt-button-group:not(.pt-minimal)>.pt-popover-target:not(:first-child) .pt-button{border-top-left-radius:0;border-bottom-left-radius:0}.pt-button-group:not(.pt-minimal)>.pt-button:not(:last-child),.pt-button-group:not(.pt-minimal)>.pt-popover-target:not(:last-child) .pt-button{margin-right:-1px;border-top-right-radius:0;border-bottom-right-radius:0}.pt-button-group.pt-minimal .pt-button{box-shadow:none;background:0 0;margin-right:11px;border-radius:3px!important;overflow:visible}.pt-button-group.pt-minimal .pt-button:focus,.pt-button-group.pt-minimal .pt-button:hover{box-shadow:none;background:rgba(167,182,194,.3);text-decoration:none;color:#182026}.pt-button-group.pt-minimal .pt-button.pt-active,.pt-button-group.pt-minimal .pt-button:active{box-shadow:none;background:rgba(115,134,148,.3);color:#182026}.pt-button-group.pt-minimal .pt-button.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-disabled:hover,.pt-button-group.pt-minimal .pt-button:disabled,.pt-button-group.pt-minimal .pt-button:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-dark .pt-button-group.pt-minimal .pt-button{box-shadow:none;background:0 0;color:inherit}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button:active,.pt-dark .pt-button-group.pt-minimal .pt-button:hover{box-shadow:none}.pt-dark .pt-button-group.pt-minimal .pt-button:hover{background:rgba(138,155,168,.15)}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button:active{background:rgba(138,155,168,.3);color:#f5f8fa}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-disabled:hover,.pt-dark .pt-button-group.pt-minimal .pt-button:disabled,.pt-dark .pt-button-group.pt-minimal .pt-button:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(191,204,214,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-primary{color:#106ba3}.pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:active,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-primary:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:hover{background:rgba(19,124,189,.15);color:#106ba3}.pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#106ba3}.pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:disabled{background:0 0;color:rgba(16,107,163,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-primary .pt-button-spinner .pt-spinner-head{stroke:#106ba3}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary{color:#48aff0}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:hover{background:rgba(19,124,189,.2);color:#48aff0}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#48aff0}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:disabled{background:0 0;color:rgba(72,175,240,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-success{color:#0d8050}.pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-success:active,.pt-button-group.pt-minimal .pt-button.pt-intent-success:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-success:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-success:hover{background:rgba(15,153,96,.15);color:#0d8050}.pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-success:active{background:rgba(15,153,96,.3);color:#0d8050}.pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-success:disabled{background:0 0;color:rgba(13,128,80,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-success .pt-button-spinner .pt-spinner-head{stroke:#0d8050}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success{color:#3dcc91}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:hover{background:rgba(15,153,96,.2);color:#3dcc91}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:active{background:rgba(15,153,96,.3);color:#3dcc91}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:disabled{background:0 0;color:rgba(61,204,145,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-warning{color:#bf7326}.pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:active,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-warning:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:hover{background:rgba(217,130,43,.15);color:#bf7326}.pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#bf7326}.pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:disabled{background:0 0;color:rgba(191,115,38,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-warning .pt-button-spinner .pt-spinner-head{stroke:#bf7326}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning{color:#ffb366}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:hover{background:rgba(217,130,43,.2);color:#ffb366}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#ffb366}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:disabled{background:0 0;color:rgba(255,179,102,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-danger{color:#c23030}.pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:active,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-danger:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:hover{background:rgba(219,55,55,.15);color:#c23030}.pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#c23030}.pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:disabled{background:0 0;color:rgba(194,48,48,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-danger .pt-button-spinner .pt-spinner-head{stroke:#c23030}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger{color:#ff7373}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:hover{background:rgba(219,55,55,.2);color:#ff7373}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#ff7373}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:disabled{background:0 0;color:rgba(255,115,115,.5)}.pt-button-group.pt-minimal .pt-button:focus{outline-style:solid}.pt-dialog:focus,.pt-editable-input:focus{outline:0}.pt-button-group.pt-minimal .pt-button::after{margin:5px;background:rgba(16,22,26,.15);width:1px;display:inline-block;position:absolute;top:10%;bottom:10%;left:100%;content:\"\"}.pt-dark .pt-button-group.pt-minimal .pt-button::after{background:rgba(255,255,255,.15)}.pt-button-group.pt-minimal>.pt-button:last-child,.pt-button-group.pt-minimal>.pt-popover-target:last-child .pt-button{margin-right:0}.pt-button-group.pt-minimal>.pt-button:last-child::after,.pt-button-group.pt-minimal>.pt-popover-target:last-child .pt-button::after{display:none}.pt-button-group.pt-fill{display:-webkit-flex;display:flex}.pt-button-group .pt-button.pt-fill,.pt-button-group.pt-fill .pt-button:not(.pt-fixed){-webkit-flex:1 1 auto;flex:1 1 auto}.pt-button-group.pt-vertical{-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;align-items:stretch;vertical-align:top}.pt-button-group.pt-vertical .pt-button{margin-right:0!important}.pt-button-group.pt-vertical>.pt-button:first-child,.pt-button-group.pt-vertical>.pt-popover-target:first-child .pt-button{border-radius:3px 3px 0 0}.pt-button-group.pt-vertical>.pt-button:last-child,.pt-button-group.pt-vertical>.pt-popover-target:last-child .pt-button{border-radius:0 0 3px 3px}.pt-button-group.pt-vertical>.pt-button:not(:last-child),.pt-button-group.pt-vertical>.pt-popover-target:not(:last-child) .pt-button{margin-bottom:-1px}.pt-button-group.pt-vertical.pt-minimal .pt-button:not(:last-child){margin-bottom:11px}.pt-button-group.pt-vertical.pt-minimal .pt-button::after{top:100%;right:0;bottom:auto;left:0;width:auto;height:1px}.pt-button-group.pt-align-left .pt-button{text-align:left}.pt-dark .pt-button-group:not(.pt-minimal)>.pt-button:not(:last-child),.pt-dark .pt-button-group:not(.pt-minimal)>.pt-popover-target:not(:last-child) .pt-button{margin-right:1px}.pt-dark .pt-button-group.pt-vertical>.pt-button:not(:last-child),.pt-dark .pt-button-group.pt-vertical>.pt-popover-target:not(:last-child) .pt-button{margin-bottom:1px}.pt-callout{line-height:1.5;font-size:14px;position:relative;border-radius:3px;background-color:rgba(138,155,168,.15);padding:10px 12px 9px}.pt-callout[class*=pt-icon-]::before,.pt-dialog-close-button{font-family:Icons20,sans-serif;font-size:20px;font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-callout[class*=pt-icon-]{padding-left:40px}.pt-callout[class*=pt-icon-]::before{line-height:1;position:absolute;top:10px;left:10px;color:#5c7080}.pt-callout h5{margin-top:0;margin-bottom:5px;line-height:20px}.pt-dark .pt-callout{background-color:rgba(138,155,168,.2)}.pt-dark .pt-callout[class*=pt-icon-]::before{color:#bfccd6}.pt-callout.pt-intent-primary{background-color:rgba(19,124,189,.15)}.pt-callout.pt-intent-primary h5,.pt-callout.pt-intent-primary[class*=pt-icon-]::before{color:#106ba3}.pt-dark .pt-callout.pt-intent-primary{background-color:rgba(19,124,189,.25)}.pt-dark .pt-callout.pt-intent-primary h5,.pt-dark .pt-callout.pt-intent-primary[class*=pt-icon-]::before{color:#48aff0}.pt-callout.pt-intent-success{background-color:rgba(15,153,96,.15)}.pt-callout.pt-intent-success h5,.pt-callout.pt-intent-success[class*=pt-icon-]::before{color:#0d8050}.pt-dark .pt-callout.pt-intent-success{background-color:rgba(15,153,96,.25)}.pt-dark .pt-callout.pt-intent-success h5,.pt-dark .pt-callout.pt-intent-success[class*=pt-icon-]::before{color:#3dcc91}.pt-callout.pt-intent-warning{background-color:rgba(217,130,43,.15)}.pt-callout.pt-intent-warning h5,.pt-callout.pt-intent-warning[class*=pt-icon-]::before{color:#bf7326}.pt-dark .pt-callout.pt-intent-warning{background-color:rgba(217,130,43,.25)}.pt-dark .pt-callout.pt-intent-warning h5,.pt-dark .pt-callout.pt-intent-warning[class*=pt-icon-]::before{color:#ffb366}.pt-callout.pt-intent-danger{background-color:rgba(219,55,55,.15)}.pt-callout.pt-intent-danger h5,.pt-callout.pt-intent-danger[class*=pt-icon-]::before{color:#c23030}.pt-dark .pt-callout.pt-intent-danger{background-color:rgba(219,55,55,.25)}.pt-dark .pt-callout.pt-intent-danger h5,.pt-dark .pt-callout.pt-intent-danger[class*=pt-icon-]::before{color:#ff7373}.pt-running-text .pt-callout{margin:20px 0}.pt-card{border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.15),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0);background-color:#fff;padding:20px;transition:box-shadow .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),box-shadow .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),box-shadow .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9)}.pt-card.pt-dark,.pt-dark .pt-card{box-shadow:0 0 0 1px rgba(16,22,26,.4),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0);background-color:#30404d}.pt-elevation-0{box-shadow:0 0 0 1px rgba(16,22,26,.15),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0)}.pt-dark .pt-elevation-0,.pt-elevation-0.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.4),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0)}.pt-elevation-1{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.2)}.pt-dark .pt-elevation-1,.pt-elevation-1.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-elevation-2{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 1px 1px rgba(16,22,26,.2),0 2px 6px rgba(16,22,26,.2)}.pt-dark .pt-elevation-2,.pt-elevation-2.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.4),0 2px 6px rgba(16,22,26,.4)}.pt-elevation-3{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2)}.pt-dark .pt-elevation-3,.pt-elevation-3.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-elevation-4{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 4px 8px rgba(16,22,26,.2),0 18px 46px 6px rgba(16,22,26,.2)}.pt-dark .pt-elevation-4,.pt-elevation-4.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 4px 8px rgba(16,22,26,.4),0 18px 46px 6px rgba(16,22,26,.4)}.pt-card.pt-interactive:hover{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);cursor:pointer}.pt-card.pt-interactive:hover.pt-dark,.pt-dark .pt-card.pt-interactive:hover{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-card.pt-interactive:active{opacity:.9;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.2);transition-duration:0}.pt-card.pt-interactive:active.pt-dark,.pt-dark .pt-card.pt-interactive:active{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-collapse{height:0;overflow-y:hidden;transition:height .2s cubic-bezier(.4,1,.75,.9)}.pt-collapse .pt-collapse-body{transition:-webkit-transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9)}.pt-context-menu .pt-popover-target{display:block}.pt-context-menu-popover-target{position:fixed}.pt-dialog{opacity:1;-webkit-transform:translateX(50%) scale(1);transform:translateX(50%) scale(1);display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;position:absolute;top:25%;right:50%;z-index:20;margin-bottom:20px;border-radius:6px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 4px 8px rgba(16,22,26,.2),0 18px 46px 6px rgba(16,22,26,.2);background:#ebf1f5;width:500px;padding-bottom:20px}.pt-dialog.pt-overlay-appear,.pt-dialog.pt-overlay-enter{opacity:0;-webkit-transform:translateX(50%) scale(.5);transform:translateX(50%) scale(.5)}.pt-dialog.pt-overlay-appear-active,.pt-dialog.pt-overlay-enter-active{opacity:1;-webkit-transform:translateX(50%) scale(1);transform:translateX(50%) scale(1);transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-dialog.pt-overlay-leave{opacity:1;-webkit-transform:translateX(50%) scale(1);transform:translateX(50%) scale(1)}.pt-dialog.pt-overlay-leave-active{opacity:0;-webkit-transform:translateX(50%) scale(.5);transform:translateX(50%) scale(.5);transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-dark .pt-dialog,.pt-dialog.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 4px 8px rgba(16,22,26,.4),0 18px 46px 6px rgba(16,22,26,.4);background:#293742;color:#f5f8fa}.pt-dialog-header{display:-webkit-flex;display:flex;-webkit-flex:0 0 auto;flex:0 0 auto;-webkit-align-items:center;align-items:center;border-radius:6px 6px 0 0;box-shadow:0 1px 0 rgba(16,22,26,.15);background:#fff;min-height:40px;padding-left:20px}.pt-dialog-header .pt-icon-large{-webkit-flex:0 0 auto;flex:0 0 auto;margin-right:10px;color:#5c7080}.pt-dialog-header h5{overflow:hidden;-webkit-flex:1 1 auto;flex:1 1 auto;margin:0;line-height:inherit}.pt-dialog-header h5:last-child{margin-right:20px}.pt-dark .pt-dialog-header{box-shadow:0 1px 0 rgba(16,22,26,.4);background:#30404d}.pt-dark .pt-dialog-header .pt-icon-large{color:#bfccd6}.pt-dark .pt-dialog-header h5{color:#f5f8fa}.pt-dialog-close-button{line-height:1;color:#5c7080;-webkit-flex:0 0 auto;flex:0 0 auto;border:none;background:0 0;cursor:pointer;padding:10px}.pt-dialog-close-button:hover{color:#182026}.pt-dark .pt-dialog-close-button{color:#bfccd6}.pt-dark .pt-dialog-close-button:hover{color:#f5f8fa}.pt-dialog-body{-webkit-flex:1 1 auto;flex:1 1 auto;margin:20px;line-height:18px}.pt-dialog-footer{-webkit-flex:0 0 auto;flex:0 0 auto;margin:0 20px}.pt-dialog-footer-actions{display:-webkit-flex;display:flex;-webkit-justify-content:flex-end;justify-content:flex-end}.pt-dialog-footer-actions .pt-button{margin-left:10px}.pt-editable-text{display:inline-block;position:relative;cursor:text;max-width:100%;vertical-align:top}.pt-editable-text::before{position:absolute;top:-3px;right:-3px;bottom:-3px;left:-3px;border-radius:3px;content:\"\";transition:background-color .1s cubic-bezier(.4,1,.75,.9),box-shadow .1s cubic-bezier(.4,1,.75,.9)}.pt-editable-text:hover::before{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.15)}.pt-editable-text.pt-editable-editing::before{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2);background-color:#fff}.pt-editable-text.pt-disabled::before{box-shadow:none}.pt-editable-text.pt-intent-primary .pt-editable-content,.pt-editable-text.pt-intent-primary .pt-editable-input{color:#137cbd}.pt-editable-text.pt-intent-primary:hover::before{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(19,124,189,.4)}.pt-editable-text.pt-intent-primary.pt-editable-editing::before{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-editable-text.pt-intent-success .pt-editable-content,.pt-editable-text.pt-intent-success .pt-editable-input{color:#0f9960}.pt-editable-text.pt-intent-success:hover::before{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px rgba(15,153,96,.4)}.pt-editable-text.pt-intent-success.pt-editable-editing::before{box-shadow:0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-editable-text.pt-intent-warning .pt-editable-content,.pt-editable-text.pt-intent-warning .pt-editable-input{color:#d9822b}.pt-editable-text.pt-intent-warning:hover::before{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px rgba(217,130,43,.4)}.pt-editable-text.pt-intent-warning.pt-editable-editing::before{box-shadow:0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-editable-text.pt-intent-danger .pt-editable-content,.pt-editable-text.pt-intent-danger .pt-editable-input{color:#db3737}.pt-editable-text.pt-intent-danger:hover::before{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px rgba(219,55,55,.4)}.pt-editable-text.pt-intent-danger.pt-editable-editing::before{box-shadow:0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-dark .pt-editable-text:hover::before{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(255,255,255,.15)}.pt-dark .pt-editable-text.pt-editable-editing::before{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background-color:rgba(16,22,26,.3)}.pt-dark .pt-editable-text.pt-disabled::before{box-shadow:none}.pt-dark .pt-editable-text.pt-intent-primary .pt-editable-content{color:#48aff0}.pt-dark .pt-editable-text.pt-intent-primary:hover::before{box-shadow:0 0 0 0 rgba(72,175,240,0),0 0 0 0 rgba(72,175,240,0),inset 0 0 0 1px rgba(72,175,240,.4)}.pt-dark .pt-editable-text.pt-intent-primary.pt-editable-editing::before{box-shadow:0 0 0 1px #48aff0,0 0 0 3px rgba(72,175,240,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-editable-text.pt-intent-success .pt-editable-content{color:#3dcc91}.pt-dark .pt-editable-text.pt-intent-success:hover::before{box-shadow:0 0 0 0 rgba(61,204,145,0),0 0 0 0 rgba(61,204,145,0),inset 0 0 0 1px rgba(61,204,145,.4)}.pt-dark .pt-editable-text.pt-intent-success.pt-editable-editing::before{box-shadow:0 0 0 1px #3dcc91,0 0 0 3px rgba(61,204,145,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-editable-text.pt-intent-warning .pt-editable-content{color:#ffb366}.pt-dark .pt-editable-text.pt-intent-warning:hover::before{box-shadow:0 0 0 0 rgba(255,179,102,0),0 0 0 0 rgba(255,179,102,0),inset 0 0 0 1px rgba(255,179,102,.4)}.pt-dark .pt-editable-text.pt-intent-warning.pt-editable-editing::before{box-shadow:0 0 0 1px #ffb366,0 0 0 3px rgba(255,179,102,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-editable-text.pt-intent-danger .pt-editable-content{color:#ff7373}.pt-dark .pt-editable-text.pt-intent-danger:hover::before{box-shadow:0 0 0 0 rgba(255,115,115,0),0 0 0 0 rgba(255,115,115,0),inset 0 0 0 1px rgba(255,115,115,.4)}.pt-dark .pt-editable-text.pt-intent-danger.pt-editable-editing::before{box-shadow:0 0 0 1px #ff7373,0 0 0 3px rgba(255,115,115,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-editable-content,.pt-editable-input{display:inherit;position:relative;min-width:inherit;max-width:inherit;vertical-align:top;text-transform:inherit;letter-spacing:inherit;color:inherit;font:inherit;resize:none}.pt-control .pt-control-indicator,.pt-input-group .pt-icon,.pt-select::after{font-style:normal;-moz-osx-font-smoothing:grayscale}.pt-editable-input{border:none;box-shadow:none;background:0 0;width:100%;padding:0;white-space:pre-wrap}.pt-editable-input::-ms-clear{display:none}.pt-editable-content{overflow:hidden;padding-right:2px;text-overflow:ellipsis;white-space:pre}.pt-editable-editing>.pt-editable-content{position:absolute;left:0;visibility:hidden}.pt-control,.pt-control-group .pt-button,.pt-control-group .pt-input,.pt-control-group .pt-select{position:relative}.pt-editable-placeholder>.pt-editable-content{color:rgba(92,112,128,.5)}.pt-dark .pt-editable-placeholder>.pt-editable-content{color:rgba(191,204,214,.5)}.pt-editable-text.pt-multiline{display:block}.pt-editable-text.pt-multiline .pt-editable-content{overflow:auto;white-space:pre-wrap}.pt-control-group{-webkit-transform:translateZ(0);transform:translateZ(0);display:-webkit-flex;display:flex;-webkit-align-items:flex-start;align-items:flex-start}.pt-control-group>*{-webkit-flex:0 0 auto;flex:0 0 auto}.pt-control-group .pt-input{z-index:2;border-radius:inherit}.pt-control-group .pt-input:focus{z-index:14;border-radius:3px}.pt-control-group .pt-input[class*=pt-intent]{z-index:13}.pt-control-group .pt-input[class*=pt-intent]:focus{z-index:15}.pt-control-group .pt-input.pt-disabled,.pt-control-group .pt-input:disabled,.pt-control-group .pt-input[readonly]{z-index:1}.pt-control-group .pt-input-group[class*=pt-intent] .pt-input{z-index:13}.pt-control-group .pt-input-group[class*=pt-intent] .pt-input:focus{z-index:15}.pt-control-group .pt-button,.pt-control-group .pt-select select{z-index:4;border-radius:inherit}.pt-control-group .pt-button:focus,.pt-control-group .pt-select select:focus{position:relative;z-index:5}.pt-control-group .pt-button:hover,.pt-control-group .pt-select select:hover{z-index:6}.pt-control-group .pt-button:active,.pt-control-group .pt-select select:active{z-index:7}.pt-control-group .pt-button.pt-disabled,.pt-control-group .pt-button:disabled,.pt-control-group .pt-button[readonly],.pt-control-group .pt-select select.pt-disabled,.pt-control-group .pt-select select:disabled,.pt-control-group .pt-select select[readonly]{z-index:3}.pt-control-group .pt-button[class*=pt-intent],.pt-control-group .pt-select select[class*=pt-intent]{z-index:9}.pt-control-group .pt-button[class*=pt-intent]:focus,.pt-control-group .pt-select select[class*=pt-intent]:focus,.pt-navbar{z-index:10}.pt-control-group .pt-button[class*=pt-intent]:hover,.pt-control-group .pt-select select[class*=pt-intent]:hover{z-index:11}.pt-control-group .pt-button[class*=pt-intent]:active,.pt-control-group .pt-select select[class*=pt-intent]:active{z-index:12}.pt-control-group .pt-button[class*=pt-intent].pt-disabled,.pt-control-group .pt-button[class*=pt-intent]:disabled,.pt-control-group .pt-button[class*=pt-intent][readonly],.pt-control-group .pt-select select[class*=pt-intent].pt-disabled,.pt-control-group .pt-select select[class*=pt-intent]:disabled,.pt-control-group .pt-select select[class*=pt-intent][readonly]{z-index:8}.pt-control-group .pt-input-group>.pt-button,.pt-control-group .pt-input-group>.pt-icon,.pt-control-group .pt-input-group>.pt-input-action{z-index:16}.pt-control-group .pt-select::after{z-index:17}.pt-control-group:not(.pt-vertical)>*{margin-right:-1px}.pt-dark .pt-control-group:not(.pt-vertical)>*{margin-right:0}.pt-dark .pt-control-group:not(.pt-vertical)>.pt-button+.pt-button{margin-left:1px}.pt-control-group>:first-child{border-radius:3px 0 0 3px}.pt-control-group>:last-child{margin-right:0;border-radius:0 3px 3px 0}.pt-control-group .pt-input-group .pt-button{border-radius:3px}.pt-control-group.pt-fill>:not(.pt-fixed),.pt-control-group>.pt-fill{-webkit-flex:1 1 auto;flex:1 1 auto}.pt-control-group.pt-vertical{-webkit-flex-direction:column;flex-direction:column}.pt-control-group.pt-vertical>*{margin-top:-1px;width:100%}.pt-control-group.pt-vertical>:first-child{margin-top:0;border-radius:3px 3px 0 0}.pt-control-group.pt-vertical>:last-child{border-radius:0 0 3px 3px}.pt-control{display:block;margin-bottom:10px;cursor:pointer;min-height:20px;padding-left:26px;text-transform:none;line-height:16px}.pt-control.pt-disabled{cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-control.pt-inline{display:inline-block;margin-right:20px}.pt-control input{position:absolute;top:0;left:0;opacity:0;z-index:-1}.pt-control .pt-control-indicator{font-family:Icons16,sans-serif;font-size:16px;font-weight:400;-webkit-font-smoothing:antialiased;background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;position:absolute;top:0;left:0;margin:0;border:none;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);background-clip:padding-box;cursor:pointer;width:16px;height:16px;line-height:16px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pt-control .pt-control-indicator::before{position:relative;content:\"\"}.pt-control input:checked~.pt-control-indicator,.pt-control.pt-checkbox input:indeterminate~.pt-control-indicator{background:#137cbd;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #137cbd;box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2);color:#fff}.pt-control:hover .pt-control-indicator{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5}.pt-control.pt-checkbox .pt-control:hover input:indeterminate~.pt-control-indicator,.pt-control.pt-checkbox:hover input:indeterminate~.pt-control-indicator,.pt-control:hover .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control:hover input:checked~.pt-control-indicator{background:#106ba3;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #106ba3;box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-control input:not(:disabled):active~.pt-control-indicator{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background:#d8e1e8}.pt-control input:not(:disabled):active:checked~.pt-control-indicator,.pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background:#0e5a8a}.pt-control input:focus~.pt-control-indicator{outline:rgba(19,124,189,.5) auto 2px;outline-offset:2px;-moz-outline-radius:6px}.pt-control input:disabled~.pt-control-indicator{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed}.pt-control input:disabled:checked~.pt-control-indicator,.pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator{box-shadow:none;background:rgba(19,124,189,.5)}.pt-control.pt-checkbox .pt-control-indicator{border-radius:3px;font-size:16px}.pt-control.pt-checkbox input:checked~.pt-control-indicator::before,.pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before{content:\"\"}.pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before{content:\"\"}.pt-control.pt-radio .pt-control-indicator{border-radius:50%;font-size:6px}.pt-control.pt-checkbox .pt-control.pt-radio input:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio input:checked~.pt-control-indicator::before{display:inline-block;position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border-radius:50%;background:#fff;width:1em;height:1em;content:\"\"}.pt-control.pt-checkbox .pt-control.pt-radio input:disabled:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio input:checked:disabled~.pt-control-indicator::before{box-shadow:none}.pt-control.pt-radio input:focus~.pt-control-indicator{-moz-outline-radius:16px}.pt-control.pt-switch{padding-left:38px}.pt-control.pt-switch .pt-control-indicator{border:none;border-radius:28px;box-shadow:none;background:rgba(167,182,194,.5);width:28px;height:16px;transition:background .1s cubic-bezier(.4,1,.75,.9)}.pt-control.pt-switch .pt-control-indicator::before{display:block;top:2px;left:2px;border-radius:16px;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2);background:#fff;background-clip:padding-box;width:12px;height:12px;content:\"\";transition:left .1s cubic-bezier(.4,1,.75,.9)}.pt-file-upload-input,.pt-input{vertical-align:middle;transition:box-shadow .1s cubic-bezier(.4,1,.75,.9);-moz-appearance:none;outline:0}.pt-control.pt-checkbox .pt-control.pt-switch input:indeterminate~.pt-control-indicator,.pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control.pt-switch input:checked~.pt-control-indicator{box-shadow:none;background-color:#137cbd;width:28px;height:16px}.pt-control.pt-checkbox .pt-control.pt-switch input:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch input:checked~.pt-control-indicator::before{left:14px;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2)}.pt-control.pt-switch:hover .pt-control-indicator{background-color:rgba(115,134,148,.5)}.pt-control.pt-checkbox .pt-control.pt-switch:hover input:indeterminate~.pt-control-indicator,.pt-control.pt-switch:hover .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control.pt-switch:hover input:checked~.pt-control-indicator{background-color:#106ba3}.pt-control.pt-switch input:not(:disabled):active~.pt-control-indicator{box-shadow:none;background-color:rgba(92,112,128,.5)}.pt-control.pt-checkbox .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator{background-color:#0e5a8a}.pt-control.pt-checkbox .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator::before{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2)}.pt-control.pt-switch input:disabled~.pt-control-indicator{background-color:rgba(206,217,224,.5)}.pt-control.pt-switch input:disabled~.pt-control-indicator::before{box-shadow:none;background-color:rgba(255,255,255,.8)}.pt-control.pt-checkbox .pt-control.pt-switch input:disabled:indeterminate~.pt-control-indicator,.pt-control.pt-switch .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator,.pt-control.pt-switch input:disabled:checked~.pt-control-indicator{background-color:rgba(19,124,189,.5)}.pt-control.pt-align-right{padding-right:26px;padding-left:0}.pt-control.pt-align-right .pt-control-indicator{right:0;left:auto}.pt-control.pt-large{padding-left:30px;line-height:20px;font-size:16px}.pt-control.pt-large .pt-control-indicator{width:20px;height:20px;line-height:20px;font-family:Icons20;font-size:20px}.pt-input-group .pt-icon,.pt-select::after{font-family:Icons16,sans-serif;font-weight:400;-webkit-font-smoothing:antialiased}.pt-control.pt-large.pt-checkbox .pt-control.pt-checkbox:hover input:indeterminate~.pt-control-indicator::before,.pt-control.pt-large.pt-checkbox input:checked~.pt-control-indicator::before,.pt-control.pt-large.pt-checkbox input:indeterminate~.pt-control-indicator::before{top:1px}.pt-control.pt-large.pt-radio .pt-control-indicator{font-size:8px}.pt-control.pt-large.pt-switch{padding-left:42px}.pt-control.pt-large.pt-switch .pt-control-indicator{width:32px;height:20px}.pt-control.pt-large.pt-switch .pt-control-indicator::before{width:16px;height:16px}.pt-control.pt-checkbox .pt-control.pt-large.pt-switch input:indeterminate~.pt-control-indicator,.pt-control.pt-large.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control.pt-large.pt-switch input:checked~.pt-control-indicator{width:32px;height:20px}.pt-control.pt-checkbox .pt-control.pt-large.pt-switch input:indeterminate~.pt-control-indicator::before,.pt-control.pt-large.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-control.pt-large.pt-switch input:checked~.pt-control-indicator::before{left:14px}.pt-dark .pt-control{color:#f5f8fa}.pt-dark .pt-control.pt-disabled{color:rgba(92,112,128,.5)}.pt-dark .pt-control .pt-control-indicator{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control input:checked~.pt-control-indicator,.pt-dark .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator{box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control:hover .pt-control-indicator{background:#30404d;background:linear-gradient(to bottom,rgba(16,22,26,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d}.pt-dark .pt-control input:not(:disabled):active~.pt-control-indicator{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background:#202b33}.pt-dark .pt-control input:not(:disabled):active:checked~.pt-control-indicator,.pt-dark .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator{box-shadow:0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#0e5a8a}.pt-dark .pt-control input:disabled~.pt-control-indicator{box-shadow:none;background:rgba(57,75,89,.5);cursor:not-allowed}.pt-dark .pt-control input:disabled:checked~.pt-control-indicator,.pt-dark .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator{box-shadow:none;background:rgba(14,90,138,.5)}.pt-dark .pt-control.pt-checkbox input:checked:disabled~.pt-control-indicator::before,.pt-dark .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator::before{color:rgba(191,204,214,.5)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-radio input:disabled:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-radio .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-radio input:checked:disabled~.pt-control-indicator::before{background:rgba(191,204,214,.5)}.pt-dark .pt-control.pt-switch .pt-control-indicator{box-shadow:none;background:rgba(16,22,26,.5)}.pt-dark .pt-control.pt-switch .pt-control-indicator::before{box-shadow:0 0 0 1px rgba(16,22,26,.4);background:#30404d}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch input:checked~.pt-control-indicator{box-shadow:none;background-color:#137cbd}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch input:checked~.pt-control-indicator::before{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control.pt-switch:hover .pt-control-indicator{background:rgba(16,22,26,.7)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch:hover input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch:hover .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch:hover input:checked~.pt-control-indicator{background:#2b95d6}.pt-dark .pt-control.pt-switch input:not(:disabled):active~.pt-control-indicator{box-shadow:none;background:rgba(16,22,26,.9)}.pt-dark .pt-control.pt-switch input:not(:disabled):active~.pt-control-indicator::before{box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator{background:#48aff0}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator::before{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control.pt-switch input:disabled~.pt-control-indicator{background:rgba(57,75,89,.5)}.pt-dark .pt-control.pt-switch input:disabled~.pt-control-indicator::before{box-shadow:none;background:rgba(16,22,26,.4)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:disabled:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch input:disabled:checked~.pt-control-indicator{background:rgba(14,90,138,.5)}.pt-file-upload{display:inline-block;position:relative;cursor:pointer;height:30px}.pt-file-upload-input,.pt-file-upload-input::after{position:absolute;height:30px;line-height:30px;right:0;top:0}.pt-file-upload input{opacity:0;margin:0;min-width:200px}.pt-file-upload input.pt-disabled+.pt-file-upload-input,.pt-file-upload input:disabled+.pt-file-upload-input{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5);resize:none}.pt-file-upload input.pt-disabled+.pt-file-upload-input::after,.pt-file-upload input:disabled+.pt-file-upload-input::after{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-file-upload input.pt-disabled+.pt-file-upload-input::after.pt-active,.pt-file-upload input:disabled+.pt-file-upload-input::after.pt-active{background:rgba(206,217,224,.7)}.pt-dark .pt-file-upload input.pt-disabled+.pt-file-upload-input,.pt-dark .pt-file-upload input:disabled+.pt-file-upload-input{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload input.pt-disabled+.pt-file-upload-input::after,.pt-dark .pt-file-upload input:disabled+.pt-file-upload-input::after{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload input.pt-disabled+.pt-file-upload-input::after.pt-active,.pt-dark .pt-file-upload input:disabled+.pt-file-upload-input::after.pt-active{background:rgba(57,75,89,.7)}.pt-file-upload.pt-fill{width:100%}.pt-file-upload-input{border:none;border-radius:3px 0 0 3px;box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2);background:#fff;padding:0 10px;font-size:14px;font-weight:400;-webkit-appearance:none;appearance:none;left:0;margin-right:70px;color:#5c7080;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pt-menu-item,.pt-skeleton{-moz-user-select:none;-ms-user-select:none}.pt-file-upload-input::-webkit-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input::-moz-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input:-ms-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input::placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-file-upload-input.pt-round,.pt-file-upload-input[type=search]{border-radius:30px;-moz-box-sizing:border-box;box-sizing:border-box;padding-left:10px}.pt-file-upload-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.15)}.pt-file-upload-input.pt-disabled,.pt-file-upload-input:disabled{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5);resize:none}.pt-file-upload-input::after,.pt-file-upload-input::after:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1)}.pt-file-upload-input::after{background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;color:#182026;left:100%;margin-left:-1px;border-radius:0 3px 3px 0;width:70px;padding:0 10px;text-align:center;content:\"Browse\"}.pt-file-upload-input::after:hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;background-clip:padding-box}.pt-file-upload-input::after.pt-active,.pt-file-upload-input::after:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-file-upload-input::after.pt-disabled,.pt-file-upload-input::after:disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-file-upload-input::after.pt-disabled.pt-active,.pt-file-upload-input::after:disabled.pt-active{background:rgba(206,217,224,.7)}.pt-file-upload-input:hover::after{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);background-clip:padding-box}.pt-file-upload-input:active::after{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-large .pt-file-upload-input{margin-right:85px;height:40px;line-height:40px;font-size:16px}.pt-large .pt-file-upload-input::after{width:85px;height:40px;padding:0 15px;line-height:40px;font-size:16px}.pt-dark .pt-file-upload-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#bfccd6}.pt-dark .pt-file-upload-input::-webkit-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::-moz-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input:-ms-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input.pt-disabled,.pt-dark .pt-file-upload-input:disabled{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::after{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-file-upload-input::after.pt-active,.pt-dark .pt-file-upload-input::after:active,.pt-dark .pt-file-upload-input::after:hover{color:#f5f8fa}.pt-dark .pt-file-upload-input::after:hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input::after.pt-active,.pt-dark .pt-file-upload-input::after:active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-file-upload-input::after.pt-disabled,.pt-dark .pt-file-upload-input::after:disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::after.pt-disabled.pt-active,.pt-dark .pt-file-upload-input::after:disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-file-upload-input::after .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-dark .pt-file-upload-input:hover::after{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input:active::after{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-form-group{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;margin:0 0 15px}.pt-form-group label.pt-label{margin-bottom:5px}.pt-form-group .pt-form-helper-text{margin-top:5px;color:#5c7080;font-size:12px}.pt-form-group.pt-intent-primary .pt-form-helper-text{color:#106ba3}.pt-form-group.pt-intent-success .pt-form-helper-text{color:#0d8050}.pt-form-group.pt-intent-warning .pt-form-helper-text{color:#bf7326}.pt-form-group.pt-intent-danger .pt-form-helper-text{color:#c23030}.pt-form-group.pt-inline{-webkit-flex-direction:row;flex-direction:row}.pt-form-group.pt-inline.pt-large label.pt-label{margin:0 10px 0 0;line-height:40px}.pt-form-group.pt-inline label.pt-label{margin:0 10px 0 0;line-height:30px}.pt-form-group.pt-disabled .pt-form-helper-text,.pt-form-group.pt-disabled .pt-label,.pt-form-group.pt-disabled .pt-text-muted{color:rgba(92,112,128,.5)!important}.pt-dark .pt-form-group.pt-intent-primary .pt-form-helper-text{color:#48aff0}.pt-dark .pt-form-group.pt-intent-success .pt-form-helper-text{color:#3dcc91}.pt-dark .pt-form-group.pt-intent-warning .pt-form-helper-text{color:#ffb366}.pt-dark .pt-form-group.pt-intent-danger .pt-form-helper-text{color:#ff7373}.pt-dark .pt-form-group .pt-form-helper-text{color:#bfccd6}.pt-dark .pt-form-group.pt-disabled .pt-form-helper-text,.pt-dark .pt-form-group.pt-disabled .pt-label,.pt-dark .pt-form-group.pt-disabled .pt-text-muted{color:rgba(191,204,214,.5)!important}.pt-input-group{display:block;position:relative}.pt-input-group .pt-input{position:relative;width:100%}.pt-input-group .pt-input:not(:first-child){padding-left:30px}.pt-input-group .pt-input:not(:last-child){padding-right:30px}.pt-input-group .pt-input-action,.pt-input-group>.pt-button,.pt-input-group>.pt-icon{position:absolute;top:0}.pt-input-group .pt-input-action:first-child,.pt-input-group>.pt-button:first-child,.pt-input-group>.pt-icon:first-child{left:0}.pt-input-group .pt-input-action:last-child,.pt-input-group>.pt-button:last-child,.pt-input-group>.pt-icon:last-child{right:0}.pt-input-group .pt-button{min-width:24px;min-height:24px;line-height:24px;margin:3px;padding-top:0;padding-bottom:0}.pt-input-group .pt-icon{font-size:16px;z-index:1;margin:0 7px;line-height:30px;color:#5c7080}.pt-input-group .pt-spinner{margin:3px}.pt-input-group .pt-tag{margin:5px}.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus),.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus){color:#5c7080}.pt-dark .pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus),.pt-dark .pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus){color:#bfccd6}.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-standard,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-standard{color:#5c7080}.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled .pt-icon-standard,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled .pt-icon-standard{color:rgba(92,112,128,.5)!important}.pt-input-group.pt-disabled{cursor:not-allowed}.pt-input-group.pt-disabled .pt-icon{color:rgba(92,112,128,.5)}.pt-input-group.pt-large .pt-button{min-width:30px;min-height:30px;margin:5px;line-height:0}.pt-input-group.pt-large .pt-icon{margin:0 12px;line-height:40px}.pt-input-group.pt-large .pt-input{height:40px;line-height:40px;font-size:16px}.pt-input-group.pt-large .pt-input.pt-round,.pt-input-group.pt-large .pt-input[type=search]{padding:0 15px}.pt-input-group.pt-large .pt-input:not(:first-child){padding-left:40px}.pt-input-group.pt-large .pt-input:not(:last-child){padding-right:40px}.pt-input-group.pt-large .pt-spinner{margin:8px}.pt-input-group.pt-round .pt-button,.pt-input-group.pt-round .pt-input,.pt-input-group.pt-round .pt-tag{border-radius:30px}.pt-dark .pt-input-group .pt-icon{color:#bfccd6}.pt-dark .pt-input-group.pt-disabled .pt-icon{color:rgba(191,204,214,.5)}.pt-input-group.pt-intent-primary .pt-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px #137cbd,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-primary .pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-primary .pt-input[readonly]{box-shadow:inset 0 0 0 1px #137cbd}.pt-input-group.pt-intent-primary .pt-input.pt-disabled,.pt-input-group.pt-intent-primary .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-primary .pt-icon{color:#106ba3}.pt-dark .pt-input-group.pt-intent-primary .pt-icon{color:#48aff0}.pt-input-group.pt-intent-success .pt-input{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px #0f9960,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-success .pt-input:focus{box-shadow:0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-success .pt-input[readonly]{box-shadow:inset 0 0 0 1px #0f9960}.pt-input-group.pt-intent-success .pt-input.pt-disabled,.pt-input-group.pt-intent-success .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-success .pt-icon{color:#0d8050}.pt-dark .pt-input-group.pt-intent-success .pt-icon{color:#3dcc91}.pt-input-group.pt-intent-warning .pt-input{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px #d9822b,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-warning .pt-input:focus{box-shadow:0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-warning .pt-input[readonly]{box-shadow:inset 0 0 0 1px #d9822b}.pt-input-group.pt-intent-warning .pt-input.pt-disabled,.pt-input-group.pt-intent-warning .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-warning .pt-icon{color:#bf7326}.pt-dark .pt-input-group.pt-intent-warning .pt-icon{color:#ffb366}.pt-input-group.pt-intent-danger .pt-input{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px #db3737,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-danger .pt-input:focus{box-shadow:0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-danger .pt-input[readonly]{box-shadow:inset 0 0 0 1px #db3737}.pt-input-group.pt-intent-danger .pt-input.pt-disabled,.pt-input-group.pt-intent-danger .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-danger .pt-icon{color:#c23030}.pt-dark .pt-input-group.pt-intent-danger .pt-icon{color:#ff7373}.pt-input{border:none;border-radius:3px;box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2);background:#fff;height:30px;padding:0 10px;line-height:30px;color:#182026;font-size:14px;font-weight:400;-webkit-appearance:none;appearance:none}.pt-input::-webkit-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input::-moz-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input:-ms-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input::placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-round,.pt-input[type=search]{border-radius:30px;-moz-box-sizing:border-box;box-sizing:border-box;padding-left:10px}.pt-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.15)}.pt-input.pt-disabled,.pt-input:disabled{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5);resize:none}.pt-input.pt-large{height:40px;line-height:40px;font-size:16px}.pt-input.pt-large.pt-round,.pt-input.pt-large[type=search]{padding:0 15px}.pt-input.pt-fill{width:100%}.pt-dark .pt-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#f5f8fa}.pt-dark .pt-input::-webkit-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input::-moz-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input:-ms-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input::placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-disabled,.pt-dark .pt-input:disabled{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-input.pt-intent-primary{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px #137cbd,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-primary:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-primary[readonly]{box-shadow:inset 0 0 0 1px #137cbd}.pt-input.pt-intent-primary.pt-disabled,.pt-input.pt-intent-primary:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-primary{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px #137cbd,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-primary:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-primary[readonly]{box-shadow:inset 0 0 0 1px #137cbd}.pt-dark .pt-input.pt-intent-primary.pt-disabled,.pt-dark .pt-input.pt-intent-primary:disabled{box-shadow:none}.pt-input.pt-intent-success{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px #0f9960,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-success:focus{box-shadow:0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-success[readonly]{box-shadow:inset 0 0 0 1px #0f9960}.pt-input.pt-intent-success.pt-disabled,.pt-input.pt-intent-success:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-success{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px #0f9960,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-success:focus{box-shadow:0 0 0 1px #0f9960,0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-success[readonly]{box-shadow:inset 0 0 0 1px #0f9960}.pt-dark .pt-input.pt-intent-success.pt-disabled,.pt-dark .pt-input.pt-intent-success:disabled{box-shadow:none}.pt-input.pt-intent-warning{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px #d9822b,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-warning:focus{box-shadow:0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-warning[readonly]{box-shadow:inset 0 0 0 1px #d9822b}.pt-input.pt-intent-warning.pt-disabled,.pt-input.pt-intent-warning:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-warning{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px #d9822b,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-warning:focus{box-shadow:0 0 0 1px #d9822b,0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-warning[readonly]{box-shadow:inset 0 0 0 1px #d9822b}.pt-dark .pt-input.pt-intent-warning.pt-disabled,.pt-dark .pt-input.pt-intent-warning:disabled{box-shadow:none}.pt-input.pt-intent-danger{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px #db3737,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-danger:focus{box-shadow:0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-danger[readonly]{box-shadow:inset 0 0 0 1px #db3737}.pt-input.pt-intent-danger.pt-disabled,.pt-input.pt-intent-danger:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-danger{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px #db3737,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-danger:focus{box-shadow:0 0 0 1px #db3737,0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-danger[readonly]{box-shadow:inset 0 0 0 1px #db3737}.pt-dark .pt-input.pt-intent-danger.pt-disabled,.pt-dark .pt-input.pt-intent-danger:disabled{box-shadow:none}.pt-input::-ms-clear{display:none}textarea.pt-input{max-width:100%;height:auto;padding:10px;line-height:1.28581}textarea.pt-input.pt-large{line-height:1.28581;font-size:16px}.pt-dark textarea.pt-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#f5f8fa}.pt-dark textarea.pt-input::-webkit-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input::-moz-placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input:-ms-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input::placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark textarea.pt-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark textarea.pt-input.pt-disabled,.pt-dark textarea.pt-input:disabled{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-select select,.pt-select select:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1)}label.pt-label{display:block;margin:0 0 15px}label.pt-label .pt-input,label.pt-label .pt-select{display:block;margin-top:5px;text-transform:none}label.pt-label .pt-select select{width:100%;vertical-align:top;font-weight:400}label.pt-label.pt-inline{line-height:30px}label.pt-label.pt-inline .pt-input,label.pt-label.pt-inline .pt-input-group,label.pt-label.pt-inline .pt-select{display:inline-block;margin:0 0 0 5px;vertical-align:top}label.pt-label.pt-inline .pt-input-group .pt-input{margin-left:0}label.pt-label.pt-inline.pt-large{line-height:40px}label.pt-label.pt-disabled,label.pt-label.pt-disabled .pt-text-muted{color:rgba(92,112,128,.5)}.pt-dark label.pt-label{color:#f5f8fa}.pt-dark label.pt-label.pt-disabled,.pt-dark label.pt-label.pt-disabled .pt-text-muted{color:rgba(191,204,214,.5)}.pt-select{display:inline-block;position:relative;vertical-align:middle;letter-spacing:normal}.pt-select select::-ms-expand{display:none}.pt-key,.pt-select select{display:inline-block;vertical-align:middle}.pt-select.pt-large::after{top:0;right:12px;line-height:40px}.pt-select.pt-fill,.pt-select.pt-fill select{width:100%}.pt-dark .pt-select option{background-color:#30404d;color:#f5f8fa}.pt-dark .pt-select::after{color:#bfccd6}.pt-select select{border:none;cursor:pointer;font-size:14px;background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;color:#182026;-webkit-appearance:none;-moz-appearance:none;border-radius:3px;height:30px;padding:0 25px 0 10px}.pt-select select:hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;background-clip:padding-box}.pt-select select.pt-active,.pt-select select:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-select select.pt-disabled,.pt-select select:disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-select select.pt-disabled.pt-active,.pt-select select:disabled.pt-active{background:rgba(206,217,224,.7)}.pt-select.pt-minimal select{box-shadow:none;background:0 0}.pt-select.pt-minimal select:focus,.pt-select.pt-minimal select:hover{box-shadow:none;background:rgba(167,182,194,.3);text-decoration:none;color:#182026}.pt-select.pt-minimal select.pt-active,.pt-select.pt-minimal select:active{box-shadow:none;background:rgba(115,134,148,.3);color:#182026}.pt-select.pt-minimal select.pt-disabled,.pt-select.pt-minimal select.pt-disabled:hover,.pt-select.pt-minimal select:disabled,.pt-select.pt-minimal select:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-dark .pt-select.pt-minimal select,.pt-select.pt-minimal .pt-dark select{box-shadow:none;background:0 0;color:inherit}.pt-dark .pt-select.pt-minimal select.pt-active,.pt-dark .pt-select.pt-minimal select:active,.pt-dark .pt-select.pt-minimal select:hover,.pt-select.pt-minimal .pt-dark select.pt-active,.pt-select.pt-minimal .pt-dark select:active,.pt-select.pt-minimal .pt-dark select:hover{box-shadow:none}.pt-dark .pt-select.pt-minimal select:hover,.pt-select.pt-minimal .pt-dark select:hover{background:rgba(138,155,168,.15)}.pt-dark .pt-select.pt-minimal select.pt-active,.pt-dark .pt-select.pt-minimal select:active,.pt-select.pt-minimal .pt-dark select.pt-active,.pt-select.pt-minimal .pt-dark select:active{background:rgba(138,155,168,.3);color:#f5f8fa}.pt-dark .pt-select.pt-minimal select.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-disabled:hover,.pt-dark .pt-select.pt-minimal select:disabled,.pt-dark .pt-select.pt-minimal select:disabled:hover,.pt-select.pt-minimal .pt-dark select.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-disabled:hover,.pt-select.pt-minimal .pt-dark select:disabled,.pt-select.pt-minimal .pt-dark select:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(191,204,214,.5)}.pt-select.pt-minimal select.pt-intent-primary{color:#106ba3}.pt-select.pt-minimal select.pt-intent-primary.pt-active,.pt-select.pt-minimal select.pt-intent-primary:active,.pt-select.pt-minimal select.pt-intent-primary:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-primary:focus,.pt-select.pt-minimal select.pt-intent-primary:hover{background:rgba(19,124,189,.15);color:#106ba3}.pt-select.pt-minimal select.pt-intent-primary.pt-active,.pt-select.pt-minimal select.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#106ba3}.pt-select.pt-minimal select.pt-intent-primary.pt-disabled,.pt-select.pt-minimal select.pt-intent-primary:disabled{background:0 0;color:rgba(16,107,163,.5)}.pt-select.pt-minimal select.pt-intent-primary .pt-button-spinner .pt-spinner-head{stroke:#106ba3}.pt-dark .pt-select.pt-minimal select.pt-intent-primary,.pt-select.pt-minimal .pt-dark select.pt-intent-primary{color:#48aff0}.pt-dark .pt-select.pt-minimal select.pt-intent-primary:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-primary:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:hover{background:rgba(19,124,189,.2);color:#48aff0}.pt-dark .pt-select.pt-minimal select.pt-intent-primary.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-primary:active,.pt-select.pt-minimal .pt-dark select.pt-intent-primary.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#48aff0}.pt-dark .pt-select.pt-minimal select.pt-intent-primary.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-primary:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-primary.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:disabled{background:0 0;color:rgba(72,175,240,.5)}.pt-select.pt-minimal select.pt-intent-success{color:#0d8050}.pt-select.pt-minimal select.pt-intent-success.pt-active,.pt-select.pt-minimal select.pt-intent-success:active,.pt-select.pt-minimal select.pt-intent-success:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-success:focus,.pt-select.pt-minimal select.pt-intent-success:hover{background:rgba(15,153,96,.15);color:#0d8050}.pt-select.pt-minimal select.pt-intent-success.pt-active,.pt-select.pt-minimal select.pt-intent-success:active{background:rgba(15,153,96,.3);color:#0d8050}.pt-select.pt-minimal select.pt-intent-success.pt-disabled,.pt-select.pt-minimal select.pt-intent-success:disabled{background:0 0;color:rgba(13,128,80,.5)}.pt-select.pt-minimal select.pt-intent-success .pt-button-spinner .pt-spinner-head{stroke:#0d8050}.pt-dark .pt-select.pt-minimal select.pt-intent-success,.pt-select.pt-minimal .pt-dark select.pt-intent-success{color:#3dcc91}.pt-dark .pt-select.pt-minimal select.pt-intent-success:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-success:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-success:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-success:hover{background:rgba(15,153,96,.2);color:#3dcc91}.pt-dark .pt-select.pt-minimal select.pt-intent-success.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-success:active,.pt-select.pt-minimal .pt-dark select.pt-intent-success.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-success:active{background:rgba(15,153,96,.3);color:#3dcc91}.pt-dark .pt-select.pt-minimal select.pt-intent-success.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-success:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-success.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-success:disabled{background:0 0;color:rgba(61,204,145,.5)}.pt-select.pt-minimal select.pt-intent-warning{color:#bf7326}.pt-select.pt-minimal select.pt-intent-warning.pt-active,.pt-select.pt-minimal select.pt-intent-warning:active,.pt-select.pt-minimal select.pt-intent-warning:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-warning:focus,.pt-select.pt-minimal select.pt-intent-warning:hover{background:rgba(217,130,43,.15);color:#bf7326}.pt-select.pt-minimal select.pt-intent-warning.pt-active,.pt-select.pt-minimal select.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#bf7326}.pt-select.pt-minimal select.pt-intent-warning.pt-disabled,.pt-select.pt-minimal select.pt-intent-warning:disabled{background:0 0;color:rgba(191,115,38,.5)}.pt-select.pt-minimal select.pt-intent-warning .pt-button-spinner .pt-spinner-head{stroke:#bf7326}.pt-dark .pt-select.pt-minimal select.pt-intent-warning,.pt-select.pt-minimal .pt-dark select.pt-intent-warning{color:#ffb366}.pt-dark .pt-select.pt-minimal select.pt-intent-warning:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-warning:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:hover{background:rgba(217,130,43,.2);color:#ffb366}.pt-dark .pt-select.pt-minimal select.pt-intent-warning.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-warning:active,.pt-select.pt-minimal .pt-dark select.pt-intent-warning.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#ffb366}.pt-dark .pt-select.pt-minimal select.pt-intent-warning.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-warning:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-warning.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:disabled{background:0 0;color:rgba(255,179,102,.5)}.pt-select.pt-minimal select.pt-intent-danger{color:#c23030}.pt-select.pt-minimal select.pt-intent-danger.pt-active,.pt-select.pt-minimal select.pt-intent-danger:active,.pt-select.pt-minimal select.pt-intent-danger:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-danger:focus,.pt-select.pt-minimal select.pt-intent-danger:hover{background:rgba(219,55,55,.15);color:#c23030}.pt-select.pt-minimal select.pt-intent-danger.pt-active,.pt-select.pt-minimal select.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#c23030}.pt-select.pt-minimal select.pt-intent-danger.pt-disabled,.pt-select.pt-minimal select.pt-intent-danger:disabled{background:0 0;color:rgba(194,48,48,.5)}.pt-select.pt-minimal select.pt-intent-danger .pt-button-spinner .pt-spinner-head{stroke:#c23030}.pt-dark .pt-select.pt-minimal select.pt-intent-danger,.pt-select.pt-minimal .pt-dark select.pt-intent-danger{color:#ff7373}.pt-dark .pt-select.pt-minimal select.pt-intent-danger:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-danger:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:hover{background:rgba(219,55,55,.2);color:#ff7373}.pt-dark .pt-select.pt-minimal select.pt-intent-danger.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-danger:active,.pt-select.pt-minimal .pt-dark select.pt-intent-danger.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#ff7373}.pt-dark .pt-select.pt-minimal select.pt-intent-danger.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-danger:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-danger.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:disabled{background:0 0;color:rgba(255,115,115,.5)}.pt-select.pt-large select{height:40px;padding-right:35px;font-size:16px}.pt-dark .pt-select select{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-select select.pt-active,.pt-dark .pt-select select:active,.pt-dark .pt-select select:hover{color:#f5f8fa}.pt-dark .pt-select select:hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-select select.pt-active,.pt-dark .pt-select select:active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-select select.pt-disabled,.pt-dark .pt-select select:disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-select select.pt-disabled.pt-active,.pt-dark .pt-select select:disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-select select .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-select select:disabled{box-shadow:none;background-color:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-select::after{font-size:16px;position:absolute;top:0;right:7px;line-height:30px;color:#5c7080;content:\"⌄\"}.pt-disabled.pt-select::after{color:rgba(92,112,128,.5)}.pt-numeric-input .pt-button-group.pt-vertical>.pt-button:first-child{min-height:16px;border-radius:0 3px 0 0}.pt-numeric-input .pt-button-group.pt-vertical>.pt-button:last-child{min-height:15px;border-radius:0 0 3px}.pt-numeric-input .pt-button-group.pt-vertical>.pt-button[class*=pt-icon-]::before{display:block;height:14px;overflow:hidden;line-height:14px}.pt-numeric-input .pt-button-group.pt-vertical:first-child>.pt-button:first-child{border-radius:3px 0 0}.pt-numeric-input .pt-button-group.pt-vertical:first-child>.pt-button:last-child{border-radius:0 0 0 3px}.pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:first-child{min-height:21px}.pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:last-child{min-height:20px}.pt-dark .pt-numeric-input .pt-button-group.pt-vertical>.pt-button{line-height:14px}.pt-dark .pt-numeric-input .pt-button-group.pt-vertical>.pt-button:first-child{min-height:15px}.pt-dark .pt-numeric-input .pt-button-group.pt-vertical>.pt-button:last-child{min-height:14px}.pt-dark .pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:first-child{min-height:20px}.pt-dark .pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:last-child{min-height:19px}form{display:block}.pt-key{border-radius:2px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 1px 1px rgba(16,22,26,.2),0 2px 6px rgba(16,22,26,.2);background:#fff;min-width:25px;height:25px;padding:3px 5px;text-align:center;line-height:20px;color:#5c7080;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,\"Open Sans\",\"Helvetica Neue\",Icons16,sans-serif;font-size:12px}.pt-menu-item::before,.pt-submenu>.pt-popover-target>.pt-menu-item::after{font-family:Icons16,sans-serif;font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-key.pt-modifier-key{padding:3px 8px 3px 6px}.pt-key.pt-modifier-key .pt-icon-standard{margin-right:5px}.pt-dark .pt-key{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.4),0 2px 6px rgba(16,22,26,.4),inset 0 1px 0 rgba(255,255,255,.05);background:#394b59;color:#bfccd6}.pt-key-combo .pt-key:not(:last-child){margin-right:5px}.pt-hotkey-dialog{top:40px;padding-bottom:0}.pt-hotkey-dialog .pt-dialog-body{margin:0;padding:0}.pt-hotkey-dialog .pt-key-combo{-webkit-flex:1 0 auto;flex:1 0 auto;width:160px;text-align:right}.pt-hotkey-column{margin:auto;max-height:80vh;overflow-y:auto;padding:30px}.pt-menu-item,body.pt-overlay-open{overflow:hidden}.pt-hotkey-column .pt-hotkey-group{margin-bottom:30px}.pt-hotkey-column .pt-hotkey-group:not(:first-child){margin-top:50px}.pt-hotkey{display:-webkit-flex;display:flex;-webkit-align-items:baseline;align-items:baseline;-webkit-justify-content:space-between;justify-content:space-between;margin-right:0;margin-left:0}.pt-hotkey:not(:last-child){margin-bottom:10px}.pt-submenu>.pt-popover-target{display:inherit}.pt-submenu>.pt-popover-target>.pt-menu-item{padding-right:26px}.pt-submenu>.pt-popover-target>.pt-menu-item::after{line-height:1;font-size:16px;position:absolute;right:5px;content:\"〉\"}.pt-large .pt-submenu>.pt-popover-target>.pt-menu-item::after{line-height:20px}.pt-submenu .pt-popover{position:relative;top:-5px;left:5px}.pt-navbar.pt-fixed-top,.pt-overlay-backdrop{top:0;right:0;left:0;position:fixed}.pt-submenu .pt-popover.pt-align-left{right:5px;left:auto}.pt-menu{margin:0;border-radius:3px;background:#fff;min-width:180px;padding:5px;list-style:none;text-align:left;color:#182026}.pt-dark .pt-menu-item,.pt-menu-item{color:inherit}.pt-menu-divider{display:block;margin:5px;border-top:1px solid rgba(16,22,26,.15)}.pt-menu-item{display:block;border-radius:2px;padding:7px;line-height:16px;-webkit-user-select:none;user-select:none}.pt-menu-item:hover,.pt-submenu>.pt-popover-open>.pt-menu-item{background-color:rgba(167,182,194,.3);cursor:pointer}.pt-dark .pt-menu-item:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-menu-item{background-color:rgba(138,155,168,.15);color:inherit}.pt-dark .pt-menu-item.pt-disabled{background-color:inherit}.pt-menu-item.pt-intent-primary,.pt-menu-item.pt-intent-primary .pt-menu-item-label,.pt-menu-item.pt-intent-primary::after,.pt-menu-item.pt-intent-primary::before{color:#106ba3}.pt-menu-item.pt-intent-primary.pt-active,.pt-menu-item.pt-intent-primary:hover,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item{background-color:#137cbd}.pt-menu-item.pt-intent-primary:active{background-color:#106ba3}.pt-menu-item.pt-intent-primary.pt-active,.pt-menu-item.pt-intent-primary.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-primary.pt-active::after,.pt-menu-item.pt-intent-primary.pt-active::before,.pt-menu-item.pt-intent-primary:active,.pt-menu-item.pt-intent-primary:active .pt-menu-item-label,.pt-menu-item.pt-intent-primary:active::after,.pt-menu-item.pt-intent-primary:active::before,.pt-menu-item.pt-intent-primary:hover,.pt-menu-item.pt-intent-primary:hover .pt-menu-item-label,.pt-menu-item.pt-intent-primary:hover::after,.pt-menu-item.pt-intent-primary:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::before{color:#fff}.pt-menu-item.pt-intent-success,.pt-menu-item.pt-intent-success .pt-menu-item-label,.pt-menu-item.pt-intent-success::after,.pt-menu-item.pt-intent-success::before{color:#0d8050}.pt-menu-item.pt-intent-success.pt-active,.pt-menu-item.pt-intent-success:hover,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item{background-color:#0f9960}.pt-menu-item.pt-intent-success:active{background-color:#0d8050}.pt-menu-item.pt-intent-success.pt-active,.pt-menu-item.pt-intent-success.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-success.pt-active::after,.pt-menu-item.pt-intent-success.pt-active::before,.pt-menu-item.pt-intent-success:active,.pt-menu-item.pt-intent-success:active .pt-menu-item-label,.pt-menu-item.pt-intent-success:active::after,.pt-menu-item.pt-intent-success:active::before,.pt-menu-item.pt-intent-success:hover,.pt-menu-item.pt-intent-success:hover .pt-menu-item-label,.pt-menu-item.pt-intent-success:hover::after,.pt-menu-item.pt-intent-success:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::before{color:#fff}.pt-menu-item.pt-intent-warning,.pt-menu-item.pt-intent-warning .pt-menu-item-label,.pt-menu-item.pt-intent-warning::after,.pt-menu-item.pt-intent-warning::before{color:#bf7326}.pt-menu-item.pt-intent-warning.pt-active,.pt-menu-item.pt-intent-warning:hover,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item{background-color:#d9822b}.pt-menu-item.pt-intent-warning:active{background-color:#bf7326}.pt-menu-item.pt-intent-warning.pt-active,.pt-menu-item.pt-intent-warning.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-warning.pt-active::after,.pt-menu-item.pt-intent-warning.pt-active::before,.pt-menu-item.pt-intent-warning:active,.pt-menu-item.pt-intent-warning:active .pt-menu-item-label,.pt-menu-item.pt-intent-warning:active::after,.pt-menu-item.pt-intent-warning:active::before,.pt-menu-item.pt-intent-warning:hover,.pt-menu-item.pt-intent-warning:hover .pt-menu-item-label,.pt-menu-item.pt-intent-warning:hover::after,.pt-menu-item.pt-intent-warning:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::before{color:#fff}.pt-menu-item.pt-intent-danger,.pt-menu-item.pt-intent-danger .pt-menu-item-label,.pt-menu-item.pt-intent-danger::after,.pt-menu-item.pt-intent-danger::before{color:#c23030}.pt-menu-item.pt-intent-danger.pt-active,.pt-menu-item.pt-intent-danger:hover,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item{background-color:#db3737}.pt-menu-item.pt-intent-danger:active{background-color:#c23030}.pt-menu-item.pt-intent-danger.pt-active,.pt-menu-item.pt-intent-danger.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-danger.pt-active::after,.pt-menu-item.pt-intent-danger.pt-active::before,.pt-menu-item.pt-intent-danger:active,.pt-menu-item.pt-intent-danger:active .pt-menu-item-label,.pt-menu-item.pt-intent-danger:active::after,.pt-menu-item.pt-intent-danger:active::before,.pt-menu-item.pt-intent-danger:hover,.pt-menu-item.pt-intent-danger:hover .pt-menu-item-label,.pt-menu-item.pt-intent-danger:hover::after,.pt-menu-item.pt-intent-danger:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::before{color:#fff}.pt-menu-item::before{line-height:1;font-size:16px;float:left;margin-right:7px}.pt-menu-item .pt-menu-item-label,.pt-menu-item::after,.pt-menu-item::before{color:#5c7080}.pt-menu-item:hover,.pt-submenu>.pt-popover-open>.pt-menu-item{color:inherit}.pt-menu-item.pt-disabled,.pt-menu-item.pt-disabled .pt-menu-item-label,.pt-menu-item.pt-disabled::after,.pt-menu-item.pt-disabled::before{color:rgba(92,112,128,.5)!important}.pt-menu-item.pt-active,.pt-menu-item:active{background-color:rgba(115,134,148,.3)}.pt-menu-item.pt-disabled{outline:0!important;background-color:inherit!important;cursor:not-allowed!important}.pt-overlay-backdrop:focus,.pt-slider,.pt-transition-container:focus{outline:0}.pt-large .pt-menu-item{padding:10px 7px;line-height:20px;font-size:16px}.pt-large .pt-menu-item::before{line-height:1;font-family:Icons20,sans-serif;font-size:20px;font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;margin-right:10px}.pt-submenu>.pt-popover-open>a.pt-menu-item,a.pt-menu-item,a.pt-menu-item:hover{text-decoration:none}button.pt-menu-item{border:none;background:0 0;width:100%;text-align:left}.pt-menu-item-label{float:right;margin-left:7px}.pt-menu-header{display:block;margin:5px;border-top:1px solid rgba(16,22,26,.15);cursor:default;padding-left:2px}.pt-menu-header:first-of-type{border-top:none}.pt-menu-header>h6{color:#182026;font-weight:600;overflow:hidden;margin:0;padding:10px 7px 0 1px;line-height:17px}.pt-menu-header:first-of-type>h6{padding-top:0}.pt-large .pt-menu-header>h6{padding-top:15px;padding-bottom:5px;font-size:18px}.pt-large .pt-menu-header:first-of-type>h6{padding-top:0}.pt-dark .pt-menu{background:#30404d;color:#f5f8fa}.pt-dark .pt-menu-item.pt-intent-primary,.pt-dark .pt-menu-item.pt-intent-primary .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary::after,.pt-dark .pt-menu-item.pt-intent-primary::before{color:#48aff0}.pt-dark .pt-menu-item.pt-intent-primary.pt-active,.pt-dark .pt-menu-item.pt-intent-primary:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item{background-color:#137cbd}.pt-dark .pt-menu-item.pt-intent-primary:active{background-color:#106ba3}.pt-dark .pt-menu-item.pt-intent-primary.pt-active,.pt-dark .pt-menu-item.pt-intent-primary.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary.pt-active::after,.pt-dark .pt-menu-item.pt-intent-primary.pt-active::before,.pt-dark .pt-menu-item.pt-intent-primary:active,.pt-dark .pt-menu-item.pt-intent-primary:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary:active::after,.pt-dark .pt-menu-item.pt-intent-primary:active::before,.pt-dark .pt-menu-item.pt-intent-primary:hover,.pt-dark .pt-menu-item.pt-intent-primary:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary:hover::after,.pt-dark .pt-menu-item.pt-intent-primary:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-intent-success,.pt-dark .pt-menu-item.pt-intent-success .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success::after,.pt-dark .pt-menu-item.pt-intent-success::before{color:#3dcc91}.pt-dark .pt-menu-item.pt-intent-success.pt-active,.pt-dark .pt-menu-item.pt-intent-success:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item{background-color:#0f9960}.pt-dark .pt-menu-item.pt-intent-success:active{background-color:#0d8050}.pt-dark .pt-menu-item.pt-intent-success.pt-active,.pt-dark .pt-menu-item.pt-intent-success.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success.pt-active::after,.pt-dark .pt-menu-item.pt-intent-success.pt-active::before,.pt-dark .pt-menu-item.pt-intent-success:active,.pt-dark .pt-menu-item.pt-intent-success:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success:active::after,.pt-dark .pt-menu-item.pt-intent-success:active::before,.pt-dark .pt-menu-item.pt-intent-success:hover,.pt-dark .pt-menu-item.pt-intent-success:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success:hover::after,.pt-dark .pt-menu-item.pt-intent-success:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-intent-warning,.pt-dark .pt-menu-item.pt-intent-warning .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning::after,.pt-dark .pt-menu-item.pt-intent-warning::before{color:#ffb366}.pt-dark .pt-menu-item.pt-intent-warning.pt-active,.pt-dark .pt-menu-item.pt-intent-warning:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item{background-color:#d9822b}.pt-dark .pt-menu-item.pt-intent-warning:active{background-color:#bf7326}.pt-dark .pt-menu-item.pt-intent-warning.pt-active,.pt-dark .pt-menu-item.pt-intent-warning.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning.pt-active::after,.pt-dark .pt-menu-item.pt-intent-warning.pt-active::before,.pt-dark .pt-menu-item.pt-intent-warning:active,.pt-dark .pt-menu-item.pt-intent-warning:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning:active::after,.pt-dark .pt-menu-item.pt-intent-warning:active::before,.pt-dark .pt-menu-item.pt-intent-warning:hover,.pt-dark .pt-menu-item.pt-intent-warning:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning:hover::after,.pt-dark .pt-menu-item.pt-intent-warning:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-intent-danger,.pt-dark .pt-menu-item.pt-intent-danger .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger::after,.pt-dark .pt-menu-item.pt-intent-danger::before{color:#ff7373}.pt-dark .pt-menu-item.pt-intent-danger.pt-active,.pt-dark .pt-menu-item.pt-intent-danger:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item{background-color:#db3737}.pt-dark .pt-menu-item.pt-intent-danger:active{background-color:#c23030}.pt-dark .pt-menu-item.pt-intent-danger.pt-active,.pt-dark .pt-menu-item.pt-intent-danger.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger.pt-active::after,.pt-dark .pt-menu-item.pt-intent-danger.pt-active::before,.pt-dark .pt-menu-item.pt-intent-danger:active,.pt-dark .pt-menu-item.pt-intent-danger:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger:active::after,.pt-dark .pt-menu-item.pt-intent-danger:active::before,.pt-dark .pt-menu-item.pt-intent-danger:hover,.pt-dark .pt-menu-item.pt-intent-danger:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger:hover::after,.pt-dark .pt-menu-item.pt-intent-danger:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item .pt-menu-item-label,.pt-dark .pt-menu-item::after,.pt-dark .pt-menu-item::before{color:#bfccd6}.pt-dark .pt-menu-item:hover::after,.pt-dark .pt-menu-item:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-active,.pt-dark .pt-menu-item:active{background-color:rgba(138,155,168,.3)}.pt-dark .pt-menu-item.pt-disabled,.pt-dark .pt-menu-item.pt-disabled .pt-menu-item-label,.pt-dark .pt-menu-item.pt-disabled::after,.pt-dark .pt-menu-item.pt-disabled::before{color:rgba(191,204,214,.5)!important}.pt-dark .pt-menu-divider,.pt-dark .pt-menu-header{border-color:rgba(255,255,255,.15)}.pt-dark .pt-menu-header>h6{color:#f5f8fa}.pt-label .pt-menu{margin-top:5px}.pt-navbar{position:relative;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.2);background-color:#fff;height:50px;padding:0 15px}.pt-dark .pt-navbar,.pt-navbar.pt-dark{background-color:#394b59}.pt-navbar.pt-dark{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-navbar{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-navbar .pt-logo{margin-right:15px;width:20px}.pt-navbar-heading{margin-right:15px;font-size:16px}.pt-navbar-group{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;height:50px}.pt-navbar-group.pt-align-left{float:left}.pt-navbar-group.pt-align-right{float:right}.pt-navbar-divider{margin:0 10px;border-left:1px solid rgba(16,22,26,.15);height:20px}.pt-dark .pt-navbar-divider{border-left-color:rgba(255,255,255,.15)}.pt-non-ideal-state{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;margin:0 auto;width:100%;max-width:400px;height:100%}.pt-non-ideal-state>:not(:last-child){margin-bottom:20px}.pt-non-ideal-state-icon .pt-icon{color:rgba(92,112,128,.5);font-size:60px}.pt-dark .pt-non-ideal-state-icon .pt-icon{color:rgba(191,204,214,.5)}.pt-non-ideal-state-description{text-align:center}.pt-overlay-backdrop{bottom:0;opacity:1;z-index:20;background-color:rgba(16,22,26,.7);overflow:auto}.pt-overlay-backdrop.pt-overlay-appear,.pt-overlay-backdrop.pt-overlay-enter{opacity:0}.pt-overlay-backdrop.pt-overlay-appear-active,.pt-overlay-backdrop.pt-overlay-enter-active{opacity:1;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-overlay-backdrop.pt-overlay-leave{opacity:1}.pt-overlay-backdrop.pt-overlay-leave-active{opacity:0;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-overlay-content{position:fixed}.pt-overlay-inline .pt-overlay-backdrop,.pt-overlay-inline .pt-overlay-content{position:absolute}.pt-overlay-scroll-container.pt-overlay-open{position:fixed;top:0;right:0;bottom:0;left:0;z-index:20;overflow:auto}.pt-overlay-scroll-container.pt-overlay-open .pt-overlay-content{position:absolute}.pt-popover{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);-webkit-transform:scale(1);transform:scale(1);display:inline-block;z-index:20;border-radius:3px}.pt-popover .pt-popover-arrow{position:absolute;width:30px;height:30px}.pt-popover .pt-popover-arrow::before{margin:5px;width:20px;height:20px;box-shadow:1px 1px 6px rgba(16,22,26,.2)}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-popover{margin-top:-17px;margin-bottom:17px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-popover>.pt-popover-arrow{bottom:-11px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover{margin-left:17px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow{left:-11px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(0);transform:rotate(0)}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-popover{margin-top:17px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-popover>.pt-popover-arrow{top:-11px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-popover{margin-right:17px;margin-left:-17px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-popover>.pt-popover-arrow{right:-11px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.pt-tether-element-attached-middle>.pt-popover>.pt-popover-arrow{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.pt-tether-element-attached-center>.pt-popover>.pt-popover-arrow{right:50%;-webkit-transform:translateX(50%);transform:translateX(50%)}.pt-tether-element-attached-top.pt-tether-target-attached-top>.pt-popover>.pt-popover-arrow{top:-.39px}.pt-tether-element-attached-right.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow{right:-.39px}.pt-tether-element-attached-left.pt-tether-target-attached-left>.pt-popover>.pt-popover-arrow{left:-.39px}.pt-tether-element-attached-bottom.pt-tether-target-attached-bottom>.pt-popover>.pt-popover-arrow{bottom:-.39px}.pt-tether-element-attached-top.pt-tether-element-attached-left>.pt-popover{-webkit-transform-origin:top left;transform-origin:top left}.pt-tether-element-attached-top.pt-tether-element-attached-center>.pt-popover{-webkit-transform-origin:top center;transform-origin:top center}.pt-tether-element-attached-top.pt-tether-element-attached-right>.pt-popover{-webkit-transform-origin:top right;transform-origin:top right}.pt-tether-element-attached-middle.pt-tether-element-attached-left>.pt-popover{-webkit-transform-origin:center left;transform-origin:center left}.pt-tether-element-attached-middle.pt-tether-element-attached-center>.pt-popover{-webkit-transform-origin:center center;transform-origin:center center}.pt-tether-element-attached-middle.pt-tether-element-attached-right>.pt-popover{-webkit-transform-origin:center right;transform-origin:center right}.pt-tether-element-attached-bottom.pt-tether-element-attached-left>.pt-popover{-webkit-transform-origin:bottom left;transform-origin:bottom left}.pt-tether-element-attached-bottom.pt-tether-element-attached-center>.pt-popover{-webkit-transform-origin:bottom center;transform-origin:bottom center}.pt-tether-element-attached-bottom.pt-tether-element-attached-right>.pt-popover{-webkit-transform-origin:bottom right;transform-origin:bottom right}.pt-popover .pt-popover-content{background:#fff;color:inherit;position:relative;border-radius:3px}.pt-popover .pt-popover-arrow-border{fill:#10161a;fill-opacity:.1}.pt-popover .pt-popover-arrow-fill{fill:#fff}.pt-popover-appear>.pt-popover,.pt-popover-enter>.pt-popover{-webkit-transform:scale(.3);transform:scale(.3)}.pt-popover-appear-active>.pt-popover,.pt-popover-enter-active>.pt-popover{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-popover-leave>.pt-popover{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-leave-active>.pt-popover{-webkit-transform:scale(.3);transform:scale(.3);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-popover.pt-popover-content-sizing .pt-popover-content{max-width:350px;padding:20px}.pt-popover-target .pt-popover.pt-popover-content-sizing{width:350px}.pt-popover.pt-minimal{margin:0!important}.pt-popover.pt-minimal .pt-popover-arrow{display:none}.pt-popover-appear>.pt-popover.pt-minimal.pt-popover,.pt-popover-enter>.pt-popover.pt-minimal.pt-popover,.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-appear-active>.pt-popover.pt-minimal.pt-popover,.pt-popover-enter-active>.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-popover-leave>.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-leave-active>.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-dark .pt-popover,.pt-popover.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-dark .pt-popover .pt-popover-content,.pt-popover.pt-dark .pt-popover-content{background:#30404d;color:inherit}.pt-dark .pt-popover .pt-popover-arrow::before,.pt-popover.pt-dark .pt-popover-arrow::before{box-shadow:1px 1px 6px rgba(16,22,26,.4)}.pt-dark .pt-popover .pt-popover-arrow-border,.pt-popover.pt-dark .pt-popover-arrow-border{fill:#10161a;fill-opacity:.2}.pt-dark .pt-popover .pt-popover-arrow-fill,.pt-popover.pt-dark .pt-popover-arrow-fill{fill:#30404d}.pt-popover-arrow::before{display:block;position:absolute;-webkit-transform:rotate(45deg);transform:rotate(45deg);border-radius:2px;content:\"\"}.pt-tether-pinned .pt-popover-arrow{display:none}.pt-popover-target,.pt-popover-target>.pt-popover-target{display:inline-block}.pt-popover-backdrop{background:rgba(255,255,255,0)}.pt-transition-container{opacity:1;z-index:20}.pt-transition-container.pt-popover-appear,.pt-transition-container.pt-popover-enter{opacity:0}.pt-transition-container.pt-popover-appear-active,.pt-transition-container.pt-popover-enter-active{opacity:1;transition-property:opacity;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-transition-container.pt-popover-leave{opacity:1}.pt-transition-container.pt-popover-leave-active{opacity:0;transition-property:opacity;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-popover-target{position:relative;vertical-align:top}.pt-popover-target .pt-transition-container,.pt-portal{position:absolute}.pt-popover-target .pt-transition-container.pt-tether-element-attached-bottom.pt-tether-target-attached-top{bottom:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-left.pt-tether-target-attached-right{left:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-top.pt-tether-target-attached-bottom{top:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-right.pt-tether-target-attached-left{right:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-top.pt-tether-target-attached-top{top:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-right.pt-tether-target-attached-right{right:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-bottom.pt-tether-target-attached-bottom{bottom:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-left.pt-tether-target-attached-left{left:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-middle{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.pt-popover-target .pt-transition-container.pt-tether-element-attached-center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pt-button-group.pt-vertical .pt-popover-target{display:block}.pt-button-group:not(.pt-vertical) .pt-popover-target,.pt-button-group:not(.pt-vertical) .pt-tether-target{float:left}.pt-portal{top:0;right:0;left:0}@-webkit-keyframes linear-progress-bar-stripes{from{background-position:0 0}to{background-position:30px 0}}@keyframes linear-progress-bar-stripes{from{background-position:0 0}to{background-position:30px 0}}.pt-progress-bar{display:block;position:relative;border-radius:40px;background:rgba(92,112,128,.2);width:100%;height:8px;overflow:hidden}.pt-progress-bar .pt-progress-meter{display:inline-block;position:absolute;border-radius:40px;background:linear-gradient(-45deg,rgba(255,255,255,.2) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.2) 50%,rgba(255,255,255,.2) 75%,transparent 75%);background-color:rgba(92,112,128,.8);background-size:30px 30px;width:100%;height:100%;transition:width .2s cubic-bezier(.4,1,.75,.9)}.pt-progress-bar:not(.pt-no-animation):not(.pt-no-stripes) .pt-progress-meter{-webkit-animation:linear-progress-bar-stripes .3s linear infinite reverse;animation:linear-progress-bar-stripes .3s linear infinite reverse}.pt-progress-bar.pt-no-stripes .pt-progress-meter{background-image:none}.pt-dark .pt-progress-bar,.pt-progress-bar.pt-dark{background:rgba(16,22,26,.5)}.pt-dark .pt-progress-bar .pt-progress-meter,.pt-progress-bar.pt-dark .pt-progress-meter{background-color:#8a9ba8}.pt-progress-bar.pt-intent-primary .pt-progress-meter{background-color:#137cbd}.pt-progress-bar.pt-intent-success .pt-progress-meter{background-color:#0f9960}.pt-progress-bar.pt-intent-warning .pt-progress-meter{background-color:#d9822b}.pt-progress-bar.pt-intent-danger .pt-progress-meter{background-color:#db3737}@-webkit-keyframes glow{0%,100%{border-color:rgba(167,182,194,.2);background-color:rgba(167,182,194,.2)}50%{border-color:rgba(92,112,128,.2);background-color:rgba(92,112,128,.2)}}@keyframes glow{0%,100%{border-color:rgba(167,182,194,.2);background-color:rgba(167,182,194,.2)}50%{border-color:rgba(92,112,128,.2);background-color:rgba(92,112,128,.2)}}.pt-skeleton{border-color:rgba(167,182,194,.2)!important;border-radius:2px;box-shadow:none!important;background:rgba(167,182,194,.2)!important;background-clip:padding-box!important;cursor:default;color:transparent!important;-webkit-animation:2s linear infinite glow;animation:2s linear infinite glow;-webkit-user-select:none;user-select:none}.pt-slider,.pt-tree-node-label{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.pt-slider{position:relative;cursor:default;width:100%;min-width:150px;height:40px;user-select:none}.pt-slider:hover{cursor:pointer}.pt-slider:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pt-slider.pt-disabled{opacity:.5;cursor:not-allowed}.pt-slider.pt-slider-unlabeled{height:16px}.pt-slider-progress,.pt-slider-track{position:absolute;top:5px;right:0;left:0;border-radius:3px;background:rgba(92,112,128,.2);height:6px}.pt-dark .pt-slider-progress,.pt-dark .pt-slider-track{background:rgba(16,22,26,.5)}.pt-dark .pt-slider-progress,.pt-slider-progress{background:#137cbd}.pt-slider-handle{background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;color:#182026;position:absolute;top:0;left:0;border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2);cursor:pointer;width:16px;height:16px}.pt-slider-handle.pt-active,.pt-slider-handle:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-slider-handle.pt-disabled,.pt-slider-handle:disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-slider-handle.pt-disabled.pt-active,.pt-slider-handle:disabled.pt-active{background:rgba(206,217,224,.7)}.pt-slider-handle:focus{z-index:1}.pt-slider-handle:hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;background-clip:padding-box;z-index:2;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2);cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.pt-slider-handle.pt-active{background-color:#d8e1e8;background-image:none;box-shadow:0 0 0 1px rgba(16,22,26,.2),inset 0 1px 1px rgba(16,22,26,.1);cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pt-disabled .pt-slider-handle{box-shadow:none;background:#bfccd6;pointer-events:none}.pt-dark .pt-slider-handle{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-slider-handle.pt-active,.pt-dark .pt-slider-handle:active,.pt-dark .pt-slider-handle:hover{color:#f5f8fa}.pt-dark .pt-slider-handle:hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-slider-handle.pt-active,.pt-dark .pt-slider-handle:active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-slider-handle.pt-disabled,.pt-dark .pt-slider-handle:disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-slider-handle.pt-disabled.pt-active,.pt-dark .pt-slider-handle:disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-slider-handle .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-dark .pt-slider-handle,.pt-dark .pt-slider-handle:hover{background-color:#394b59}.pt-dark .pt-slider-handle.pt-active{background-color:#293742}.pt-dark .pt-disabled .pt-slider-handle{border-color:#5c7080;box-shadow:none;background:#5c7080}.pt-slider-handle .pt-slider-label{-webkit-transform:translate(-50%,20px);transform:translate(-50%,20px);margin-left:8px;border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);background:#394b59;color:#f5f8fa}.pt-dark .pt-slider-handle .pt-slider-label{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4);background:#e1e8ed;color:#394b59}.pt-disabled .pt-slider-handle .pt-slider-label{box-shadow:none}.pt-slider-label{display:inline-block;padding:2px 5px;vertical-align:top;line-height:1;font-size:12px;position:absolute;-webkit-transform:translate(-50%,20px);transform:translate(-50%,20px)}.pt-range-slider .pt-slider-handle{width:8px}.pt-range-slider .pt-slider-handle:first-of-type{border-top-right-radius:0;border-bottom-right-radius:0}.pt-range-slider .pt-slider-handle:last-of-type{margin-left:8px;border-top-left-radius:0;border-bottom-left-radius:0}.pt-range-slider .pt-slider-handle:last-of-type .pt-slider-label{margin-left:0}.pt-range-slider .pt-slider-progress{border-radius:0}@-webkit-keyframes pt-spinner-animation{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes pt-spinner-animation{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.pt-spinner:not(.pt-svg-spinner){width:50px;display:inline-block}.pt-spinner.pt-svg-spinner,.pt-spinner:not(.pt-svg-spinner) .pt-spinner-svg-container{-webkit-animation-duration:.4s;animation-duration:.4s}.pt-spinner .pt-svg-spinner-transform-group{-webkit-transform:scale(.5);transform:scale(.5)}.pt-spinner path{stroke-width:5;fill-opacity:0}.pt-spinner .pt-spinner-head{transition:stroke-dashoffset .2s cubic-bezier(.4,1,.75,.9);stroke:rgba(92,112,128,.8);stroke-linecap:round}.pt-spinner .pt-spinner-track{stroke:rgba(92,112,128,.2)}.pt-spinner.pt-small:not(.pt-svg-spinner){width:24px}.pt-spinner.pt-small.pt-svg-spinner,.pt-spinner.pt-small:not(.pt-svg-spinner) .pt-spinner-svg-container{-webkit-animation-duration:.4s;animation-duration:.4s}.pt-spinner.pt-small .pt-svg-spinner-transform-group{-webkit-transform:scale(.24);transform:scale(.24)}.pt-spinner.pt-small path{stroke-width:12}.pt-spinner.pt-large:not(.pt-svg-spinner){width:100px}.pt-spinner.pt-large.pt-svg-spinner,.pt-spinner.pt-large:not(.pt-svg-spinner) .pt-spinner-svg-container{-webkit-animation-duration:450ms;animation-duration:450ms}.pt-spinner.pt-large .pt-svg-spinner-transform-group{-webkit-transform:scale(1);transform:scale(1)}.pt-spinner.pt-large path{stroke-width:3}.pt-spinner:not(.pt-svg-spinner) .pt-spinner-svg-container{position:relative;width:100%;height:0;padding:0 0 100%;-webkit-animation:pt-spinner-animation .4s linear infinite;animation:pt-spinner-animation .4s linear infinite}.pt-spinner:not(.pt-svg-spinner).pt-no-spin .pt-spinner-svg-container{-webkit-animation:none;animation:none}.pt-spinner:not(.pt-svg-spinner) svg{position:absolute;top:0;left:0;width:100%;height:100%}.pt-svg-spinner{-webkit-transform-origin:center;transform-origin:center;-webkit-animation:pt-spinner-animation .4s linear infinite;animation:pt-spinner-animation .4s linear infinite}.pt-svg-spinner.pt-no-spin{-webkit-animation:none;animation:none}.pt-svg-spinner-transform-group{-webkit-transform-origin:center;transform-origin:center}.pt-dark .pt-spinner .pt-spinner-head{stroke:#8a9ba8}.pt-dark .pt-spinner .pt-spinner-track{stroke:rgba(16,22,26,.5)}.pt-spinner.pt-intent-primary .pt-spinner-head{stroke:#137cbd}.pt-spinner.pt-intent-success .pt-spinner-head{stroke:#0f9960}.pt-spinner.pt-intent-warning .pt-spinner-head{stroke:#d9822b}.pt-spinner.pt-intent-danger .pt-spinner-head{stroke:#db3737}table.pt-table{border-spacing:0;font-size:14px}table.pt-table td,table.pt-table th{padding:11px;vertical-align:top;text-align:left}table.pt-table th{color:#182026;font-weight:600}table.pt-table td{color:#182026}table.pt-table tbody tr:first-child td{box-shadow:inset 0 1px 0 0 rgba(16,22,26,.15)}table.pt-table.pt-condensed td,table.pt-table.pt-condensed th{padding-top:6px;padding-bottom:6px}table.pt-table.pt-striped tbody tr:nth-child(odd) td{background:rgba(191,204,214,.2)}table.pt-table.pt-bordered th:not(:first-child){box-shadow:inset 1px 0 0 0 rgba(16,22,26,.15)}table.pt-table.pt-bordered tbody tr td{box-shadow:inset 0 1px 0 0 rgba(16,22,26,.15)}table.pt-table.pt-bordered tbody tr td:not(:first-child){box-shadow:inset 1px 1px 0 0 rgba(16,22,26,.15)}table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td{box-shadow:none}table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td:not(:first-child){box-shadow:inset 1px 0 0 0 rgba(16,22,26,.15)}table.pt-table.pt-interactive tbody tr:hover td{background-color:rgba(191,204,214,.4);cursor:pointer}.pt-dark table.pt-table td,.pt-dark table.pt-table th{color:#f5f8fa}.pt-dark table.pt-table tbody tr:first-child td{box-shadow:inset 0 1px 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-striped tbody tr:nth-child(odd) td{background:rgba(92,112,128,.15)}.pt-dark table.pt-table.pt-bordered th:not(:first-child){box-shadow:inset 1px 0 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered tbody tr td{box-shadow:inset 0 1px 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered tbody tr td:not(:first-child){box-shadow:inset 1px 1px 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td{box-shadow:inset 1px 0 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td:first-child{box-shadow:none}.pt-dark table.pt-table.pt-interactive tbody tr:hover td{background-color:rgba(92,112,128,.3);cursor:pointer}.pt-tabs.pt-vertical{display:-webkit-flex;display:flex}.pt-tabs.pt-vertical>.pt-tab-list{-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:flex-start;align-items:flex-start}.pt-tabs.pt-vertical>.pt-tab-list .pt-tab{width:100%;padding:0 10px}.pt-tabs.pt-vertical>.pt-tab-list .pt-tab-indicator-wrapper .pt-tab-indicator{top:0;right:0;bottom:0;left:0;border-radius:3px;background-color:rgba(19,124,189,.2);height:auto}.pt-tabs.pt-vertical>.pt-tab-panel{margin-top:0;padding-left:20px}.pt-tab-list{display:-webkit-flex;display:flex;-webkit-flex:0 0 auto;flex:0 0 auto;-webkit-align-items:flex-end;align-items:flex-end;position:relative;margin:0;border:none;padding:0;list-style:none}.pt-tab-list>:not(:last-child){margin-right:20px}.pt-tab{overflow:hidden;-webkit-flex:0 0 auto;flex:0 0 auto;position:relative;cursor:pointer;max-width:100%;vertical-align:top;line-height:30px;color:#182026;font-size:14px}.pt-tab-indicator-wrapper~.pt-tab{box-shadow:none!important}.pt-tab[aria-disabled=true]{cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-tab[aria-selected=true]{border-radius:0;box-shadow:inset 0 -3px 0 #106ba3}.pt-tab:not([aria-disabled=true]):hover,.pt-tab[aria-selected=true]{color:#106ba3}.pt-tab:focus{-moz-outline-radius:0}.pt-large .pt-tab{line-height:40px;font-size:16px}.pt-tab-panel{margin-top:20px}.pt-tab-panel[aria-hidden=true]{display:none}.pt-tab-indicator-wrapper{position:absolute;top:0;left:0;-webkit-transform:translateX(0),translateY(0);transform:translateX(0),translateY(0);transition:height,width,-webkit-transform;transition:height,transform,width;transition:height,transform,width,-webkit-transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,1,.75,.9)}.pt-tab-indicator-wrapper .pt-tab-indicator{position:absolute;right:0;bottom:0;left:0;background-color:#106ba3;height:3px}.pt-tab-indicator-wrapper.pt-no-animation{transition:none}.pt-dark .pt-tab{color:#f5f8fa}.pt-dark .pt-tab[aria-disabled=true]{color:rgba(191,204,214,.5)}.pt-dark .pt-tab[aria-selected=true]{box-shadow:inset 0 -3px 0 #48aff0}.pt-dark .pt-tab:not([aria-disabled=true]):hover,.pt-dark .pt-tab[aria-selected=true]{color:#48aff0}.pt-dark .pt-tab-indicator{background-color:#48aff0}.pt-flex-expander{-webkit-flex:1 1;flex:1 1}.pt-tag{display:inline-block;position:relative;border:none;border-radius:3px;box-shadow:none;background-color:#5c7080;min-width:20px;padding:2px 6px;line-height:16px;color:#f5f8fa;font-size:12px}.pt-tag.pt-round{border-radius:10px}.pt-dark .pt-tag{background-color:#bfccd6;color:#182026}.pt-tag.pt-tag-removable{padding-right:20px}.pt-large .pt-tag,.pt-tag.pt-large{min-width:30px;padding:5px 10px;line-height:20px;font-size:14px}.pt-large .pt-tag-remove,.pt-tag-remove{line-height:1;font-weight:400;font-style:normal}.pt-large .pt-tag.pt-round,.pt-tag.pt-large.pt-round{border-radius:15px}.pt-large .pt-tag.pt-tag-removable,.pt-tag.pt-large.pt-tag-removable{padding-right:30px}.pt-tag.pt-intent-primary{background:#137cbd;color:#fff}.pt-tag.pt-intent-success{background:#0f9960;color:#fff}.pt-tag.pt-intent-warning{background:#d9822b;color:#fff}.pt-tag.pt-intent-danger{background:#db3737;color:#fff}.pt-tag.pt-minimal:not([class*=pt-intent-]){background-color:rgba(138,155,168,.2);color:#182026}.pt-dark .pt-tag.pt-minimal:not([class*=pt-intent-]){color:#f5f8fa}.pt-tag.pt-minimal.pt-intent-primary{background-color:rgba(19,124,189,.15);color:#106ba3}.pt-dark .pt-tag.pt-minimal.pt-intent-primary{background-color:rgba(19,124,189,.25);color:#48aff0}.pt-tag.pt-minimal.pt-intent-success{background-color:rgba(15,153,96,.15);color:#0d8050}.pt-dark .pt-tag.pt-minimal.pt-intent-success{background-color:rgba(15,153,96,.25);color:#3dcc91}.pt-tag.pt-minimal.pt-intent-warning{background-color:rgba(217,130,43,.15);color:#bf7326}.pt-dark .pt-tag.pt-minimal.pt-intent-warning{background-color:rgba(217,130,43,.25);color:#ffb366}.pt-tag.pt-minimal.pt-intent-danger{background-color:rgba(219,55,55,.15);color:#c23030}.pt-dark .pt-tag.pt-minimal.pt-intent-danger{background-color:rgba(219,55,55,.25);color:#ff7373}.pt-tag-remove{font-family:Icons16,sans-serif;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;position:absolute;top:0;right:0;opacity:.5;border:none;background:0 0;cursor:pointer;padding:2px;color:inherit}.pt-tag-remove:hover{opacity:.8;background:0 0;text-decoration:none}.pt-tag-remove:active{opacity:1}.pt-tag-remove::before{content:\"\"}.pt-large .pt-tag-remove{font-family:Icons20,sans-serif;font-size:20px;padding:5px}.pt-toast{display:-webkit-flex;display:flex;-webkit-align-items:flex-start;align-items:flex-start;position:relative!important;margin:15px 0 0;border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);background-color:#fff;min-width:300px;max-width:500px;pointer-events:all}.pt-toast.pt-toast-appear,.pt-toast.pt-toast-enter{-webkit-transform:translateY(-40px);transform:translateY(-40px)}.pt-toast.pt-toast-appear-active,.pt-toast.pt-toast-enter-active{-webkit-transform:translateY(0);transform:translateY(0);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-toast.pt-toast-appear~.pt-toast,.pt-toast.pt-toast-enter~.pt-toast{-webkit-transform:translateY(-40px);transform:translateY(-40px)}.pt-toast.pt-toast-appear-active~.pt-toast,.pt-toast.pt-toast-enter-active~.pt-toast{-webkit-transform:translateY(0);transform:translateY(0);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-toast.pt-toast-leave{opacity:1;-webkit-filter:blur(0);filter:blur(0)}.pt-toast.pt-toast-leave-active{opacity:0;-webkit-filter:blur(10px);filter:blur(10px);transition-property:opacity,-webkit-filter;transition-property:opacity,filter;transition-property:opacity,filter,-webkit-filter;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-popover-appear-active>.pt-tooltip,.pt-popover-enter-active>.pt-tooltip,.pt-popover-leave-active>.pt-tooltip,.pt-toast.pt-toast-leave-active~.pt-toast{transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9)}.pt-toast.pt-toast-leave~.pt-toast{-webkit-transform:translateY(0);transform:translateY(0)}.pt-toast.pt-toast-leave-active~.pt-toast{-webkit-transform:translateY(-40px);transform:translateY(-40px);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-delay:50ms}.pt-toast .pt-button-group{-webkit-flex:0 0 auto;flex:0 0 auto;padding:5px 5px 5px 0}.pt-toast>.pt-icon-standard{padding:12px 0 12px 12px;color:#5c7080}.pt-dark .pt-toast,.pt-toast.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4);background-color:#394b59}.pt-dark .pt-toast>.pt-icon-standard,.pt-toast.pt-dark>.pt-icon-standard{color:#bfccd6}.pt-toast[class*=pt-intent-] a{color:rgba(255,255,255,.7)}.pt-toast[class*=pt-intent-] a:hover,.pt-toast[class*=pt-intent-]>.pt-icon-standard{color:#fff}.pt-toast[class*=pt-intent-] .pt-button,.pt-toast[class*=pt-intent-] .pt-button::before,.pt-toast[class*=pt-intent-] .pt-button:active{color:rgba(255,255,255,.7)!important}.pt-toast[class*=pt-intent-] .pt-button:focus{outline-color:rgba(255,255,255,.5)}.pt-toast[class*=pt-intent-] .pt-button:hover{background-color:rgba(255,255,255,.15)!important;color:#fff!important}.pt-toast[class*=pt-intent-] .pt-button:active{background-color:rgba(255,255,255,.3)!important;color:#fff!important}.pt-toast[class*=pt-intent-] .pt-button::after{background:rgba(255,255,255,.3)!important}.pt-toast.pt-intent-primary{background-color:#137cbd;color:#fff}.pt-toast.pt-intent-success{background-color:#0f9960;color:#fff}.pt-toast.pt-intent-warning{background-color:#d9822b;color:#fff}.pt-toast.pt-intent-danger{background-color:#db3737;color:#fff}.pt-toast-message{-webkit-flex:1 1 auto;flex:1 1 auto;padding:11px}.pt-toast-container{position:fixed;right:0;left:0;z-index:40;padding:0 15px 15px;pointer-events:none}.pt-toast-container>span{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:center;align-items:center}.pt-toast-container.pt-toast-container-top{top:0}.pt-toast-container.pt-toast-container-bottom{bottom:0}.pt-toast-container.pt-toast-container-bottom>span{-webkit-flex-direction:column-reverse;flex-direction:column-reverse}.pt-toast-container.pt-toast-container-left>span{-webkit-align-items:flex-start;align-items:flex-start}.pt-toast-container.pt-toast-container-right>span{-webkit-align-items:flex-end;align-items:flex-end}.pt-toast-container-bottom .pt-toast.pt-toast-enter:not(.pt-toast-enter-active),.pt-toast-container-bottom .pt-toast.pt-toast-enter:not(.pt-toast-enter-active)~.pt-toast,.pt-toast-container-bottom .pt-toast.pt-toast-leave-active~.pt-toast{-webkit-transform:translateY(55px);transform:translateY(55px)}.pt-tooltip{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);-webkit-transform:scale(1);transform:scale(1)}.pt-tooltip .pt-popover-arrow{position:absolute;width:22px;height:22px}.pt-tooltip .pt-popover-arrow::before{margin:4px;width:14px;height:14px;box-shadow:1px 1px 6px rgba(16,22,26,.2)}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-tooltip{margin-top:-11px;margin-bottom:11px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-tooltip>.pt-popover-arrow{bottom:-8px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-tooltip{margin-left:11px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-tooltip>.pt-popover-arrow{left:-8px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(0);transform:rotate(0)}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-tooltip{margin-top:11px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-tooltip>.pt-popover-arrow{top:-8px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-tooltip{margin-right:11px;margin-left:-11px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-tooltip>.pt-popover-arrow{right:-8px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.pt-tether-element-attached-middle>.pt-tooltip>.pt-popover-arrow{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.pt-tether-element-attached-center>.pt-tooltip>.pt-popover-arrow{right:50%;-webkit-transform:translateX(50%);transform:translateX(50%)}.pt-tether-element-attached-top.pt-tether-target-attached-top>.pt-tooltip>.pt-popover-arrow{top:-.22px}.pt-tether-element-attached-right.pt-tether-target-attached-right>.pt-tooltip>.pt-popover-arrow{right:-.22px}.pt-tether-element-attached-left.pt-tether-target-attached-left>.pt-tooltip>.pt-popover-arrow{left:-.22px}.pt-tether-element-attached-bottom.pt-tether-target-attached-bottom>.pt-tooltip>.pt-popover-arrow{bottom:-.22px}.pt-tether-element-attached-top.pt-tether-element-attached-left>.pt-tooltip{-webkit-transform-origin:top left;transform-origin:top left}.pt-tether-element-attached-top.pt-tether-element-attached-center>.pt-tooltip{-webkit-transform-origin:top center;transform-origin:top center}.pt-tether-element-attached-top.pt-tether-element-attached-right>.pt-tooltip{-webkit-transform-origin:top right;transform-origin:top right}.pt-tether-element-attached-middle.pt-tether-element-attached-left>.pt-tooltip{-webkit-transform-origin:center left;transform-origin:center left}.pt-tether-element-attached-middle.pt-tether-element-attached-center>.pt-tooltip{-webkit-transform-origin:center center;transform-origin:center center}.pt-tether-element-attached-middle.pt-tether-element-attached-right>.pt-tooltip{-webkit-transform-origin:center right;transform-origin:center right}.pt-tether-element-attached-bottom.pt-tether-element-attached-left>.pt-tooltip{-webkit-transform-origin:bottom left;transform-origin:bottom left}.pt-tether-element-attached-bottom.pt-tether-element-attached-center>.pt-tooltip{-webkit-transform-origin:bottom center;transform-origin:bottom center}.pt-tether-element-attached-bottom.pt-tether-element-attached-right>.pt-tooltip{-webkit-transform-origin:bottom right;transform-origin:bottom right}.pt-tooltip .pt-popover-content{background:#394b59;color:#f5f8fa;padding:10px 12px}.pt-tree-node-content-0,.pt-tree-root{padding-left:0}.pt-tooltip .pt-popover-arrow-border{fill:#10161a;fill-opacity:.1}.pt-tooltip .pt-popover-arrow-fill{fill:#394b59}.pt-popover-appear>.pt-tooltip,.pt-popover-enter>.pt-tooltip{-webkit-transform:scale(.8);transform:scale(.8)}.pt-popover-appear-active>.pt-tooltip,.pt-popover-enter-active>.pt-tooltip{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-delay:0}.pt-popover-leave>.pt-tooltip{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-leave-active>.pt-tooltip{-webkit-transform:scale(.8);transform:scale(.8);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-delay:0}.pt-dark .pt-tooltip,.pt-tooltip.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-dark .pt-tooltip .pt-popover-content,.pt-tooltip.pt-dark .pt-popover-content{background:#e1e8ed;color:#394b59}.pt-dark .pt-tooltip .pt-popover-arrow::before,.pt-tooltip.pt-dark .pt-popover-arrow::before{box-shadow:1px 1px 6px rgba(16,22,26,.4)}.pt-dark .pt-tooltip .pt-popover-arrow-border,.pt-tooltip.pt-dark .pt-popover-arrow-border{fill:#10161a;fill-opacity:.2}.pt-dark .pt-tooltip .pt-popover-arrow-fill,.pt-tooltip.pt-dark .pt-popover-arrow-fill{fill:#e1e8ed}.pt-tooltip.pt-intent-primary .pt-popover-content{background:#137cbd;color:#fff}.pt-tooltip.pt-intent-primary .pt-popover-arrow-fill{fill:#137cbd}.pt-tooltip.pt-intent-success .pt-popover-content{background:#0f9960;color:#fff}.pt-tooltip.pt-intent-success .pt-popover-arrow-fill{fill:#0f9960}.pt-tooltip.pt-intent-warning .pt-popover-content{background:#d9822b;color:#fff}.pt-tooltip.pt-intent-warning .pt-popover-arrow-fill{fill:#d9822b}.pt-tooltip.pt-intent-danger .pt-popover-content{background:#db3737;color:#fff}.pt-tooltip.pt-intent-danger .pt-popover-arrow-fill{fill:#db3737}.pt-tooltip-indicator{border-bottom:dotted 1px;cursor:help}.pt-tree-node-list{margin:0;padding-left:0;list-style:none}.pt-tree-root{position:relative;background-color:transparent;cursor:default}.pt-tree-node-content-1{padding-left:23px}.pt-tree-node-content-2{padding-left:46px}.pt-tree-node-content-3{padding-left:69px}.pt-tree-node-content-4{padding-left:92px}.pt-tree-node-content-5{padding-left:115px}.pt-tree-node-content-6{padding-left:138px}.pt-tree-node-content-7{padding-left:161px}.pt-tree-node-content-8{padding-left:184px}.pt-tree-node-content-9{padding-left:207px}.pt-tree-node-content-10{padding-left:230px}.pt-tree-node-content-11{padding-left:253px}.pt-tree-node-content-12{padding-left:276px}.pt-tree-node-content-13{padding-left:299px}.pt-tree-node-content-14{padding-left:322px}.pt-tree-node-content-15{padding-left:345px}.pt-tree-node-content-16{padding-left:368px}.pt-tree-node-content-17{padding-left:391px}.pt-tree-node-content-18{padding-left:414px}.pt-tree-node-content-19{padding-left:437px}.pt-tree-node-content-20{padding-left:460px}.pt-tree-node-content{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;width:100%;height:30px;padding-right:5px}.pt-tree-node-content:hover{background-color:rgba(191,204,214,.4)}.pt-tree-node-caret,.pt-tree-node-caret-none{position:relative;min-width:30px;line-height:30px!important}.pt-tree-node-caret{color:#5c7080;cursor:pointer;text-align:center}.pt-tree-node-caret:hover{color:#182026}.pt-dark .pt-tree-node-caret{color:#bfccd6}.pt-dark .pt-tree-node-caret:hover{color:#f5f8fa}.pt-tree-node-caret::before{display:inline-block;content:\"\";transition:-webkit-transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9)}.pt-tree-node-caret.pt-tree-node-caret-open::before{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.pt-tree-node-icon{position:relative;margin-right:7px;color:#5c7080}.pt-tree-node-label{overflow:hidden;-webkit-flex:1 1 auto;flex:1 1 auto;position:relative;user-select:none}.pt-tree-node-label span{display:inline}.pt-tree-node-secondary-label{padding:0 5px;line-height:30px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content{background-color:#137cbd}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content,.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-icon,.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-icon-large,.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-icon-standard{color:#fff}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-tree-node-caret::before{color:rgba(255,255,255,.7)}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-tree-node-caret:hover::before{color:#fff}.pt-dark .pt-tree-node-content:hover{background-color:rgba(92,112,128,.3)}.pt-dark .pt-tree-node-icon{color:#bfccd6}.pt-dark .pt-tree-node.pt-tree-node-selected>.pt-tree-node-content{background-color:#137cbd}\n\n      body {\n        background-color: #f5f8fa;\n        width: 100%;\n        height: 100%;\n      }\n\n      body.pt-dark {\n        background-color: #293742;\n      }\n\n      form {\n        margin: 0;\n      }\n\n      #logo-light,\n      #logo-dark {\n        height: 34px;\n      }\n\n      #logo-dark {\n        display: none;\n      }\n\n      .pt-card.box {\n        padding: 0;\n      }\n\n      .pt-card.box .box-header {\n        background: rgba(0, 0, 0, 0.07);\n        padding: 10px;\n      }\n\n      .pt-dark .pt-card.box .box-header {\n        background: rgba(0, 0, 0, 0.16);\n        padding: 10px;\n      }\n\n      .pt-card.box .box-body {\n        padding: 40px 50px 50px 50px;\n      }\n\n      .pt-dark #logo-light {\n        display: none;\n      }\n      .pt-dark #logo-dark {\n        display: block;\n      }\n\n      .pt-card {\n        padding: 50px;\n        min-width: 260px;\n        max-width: 300px;\n        margin: 0 auto;\n        position: relative;\n        top: 50%;\n        transform: translateY(-50%);\n      }\n\n      .pt-card .pt-button {\n        width: 100%;\n        margin-bottom: 7px;\n      }\n\n      .pt-input {\n        width: 100%;\n      }\n\n      .device-info {\n        margin-bottom: 16px;\n      }\n\n      .pt-card .pt-button.auth-cancel,\n      #auth-buttons .pt-button:last-child,\n      #auth-secondary .pt-button:last-child {\n        margin-bottom: 0;\n      }\n\n      #alert,\n      #secondary-alert,\n      #device-alert {\n        display: none;\n        margin-bottom: 15px;\n        transition: background-color ease-in-out 0.1s,\n          background-color ease-in-out 0.1s;\n        word-break: break-word;\n      }\n\n      #alert.flash,\n      #secondary-alert.flash,\n      #device-alert.flash {\n        background-color: rgba(255, 255, 255, 0.50);\n      }\n\n      .auth-button {\n        padding: 15px 10px;\n      }\n\n      #auth-local {\n        display: none;\n      }\n\n      #auth-secondary {\n        display: none;\n      }\n\n      #auth-device {\n        display: none;\n      }\n\n      #secondary-passcode {\n        margin-bottom: 7px;\n      }\n    </style>\n  </head>\n  <body class=\"pt-dark pt-focus-disabled\">\n    <div class=\"box pt-card pt-elevation-2\">\n      <div class=\"box-header\">\n        <svg id=\"logo-light\" role=\"img\" viewBox=\"0 0 123.159 21.35\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#2e4153\">\n          <path d=\"M80.048 16.606q-1.126 0-2.067-.348-.92-.347-1.596-.982-.675-.655-1.043-1.534-.348-.9-.348-2.006 0-1.104.348-1.984.368-.9 1.023-1.555.675-.655 1.596-1.003.92-.348 2.046-.348.675 0 1.37.246.717.225 1.29.614.573.368.818.839.143.266.143.388 0 .123-.102.246-.123.143-.286.225-.144.082-.266.082-.246 0-.471-.287-.205-.286-.491-.511t-.634-.389q-.328-.164-.676-.246-.347-.102-.695-.102-1.146 0-2.005.47-.86.471-1.33 1.33-.471.84-.471 1.985 0 1.126.47 1.985.471.84 1.31 1.31.86.47 1.985.47.47 0 .941-.122.47-.123.9-.348.43-.246.737-.553.164-.143.266-.225.123-.102.286-.102.123 0 .225.102.123.082.246.246.082.082.102.204.02.103-.02.225-.02.123-.123.225-.43.43-1.043.758-.594.327-1.249.511-.634.184-1.186.184m8.675 0q-.573 0-1.043-.123-.45-.122-.798-.368-.348-.245-.594-.614-.225-.368-.347-.86-.103-.49-.103-1.084V2.917q0-.245.041-.368.061-.143.184-.204.143-.062.348-.062.225 0 .348.062.143.04.205.164.06.122.06.347v10.742q0 .45.103.798.103.328.307.573.225.225.532.348.328.102.737.102.245 0 .368.062.143.04.205.184.061.123.061.348t-.061.368q-.062.123-.205.164-.123.061-.348.061m7.12.082q-1.022 0-1.923-.389-.9-.388-1.596-1.064-.675-.675-1.064-1.575-.388-.9-.388-1.924 0-1.043.368-1.923.389-.9 1.064-1.575.696-.696 1.575-1.064.9-.39 1.924-.39 1.37 0 2.476.656 1.104.634 1.76 1.76.654 1.104.654 2.516 0 1.064-.389 1.964-.368.9-1.043 1.576-.655.675-1.535 1.064-.88.368-1.882.368m-.04-1.105q1.043 0 1.882-.511.86-.532 1.35-1.412.512-.9.512-1.985 0-1.105-.49-1.964-.492-.86-1.351-1.33-.84-.491-1.924-.491-1.064 0-1.923.511-.839.491-1.35 1.35-.492.86-.492 1.945 0 1.104.492 1.984.511.88 1.37 1.392t1.924.511m10.906 1.044q-.88 0-1.617-.287-.737-.307-1.289-.86-.552-.572-.86-1.37-.306-.818-.306-1.862V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.348-.062.225 0 .347.062.144.061.185.204.061.123.061.348v4.808q0 .983.327 1.74.328.736.983 1.145.654.41 1.636.41 1.003 0 1.72-.41.715-.409 1.104-1.145.389-.757.389-1.74V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.368-.062.205 0 .328.062.122.061.163.204.062.123.062.348v8.594q0 .184-.062.307-.04.122-.163.163-.123.062-.348.062-.144 0-.246-.041-.102-.02-.164-.082-.06-.061-.102-.164-.02-.102-.02-.245v-1.207q-.266.347-.594.675-.327.307-.736.573-.39.245-.88.388-.491.144-1.085.144m11.478-.02q-.941 0-1.78-.349-.818-.347-1.432-1.002t-.962-1.555q-.348-.921-.348-2.026 0-1.084.348-1.964.348-.9.962-1.535.614-.655 1.432-1.002.839-.348 1.78-.348 1.003 0 1.842.348.839.347 1.452 1.002.614.655.942 1.555.347.9.347 2.005t-.347 2.006q-.328.88-.942 1.534-.613.635-1.452.982-.84.348-1.842.348m.184-1.085q1.023 0 1.821-.47.798-.492 1.269-1.33.47-.86.47-1.986 0-1.145-.47-2.005t-1.29-1.33q-.797-.49-1.8-.49-1.002 0-1.82.49-.82.47-1.29 1.33t-.47 2.005q0 1.126.47 1.985.47.84 1.29 1.33.818.47 1.82.47m4.215 1.084q-.327 0-.47-.143-.123-.143-.123-.47V2.958q0-.246.04-.369.062-.143.185-.204.143-.062.348-.062.225 0 .348.062.122.061.184.204.061.123.061.348v13.034q0 .225-.061.368-.041.143-.164.205-.123.061-.348.061\" />\n          <path d=\"M68.888 21.35q-.24 0-.408-.072-.144-.072-.216-.24-.048-.144-.048-.408V.696q0-.264.048-.408.072-.168.216-.216.168-.072.432-.072t.408.072.192.24q.072.144.072.432v19.91q0 .264-.072.408-.048.168-.216.216-.144.072-.408.072\" />\n          <path d=\"M5.606 16.685q-1.002 0-1.739-.41-.736-.409-1.207-1.104-.47-.717-.696-1.617t-.204-1.903q.02-.982.245-1.903.246-.92.716-1.636.47-.717 1.187-1.126.716-.43 1.698-.43.9 0 1.74.43.859.43 1.534 1.166.675.717 1.064 1.637.389.921.389 1.903 0 1.023-.389 1.944-.389.9-1.064 1.596-.655.675-1.514 1.064-.839.389-1.76.389m-3.785 4.583q-.9 0-1.289-.266t-.47-.675Q0 19.917 0 19.467V8.357q0-.45.061-.86.082-.409.45-.675.39-.286 1.29-.286.716 0 1.186.205.47.184.676.593v12.113q.04.45-.062.86-.082.429-.47.695-.39.266-1.31.266m3.294-8.062q.41 0 .737-.204.348-.205.552-.532.205-.348.205-.737 0-.41-.205-.736-.184-.348-.532-.532-.327-.205-.736-.205-.41 0-.757.205-.328.204-.532.532-.184.327-.184.736 0 .389.204.737.205.327.532.532.328.204.716.204m7.878 3.724q-.92 0-1.31-.266-.388-.286-.47-.716-.062-.45-.062-.9v-6.67q0-.471.082-.88.082-.43.47-.696.39-.266 1.31-.266.9 0 1.269.246.389.245.47.552.082.286.082.45l-.327-.123.164-.184q.163-.184.47-.43.328-.245.757-.43.45-.183 1.003-.183.184 0 .45.04.286.021.593.103.328.082.594.245.286.164.45.43.184.266.184.655 0 1.023-.45 1.74-.43.715-1.146.715-.389 0-.552-.04-.164-.062-.246-.103-.061-.061-.164-.102-.081-.062-.327-.062-.307 0-.573.082-.245.062-.45.205-.184.143-.286.348-.103.184-.103.409v3.99q0 .45-.082.88-.081.43-.47.695-.389.266-1.33.266m8.941 0q-.92 0-1.31-.266-.388-.286-.47-.716-.061-.43-.061-.88v-6.67q0-.45.082-.86.081-.43.47-.695.389-.287 1.31-.287.92 0 1.289.287.389.266.47.696.082.43.082.9v6.65q0 .45-.082.88-.081.43-.47.695-.368.266-1.31.266m0-11.376q-.92 0-1.31-.266-.388-.287-.47-.716-.061-.43-.061-.88 0-.47.082-.88.081-.43.47-.696t1.31-.266 1.289.287q.389.266.47.696.082.43.082.88t-.082.88-.47.695q-.368.266-1.31.266m8.758 11.376q-1.085 0-1.883-.245-.798-.246-1.33-.757-.511-.512-.777-1.29-.246-.777-.246-1.82v-7.94q0-.47.082-.859.082-.409.47-.654.39-.266 1.31-.266.9 0 1.29.286.388.266.47.696t.082.88v7.672q0 .226.02.39.041.142.102.224.062.082.164.123t.266.041q.532 0 .941.082.43.082.655.43.246.347.246 1.166 0 .92-.287 1.31-.286.388-.716.45-.43.081-.86.081M26.026 6.66h2.394l2.824-.103q.45 0 .86.082.43.082.695.47.287.39.287 1.33 0 .86-.287 1.249-.266.389-.696.491-.409.102-.88.102l-2.598-.061h-2.68q-.696-.04-.962-.45t-.266-1.35q0-.88.327-1.31.328-.45.982-.45m12.523 10.23q-.982 0-1.842-.388-.839-.39-1.473-1.085-.634-.716-.982-1.657t-.348-2.046V8.378q0-.471.082-.88.082-.43.47-.696.39-.286 1.31-.286.941 0 1.31.286.388.266.47.696t.082.9v3.315q0 .47.164.798.184.327.511.511.348.164.798.164t.778-.184.511-.512.184-.777V8.357q0-.47.082-.88.082-.43.47-.695.39-.266 1.31-.266.942 0 1.31.286.389.266.47.696.082.43.082.88v6.731q0 .45-.082.86-.081.409-.47.675t-1.31.266q-.675 0-1.064-.164-.368-.143-.532-.368-.163-.246-.204-.47-.02-.246-.02-.41l.347.102-.164.205q-.163.184-.49.45-.308.246-.758.45-.43.184-1.002.184m8.818.02q-.92 0-1.31-.265-.388-.287-.47-.716-.061-.43-.061-.88V8.316q0-.45.082-.86.082-.408.47-.654.39-.266 1.31-.266.839 0 1.207.225.389.205.491.512.103.307.082.573 0 .245.041.306l-.164.123q-.02-.307.225-.614.266-.307.676-.552.43-.266.9-.41.49-.163.92-.163 1.024 0 1.801.389.778.389 1.29 1.105.531.695.797 1.637.266.94.266 2.046v3.355q0 .45-.082.88-.081.43-.47.696-.369.266-1.31.266t-1.33-.266q-.388-.287-.47-.716-.082-.43-.082-.88v-3.335q0-.47-.184-.798-.164-.328-.491-.491-.307-.185-.778-.185-.45 0-.798.185-.327.184-.511.511-.185.328-.185.778v3.355q0 .45-.081.88-.082.43-.471.696-.368.266-1.31.266m14.078.021q-.942 0-1.658-.082-.695-.081-1.186-.327-.491-.245-.798-.696-.307-.47-.45-1.207-.144-.757-.144-1.882V3.67q0-.47.082-.88.082-.429.47-.695.39-.286 1.31-.286.921 0 1.29.286.388.266.47.696t.082.9V13q.02.164.061.246.061.082.164.103h.327q.266 0 .553.081.307.062.511.43.225.348.225 1.228 0 .92-.225 1.31-.204.388-.511.45-.307.081-.573.081\" />\n        </svg>\n        <svg id=\"logo-dark\" role=\"img\" viewBox=\"0 0 123.159 21.35\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\">\n          <path d=\"M80.048 16.606q-1.126 0-2.067-.348-.92-.347-1.596-.982-.675-.655-1.043-1.534-.348-.9-.348-2.006 0-1.104.348-1.984.368-.9 1.023-1.555.675-.655 1.596-1.003.92-.348 2.046-.348.675 0 1.37.246.717.225 1.29.614.573.368.818.839.143.266.143.388 0 .123-.102.246-.123.143-.286.225-.144.082-.266.082-.246 0-.471-.287-.205-.286-.491-.511t-.634-.389q-.328-.164-.676-.246-.347-.102-.695-.102-1.146 0-2.005.47-.86.471-1.33 1.33-.471.84-.471 1.985 0 1.126.47 1.985.471.84 1.31 1.31.86.47 1.985.47.47 0 .941-.122.47-.123.9-.348.43-.246.737-.553.164-.143.266-.225.123-.102.286-.102.123 0 .225.102.123.082.246.246.082.082.102.204.02.103-.02.225-.02.123-.123.225-.43.43-1.043.758-.594.327-1.249.511-.634.184-1.186.184m8.675 0q-.573 0-1.043-.123-.45-.122-.798-.368-.348-.245-.594-.614-.225-.368-.347-.86-.103-.49-.103-1.084V2.917q0-.245.041-.368.061-.143.184-.204.143-.062.348-.062.225 0 .348.062.143.04.205.164.06.122.06.347v10.742q0 .45.103.798.103.328.307.573.225.225.532.348.328.102.737.102.245 0 .368.062.143.04.205.184.061.123.061.348t-.061.368q-.062.123-.205.164-.123.061-.348.061m7.12.082q-1.022 0-1.923-.389-.9-.388-1.596-1.064-.675-.675-1.064-1.575-.388-.9-.388-1.924 0-1.043.368-1.923.389-.9 1.064-1.575.696-.696 1.575-1.064.9-.39 1.924-.39 1.37 0 2.476.656 1.104.634 1.76 1.76.654 1.104.654 2.516 0 1.064-.389 1.964-.368.9-1.043 1.576-.655.675-1.535 1.064-.88.368-1.882.368m-.04-1.105q1.043 0 1.882-.511.86-.532 1.35-1.412.512-.9.512-1.985 0-1.105-.49-1.964-.492-.86-1.351-1.33-.84-.491-1.924-.491-1.064 0-1.923.511-.839.491-1.35 1.35-.492.86-.492 1.945 0 1.104.492 1.984.511.88 1.37 1.392t1.924.511m10.906 1.044q-.88 0-1.617-.287-.737-.307-1.289-.86-.552-.572-.86-1.37-.306-.818-.306-1.862V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.348-.062.225 0 .347.062.144.061.185.204.061.123.061.348v4.808q0 .983.327 1.74.328.736.983 1.145.654.41 1.636.41 1.003 0 1.72-.41.715-.409 1.104-1.145.389-.757.389-1.74V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.368-.062.205 0 .328.062.122.061.163.204.062.123.062.348v8.594q0 .184-.062.307-.04.122-.163.163-.123.062-.348.062-.144 0-.246-.041-.102-.02-.164-.082-.06-.061-.102-.164-.02-.102-.02-.245v-1.207q-.266.347-.594.675-.327.307-.736.573-.39.245-.88.388-.491.144-1.085.144m11.478-.02q-.941 0-1.78-.349-.818-.347-1.432-1.002t-.962-1.555q-.348-.921-.348-2.026 0-1.084.348-1.964.348-.9.962-1.535.614-.655 1.432-1.002.839-.348 1.78-.348 1.003 0 1.842.348.839.347 1.452 1.002.614.655.942 1.555.347.9.347 2.005t-.347 2.006q-.328.88-.942 1.534-.613.635-1.452.982-.84.348-1.842.348m.184-1.085q1.023 0 1.821-.47.798-.492 1.269-1.33.47-.86.47-1.986 0-1.145-.47-2.005t-1.29-1.33q-.797-.49-1.8-.49-1.002 0-1.82.49-.82.47-1.29 1.33t-.47 2.005q0 1.126.47 1.985.47.84 1.29 1.33.818.47 1.82.47m4.215 1.084q-.327 0-.47-.143-.123-.143-.123-.47V2.958q0-.246.04-.369.062-.143.185-.204.143-.062.348-.062.225 0 .348.062.122.061.184.204.061.123.061.348v13.034q0 .225-.061.368-.041.143-.164.205-.123.061-.348.061\" />\n          <path d=\"M68.888 21.35q-.24 0-.408-.072-.144-.072-.216-.24-.048-.144-.048-.408V.696q0-.264.048-.408.072-.168.216-.216.168-.072.432-.072t.408.072.192.24q.072.144.072.432v19.91q0 .264-.072.408-.048.168-.216.216-.144.072-.408.072\" />\n          <path d=\"M5.606 16.685q-1.002 0-1.739-.41-.736-.409-1.207-1.104-.47-.717-.696-1.617t-.204-1.903q.02-.982.245-1.903.246-.92.716-1.636.47-.717 1.187-1.126.716-.43 1.698-.43.9 0 1.74.43.859.43 1.534 1.166.675.717 1.064 1.637.389.921.389 1.903 0 1.023-.389 1.944-.389.9-1.064 1.596-.655.675-1.514 1.064-.839.389-1.76.389m-3.785 4.583q-.9 0-1.289-.266t-.47-.675Q0 19.917 0 19.467V8.357q0-.45.061-.86.082-.409.45-.675.39-.286 1.29-.286.716 0 1.186.205.47.184.676.593v12.113q.04.45-.062.86-.082.429-.47.695-.39.266-1.31.266m3.294-8.062q.41 0 .737-.204.348-.205.552-.532.205-.348.205-.737 0-.41-.205-.736-.184-.348-.532-.532-.327-.205-.736-.205-.41 0-.757.205-.328.204-.532.532-.184.327-.184.736 0 .389.204.737.205.327.532.532.328.204.716.204m7.878 3.724q-.92 0-1.31-.266-.388-.286-.47-.716-.062-.45-.062-.9v-6.67q0-.471.082-.88.082-.43.47-.696.39-.266 1.31-.266.9 0 1.269.246.389.245.47.552.082.286.082.45l-.327-.123.164-.184q.163-.184.47-.43.328-.245.757-.43.45-.183 1.003-.183.184 0 .45.04.286.021.593.103.328.082.594.245.286.164.45.43.184.266.184.655 0 1.023-.45 1.74-.43.715-1.146.715-.389 0-.552-.04-.164-.062-.246-.103-.061-.061-.164-.102-.081-.062-.327-.062-.307 0-.573.082-.245.062-.45.205-.184.143-.286.348-.103.184-.103.409v3.99q0 .45-.082.88-.081.43-.47.695-.389.266-1.33.266m8.941 0q-.92 0-1.31-.266-.388-.286-.47-.716-.061-.43-.061-.88v-6.67q0-.45.082-.86.081-.43.47-.695.389-.287 1.31-.287.92 0 1.289.287.389.266.47.696.082.43.082.9v6.65q0 .45-.082.88-.081.43-.47.695-.368.266-1.31.266m0-11.376q-.92 0-1.31-.266-.388-.287-.47-.716-.061-.43-.061-.88 0-.47.082-.88.081-.43.47-.696t1.31-.266 1.289.287q.389.266.47.696.082.43.082.88t-.082.88-.47.695q-.368.266-1.31.266m8.758 11.376q-1.085 0-1.883-.245-.798-.246-1.33-.757-.511-.512-.777-1.29-.246-.777-.246-1.82v-7.94q0-.47.082-.859.082-.409.47-.654.39-.266 1.31-.266.9 0 1.29.286.388.266.47.696t.082.88v7.672q0 .226.02.39.041.142.102.224.062.082.164.123t.266.041q.532 0 .941.082.43.082.655.43.246.347.246 1.166 0 .92-.287 1.31-.286.388-.716.45-.43.081-.86.081M26.026 6.66h2.394l2.824-.103q.45 0 .86.082.43.082.695.47.287.39.287 1.33 0 .86-.287 1.249-.266.389-.696.491-.409.102-.88.102l-2.598-.061h-2.68q-.696-.04-.962-.45t-.266-1.35q0-.88.327-1.31.328-.45.982-.45m12.523 10.23q-.982 0-1.842-.388-.839-.39-1.473-1.085-.634-.716-.982-1.657t-.348-2.046V8.378q0-.471.082-.88.082-.43.47-.696.39-.286 1.31-.286.941 0 1.31.286.388.266.47.696t.082.9v3.315q0 .47.164.798.184.327.511.511.348.164.798.164t.778-.184.511-.512.184-.777V8.357q0-.47.082-.88.082-.43.47-.695.39-.266 1.31-.266.942 0 1.31.286.389.266.47.696.082.43.082.88v6.731q0 .45-.082.86-.081.409-.47.675t-1.31.266q-.675 0-1.064-.164-.368-.143-.532-.368-.163-.246-.204-.47-.02-.246-.02-.41l.347.102-.164.205q-.163.184-.49.45-.308.246-.758.45-.43.184-1.002.184m8.818.02q-.92 0-1.31-.265-.388-.287-.47-.716-.061-.43-.061-.88V8.316q0-.45.082-.86.082-.408.47-.654.39-.266 1.31-.266.839 0 1.207.225.389.205.491.512.103.307.082.573 0 .245.041.306l-.164.123q-.02-.307.225-.614.266-.307.676-.552.43-.266.9-.41.49-.163.92-.163 1.024 0 1.801.389.778.389 1.29 1.105.531.695.797 1.637.266.94.266 2.046v3.355q0 .45-.082.88-.081.43-.47.696-.369.266-1.31.266t-1.33-.266q-.388-.287-.47-.716-.082-.43-.082-.88v-3.335q0-.47-.184-.798-.164-.328-.491-.491-.307-.185-.778-.185-.45 0-.798.185-.327.184-.511.511-.185.328-.185.778v3.355q0 .45-.081.88-.082.43-.471.696-.368.266-1.31.266m14.078.021q-.942 0-1.658-.082-.695-.081-1.186-.327-.491-.245-.798-.696-.307-.47-.45-1.207-.144-.757-.144-1.882V3.67q0-.47.082-.88.082-.429.47-.695.39-.286 1.31-.286.921 0 1.29.286.388.266.47.696t.082.9V13q.02.164.061.246.061.082.164.103h.327q.266 0 .553.081.307.062.511.43.225.348.225 1.228 0 .92-.225 1.31-.204.388-.511.45-.307.081-.573.081\" />\n        </svg>\n      </div>\n      <div class=\"box-body\">\n        <div id=\"auth-buttons\">\n        </div>\n        <div id=\"auth-secondary\">\n          <h5 id=\"secondary-label\"></h5>\n          <div id=\"secondary-alert\" class=\"pt-callout\"></div>\n          <button id=\"secondary-push\" class=\"pt-button auth-button\">Push</button>\n          <button id=\"secondary-phone\" class=\"pt-button auth-button\">Call Me</button>\n          <button id=\"secondary-sms\" class=\"pt-button auth-button\">Text Me</button>\n          <input id=\"secondary-passcode\" class=\"pt-input\"\n            autocapitalize=\"off\" spellcheck=\"false\" autocomplete=\"off\"\n            type=\"text\" placeholder=\"Passcode\"/>\n          <button id=\"secondary-submit\" class=\"pt-button auth-button\">Submit</button>\n          <button id=\"secondary-cancel\" class=\"pt-button auth-button\">Cancel</button>\n        </div>\n        <div id=\"auth-device\">\n          <h5 id=\"device-label\"></h5>\n          <div id=\"device-alert\" class=\"pt-callout\"></div>\n          <form id=\"device-form\">\n            <label class=\"pt-label\">\n              <input id=\"device-name\" class=\"pt-input\" autocomplete=\"off\"\n                type=\"text\" placeholder=\"Device name\" autofocus/>\n            </label>\n            <input id=\"device-submit\" class=\"pt-button\" disabled\n              type=\"submit\" value=\"Register Device\"/>\n          </form>\n        </div>\n        <div id=\"auth-local\">\n          <div id=\"alert\" class=\"pt-callout\"></div>\n          <form id=\"login-form\">\n            <label class=\"pt-label\">\n              <input id=\"username\" class=\"pt-input\"\n                autocapitalize=\"off\" spellcheck=\"false\" autocomplete=\"off\"\n                type=\"text\" placeholder=\"Username\" autofocus/>\n            </label>\n            <label class=\"pt-label\">\n              <input id=\"password\" class=\"pt-input\" autocomplete=\"off\"\n                type=\"password\" placeholder=\"Password\"/>\n            </label>\n            <input id=\"submit\" class=\"pt-button\" type=\"submit\" value=\"Login\"/>\n          </form>\n          <button class=\"pt-button auth-cancel\">Cancel</button>\n        </div>\n      </div>\n    </div>\n    <script>\n      var __defProp = Object.defineProperty;\n      var __export = (target, all) => {\n        for (var name in all)\n          __defProp(target, name, { get: all[name], enumerable: true });\n      };\n      var __async = (__this, __arguments, generator) => {\n        return new Promise((resolve, reject) => {\n          var fulfilled = (value) => {\n            try {\n              step(generator.next(value));\n            } catch (e) {\n              reject(e);\n            }\n          };\n          var rejected = (value) => {\n            try {\n              step(generator.throw(value));\n            } catch (e) {\n              reject(e);\n            }\n          };\n          var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);\n          step((generator = generator.apply(__this, __arguments)).next());\n        });\n      };\n\n      // src/webauthn-json/index.ts\n      var webauthn_json_exports = {};\n      __export(webauthn_json_exports, {\n        create: () => create,\n        get: () => get,\n        schema: () => schema,\n        supported: () => supported\n      });\n\n      // src/webauthn-json/base64url.ts\n      function base64urlToBuffer(baseurl64String) {\n        const padding = \"==\".slice(0, (4 - baseurl64String.length % 4) % 4);\n        const base64String = baseurl64String.replace(/-/g, \"+\").replace(/_/g, \"/\") + padding;\n        const str = atob(base64String);\n        const buffer = new ArrayBuffer(str.length);\n        const byteView = new Uint8Array(buffer);\n        for (let i = 0; i < str.length; i++) {\n          byteView[i] = str.charCodeAt(i);\n        }\n        return buffer;\n      }\n      function bufferToBase64url(buffer) {\n        const byteView = new Uint8Array(buffer);\n        let str = \"\";\n        for (const charCode of byteView) {\n          str += String.fromCharCode(charCode);\n        }\n        const base64String = btoa(str);\n        const base64urlString = base64String.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=/g, \"\");\n        return base64urlString;\n      }\n\n      // src/webauthn-json/convert.ts\n      var copyValue = \"copy\";\n      var convertValue = \"convert\";\n      function convert(conversionFn, schema2, input) {\n        if (schema2 === copyValue) {\n          return input;\n        }\n        if (schema2 === convertValue) {\n          return conversionFn(input);\n        }\n        if (schema2 instanceof Array) {\n          return input.map((v) => convert(conversionFn, schema2[0], v));\n        }\n        if (schema2 instanceof Object) {\n          const output = {};\n          for (const [key, schemaField] of Object.entries(schema2)) {\n            if (schemaField.deriveFn) {\n              const v = schemaField.deriveFn(input);\n              if (v !== void 0) {\n                input[key] = v;\n              }\n            }\n            if (!(key in input)) {\n              if (schemaField.required) {\n                throw new Error(`Missing key: ${key}`);\n              }\n              continue;\n            }\n            if (input[key] == null) {\n              output[key] = null;\n              continue;\n            }\n            output[key] = convert(conversionFn, schemaField.schema, input[key]);\n          }\n          return output;\n        }\n      }\n      function derived(schema2, deriveFn) {\n        return {\n          required: true,\n          schema: schema2,\n          deriveFn\n        };\n      }\n      function required(schema2) {\n        return {\n          required: true,\n          schema: schema2\n        };\n      }\n      function optional(schema2) {\n        return {\n          required: false,\n          schema: schema2\n        };\n      }\n\n      // src/webauthn-json/basic/schema.ts\n      var publicKeyCredentialDescriptorSchema = {\n        type: required(copyValue),\n        id: required(convertValue),\n        transports: optional(copyValue)\n      };\n      var simplifiedExtensionsSchema = {\n        appid: optional(copyValue),\n        appidExclude: optional(copyValue),\n        credProps: optional(copyValue)\n      };\n      var simplifiedClientExtensionResultsSchema = {\n        appid: optional(copyValue),\n        appidExclude: optional(copyValue),\n        credProps: optional(copyValue)\n      };\n      var credentialCreationOptions = {\n        publicKey: required({\n          rp: required(copyValue),\n          user: required({\n            id: required(convertValue),\n            name: required(copyValue),\n            displayName: required(copyValue)\n          }),\n          challenge: required(convertValue),\n          pubKeyCredParams: required(copyValue),\n          timeout: optional(copyValue),\n          excludeCredentials: optional([publicKeyCredentialDescriptorSchema]),\n          authenticatorSelection: optional(copyValue),\n          attestation: optional(copyValue),\n          extensions: optional(simplifiedExtensionsSchema)\n        }),\n        signal: optional(copyValue)\n      };\n      var publicKeyCredentialWithAttestation = {\n        type: required(copyValue),\n        id: required(copyValue),\n        rawId: required(convertValue),\n        response: required({\n          clientDataJSON: required(convertValue),\n          attestationObject: required(convertValue),\n          transports: derived(copyValue, (response) => {\n            var _a;\n            return ((_a = response.getTransports) == null ? void 0 : _a.call(response)) || [];\n          })\n        }),\n        clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults())\n      };\n      var credentialRequestOptions = {\n        mediation: optional(copyValue),\n        publicKey: required({\n          challenge: required(convertValue),\n          timeout: optional(copyValue),\n          rpId: optional(copyValue),\n          allowCredentials: optional([publicKeyCredentialDescriptorSchema]),\n          userVerification: optional(copyValue),\n          extensions: optional(simplifiedExtensionsSchema)\n        }),\n        signal: optional(copyValue)\n      };\n      var publicKeyCredentialWithAssertion = {\n        type: required(copyValue),\n        id: required(copyValue),\n        rawId: required(convertValue),\n        response: required({\n          clientDataJSON: required(convertValue),\n          authenticatorData: required(convertValue),\n          signature: required(convertValue),\n          userHandle: required(convertValue)\n        }),\n        clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults())\n      };\n      var schema = {\n        credentialCreationOptions,\n        publicKeyCredentialWithAttestation,\n        credentialRequestOptions,\n        publicKeyCredentialWithAssertion\n      };\n\n      // src/webauthn-json/basic/api.ts\n      function createRequestFromJSON(requestJSON) {\n        return convert(base64urlToBuffer, credentialCreationOptions, requestJSON);\n      }\n      function createResponseToJSON(credential) {\n        return convert(bufferToBase64url, publicKeyCredentialWithAttestation, credential);\n      }\n      function create(requestJSON) {\n        return __async(this, null, function* () {\n          const credential = yield navigator.credentials.create(createRequestFromJSON(requestJSON));\n          return createResponseToJSON(credential);\n        });\n      }\n      function getRequestFromJSON(requestJSON) {\n        return convert(base64urlToBuffer, credentialRequestOptions, requestJSON);\n      }\n      function getResponseToJSON(credential) {\n        return convert(bufferToBase64url, publicKeyCredentialWithAssertion, credential);\n      }\n      function get(requestJSON) {\n        return __async(this, null, function* () {\n          const credential = yield navigator.credentials.get(getRequestFromJSON(requestJSON));\n          return getResponseToJSON(credential);\n        });\n      }\n\n      // src/webauthn-json/basic/supported.ts\n      function supported() {\n        return !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get && window.PublicKeyCredential);\n      }\n\n      // src/webauthn-json/browser-global.ts\n      globalThis.webauthnJSON = webauthn_json_exports;\n      //# sourceMappingURL=webauthn-json.browser-global.js.map\n    </script>\n    <script type=\"text/javascript\">\n      var i;\n      var state;\n      var authButtons = document.getElementById('auth-buttons');\n      var authLocal = document.getElementById('auth-local');\n      var alertElm = document.getElementById('alert');\n      var usernameElm = document.getElementById('username');\n      var passwordElm = document.getElementById('password');\n      var submitElm = document.getElementById('submit');\n\n      var secondaryToken = null;\n      var secondaryLabl = '';\n      var secondaryFactors = {\n        push: false,\n        phone: false,\n        sms: false,\n        passcode: false,\n        device: false,\n        device_register: false\n      };\n      var secondaryAuth = document.getElementById('auth-secondary');\n      var secondaryLabel = document.getElementById('secondary-label');\n      var secondaryAlert = document.getElementById('secondary-alert');\n      var secondaryPush = document.getElementById('secondary-push');\n      var secondaryPhone = document.getElementById('secondary-phone');\n      var secondarySms = document.getElementById('secondary-sms');\n      var secondaryPasscode = document.getElementById('secondary-passcode');\n      var secondarySubmit = document.getElementById('secondary-submit');\n      var secondaryCancel = document.getElementById('secondary-cancel');\n\n      var queryVals = window.location.search.substring(1).split('&');\n      for (i = 0; i < queryVals.length; i++) {\n        var queryKeyVal = queryVals[i].split('=');\n        if (queryKeyVal[0] === 'secondary') {\n          secondaryToken = queryKeyVal[1];\n        } else if (queryKeyVal[0] === 'label') {\n          secondaryLabl = decodeURIComponent(queryKeyVal[1]);\n        } else if (queryKeyVal[0] === 'factors') {\n          if (queryKeyVal[1].indexOf('push') !== -1) {\n            secondaryFactors.push = true;\n          }\n          if (queryKeyVal[1].indexOf('phone') !== -1) {\n            secondaryFactors.phone = true;\n          }\n          if (queryKeyVal[1].indexOf('sms') !== -1) {\n            secondaryFactors.sms = true;\n          }\n          if (queryKeyVal[1].indexOf('passcode') !== -1) {\n            secondaryFactors.passcode = true;\n          }\n          if (queryKeyVal[1].indexOf('device') !== -1) {\n            secondaryFactors.device = true;\n          }\n          if (queryKeyVal[1].indexOf('device_register') !== -1) {\n            secondaryFactors.device_register = true;\n          }\n        }\n      }\n\n      var deviceAuthElm = document.getElementById('auth-device');\n      var deviceLabelElm = document.getElementById('device-label');\n      var deviceFormElm = document.getElementById('device-form');\n      var deviceNameElm = document.getElementById('device-name');\n      var deviceSubmitElm = document.getElementById('device-submit');\n      var deviceAlertElm = document.getElementById('device-alert');\n\n      var deviceRegisterPost = function(cred) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              setDeviceAlert();\n              window.location = '/';\n            } else if (xmlhttp.status === 202) {\n              setDeviceAlert();\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setDeviceAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/webauthn/register' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify(cred));\n      }\n\n      var deviceRegister = function() {\n        setDeviceAlert('WebAuthn device registration required', 'warning');\n\n        deviceNameElm.onkeyup = function(evt) {\n          deviceSubmitElm.disabled = !evt.target.value;\n        };\n\n        deviceFormElm.onsubmit = function(evt) {\n          if (evt) {\n            evt.preventDefault ? evt.preventDefault() :\n              evt.returnValue = false;\n          }\n\n          deviceSubmitElm.disabled = true;\n          deviceNameElm.disabled = true;\n\n          var xmlhttp = new XMLHttpRequest();\n\n          xmlhttp.onreadystatechange = function() {\n            if (xmlhttp.readyState === 4) {\n              if (xmlhttp.status === 200) {\n                setDeviceAlert(\n                  'Insert your security key and tap the button', 'info');\n\n                var resp = JSON.parse(xmlhttp.responseText);\n                create(resp).then(function (cred) {\n                  cred.name = deviceNameElm.value;\n                  cred.token = secondaryToken;\n                  deviceRegisterPost(cred);\n                }).catch(function (err) {\n                  setDeviceAlert(err, 'danger');\n                });\n              } else {\n                var errorMsg;\n\n                try {\n                  errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n                } catch(error) {\n                  errorMsg = 'Server error occurred';\n                }\n\n                setDeviceAlert(errorMsg, 'danger');\n              }\n            }\n          };\n\n          xmlhttp.open(\n            'GET',\n            '/auth/webauthn/register?token=' + secondaryToken,\n            true\n          );\n          xmlhttp.send();\n        }\n      };\n\n      var deviceRespond = function(cred) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              window.location = '/';\n            } else if (xmlhttp.status === 201) {\n              setSecondaryAlert();\n\n              var data = JSON.parse(xmlhttp.response);\n              secondaryToken = data['token'];\n              secondaryLabl = data['label'];\n              secondaryFactors.push = data['push'];\n              secondaryFactors.phone = data['phone'];\n              secondaryFactors.sms = data['sms'];\n              secondaryFactors.passcode = data['passcode'];\n              secondaryFactors.device = data['device'];\n              secondaryFactors.device_register = data['device_register'];\n\n              secondaryLabel.innerText = secondaryLabl;\n              deviceLabelElm.innerText = secondaryLabl;\n              secondaryPush.style.display = secondaryFactors.push ?\n                'block' : 'none';\n              secondaryPhone.style.display = secondaryFactors.phone ?\n                'block' : 'none';\n              secondarySms.style.display = secondaryFactors.sms ?\n                'block' : 'none';\n              secondaryPasscode.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              secondarySubmit.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              authLocal.style.display = 'none';\n\n              if (secondaryFactors.device_register) {\n                deviceAuthElm.style.display = 'block';\n                deviceRegister();\n                return;\n              }\n\n              secondaryAuth.style.display = 'block';\n              if (secondaryFactors.device) {\n                deviceAuth();\n              }\n            } else if (xmlhttp.status === 202) {\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setSecondaryAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/webauthn/respond' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify(cred));\n      };\n\n      var deviceAuth = function() {\n        setSecondaryAlert(\n          'Insert your security key and tap the button.', 'info');\n\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              var options = JSON.parse(xmlhttp.responseText);\n\n              get(options).then(function(cred) {\n                cred.token = secondaryToken;\n                deviceRespond(cred);\n              }).catch(function(err) {\n                var errorMsg = 'Webauthn error ' + err;\n                setSecondaryAlert(errorMsg, 'danger');\n              });\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setSecondaryAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'GET',\n          '/auth/webauthn/request?token=' + secondaryToken,\n          true\n        );\n        xmlhttp.send();\n      };\n\n      var loadState = function(redirect) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              state = JSON.parse(xmlhttp.responseText);\n\n              var buttons = '';\n\n              var provider;\n              for (i = 0; i < state.providers.length; i++) {\n                provider = state.providers[i];\n\n                if (provider.id === 'demo') {\n                  usernameElm.value = 'demo';\n                  passwordElm.value = 'demo';\n                  continue;\n                }\n\n                if (provider.type === 'local') {\n                  buttons += '<button id=\"auth-local-btn\" ' +\n                    'class=\"pt-button auth-button\">Username/Password</button>';\n                  continue;\n                }\n\n                buttons += '<button id=\"' + provider.id + '\" ' +\n                  'class=\"pt-button auth-button auth-' + provider.type + '\"' +\n                  '>' + provider.label + '</button>';\n              }\n\n              authButtons.innerHTML = buttons;\n\n              bindState();\n\n              // if (redirect && state.providers.length === 1) {\n              //   provider = state.providers[0];\n              //\n              //   if (provider.type === 'local') {\n              //     onAuthLocal();\n              //   } else {\n              //     onAuthProvider(provider.id);\n              //   }\n              // }\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open('GET', '/auth/state', true);\n        xmlhttp.send();\n\n        return false;\n      };\n\n      var onAuthLocal = function () {\n        authButtons.style.display = 'none';\n        authLocal.style.display = 'block';\n      };\n\n      var onAuthProvider = function (providerId) {\n        var location = '/auth/request';\n\n        if (window.location.search !== '') {\n          location += window.location.search + '&';\n        } else {\n          location += '?';\n        }\n\n        location += 'id=' + providerId;\n\n        window.location = location;\n      };\n\n      var bindState = function() {\n        for (i = 0; i < state.providers.length; i++) {\n          if (state.providers[i].type === 'local') {\n            document.getElementById('auth-local-btn').onclick = onAuthLocal;\n            continue;\n          }\n\n          (function(provider) {\n            document.getElementById(provider.id).onclick = function() {\n              onAuthProvider(provider.id);\n            };\n          })(state.providers[i]);\n        }\n      };\n\n      var flashAlert = function() {\n        var alertClass = alertElm.className;\n        var alertClassFlash = alertClass + ' flash';\n\n        alertElm.className = alertClassFlash;\n        setTimeout(function() {\n          alertElm.className = alertClass;\n          setTimeout(function() {\n            alertElm.className = alertClassFlash;\n            setTimeout(function() {\n              alertElm.className = alertClass;\n            }.bind(this), 150);\n          }.bind(this), 150);\n        }.bind(this), 150);\n      };\n\n      var setAlert = function(alert, intent) {\n        intent = intent || 'default';\n\n        if (alert) {\n          alertElm.className = 'pt-callout pt-intent-' + intent;\n          alertElm.innerHTML = alert;\n          if (alertElm.style.display === 'block') {\n            flashAlert(alertElm);\n          } else {\n            alertElm.style.display = 'block';\n          }\n        } else {\n          alertElm.style.display = 'none';\n        }\n      };\n\n      document.getElementById('login-form').onsubmit = function(evt) {\n        if (evt) {\n          evt.preventDefault ? evt.preventDefault() : evt.returnValue = false;\n        }\n\n        submitElm.disabled = true;\n        for (i = 0; i < authCancels.length; i++) {\n          authCancels[i].disabled = true;\n        }\n\n        var username = usernameElm.value;\n        var password = passwordElm.value;\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            setTimeout(function() {\n              submitElm.disabled = false;\n              for (i = 0; i < authCancels.length; i++) {\n                authCancels[i].disabled = false;\n              }\n            }, 500);\n\n            if (xmlhttp.status === 200) {\n              setAlert();\n              window.location = '/';\n            } else if (xmlhttp.status === 201) {\n              setAlert();\n\n              var data = JSON.parse(xmlhttp.response);\n              secondaryToken = data['token'];\n              secondaryLabl = data['label'];\n              secondaryFactors.push = data['push'];\n              secondaryFactors.phone = data['phone'];\n              secondaryFactors.sms = data['sms'];\n              secondaryFactors.passcode = data['passcode'];\n              secondaryFactors.device = data['device'];\n              secondaryFactors.device_register = data['device_register'];\n\n              secondaryLabel.innerText = secondaryLabl;\n              deviceLabelElm.innerText = secondaryLabl;\n              secondaryPush.style.display = secondaryFactors.push ?\n                'block' : 'none';\n              secondaryPhone.style.display = secondaryFactors.phone ?\n                'block' : 'none';\n              secondarySms.style.display = secondaryFactors.sms ?\n                'block' : 'none';\n              secondaryPasscode.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              secondarySubmit.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              authLocal.style.display = 'none';\n\n              if (secondaryFactors.device_register) {\n                deviceAuthElm.style.display = 'block';\n                deviceRegister();\n              } else {\n                secondaryAuth.style.display = 'block';\n                if (secondaryFactors.device) {\n                  deviceAuth();\n                }\n              }\n            } else if (xmlhttp.status === 202) {\n              setAlert();\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/session' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify({\n          'username': username,\n          'password': password\n        }));\n\n        return false;\n      };\n\n      var flashSecondaryAlert = function() {\n        var alertClass = secondaryAlert.className;\n        var alertClassFlash = alertClass + ' flash';\n\n        secondaryAlert.className = alertClassFlash;\n        setTimeout(function() {\n          secondaryAlert.className = alertClass;\n          setTimeout(function() {\n            secondaryAlert.className = alertClassFlash;\n            setTimeout(function() {\n              secondaryAlert.className = alertClass;\n            }.bind(this), 150);\n          }.bind(this), 150);\n        }.bind(this), 150);\n      };\n\n      var setSecondaryAlert = function(alert, intent) {\n        intent = intent || 'default';\n\n        if (alert) {\n          secondaryAlert.className = 'pt-callout pt-intent-' + intent;\n          secondaryAlert.innerHTML = alert;\n          if (secondaryAlert.style.display === 'block') {\n            flashSecondaryAlert(secondaryAlert);\n          } else {\n            secondaryAlert.style.display = 'block';\n          }\n        } else {\n          secondaryAlert.style.display = 'none';\n        }\n      };\n\n      var flashDeviceAlert = function() {\n        var alertClass = deviceAlertElm.className;\n        var alertClassFlash = alertClass + ' flash';\n\n        deviceAlertElm.className = alertClassFlash;\n        setTimeout(function() {\n          deviceAlertElm.className = alertClass;\n          setTimeout(function() {\n            deviceAlertElm.className = alertClassFlash;\n            setTimeout(function() {\n              deviceAlertElm.className = alertClass;\n            }.bind(this), 150);\n          }.bind(this), 150);\n        }.bind(this), 150);\n      };\n\n      var setDeviceAlert = function(alert, intent) {\n        intent = intent || 'default';\n\n        if (alert) {\n          deviceAlertElm.className = 'pt-callout pt-intent-' + intent;\n          deviceAlertElm.innerHTML = alert;\n          if (deviceAlertElm.style.display === 'block') {\n            flashDeviceAlert(deviceAlertElm);\n          } else {\n            deviceAlertElm.style.display = 'block';\n          }\n        } else {\n          deviceAlertElm.style.display = 'none';\n        }\n      };\n\n      var secondaryChallenge = function(factor, passcode, callback) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              setSecondaryAlert();\n              callback(true);\n              window.location = '/';\n            } else if (xmlhttp.status === 201) {\n              setSecondaryAlert();\n              callback(true);\n\n              var data = JSON.parse(xmlhttp.response);\n              secondaryToken = data['token'];\n              secondaryLabl = data['label'];\n              secondaryFactors.push = data['push'];\n              secondaryFactors.phone = data['phone'];\n              secondaryFactors.sms = data['sms'];\n              secondaryFactors.passcode = data['passcode'];\n              secondaryFactors.device = data['device'];\n              secondaryFactors.device_register = data['device_register'];\n\n              deviceLabelElm.innerText = secondaryLabl;\n\n              secondaryAuth.style.display = 'none';\n              deviceAuthElm.style.display = 'block';\n\n              deviceRegister();\n            } else if (xmlhttp.status === 202) {\n              setSecondaryAlert();\n              callback(true);\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else if (xmlhttp.status === 206 && factor === 'sms') {\n              setSecondaryAlert('Text message sent', 'info');\n              callback(true);\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setSecondaryAlert(errorMsg, 'danger');\n              callback(xmlhttp.status === 401);\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/secondary' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify({\n          'token': secondaryToken,\n          'factor': factor,\n          'passcode': passcode\n        }));\n      };\n\n      secondaryPush.onclick = function() {\n        secondaryPush.disabled = true;\n        secondaryChallenge('push', null, function(success) {\n          if (!success) {\n            secondaryPush.disabled = false;\n          }\n        });\n      };\n      secondaryPhone.onclick = function() {\n        secondaryPhone.disabled = true;\n        secondaryChallenge('phone', null, function(success) {\n          if (!success) {\n            secondaryPhone.disabled = false;\n          }\n        });\n      };\n      secondarySms.onclick = function() {\n        secondarySms.disabled = true;\n        secondaryChallenge('sms', null, function(success) {\n          if (!success) {\n            secondarySms.disabled = false;\n          }\n        });\n      };\n      var secondarySub = function() {\n        var val = secondaryPasscode.value;\n        if (!val) {\n          return;\n        }\n\n        secondaryPasscode.disabled = true;\n        secondarySubmit.disabled = true;\n        secondaryChallenge('passcode', val, function() {\n          secondaryPasscode.disabled = false;\n          secondarySubmit.disabled = false;\n        });\n      };\n      secondaryPasscode.onkeypress = function(evt) {\n        if (evt.keyCode === 13) {\n          secondarySub();\n        }\n      };\n      secondarySubmit.onclick = secondarySub;\n      secondaryCancel.onclick = function() {\n        var query = [];\n\n        var queryVals = window.location.search.substring(1).split('&');\n        for (i = 0; i < queryVals.length; i++) {\n          var queryKeyVal = queryVals[i].split('=');\n          if (queryKeyVal[0] === 'secondary' ||\n              queryKeyVal[0] === 'label' ||\n              queryKeyVal[0] === 'factors') {\n            continue;\n          }\n          query.push(queryVals[i]);\n        }\n\n        var location = '/login';\n        if (query.length) {\n          location += '?' + query.join('&');\n        }\n        window.location = location;\n      };\n\n      var authCancels = document.querySelectorAll('.auth-cancel');\n      for (i = 0; i < authCancels.length; i++) {\n        authCancels[i].onclick = function() {\n          authLocal.style.display = 'none';\n          authButtons.style.display = 'block';\n        };\n      }\n\n      if (secondaryToken) {\n        primary = false;\n        secondaryLabel.innerText = secondaryLabl;\n        deviceLabelElm.innerText = secondaryLabl;\n        secondaryPush.style.display = secondaryFactors.push ?\n          'block' : 'none';\n        secondaryPhone.style.display = secondaryFactors.phone ?\n          'block' : 'none';\n        secondarySms.style.display = secondaryFactors.sms ?\n          'block' : 'none';\n        secondaryPasscode.style.display = secondaryFactors.passcode ?\n          'block' : 'none';\n        secondarySubmit.style.display = secondaryFactors.passcode ?\n          'block' : 'none';\n        authButtons.style.display = 'none';\n\n        if (secondaryFactors.device_register) {\n          deviceAuthElm.style.display = 'block';\n          deviceRegister();\n        } else {\n          secondaryAuth.style.display = 'block';\n          if (secondaryFactors.device) {\n            deviceAuth();\n          }\n        }\n\n        loadState(false);\n      } else {\n        loadState(true);\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/dist-dev/static/blueprint-datetime2.css",
    "content": ".rdp{\n  --rdp-cell-size:40px;\n  --rdp-caption-font-size:18px;\n  --rdp-accent-color:#0000ff;\n  --rdp-background-color:#e7edff;\n  --rdp-accent-color-dark:#3003e1;\n  --rdp-background-color-dark:#180270;\n  --rdp-outline:2px solid var(--rdp-accent-color);\n  --rdp-outline-selected:3px solid var(--rdp-accent-color);\n  --rdp-selected-color:#fff;\n  margin:1em;\n}\n.rdp-vhidden{\n  box-sizing:border-box;\n  padding:0;\n  margin:0;\n  background:transparent;\n  border:0;\n  -moz-appearance:none;\n  -webkit-appearance:none;\n  appearance:none;\n  position:absolute !important;\n  top:0;\n  width:1px !important;\n  height:1px !important;\n  padding:0 !important;\n  overflow:hidden !important;\n  clip:rect(1px, 1px, 1px, 1px) !important;\n  border:0 !important;\n}\n.rdp-button_reset{\n  appearance:none;\n  position:relative;\n  margin:0;\n  padding:0;\n  cursor:default;\n  color:inherit;\n  background:none;\n  font:inherit;\n  -moz-appearance:none;\n  -webkit-appearance:none;\n}\n\n.rdp-button_reset:focus-visible{\n  outline:none;\n}\n\n.rdp-button{\n  border:2px solid transparent;\n}\n\n.rdp-button[disabled]:not(.rdp-day_selected){\n  opacity:0.25;\n}\n\n.rdp-button:not([disabled]){\n  cursor:pointer;\n}\n\n.rdp-button:focus-visible:not([disabled]){\n  color:inherit;\n  background-color:var(--rdp-background-color);\n  border:var(--rdp-outline);\n}\n\n.rdp-button:hover:not([disabled]):not(.rdp-day_selected){\n  background-color:var(--rdp-background-color);\n}\n\n.rdp-months{\n  display:flex;\n}\n\n.rdp-month{\n  margin:0 1em;\n}\n\n.rdp-month:first-child{\n  margin-left:0;\n}\n\n.rdp-month:last-child{\n  margin-right:0;\n}\n\n.rdp-table{\n  margin:0;\n  max-width:calc(var(--rdp-cell-size) * 7);\n  border-collapse:collapse;\n}\n\n.rdp-with_weeknumber .rdp-table{\n  max-width:calc(var(--rdp-cell-size) * 8);\n  border-collapse:collapse;\n}\n\n.rdp-caption{\n  display:flex;\n  align-items:center;\n  justify-content:space-between;\n  padding:0;\n  text-align:left;\n}\n\n.rdp-multiple_months .rdp-caption{\n  position:relative;\n  display:block;\n  text-align:center;\n}\n\n.rdp-caption_dropdowns{\n  position:relative;\n  display:inline-flex;\n}\n\n.rdp-caption_label{\n  position:relative;\n  z-index:1;\n  display:inline-flex;\n  align-items:center;\n  margin:0;\n  padding:0 0.25em;\n  white-space:nowrap;\n  color:currentColor;\n  border:0;\n  border:2px solid transparent;\n  font-family:inherit;\n  font-size:var(--rdp-caption-font-size);\n  font-weight:bold;\n}\n\n.rdp-nav{\n  white-space:nowrap;\n}\n\n.rdp-multiple_months .rdp-caption_start .rdp-nav{\n  position:absolute;\n  top:50%;\n  left:0;\n  transform:translateY(-50%);\n}\n\n.rdp-multiple_months .rdp-caption_end .rdp-nav{\n  position:absolute;\n  top:50%;\n  right:0;\n  transform:translateY(-50%);\n}\n\n.rdp-nav_button{\n  display:inline-flex;\n  align-items:center;\n  justify-content:center;\n  width:var(--rdp-cell-size);\n  height:var(--rdp-cell-size);\n  padding:0.25em;\n  border-radius:100%;\n}\n.rdp-dropdown_year,\n.rdp-dropdown_month{\n  position:relative;\n  display:inline-flex;\n  align-items:center;\n}\n\n.rdp-dropdown{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  position:absolute;\n  z-index:2;\n  top:0;\n  bottom:0;\n  left:0;\n  width:100%;\n  margin:0;\n  padding:0;\n  cursor:inherit;\n  opacity:0;\n  border:none;\n  background-color:transparent;\n  font-family:inherit;\n  font-size:inherit;\n  line-height:inherit;\n}\n\n.rdp-dropdown[disabled]{\n  opacity:unset;\n  color:unset;\n}\n\n.rdp-dropdown:focus-visible:not([disabled]) + .rdp-caption_label{\n  background-color:var(--rdp-background-color);\n  border:var(--rdp-outline);\n  border-radius:6px;\n}\n\n.rdp-dropdown_icon{\n  margin:0 0 0 5px;\n}\n\n.rdp-head{\n  border:0;\n}\n\n.rdp-head_row,\n.rdp-row{\n  height:100%;\n}\n\n.rdp-head_cell{\n  vertical-align:middle;\n  font-size:0.75em;\n  font-weight:700;\n  text-align:center;\n  height:100%;\n  height:var(--rdp-cell-size);\n  padding:0;\n  text-transform:uppercase;\n}\n\n.rdp-tbody{\n  border:0;\n}\n\n.rdp-tfoot{\n  margin:0.5em;\n}\n\n.rdp-cell{\n  width:var(--rdp-cell-size);\n  height:100%;\n  height:var(--rdp-cell-size);\n  padding:0;\n  text-align:center;\n}\n\n.rdp-weeknumber{\n  font-size:0.75em;\n}\n\n.rdp-weeknumber,\n.rdp-day{\n  display:flex;\n  overflow:hidden;\n  align-items:center;\n  justify-content:center;\n  box-sizing:border-box;\n  width:var(--rdp-cell-size);\n  max-width:var(--rdp-cell-size);\n  height:var(--rdp-cell-size);\n  margin:0;\n  border:2px solid transparent;\n  border-radius:100%;\n}\n\n.rdp-day_today:not(.rdp-day_outside){\n  font-weight:bold;\n}\n\n.rdp-day_selected,\n.rdp-day_selected:focus-visible,\n.rdp-day_selected:hover{\n  color:var(--rdp-selected-color);\n  opacity:1;\n  background-color:var(--rdp-accent-color);\n}\n\n.rdp-day_outside{\n  opacity:0.5;\n}\n\n.rdp-day_selected:focus-visible{\n  outline:var(--rdp-outline);\n  outline-offset:2px;\n  z-index:1;\n}\n\n.rdp:not([dir=rtl]) .rdp-day_range_start:not(.rdp-day_range_end){\n  border-top-right-radius:0;\n  border-bottom-right-radius:0;\n}\n\n.rdp:not([dir=rtl]) .rdp-day_range_end:not(.rdp-day_range_start){\n  border-top-left-radius:0;\n  border-bottom-left-radius:0;\n}\n\n.rdp[dir=rtl] .rdp-day_range_start:not(.rdp-day_range_end){\n  border-top-left-radius:0;\n  border-bottom-left-radius:0;\n}\n\n.rdp[dir=rtl] .rdp-day_range_end:not(.rdp-day_range_start){\n  border-top-right-radius:0;\n  border-bottom-right-radius:0;\n}\n\n.rdp-day_range_end.rdp-day_range_start{\n  border-radius:100%;\n}\n\n.rdp-day_range_middle{\n  border-radius:0;\n}\n\n\n.bp5-datepicker-content .rdp{\n  --rdp-cell-size:30px;\n  --rdp-accent-color:#2d72d2;\n  --rdp-background-color:#ffffff;\n  --rdp-accent-color-dark:#215db0;\n  --rdp-background-color-dark:#2f343c;\n  --rdp-outline:2px solid var(--rdp-accent-color);\n  --rdp-outline-selected:2px solid rgba(0, 0, 0, 75%);\n  margin:0;\n  min-width:auto;\n}\n.bp5-datepicker .rdp{\n  display:inline-block;\n  min-width:210px;\n  position:relative;\n  vertical-align:top;\n}\n.bp5-datepicker .rdp:focus{\n  outline:none;\n}\n.bp5-datepicker .rdp-month{\n  display:flex;\n  flex-direction:column;\n  margin:0 5px;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-datepicker .rdp-month + .bp5-datepicker .rdp-month{\n  margin-left:10px;\n}\n.bp5-datepicker .rdp-caption{\n  border-bottom:solid 1px rgba(17, 20, 24, 0.15);\n  padding-bottom:5px;\n}\n.bp5-datepicker .rdp-table{\n  align-self:center;\n}\n.bp5-datepicker .rdp-head_cell{\n  font-size:inherit;\n  font-weight:600;\n  padding-top:5px;\n  text-decoration:none;\n  text-transform:none;\n}\n.bp5-datepicker .rdp-weeknumber{\n  color:#5f6b7c;\n}\n.bp5-datepicker .rdp-day{\n  border-radius:2px;\n}\n.bp5-datepicker .rdp-day.rdp-day_outside{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-datepicker .rdp-day.rdp-day_today{\n  font-weight:400;\n}\n.bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):hover, .bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):focus{\n  background:rgba(143, 153, 168, 0.15);\n  color:#1c2127;\n}\n.bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-datepicker .rdp-day.rdp-day_disabled{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-datepicker .rdp-day.rdp-day_selected{\n  background-color:#2d72d2;\n  border-radius:2px;\n  color:#ffffff;\n}\n.bp5-datepicker .rdp-day.rdp-day_selected:hover{\n  background-color:#215db0;\n  color:#ffffff;\n}\n.bp5-datepicker .rdp-day.rdp-day_selected:active{\n  background-color:#184a90;\n}\n.bp5-datepicker.bp5-datepicker-highlight-current-day .rdp-day.rdp-day_today{\n  border:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-datepicker.bp5-datepicker-reverse-month-and-year .rdp-caption_dropdowns{\n  flex-direction:row-reverse;\n}\n\n.bp5-datepicker-content{\n  align-items:center;\n  display:flex;\n  flex-direction:column;\n  gap:5px;\n}\n.bp5-datepicker-content > .bp5-divider{\n  margin:0;\n  width:calc(100% - 10px);\n}\n\n.bp5-datepicker-month-select select,\n.bp5-datepicker-year-select select{\n  font-weight:600;\n  padding-left:5px;\n  padding-right:16px;\n}\n.bp5-datepicker-month-select select + .bp5-icon,\n.bp5-datepicker-year-select select + .bp5-icon{\n  right:2px;\n}\n\n.bp5-datepicker-footer{\n  display:flex;\n  justify-content:space-between;\n  width:100%;\n}\n\n.bp5-dark .bp5-datepicker{\n  background:#2f343c;\n}\n.bp5-dark .bp5-datepicker .rdp-week-number{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_outside{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):hover, .bp5-dark .bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):focus{\n  background:rgba(143, 153, 168, 0.15);\n  color:#ffffff;\n}\n.bp5-dark .bp5-datepicker .rdp-day:not([disabled], .rdp-day_selected):active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_selected{\n  background-color:#2d72d2;\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_selected:hover{\n  background-color:#215db0;\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_selected:active{\n  background-color:#184a90;\n}\n.bp5-dark .bp5-datepicker .rdp-day.rdp-day_disabled{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-datepicker.bp5-datepicker-highlight-current-day .rdp-day.rdp-day_today{\n  border:1px solid rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-datepicker .bp5-datepicker-footer{\n  border-top-color:rgba(17, 20, 24, 0.4);\n}\n\n.bp5-datepicker-timepicker-wrapper{\n  align-items:center;\n  display:flex;\n  flex-direction:column;\n}\n.bp5-datepicker-timepicker-wrapper .bp5-timepicker-arrow-row:empty + .bp5-timepicker-input-row{\n  margin:5px 0;\n}\n.bp5-datepicker-caption.rdp-caption{\n  display:flex;\n  flex-direction:row;\n  justify-content:space-between;\n  margin:0;\n}\n.bp5-datepicker-caption.rdp-caption > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-datepicker-caption.rdp-caption > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-datepicker-caption.rdp-caption .bp5-html-select select{\n  font-weight:600;\n  padding-left:5px;\n  padding-right:16px;\n}\n.bp5-datepicker-caption.rdp-caption .bp5-html-select select + .bp5-icon{\n  right:2px;\n}\n.bp5-datepicker-caption.rdp-caption + .bp5-divider{\n  margin:0;\n}\n.bp5-datepicker-caption.rdp-caption .bp5-datepicker-nav-button-hidden{\n  visibility:hidden;\n}\n\n.bp5-datepicker-month-select{\n  flex-shrink:1;\n}\n\n.bp5-datepicker-year-select{\n  flex-shrink:1;\n  min-width:60px;\n}\n\n.bp5-datepicker-caption-measure{\n  font-weight:600;\n  padding-left:5px;\n}\n.bp5-daterangepicker{\n}\n.bp5-daterangepicker.bp5-daterangepicker-contiguous .rdp{\n  min-width:220px;\n}\n.bp5-daterangepicker.bp5-daterangepicker-single-month .rdp{\n  min-width:210px;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption{\n  display:flex;\n  flex-direction:row;\n  justify-content:space-between;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption_start .rdp-caption{\n  flex-direction:row-reverse;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption_start .rdp-caption::before{\n  content:\"\";\n  height:30px;\n  width:30px;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption_end .rdp-caption{\n  flex-direction:row;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-caption_end .rdp-caption::before{\n  content:\"\";\n  height:30px;\n  width:30px;\n}\n.bp5-daterangepicker .rdp.rdp-multiple_months .rdp-nav{\n  left:initial;\n  position:initial;\n  top:initial;\n  transform:none;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_outside{\n  visibility:hidden;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_hovered:not(.rdp-day_selected){\n  border-radius:0;\n  color:#215db0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_hovered:not(.rdp-day_selected):not(.rdp-day_range_start, .rdp-day_range_middle, .rdp-day_range_end){\n  background-color:rgba(45, 114, 210, 0.1);\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_range_middle{\n  background-color:rgba(45, 114, 210, 0.1);\n  border-radius:0;\n  color:#215db0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_range_middle:hover{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#215db0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_range_start:not(.rdp-day_range_end, .rdp-day_hovered_end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_range_end:not(.rdp-day_range_start, .rdp_day_hovered_start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_hovered_start:not(.rdp-day_hovered_end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n}\n.bp5-daterangepicker.bp5-datepicker .rdp-day_hovered_end:not(.rdp-day_hovered_start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-daterangepicker.bp5-datepicker-highlight-current-day .rdp-day.rdp-day_today{\n  border:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-daterangepicker.bp5-daterangepicker-reverse-month-and-year.bp5-daterangepicker-contiguous.rdp-caption_dropdowns{\n  flex-direction:row-reverse;\n}\n\n.bp5-daterangepicker-timepickers.bp5-daterangepicker-timepickers-stacked{\n  align-items:center;\n  flex-direction:column;\n}\n\n.bp5-dark .bp5-daterangepicker.bp5-datepicker .rdp-day_hovered{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-daterangepicker.bp5-datepicker .rdp-day_hovered:not(.rdp-day_selected, .rdp-day_range_start, .rdp-day_range_middle, .rdp-day_range_end){\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-dark .bp5-daterangepicker.bp5-datepicker .rdp-day_range_middle{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-daterangepicker.bp5-datepicker .rdp-day_range_middle:hover{\n  background-color:rgba(45, 114, 210, 0.4);\n}\n.bp5-dark .bp5-daterangepicker.bp5-datepicker-highlight-current-day .rdp-day.rdp-day_today{\n  border:1px solid rgba(255, 255, 255, 0.2);\n}"
  },
  {
    "path": "www/dist-dev/static/blueprint-icons.css",
    "content": "@font-face{\n  font-family:\"blueprint-icons-16\";\n  src:url(\"./blueprint-icons-16.ttf?18de873f8edfd138f084e5f339eab193\") format(\"truetype\"), url(\"./blueprint-icons-16.eot?18de873f8edfd138f084e5f339eab193#iefix\") format(\"embedded-opentype\"), url(\"./blueprint-icons-16.woff2?18de873f8edfd138f084e5f339eab193\") format(\"woff2\"), url(\"./blueprint-icons-16.woff?18de873f8edfd138f084e5f339eab193\") format(\"woff\"), url(\"./blueprint-icons-16.svg?18de873f8edfd138f084e5f339eab193#blueprint-icons-16\") format(\"svg\");\n}\n@font-face{\n  font-family:\"blueprint-icons-20\";\n  src:url(\"./blueprint-icons-20.ttf?dce239fc78c479d44f652d039433588a\") format(\"truetype\"), url(\"./blueprint-icons-20.eot?dce239fc78c479d44f652d039433588a#iefix\") format(\"embedded-opentype\"), url(\"./blueprint-icons-20.woff2?dce239fc78c479d44f652d039433588a\") format(\"woff2\"), url(\"./blueprint-icons-20.woff?dce239fc78c479d44f652d039433588a\") format(\"woff\"), url(\"./blueprint-icons-20.svg?dce239fc78c479d44f652d039433588a#blueprint-icons-20\") format(\"svg\");\n}"
  },
  {
    "path": "www/dist-dev/static/blueprint3.css",
    "content": "@charset \"UTF-8\";\nhtml{\n  -webkit-box-sizing:border-box;\n          box-sizing:border-box; }\n\n*,\n*::before,\n*::after{\n  -webkit-box-sizing:inherit;\n          box-sizing:inherit; }\n\nbody {\n  background-color: #f5f8fa !important;\n}\nbody.bp5-dark {\n  background-color: #293742 !important;\n}\n\nbody{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none;\n  color:#182026;\n  background-color: #f5f8fa;\n  font-family:-apple-system, \"BlinkMacSystemFont\", \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Open Sans\", \"Helvetica Neue\", \"blueprint-icons-16\", sans-serif; }\n\np{\n  margin-bottom:10px;\n  margin-top:0; }\n\nsmall{\n  font-size:12px; }\n\nstrong{\n  font-weight:600; }\n\n::-moz-selection{\n  background:rgba(125, 188, 255, 0.6); }\n\n::selection{\n  background:rgba(125, 188, 255, 0.6); }\n.bp5-heading{\n  color:#182026;\n  font-weight:600;\n  margin:0 0 10px;\n  padding:0; }\n  .bp5-dark .bp5-heading{\n    color:#f5f8fa; }\n\nh1.bp5-heading, .bp5-running-text h1{\n  font-size:36px;\n  line-height:40px; }\n\nh2.bp5-heading, .bp5-running-text h2{\n  font-size:28px;\n  line-height:32px; }\n\nh3.bp5-heading, .bp5-running-text h3{\n  font-size:22px;\n  line-height:25px; }\n\nh4.bp5-heading, .bp5-running-text h4{\n  font-size:18px;\n  line-height:21px; }\n\nh5.bp5-heading, .bp5-running-text h5{\n  font-size:16px;\n  line-height:19px; }\n\nh6.bp5-heading, .bp5-running-text h6{\n  font-size:14px;\n  line-height:16px; }\n.bp5-ui-text{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none; }\n\n.bp5-monospace-text{\n  font-family:monospace;\n  text-transform:none; }\n\n.bp5-text-muted{\n  color:#5c7080; }\n  .bp5-dark .bp5-text-muted{\n    color:#a7b6c2; }\n\n.bp5-text-disabled{\n  color:rgba(92, 112, 128, 0.6); }\n  .bp5-dark .bp5-text-disabled{\n    color:rgba(167, 182, 194, 0.6); }\n\n.bp5-text-overflow-ellipsis{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal; }\n.bp5-running-text{\n  font-size:14px;\n  line-height:1.5; }\n  .bp5-running-text h1{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h1{\n      color:#f5f8fa; }\n  .bp5-running-text h2{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h2{\n      color:#f5f8fa; }\n  .bp5-running-text h3{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h3{\n      color:#f5f8fa; }\n  .bp5-running-text h4{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h4{\n      color:#f5f8fa; }\n  .bp5-running-text h5{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h5{\n      color:#f5f8fa; }\n  .bp5-running-text h6{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h6{\n      color:#f5f8fa; }\n  .bp5-running-text hr{\n    border:none;\n    border-bottom:1px solid rgba(16, 22, 26, 0.15);\n    margin:20px 0; }\n    .bp5-dark .bp5-running-text hr{\n      border-color:rgba(255, 255, 255, 0.15); }\n  .bp5-running-text p{\n    margin:0 0 10px;\n    padding:0; }\n\n.bp5-text-large{\n  font-size:16px; }\n\n.bp5-text-small{\n  font-size:12px; }\na{\n  color:#106ba3;\n  text-decoration:none; }\n  a:hover{\n    color:#106ba3;\n    cursor:pointer;\n    text-decoration:underline; }\n  a .bp5-icon, a .bp5-icon-standard, a .bp5-icon-large{\n    color:inherit; }\n  a code,\n  .bp5-dark a code{\n    color:inherit; }\n  .bp5-dark a,\n  .bp5-dark a:hover{\n    color:#48aff0; }\n    .bp5-dark a .bp5-icon, .bp5-dark a .bp5-icon-standard, .bp5-dark a .bp5-icon-large,\n    .bp5-dark a:hover .bp5-icon,\n    .bp5-dark a:hover .bp5-icon-standard,\n    .bp5-dark a:hover .bp5-icon-large{\n      color:inherit; }\n.bp5-running-text code, .bp5-code{\n  font-family:monospace;\n  text-transform:none;\n  background:rgba(255, 255, 255, 0.7);\n  border-radius:3px;\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2);\n  color:#5c7080;\n  font-size:smaller;\n  padding:2px 5px; }\n  .bp5-dark .bp5-running-text code, .bp5-running-text .bp5-dark code, .bp5-dark .bp5-code{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#a7b6c2; }\n  .bp5-running-text a > code, a > .bp5-code{\n    color:#137cbd; }\n    .bp5-dark .bp5-running-text a > code, .bp5-running-text .bp5-dark a > code, .bp5-dark a > .bp5-code{\n      color:inherit; }\n\n.bp5-running-text pre, .bp5-code-block{\n  font-family:monospace;\n  text-transform:none;\n  background:rgba(255, 255, 255, 0.7);\n  border-radius:3px;\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n  color:#182026;\n  display:block;\n  font-size:13px;\n  line-height:1.4;\n  margin:10px 0;\n  padding:13px 15px 12px;\n  word-break:break-all;\n  word-wrap:break-word; }\n  .bp5-dark .bp5-running-text pre, .bp5-running-text .bp5-dark pre, .bp5-dark .bp5-code-block{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n  .bp5-running-text pre > code, .bp5-code-block > code{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:inherit;\n    font-size:inherit;\n    padding:0; }\n\n.bp5-running-text kbd, .bp5-key{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n  color:#5c7080;\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  font-family:inherit;\n  font-size:12px;\n  height:24px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  line-height:24px;\n  min-width:24px;\n  padding:3px 6px;\n  vertical-align:middle; }\n  .bp5-running-text kbd .bp5-icon, .bp5-key .bp5-icon, .bp5-running-text kbd .bp5-icon-standard, .bp5-key .bp5-icon-standard, .bp5-running-text kbd .bp5-icon-large, .bp5-key .bp5-icon-large{\n    margin-right:5px; }\n  .bp5-dark .bp5-running-text kbd, .bp5-running-text .bp5-dark kbd, .bp5-dark .bp5-key{\n    background:#394b59;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#a7b6c2; }\n.bp5-running-text blockquote, .bp5-blockquote{\n  border-left:solid 4px rgba(167, 182, 194, 0.5);\n  margin:0 0 10px;\n  padding:0 20px; }\n  .bp5-dark .bp5-running-text blockquote, .bp5-running-text .bp5-dark blockquote, .bp5-dark .bp5-blockquote{\n    border-color:rgba(115, 134, 148, 0.5); }\n.bp5-running-text ul,\n.bp5-running-text ol, .bp5-list{\n  margin:10px 0;\n  padding-left:30px; }\n  .bp5-running-text ul li:not(:last-child), .bp5-running-text ol li:not(:last-child), .bp5-list li:not(:last-child){\n    margin-bottom:5px; }\n  .bp5-running-text ul ol, .bp5-running-text ol ol, .bp5-list ol,\n  .bp5-running-text ul ul,\n  .bp5-running-text ol ul,\n  .bp5-list ul{\n    margin-top:5px; }\n\n.bp5-list-unstyled{\n  list-style:none;\n  margin:0;\n  padding:0; }\n  .bp5-list-unstyled li{\n    padding:0; }\n.bp5-rtl{\n  text-align:right; }\n\n.bp5-dark{\n  color:#f5f8fa; }\n\n:focus{\n  outline:rgba(19, 124, 189, 0.6) auto 2px;\n  outline-offset:2px;\n  -moz-outline-radius:6px; }\n\n.bp5-focus-disabled :focus{\n  outline:none !important; }\n  .bp5-focus-disabled :focus ~ .bp5-control-indicator{\n    outline:none !important; }\n.bp5-alert{\n  max-width:400px;\n  padding:20px; }\n\n.bp5-alert-body{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex; }\n  .bp5-alert-body .bp5-icon{\n    font-size:40px;\n    margin-right:20px;\n    margin-top:0; }\n\n.bp5-alert-contents{\n  word-break:break-word; }\n\n.bp5-alert-footer{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:reverse;\n      -ms-flex-direction:row-reverse;\n          flex-direction:row-reverse;\n  margin-top:10px; }\n  .bp5-alert-footer .bp5-button{\n    margin-left:10px; }\n.bp5-breadcrumbs{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  cursor:default;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-wrap:wrap;\n      flex-wrap:wrap;\n  height:30px;\n  list-style:none;\n  margin:0;\n  padding:0; }\n  .bp5-breadcrumbs > li{\n    -webkit-box-align:center;\n        -ms-flex-align:center;\n            align-items:center;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex; }\n    .bp5-breadcrumbs > li::after{\n      background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 00-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='%235C7080'/%3e%3c/svg%3e\");\n      content:\"\";\n      display:block;\n      height:16px;\n      margin:0 5px;\n      width:16px; }\n    .bp5-breadcrumbs > li:last-of-type::after{\n      display:none; }\n\n.bp5-breadcrumb,\n.bp5-breadcrumb-current,\n.bp5-breadcrumbs-collapsed{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  font-size:16px; }\n\n.bp5-breadcrumb,\n.bp5-breadcrumbs-collapsed{\n  color:#5c7080; }\n\n.bp5-breadcrumb:hover{\n  text-decoration:none; }\n\n.bp5-breadcrumb.bp5-disabled{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-breadcrumb .bp5-icon{\n  margin-right:5px; }\n\n.bp5-breadcrumb-current{\n  color:inherit;\n  font-weight:600; }\n  .bp5-breadcrumb-current .bp5-input{\n    font-size:inherit;\n    font-weight:inherit;\n    vertical-align:baseline; }\n\n.bp5-breadcrumbs-collapsed{\n  background:#ced9e0;\n  border:none;\n  border-radius:3px;\n  cursor:pointer;\n  margin-right:2px;\n  padding:1px 5px;\n  vertical-align:text-bottom; }\n  .bp5-breadcrumbs-collapsed::before{\n    background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cg fill='%235C7080'%3e%3ccircle cx='2' cy='8.03' r='2'/%3e%3ccircle cx='14' cy='8.03' r='2'/%3e%3ccircle cx='8' cy='8.03' r='2'/%3e%3c/g%3e%3c/svg%3e\") center no-repeat;\n    content:\"\";\n    display:block;\n    height:16px;\n    width:16px; }\n  .bp5-breadcrumbs-collapsed:hover{\n    background:#bfccd6;\n    color:#182026;\n    text-decoration:none; }\n\n.bp5-dark .bp5-breadcrumb,\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  color:#a7b6c2; }\n\n.bp5-dark .bp5-breadcrumbs > li::after{\n  color:#a7b6c2; }\n\n.bp5-dark .bp5-breadcrumb.bp5-disabled{\n  color:rgba(167, 182, 194, 0.6); }\n\n.bp5-dark .bp5-breadcrumb-current{\n  color:#f5f8fa; }\n\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  background:rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-breadcrumbs-collapsed:hover{\n    background:rgba(16, 22, 26, 0.6);\n    color:#f5f8fa; }\n.bp5-button{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  border:none;\n  border-radius:3px;\n  cursor:pointer;\n  font-size:14px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  min-height:30px;\n  min-width:30px; }\n  .bp5-button > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-button > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-button::before,\n  .bp5-button > *{\n    margin-right:7px; }\n  .bp5-button:empty::before,\n  .bp5-button > :last-child{\n    margin-right:0; }\n  .bp5-button:empty{\n    padding:0 !important; }\n  .bp5-button:disabled, .bp5-button.bp5-disabled{\n    cursor:not-allowed; }\n  .bp5-button.bp5-fill{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    width:100%; }\n  .bp5-button.bp5-align-right,\n  .bp5-align-right .bp5-button{\n    text-align:right; }\n  .bp5-button.bp5-align-left,\n  .bp5-align-left .bp5-button{\n    text-align:left; }\n  .bp5-button:not([class*=\"bp5-intent-\"]){\n    background-color:#f5f8fa;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    color:#182026; }\n    .bp5-button:not([class*=\"bp5-intent-\"]):hover{\n      background-clip:padding-box;\n      background-color:#ebf1f5;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n    .bp5-button:not([class*=\"bp5-intent-\"]):active, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-active{\n      background-color:#d8e1e8;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button:not([class*=\"bp5-intent-\"]):disabled, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled{\n      background-color:rgba(206, 217, 224, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed;\n      outline:none; }\n      .bp5-button:not([class*=\"bp5-intent-\"]):disabled.bp5-active, .bp5-button:not([class*=\"bp5-intent-\"]):disabled.bp5-active:hover, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled.bp5-active, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled.bp5-active:hover{\n        background:rgba(206, 217, 224, 0.7); }\n  .bp5-button.bp5-intent-primary{\n    background-color:#137cbd;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-primary:hover, .bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-primary:hover{\n      background-color:#106ba3;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n      background-color:#0e5a8a;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-primary:disabled, .bp5-button.bp5-intent-primary.bp5-disabled{\n      background-color:rgba(19, 124, 189, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button.bp5-intent-success{\n    background-color:#0f9960;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-success:hover, .bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-success:hover{\n      background-color:#0d8050;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n      background-color:#0a6640;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-success:disabled, .bp5-button.bp5-intent-success.bp5-disabled{\n      background-color:rgba(15, 153, 96, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button.bp5-intent-warning{\n    background-color:#d9822b;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-warning:hover, .bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-warning:hover{\n      background-color:#bf7326;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n      background-color:#a66321;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-warning:disabled, .bp5-button.bp5-intent-warning.bp5-disabled{\n      background-color:rgba(217, 130, 43, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button.bp5-intent-danger{\n    background-color:#db3737;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-danger:hover, .bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-danger:hover{\n      background-color:#c23030;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n      background-color:#a82a2a;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-danger:disabled, .bp5-button.bp5-intent-danger.bp5-disabled{\n      background-color:rgba(219, 55, 55, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button[class*=\"bp5-intent-\"] .bp5-button-spinner .bp5-spinner-head{\n    stroke:#ffffff; }\n  .bp5-button.bp5-large,\n  .bp5-large .bp5-button{\n    min-height:40px;\n    min-width:40px;\n    font-size:16px;\n    padding:5px 15px; }\n    .bp5-button.bp5-large::before,\n    .bp5-button.bp5-large > *,\n    .bp5-large .bp5-button::before,\n    .bp5-large .bp5-button > *{\n      margin-right:10px; }\n    .bp5-button.bp5-large:empty::before,\n    .bp5-button.bp5-large > :last-child,\n    .bp5-large .bp5-button:empty::before,\n    .bp5-large .bp5-button > :last-child{\n      margin-right:0; }\n  .bp5-button.bp5-small,\n  .bp5-small .bp5-button{\n    min-height:24px;\n    min-width:24px;\n    padding:0 7px; }\n  .bp5-button.bp5-loading{\n    position:relative; }\n    .bp5-button.bp5-loading[class*=\"bp5-icon-\"]::before{\n      visibility:hidden; }\n    .bp5-button.bp5-loading .bp5-button-spinner{\n      margin:0;\n      position:absolute; }\n    .bp5-button.bp5-loading > :not(.bp5-button-spinner){\n      visibility:hidden; }\n  .bp5-button[class*=\"bp5-icon-\"]::before{\n    font-family:\"blueprint-icons-16\", sans-serif;\n    font-size:16px;\n    font-style:normal;\n    font-weight:400;\n    line-height:1;\n    -moz-osx-font-smoothing:grayscale;\n    -webkit-font-smoothing:antialiased;\n    color:#5c7080; }\n  .bp5-button .bp5-icon, .bp5-button .bp5-icon-standard, .bp5-button .bp5-icon-large{\n    color:#5c7080; }\n    .bp5-button .bp5-icon.bp5-align-right, .bp5-button .bp5-icon-standard.bp5-align-right, .bp5-button .bp5-icon-large.bp5-align-right{\n      margin-left:7px; }\n  .bp5-button .bp5-icon:first-child:last-child,\n  .bp5-button .bp5-spinner + .bp5-icon:last-child{\n    margin:0 -7px; }\n  .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]){\n    background-color:#394b59;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):hover, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):active, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-active{\n      color:#f5f8fa; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):hover{\n      background-color:#30404d;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):active, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-active{\n      background-color:#202b33;\n      background-image:none;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):disabled, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled{\n      background-color:rgba(57, 75, 89, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n      .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):disabled.bp5-active, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled.bp5-active{\n        background:rgba(57, 75, 89, 0.7); }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-button-spinner .bp5-spinner-head{\n      background:rgba(16, 22, 26, 0.5);\n      stroke:#8a9ba8; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"])[class*=\"bp5-icon-\"]::before{\n      color:#a7b6c2; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-icon:not([class*=\"bp5-intent-\"]), .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-icon-standard:not([class*=\"bp5-intent-\"]), .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-icon-large:not([class*=\"bp5-intent-\"]){\n      color:#a7b6c2; }\n  .bp5-dark .bp5-button[class*=\"bp5-intent-\"]{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"]:hover{\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"]:active, .bp5-dark .bp5-button[class*=\"bp5-intent-\"].bp5-active{\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"]:disabled, .bp5-dark .bp5-button[class*=\"bp5-intent-\"].bp5-disabled{\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.3); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"] .bp5-button-spinner .bp5-spinner-head{\n      stroke:#8a9ba8; }\n  .bp5-button:disabled::before,\n  .bp5-button:disabled .bp5-icon, .bp5-button:disabled .bp5-icon-standard, .bp5-button:disabled .bp5-icon-large, .bp5-button.bp5-disabled::before,\n  .bp5-button.bp5-disabled .bp5-icon, .bp5-button.bp5-disabled .bp5-icon-standard, .bp5-button.bp5-disabled .bp5-icon-large, .bp5-button[class*=\"bp5-intent-\"]::before,\n  .bp5-button[class*=\"bp5-intent-\"] .bp5-icon, .bp5-button[class*=\"bp5-intent-\"] .bp5-icon-standard, .bp5-button[class*=\"bp5-intent-\"] .bp5-icon-large{\n    color:inherit !important; }\n  .bp5-button.bp5-minimal{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n    .bp5-button.bp5-minimal:hover{\n      background:rgba(167, 182, 194, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026;\n      text-decoration:none; }\n    .bp5-button.bp5-minimal:active, .bp5-button.bp5-minimal.bp5-active{\n      background:rgba(115, 134, 148, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026; }\n    .bp5-button.bp5-minimal:disabled, .bp5-button.bp5-minimal:disabled:hover, .bp5-button.bp5-minimal.bp5-disabled, .bp5-button.bp5-minimal.bp5-disabled:hover{\n      background:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed; }\n      .bp5-button.bp5-minimal:disabled.bp5-active, .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n        background:rgba(115, 134, 148, 0.3); }\n    .bp5-dark .bp5-button.bp5-minimal{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:inherit; }\n      .bp5-dark .bp5-button.bp5-minimal:hover, .bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n      .bp5-dark .bp5-button.bp5-minimal:hover{\n        background:rgba(138, 155, 168, 0.15); }\n      .bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n        background:rgba(138, 155, 168, 0.3);\n        color:#f5f8fa; }\n      .bp5-dark .bp5-button.bp5-minimal:disabled, .bp5-dark .bp5-button.bp5-minimal:disabled:hover, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover{\n        background:none;\n        color:rgba(167, 182, 194, 0.6);\n        cursor:not-allowed; }\n        .bp5-dark .bp5-button.bp5-minimal:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n          background:rgba(138, 155, 168, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-primary{\n      color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:hover, .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.15);\n        color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(16, 107, 163, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n        stroke:#106ba3; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary{\n        color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:hover{\n          background:rgba(19, 124, 189, 0.2);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n          background:rgba(19, 124, 189, 0.3);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n          background:none;\n          color:rgba(72, 175, 240, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n            background:rgba(19, 124, 189, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-success{\n      color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:hover, .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.15);\n        color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(13, 128, 80, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n        stroke:#0d8050; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success{\n        color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:hover{\n          background:rgba(15, 153, 96, 0.2);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n          background:rgba(15, 153, 96, 0.3);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n          background:none;\n          color:rgba(61, 204, 145, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n            background:rgba(15, 153, 96, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-warning{\n      color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:hover, .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.15);\n        color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(191, 115, 38, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n        stroke:#bf7326; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning{\n        color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:hover{\n          background:rgba(217, 130, 43, 0.2);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n          background:rgba(217, 130, 43, 0.3);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n          background:none;\n          color:rgba(255, 179, 102, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n            background:rgba(217, 130, 43, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-danger{\n      color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:hover, .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.15);\n        color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(194, 48, 48, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n        stroke:#c23030; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger{\n        color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:hover{\n          background:rgba(219, 55, 55, 0.2);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n          background:rgba(219, 55, 55, 0.3);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n          background:none;\n          color:rgba(255, 115, 115, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n            background:rgba(219, 55, 55, 0.3); }\n  .bp5-button.bp5-outlined{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    border:1px solid rgba(24, 32, 38, 0.2);\n    -webkit-box-sizing:border-box;\n            box-sizing:border-box; }\n    .bp5-button.bp5-outlined:hover{\n      background:rgba(167, 182, 194, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026;\n      text-decoration:none; }\n    .bp5-button.bp5-outlined:active, .bp5-button.bp5-outlined.bp5-active{\n      background:rgba(115, 134, 148, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026; }\n    .bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined.bp5-disabled:hover{\n      background:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed; }\n      .bp5-button.bp5-outlined:disabled.bp5-active, .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n        background:rgba(115, 134, 148, 0.3); }\n    .bp5-dark .bp5-button.bp5-outlined{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:inherit; }\n      .bp5-dark .bp5-button.bp5-outlined:hover, .bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n      .bp5-dark .bp5-button.bp5-outlined:hover{\n        background:rgba(138, 155, 168, 0.15); }\n      .bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n        background:rgba(138, 155, 168, 0.3);\n        color:#f5f8fa; }\n      .bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n        background:none;\n        color:rgba(167, 182, 194, 0.6);\n        cursor:not-allowed; }\n        .bp5-dark .bp5-button.bp5-outlined:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n          background:rgba(138, 155, 168, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-primary{\n      color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:hover, .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.15);\n        color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(16, 107, 163, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n        stroke:#106ba3; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n        color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:hover{\n          background:rgba(19, 124, 189, 0.2);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n          background:rgba(19, 124, 189, 0.3);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n          background:none;\n          color:rgba(72, 175, 240, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n            background:rgba(19, 124, 189, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-success{\n      color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:hover, .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.15);\n        color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(13, 128, 80, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n        stroke:#0d8050; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n        color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:hover{\n          background:rgba(15, 153, 96, 0.2);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n          background:rgba(15, 153, 96, 0.3);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n          background:none;\n          color:rgba(61, 204, 145, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n            background:rgba(15, 153, 96, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-warning{\n      color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:hover, .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.15);\n        color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(191, 115, 38, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n        stroke:#bf7326; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n        color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:hover{\n          background:rgba(217, 130, 43, 0.2);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n          background:rgba(217, 130, 43, 0.3);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n          background:none;\n          color:rgba(255, 179, 102, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n            background:rgba(217, 130, 43, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-danger{\n      color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:hover, .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.15);\n        color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(194, 48, 48, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n        stroke:#c23030; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n        color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:hover{\n          background:rgba(219, 55, 55, 0.2);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n          background:rgba(219, 55, 55, 0.3);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n          background:none;\n          color:rgba(255, 115, 115, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n            background:rgba(219, 55, 55, 0.3); }\n    .bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled:hover{\n      border-color:rgba(92, 112, 128, 0.1); }\n    .bp5-dark .bp5-button.bp5-outlined{\n      border-color:rgba(255, 255, 255, 0.4); }\n      .bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n        border-color:rgba(255, 255, 255, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-primary{\n      border-color:rgba(16, 107, 163, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n        border-color:rgba(16, 107, 163, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n        border-color:rgba(72, 175, 240, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n          border-color:rgba(72, 175, 240, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-success{\n      border-color:rgba(13, 128, 80, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n        border-color:rgba(13, 128, 80, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n        border-color:rgba(61, 204, 145, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n          border-color:rgba(61, 204, 145, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-warning{\n      border-color:rgba(191, 115, 38, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n        border-color:rgba(191, 115, 38, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n        border-color:rgba(255, 179, 102, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n          border-color:rgba(255, 179, 102, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-danger{\n      border-color:rgba(194, 48, 48, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n        border-color:rgba(194, 48, 48, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n        border-color:rgba(255, 115, 115, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n          border-color:rgba(255, 115, 115, 0.2); }\n\na.bp5-button{\n  text-align:center;\n  text-decoration:none;\n  -webkit-transition:none;\n  transition:none; }\n  a.bp5-button, a.bp5-button:hover, a.bp5-button:active{\n    color:#182026; }\n  a.bp5-button.bp5-disabled{\n    color:rgba(92, 112, 128, 0.6); }\n\n.bp5-button-text{\n  -webkit-box-flex:0;\n      -ms-flex:0 1 auto;\n          flex:0 1 auto; }\n\n.bp5-button.bp5-align-left .bp5-button-text, .bp5-button.bp5-align-right .bp5-button-text,\n.bp5-button-group.bp5-align-left .bp5-button-text,\n.bp5-button-group.bp5-align-right .bp5-button-text{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto; }\n.bp5-button-group{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex; }\n  .bp5-button-group .bp5-button{\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    position:relative;\n    z-index:4; }\n    .bp5-button-group .bp5-button:focus{\n      z-index:5; }\n    .bp5-button-group .bp5-button:hover{\n      z-index:6; }\n    .bp5-button-group .bp5-button:active, .bp5-button-group .bp5-button.bp5-active{\n      z-index:7; }\n    .bp5-button-group .bp5-button:disabled, .bp5-button-group .bp5-button.bp5-disabled{\n      z-index:3; }\n    .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]{\n      z-index:9; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:focus{\n        z-index:10; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:hover{\n        z-index:11; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:active, .bp5-button-group .bp5-button[class*=\"bp5-intent-\"].bp5-active{\n        z-index:12; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:disabled, .bp5-button-group .bp5-button[class*=\"bp5-intent-\"].bp5-disabled{\n        z-index:8; }\n  .bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:first-child) .bp5-button,\n  .bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:first-child){\n    border-bottom-left-radius:0;\n    border-top-left-radius:0; }\n  .bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n  .bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:last-child){\n    border-bottom-right-radius:0;\n    border-top-right-radius:0;\n    margin-right:-1px; }\n  .bp5-button-group.bp5-minimal .bp5-button{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n    .bp5-button-group.bp5-minimal .bp5-button:hover{\n      background:rgba(167, 182, 194, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026;\n      text-decoration:none; }\n    .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-active{\n      background:rgba(115, 134, 148, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026; }\n    .bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover{\n      background:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed; }\n      .bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active{\n        background:rgba(115, 134, 148, 0.3); }\n    .bp5-dark .bp5-button-group.bp5-minimal .bp5-button{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:inherit; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover{\n        background:rgba(138, 155, 168, 0.15); }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active{\n        background:rgba(138, 155, 168, 0.3);\n        color:#f5f8fa; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover{\n        background:none;\n        color:rgba(167, 182, 194, 0.6);\n        cursor:not-allowed; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active{\n          background:rgba(138, 155, 168, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary{\n      color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.15);\n        color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(16, 107, 163, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n        stroke:#106ba3; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary{\n        color:#48aff0; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover{\n          background:rgba(19, 124, 189, 0.2);\n          color:#48aff0; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active{\n          background:rgba(19, 124, 189, 0.3);\n          color:#48aff0; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled{\n          background:none;\n          color:rgba(72, 175, 240, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n            background:rgba(19, 124, 189, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success{\n      color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.15);\n        color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(13, 128, 80, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n        stroke:#0d8050; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success{\n        color:#3dcc91; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover{\n          background:rgba(15, 153, 96, 0.2);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active{\n          background:rgba(15, 153, 96, 0.3);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled{\n          background:none;\n          color:rgba(61, 204, 145, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n            background:rgba(15, 153, 96, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning{\n      color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.15);\n        color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(191, 115, 38, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n        stroke:#bf7326; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning{\n        color:#ffb366; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover{\n          background:rgba(217, 130, 43, 0.2);\n          color:#ffb366; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active{\n          background:rgba(217, 130, 43, 0.3);\n          color:#ffb366; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled{\n          background:none;\n          color:rgba(255, 179, 102, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n            background:rgba(217, 130, 43, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger{\n      color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.15);\n        color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(194, 48, 48, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n        stroke:#c23030; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger{\n        color:#ff7373; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover{\n          background:rgba(219, 55, 55, 0.2);\n          color:#ff7373; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active{\n          background:rgba(219, 55, 55, 0.3);\n          color:#ff7373; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled{\n          background:none;\n          color:rgba(255, 115, 115, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n            background:rgba(219, 55, 55, 0.3); }\n  .bp5-button-group .bp5-popover-wrapper,\n  .bp5-button-group .bp5-popover-target{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-button-group.bp5-fill{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    width:100%; }\n  .bp5-button-group .bp5-button.bp5-fill,\n  .bp5-button-group.bp5-fill .bp5-button:not(.bp5-fixed){\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-button-group.bp5-vertical{\n    -webkit-box-align:stretch;\n        -ms-flex-align:stretch;\n            align-items:stretch;\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:column;\n            flex-direction:column;\n    vertical-align:top; }\n    .bp5-button-group.bp5-vertical.bp5-fill{\n      height:100%;\n      width:unset; }\n    .bp5-button-group.bp5-vertical .bp5-button{\n      margin-right:0 !important;\n      width:100%; }\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:first-child .bp5-button,\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:first-child{\n      border-radius:3px 3px 0 0; }\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:last-child .bp5-button,\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:last-child{\n      border-radius:0 0 3px 3px; }\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:not(:last-child){\n      margin-bottom:-1px; }\n  .bp5-button-group.bp5-align-left .bp5-button{\n    text-align:left; }\n  .bp5-dark .bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n  .bp5-dark .bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:last-child){\n    margin-right:1px; }\n  .bp5-dark .bp5-button-group.bp5-vertical > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n  .bp5-dark .bp5-button-group.bp5-vertical > .bp5-button:not(:last-child){\n    margin-bottom:1px; }\n.bp5-callout{\n  font-size:14px;\n  line-height:1.5;\n  background-color:rgba(138, 155, 168, 0.15);\n  border-radius:3px;\n  padding:10px 12px 9px;\n  position:relative;\n  width:100%; }\n  .bp5-callout[class*=\"bp5-icon-\"]{\n    padding-left:40px; }\n    .bp5-callout[class*=\"bp5-icon-\"]::before{\n      font-family:\"blueprint-icons-20\", sans-serif;\n      font-size:20px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1;\n      -moz-osx-font-smoothing:grayscale;\n      -webkit-font-smoothing:antialiased;\n      color:#5c7080;\n      left:10px;\n      position:absolute;\n      top:10px; }\n  .bp5-callout.bp5-callout-icon{\n    padding-left:40px; }\n    .bp5-callout.bp5-callout-icon > .bp5-icon:first-child{\n      color:#5c7080;\n      left:10px;\n      position:absolute;\n      top:10px; }\n  .bp5-callout .bp5-heading{\n    line-height:20px;\n    margin-bottom:5px;\n    margin-top:0; }\n    .bp5-callout .bp5-heading:last-child{\n      margin-bottom:0; }\n  .bp5-dark .bp5-callout{\n    background-color:rgba(138, 155, 168, 0.2); }\n    .bp5-dark .bp5-callout[class*=\"bp5-icon-\"]::before{\n      color:#a7b6c2; }\n  .bp5-callout.bp5-intent-primary{\n    background-color:rgba(19, 124, 189, 0.15); }\n    .bp5-callout.bp5-intent-primary[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-primary .bp5-heading{\n      color:#106ba3; }\n    .bp5-dark .bp5-callout.bp5-intent-primary{\n      background-color:rgba(19, 124, 189, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-primary[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-primary .bp5-heading{\n        color:#48aff0; }\n  .bp5-callout.bp5-intent-success{\n    background-color:rgba(15, 153, 96, 0.15); }\n    .bp5-callout.bp5-intent-success[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-success .bp5-heading{\n      color:#0d8050; }\n    .bp5-dark .bp5-callout.bp5-intent-success{\n      background-color:rgba(15, 153, 96, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-success[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-success .bp5-heading{\n        color:#3dcc91; }\n  .bp5-callout.bp5-intent-warning{\n    background-color:rgba(217, 130, 43, 0.15); }\n    .bp5-callout.bp5-intent-warning[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-warning .bp5-heading{\n      color:#bf7326; }\n    .bp5-dark .bp5-callout.bp5-intent-warning{\n      background-color:rgba(217, 130, 43, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-warning[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-warning .bp5-heading{\n        color:#ffb366; }\n  .bp5-callout.bp5-intent-danger{\n    background-color:rgba(219, 55, 55, 0.15); }\n    .bp5-callout.bp5-intent-danger[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-danger .bp5-heading{\n      color:#c23030; }\n    .bp5-dark .bp5-callout.bp5-intent-danger{\n      background-color:rgba(219, 55, 55, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-danger[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-danger .bp5-heading{\n        color:#ff7373; }\n  .bp5-running-text .bp5-callout{\n    margin:20px 0; }\n.bp5-card{\n  background-color:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n  padding:20px;\n  -webkit-transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-card.bp5-dark,\n  .bp5-dark .bp5-card{\n    background-color:#30404d;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); }\n\n.bp5-elevation-0{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); }\n  .bp5-elevation-0.bp5-dark,\n  .bp5-dark .bp5-elevation-0{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); }\n\n.bp5-elevation-1{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-1.bp5-dark,\n  .bp5-dark .bp5-elevation-1{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-elevation-2{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 1px 1px rgba(16, 22, 26, 0.2), 0 2px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 1px 1px rgba(16, 22, 26, 0.2), 0 2px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-2.bp5-dark,\n  .bp5-dark .bp5-elevation-2{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.4), 0 2px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.4), 0 2px 6px rgba(16, 22, 26, 0.4); }\n\n.bp5-elevation-3{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-3.bp5-dark,\n  .bp5-dark .bp5-elevation-3{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n\n.bp5-elevation-4{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-4.bp5-dark,\n  .bp5-dark .bp5-elevation-4{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4); }\n\n.bp5-card.bp5-interactive:hover{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  cursor:pointer; }\n  .bp5-card.bp5-interactive:hover.bp5-dark,\n  .bp5-dark .bp5-card.bp5-interactive:hover{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n\n.bp5-card.bp5-interactive:active{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n  opacity:0.9;\n  -webkit-transition-duration:0;\n          transition-duration:0; }\n  .bp5-card.bp5-interactive:active.bp5-dark,\n  .bp5-dark .bp5-card.bp5-interactive:active{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-collapse{\n  height:0;\n  overflow-y:hidden;\n  -webkit-transition:height 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:height 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-collapse .bp5-collapse-body{\n    -webkit-transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-collapse .bp5-collapse-body[aria-hidden=\"true\"]{\n      display:none; }\n\n.bp5-context-menu .bp5-popover-target{\n  display:block; }\n\n.bp5-context-menu-popover-target{\n  position:fixed; }\n.bp5-divider{\n  border-bottom:1px solid rgba(16, 22, 26, 0.15);\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  margin:5px; }\n  .bp5-dark .bp5-divider{\n    border-color:rgba(16, 22, 26, 0.4); }\n.bp5-dialog-container{\n  opacity:1;\n  -webkit-transform:scale(1);\n          transform:scale(1);\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  min-height:100%;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  width:100%; }\n  .bp5-dialog-container.bp5-overlay-enter > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear > .bp5-dialog{\n    opacity:0;\n    -webkit-transform:scale(0.5);\n            transform:scale(0.5); }\n  .bp5-dialog-container.bp5-overlay-enter-active > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear-active > .bp5-dialog{\n    opacity:1;\n    -webkit-transform:scale(1);\n            transform:scale(1);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:opacity, -webkit-transform;\n    transition-property:opacity, -webkit-transform;\n    transition-property:opacity, transform;\n    transition-property:opacity, transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-dialog-container.bp5-overlay-exit > .bp5-dialog{\n    opacity:1;\n    -webkit-transform:scale(1);\n            transform:scale(1); }\n  .bp5-dialog-container.bp5-overlay-exit-active > .bp5-dialog{\n    opacity:0;\n    -webkit-transform:scale(0.5);\n            transform:scale(0.5);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:opacity, -webkit-transform;\n    transition-property:opacity, -webkit-transform;\n    transition-property:opacity, transform;\n    transition-property:opacity, transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n\n.bp5-dialog{\n  background:#ebf1f5;\n  border-radius:6px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin:30px 0;\n  padding-bottom:20px;\n  pointer-events:all;\n  -webkit-user-select:text;\n     -moz-user-select:text;\n      -ms-user-select:text;\n          user-select:text;\n  width:500px; }\n  .bp5-dialog:focus{\n    outline:0; }\n  .bp5-dialog.bp5-dark,\n  .bp5-dark .bp5-dialog{\n    background:#293742;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n\n.bp5-dialog-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background:#ffffff;\n  border-radius:6px 6px 0 0;\n  -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  min-height:40px;\n  padding-left:20px;\n  padding-right:5px;\n  z-index:0; }\n  .bp5-dialog-header .bp5-icon-large,\n  .bp5-dialog-header .bp5-icon{\n    color:#5c7080;\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    margin-right:10px; }\n  .bp5-dialog-header .bp5-heading{\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    line-height:inherit;\n    margin:0; }\n    .bp5-dialog-header .bp5-heading:last-child{\n      margin-right:20px; }\n  .bp5-dark .bp5-dialog-header{\n    background:#30404d;\n    -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.4);\n            box-shadow:0 1px 0 rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-dialog-header .bp5-icon-large,\n    .bp5-dark .bp5-dialog-header .bp5-icon{\n      color:#a7b6c2; }\n\n.bp5-dialog-body{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  line-height:18px;\n  margin:20px; }\n\n.bp5-dialog-footer{\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  margin:0 20px; }\n\n.bp5-dialog-footer-actions{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:end;\n      -ms-flex-pack:end;\n          justify-content:flex-end; }\n  .bp5-dialog-footer-actions .bp5-button{\n    margin-left:10px; }\n.bp5-multistep-dialog-panels{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex; }\n  .bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:first-child{\n    border-radius:6px 0 0 0; }\n  .bp5-multistep-dialog-panels:first-child .bp5-multistep-dialog-right-panel{\n    border-top-right-radius:6px; }\n\n.bp5-multistep-dialog-left-panel{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:1;\n      -ms-flex:1;\n          flex:1;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column; }\n  .bp5-dark .bp5-multistep-dialog-left-panel{\n    background:#202b33; }\n\n.bp5-multistep-dialog-right-panel{\n  background-color:#f5f8fa;\n  border-left:1px solid rgba(16, 22, 26, 0.15);\n  border-radius:0 0 6px 0;\n  -webkit-box-flex:3;\n      -ms-flex:3;\n          flex:3;\n  min-width:0; }\n  .bp5-dark .bp5-multistep-dialog-right-panel{\n    background-color:#293742;\n    border-left:1px solid rgba(16, 22, 26, 0.4); }\n\n.bp5-multistep-dialog-footer{\n  background-color:#ffffff;\n  border-radius:0 0 6px 0;\n  border-top:1px solid rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:justify;\n      -ms-flex-pack:justify;\n          justify-content:space-between;\n  padding:10px; }\n  .bp5-dark .bp5-multistep-dialog-footer{\n    background:#30404d;\n    border-top:1px solid rgba(16, 22, 26, 0.4); }\n  .bp5-multistep-dialog-footer .bp5-dialog-footer-actions{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1; }\n\n.bp5-dialog-step-container{\n  background-color:#f5f8fa;\n  border-bottom:1px solid rgba(16, 22, 26, 0.15); }\n  .bp5-dark .bp5-dialog-step-container{\n    background:#293742;\n    border-bottom:1px solid rgba(16, 22, 26, 0.4); }\n  .bp5-dialog-step-container.bp5-dialog-step-viewed{\n    background-color:#ffffff; }\n    .bp5-dark .bp5-dialog-step-container.bp5-dialog-step-viewed{\n      background:#30404d; }\n\n.bp5-dialog-step{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background-color:#f5f8fa;\n  border-radius:6px;\n  cursor:not-allowed;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  margin:4px;\n  padding:6px 14px; }\n  .bp5-dark .bp5-dialog-step{\n    background:#293742; }\n  .bp5-dialog-step-viewed .bp5-dialog-step{\n    background-color:#ffffff;\n    cursor:pointer; }\n    .bp5-dark .bp5-dialog-step-viewed .bp5-dialog-step{\n      background:#30404d; }\n  .bp5-dialog-step:hover{\n    background-color:#f5f8fa; }\n    .bp5-dark .bp5-dialog-step:hover{\n      background:#293742; }\n\n.bp5-dialog-step-icon{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background-color:rgba(92, 112, 128, 0.6);\n  border-radius:50%;\n  color:#ffffff;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:25px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  width:25px; }\n  .bp5-dark .bp5-dialog-step-icon{\n    background-color:rgba(167, 182, 194, 0.6); }\n  .bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-icon{\n    background-color:#2b95d6; }\n  .bp5-dialog-step-viewed .bp5-dialog-step-icon{\n    background-color:#8a9ba8; }\n\n.bp5-dialog-step-title{\n  color:rgba(92, 112, 128, 0.6);\n  -webkit-box-flex:1;\n      -ms-flex:1;\n          flex:1;\n  padding-left:10px; }\n  .bp5-dark .bp5-dialog-step-title{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-title{\n    color:#2b95d6; }\n  .bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n    color:#182026; }\n    .bp5-dark .bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n      color:#f5f8fa; }\n.bp5-drawer{\n  background:#ffffff;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin:0;\n  padding:0; }\n  .bp5-drawer:focus{\n    outline:0; }\n  .bp5-drawer.bp5-position-top{\n    height:50%;\n    left:0;\n    right:0;\n    top:0; }\n    .bp5-drawer.bp5-position-top.bp5-overlay-enter, .bp5-drawer.bp5-position-top.bp5-overlay-appear{\n      -webkit-transform:translateY(-100%);\n              transform:translateY(-100%); }\n    .bp5-drawer.bp5-position-top.bp5-overlay-enter-active, .bp5-drawer.bp5-position-top.bp5-overlay-appear-active{\n      -webkit-transform:translateY(0);\n              transform:translateY(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-top.bp5-overlay-exit{\n      -webkit-transform:translateY(0);\n              transform:translateY(0); }\n    .bp5-drawer.bp5-position-top.bp5-overlay-exit-active{\n      -webkit-transform:translateY(-100%);\n              transform:translateY(-100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-position-bottom{\n    bottom:0;\n    height:50%;\n    left:0;\n    right:0; }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-enter, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%); }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-enter-active, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear-active{\n      -webkit-transform:translateY(0);\n              transform:translateY(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-exit{\n      -webkit-transform:translateY(0);\n              transform:translateY(0); }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-exit-active{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-position-left{\n    bottom:0;\n    left:0;\n    top:0;\n    width:50%; }\n    .bp5-drawer.bp5-position-left.bp5-overlay-enter, .bp5-drawer.bp5-position-left.bp5-overlay-appear{\n      -webkit-transform:translateX(-100%);\n              transform:translateX(-100%); }\n    .bp5-drawer.bp5-position-left.bp5-overlay-enter-active, .bp5-drawer.bp5-position-left.bp5-overlay-appear-active{\n      -webkit-transform:translateX(0);\n              transform:translateX(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-left.bp5-overlay-exit{\n      -webkit-transform:translateX(0);\n              transform:translateX(0); }\n    .bp5-drawer.bp5-position-left.bp5-overlay-exit-active{\n      -webkit-transform:translateX(-100%);\n              transform:translateX(-100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-position-right{\n    bottom:0;\n    right:0;\n    top:0;\n    width:50%; }\n    .bp5-drawer.bp5-position-right.bp5-overlay-enter, .bp5-drawer.bp5-position-right.bp5-overlay-appear{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%); }\n    .bp5-drawer.bp5-position-right.bp5-overlay-enter-active, .bp5-drawer.bp5-position-right.bp5-overlay-appear-active{\n      -webkit-transform:translateX(0);\n              transform:translateX(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-right.bp5-overlay-exit{\n      -webkit-transform:translateX(0);\n              transform:translateX(0); }\n    .bp5-drawer.bp5-position-right.bp5-overlay-exit-active{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n  .bp5-position-right):not(.bp5-vertical){\n    bottom:0;\n    right:0;\n    top:0;\n    width:50%; }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-enter, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-appear{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-enter-active, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-appear-active{\n      -webkit-transform:translateX(0);\n              transform:translateX(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-exit{\n      -webkit-transform:translateX(0);\n              transform:translateX(0); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-exit-active{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n  .bp5-position-right).bp5-vertical{\n    bottom:0;\n    height:50%;\n    left:0;\n    right:0; }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-enter, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-appear{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-enter-active, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-appear-active{\n      -webkit-transform:translateY(0);\n              transform:translateY(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-exit{\n      -webkit-transform:translateY(0);\n              transform:translateY(0); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-exit-active{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-dark,\n  .bp5-dark .bp5-drawer{\n    background:#30404d;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n\n.bp5-drawer-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  border-radius:0;\n  -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  min-height:40px;\n  padding:5px;\n  padding-left:20px;\n  position:relative; }\n  .bp5-drawer-header .bp5-icon-large,\n  .bp5-drawer-header .bp5-icon{\n    color:#5c7080;\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    margin-right:10px; }\n  .bp5-drawer-header .bp5-heading{\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    line-height:inherit;\n    margin:0; }\n    .bp5-drawer-header .bp5-heading:last-child{\n      margin-right:20px; }\n  .bp5-dark .bp5-drawer-header{\n    -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.4);\n            box-shadow:0 1px 0 rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-drawer-header .bp5-icon-large,\n    .bp5-dark .bp5-drawer-header .bp5-icon{\n      color:#a7b6c2; }\n\n.bp5-drawer-body{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  line-height:18px;\n  overflow:auto; }\n\n.bp5-drawer-footer{\n  -webkit-box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.15);\n          box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.15);\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  padding:10px 20px;\n  position:relative; }\n  .bp5-dark .bp5-drawer-footer{\n    -webkit-box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.4); }\n.bp5-editable-text{\n  cursor:text;\n  display:inline-block;\n  max-width:100%;\n  position:relative;\n  vertical-align:top;\n  white-space:nowrap; }\n  .bp5-editable-text::before{\n    bottom:-3px;\n    left:-3px;\n    position:absolute;\n    right:-3px;\n    top:-3px;\n    border-radius:3px;\n    content:\"\";\n    -webkit-transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-editable-text:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15); }\n  .bp5-editable-text.bp5-editable-text-editing::before{\n    background-color:#ffffff;\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-disabled::before{\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-primary .bp5-editable-text-content{\n    color:#137cbd; }\n  .bp5-editable-text.bp5-intent-primary:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(19, 124, 189, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(19, 124, 189, 0.4); }\n  .bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-intent-success .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-success .bp5-editable-text-content{\n    color:#0f9960; }\n  .bp5-editable-text.bp5-intent-success:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px rgba(15, 153, 96, 0.4);\n            box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px rgba(15, 153, 96, 0.4); }\n  .bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-warning .bp5-editable-text-content{\n    color:#d9822b; }\n  .bp5-editable-text.bp5-intent-warning:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px rgba(217, 130, 43, 0.4);\n            box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px rgba(217, 130, 43, 0.4); }\n  .bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-danger .bp5-editable-text-content{\n    color:#db3737; }\n  .bp5-editable-text.bp5-intent-danger:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px rgba(219, 55, 55, 0.4);\n            box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px rgba(219, 55, 55, 0.4); }\n  .bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-editable-text:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.15);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.15); }\n  .bp5-dark .bp5-editable-text.bp5-editable-text-editing::before{\n    background-color:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-disabled::before{\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-content{\n    color:#48aff0; }\n  .bp5-dark .bp5-editable-text.bp5-intent-primary:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(72, 175, 240, 0), 0 0 0 0 rgba(72, 175, 240, 0), inset 0 0 0 1px rgba(72, 175, 240, 0.4);\n            box-shadow:0 0 0 0 rgba(72, 175, 240, 0), 0 0 0 0 rgba(72, 175, 240, 0), inset 0 0 0 1px rgba(72, 175, 240, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #48aff0, 0 0 0 3px rgba(72, 175, 240, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #48aff0, 0 0 0 3px rgba(72, 175, 240, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-content{\n    color:#3dcc91; }\n  .bp5-dark .bp5-editable-text.bp5-intent-success:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(61, 204, 145, 0), 0 0 0 0 rgba(61, 204, 145, 0), inset 0 0 0 1px rgba(61, 204, 145, 0.4);\n            box-shadow:0 0 0 0 rgba(61, 204, 145, 0), 0 0 0 0 rgba(61, 204, 145, 0), inset 0 0 0 1px rgba(61, 204, 145, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #3dcc91, 0 0 0 3px rgba(61, 204, 145, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #3dcc91, 0 0 0 3px rgba(61, 204, 145, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-content{\n    color:#ffb366; }\n  .bp5-dark .bp5-editable-text.bp5-intent-warning:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(255, 179, 102, 0), 0 0 0 0 rgba(255, 179, 102, 0), inset 0 0 0 1px rgba(255, 179, 102, 0.4);\n            box-shadow:0 0 0 0 rgba(255, 179, 102, 0), 0 0 0 0 rgba(255, 179, 102, 0), inset 0 0 0 1px rgba(255, 179, 102, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #ffb366, 0 0 0 3px rgba(255, 179, 102, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #ffb366, 0 0 0 3px rgba(255, 179, 102, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-content{\n    color:#ff7373; }\n  .bp5-dark .bp5-editable-text.bp5-intent-danger:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(255, 115, 115, 0), 0 0 0 0 rgba(255, 115, 115, 0), inset 0 0 0 1px rgba(255, 115, 115, 0.4);\n            box-shadow:0 0 0 0 rgba(255, 115, 115, 0), 0 0 0 0 rgba(255, 115, 115, 0), inset 0 0 0 1px rgba(255, 115, 115, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #ff7373, 0 0 0 3px rgba(255, 115, 115, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #ff7373, 0 0 0 3px rgba(255, 115, 115, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-editable-text-input,\n.bp5-editable-text-content{\n  color:inherit;\n  display:inherit;\n  font:inherit;\n  letter-spacing:inherit;\n  max-width:inherit;\n  min-width:inherit;\n  position:relative;\n  resize:none;\n  text-transform:inherit;\n  vertical-align:top; }\n\n.bp5-editable-text-input{\n  background:none;\n  border:none;\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  padding:0;\n  white-space:pre-wrap;\n  width:100%; }\n  .bp5-editable-text-input::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input:focus{\n    outline:none; }\n  .bp5-editable-text-input::-ms-clear{\n    display:none; }\n\n.bp5-editable-text-content{\n  overflow:hidden;\n  padding-right:2px;\n  text-overflow:ellipsis;\n  white-space:pre; }\n  .bp5-editable-text-editing > .bp5-editable-text-content{\n    left:0;\n    position:absolute;\n    visibility:hidden; }\n  .bp5-editable-text-placeholder > .bp5-editable-text-content{\n    color:rgba(92, 112, 128, 0.6); }\n    .bp5-dark .bp5-editable-text-placeholder > .bp5-editable-text-content{\n      color:rgba(167, 182, 194, 0.6); }\n\n.bp5-editable-text.bp5-multiline{\n  display:block; }\n  .bp5-editable-text.bp5-multiline .bp5-editable-text-content{\n    overflow:auto;\n    white-space:pre-wrap;\n    word-wrap:break-word; }\n.bp5-divider{\n  border-bottom:1px solid rgba(16, 22, 26, 0.15);\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  margin:5px; }\n  .bp5-dark .bp5-divider{\n    border-color:rgba(16, 22, 26, 0.4); }\n.bp5-control-group{\n  -webkit-transform:translateZ(0);\n          transform:translateZ(0);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:stretch;\n      -ms-flex-align:stretch;\n          align-items:stretch; }\n  .bp5-control-group > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-control-group > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-control-group .bp5-button,\n  .bp5-control-group .bp5-html-select,\n  .bp5-control-group .bp5-input,\n  .bp5-control-group .bp5-select{\n    position:relative; }\n  .bp5-control-group .bp5-input{\n    border-radius:inherit;\n    z-index:2; }\n    .bp5-control-group .bp5-input:focus{\n      border-radius:3px;\n      z-index:14; }\n    .bp5-control-group .bp5-input[class*=\"bp5-intent\"]{\n      z-index:13; }\n      .bp5-control-group .bp5-input[class*=\"bp5-intent\"]:focus{\n        z-index:15; }\n    .bp5-control-group .bp5-input[readonly], .bp5-control-group .bp5-input:disabled, .bp5-control-group .bp5-input.bp5-disabled{\n      z-index:1; }\n  .bp5-control-group .bp5-input-group[class*=\"bp5-intent\"] .bp5-input{\n    z-index:13; }\n    .bp5-control-group .bp5-input-group[class*=\"bp5-intent\"] .bp5-input:focus{\n      z-index:15; }\n  .bp5-control-group .bp5-button,\n  .bp5-control-group .bp5-html-select select,\n  .bp5-control-group .bp5-select select{\n    -webkit-transform:translateZ(0);\n            transform:translateZ(0);\n    border-radius:inherit;\n    z-index:4; }\n    .bp5-control-group .bp5-button:focus,\n    .bp5-control-group .bp5-html-select select:focus,\n    .bp5-control-group .bp5-select select:focus{\n      z-index:5; }\n    .bp5-control-group .bp5-button:hover,\n    .bp5-control-group .bp5-html-select select:hover,\n    .bp5-control-group .bp5-select select:hover{\n      z-index:6; }\n    .bp5-control-group .bp5-button:active,\n    .bp5-control-group .bp5-html-select select:active,\n    .bp5-control-group .bp5-select select:active{\n      z-index:7; }\n    .bp5-control-group .bp5-button[readonly], .bp5-control-group .bp5-button:disabled, .bp5-control-group .bp5-button.bp5-disabled,\n    .bp5-control-group .bp5-html-select select[readonly],\n    .bp5-control-group .bp5-html-select select:disabled,\n    .bp5-control-group .bp5-html-select select.bp5-disabled,\n    .bp5-control-group .bp5-select select[readonly],\n    .bp5-control-group .bp5-select select:disabled,\n    .bp5-control-group .bp5-select select.bp5-disabled{\n      z-index:3; }\n    .bp5-control-group .bp5-button[class*=\"bp5-intent\"],\n    .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"],\n    .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]{\n      z-index:9; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:focus,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:focus,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:focus{\n        z-index:10; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:hover,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:hover,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:hover{\n        z-index:11; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:active,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:active,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:active{\n        z-index:12; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"][readonly], .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:disabled, .bp5-control-group .bp5-button[class*=\"bp5-intent\"].bp5-disabled,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"][readonly],\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:disabled,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"].bp5-disabled,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"][readonly],\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:disabled,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"].bp5-disabled{\n        z-index:8; }\n  .bp5-control-group .bp5-input-group > .bp5-icon,\n  .bp5-control-group .bp5-input-group > .bp5-button,\n  .bp5-control-group .bp5-input-group > .bp5-input-left-container,\n  .bp5-control-group .bp5-input-group > .bp5-input-action{\n    z-index:16; }\n  .bp5-control-group .bp5-select::after,\n  .bp5-control-group .bp5-html-select::after,\n  .bp5-control-group .bp5-select > .bp5-icon,\n  .bp5-control-group .bp5-html-select > .bp5-icon{\n    z-index:17; }\n  .bp5-control-group .bp5-select:focus-within{\n    z-index:5; }\n  .bp5-control-group:not(.bp5-vertical) > *:not(.bp5-divider){\n    margin-right:-1px; }\n  .bp5-control-group:not(.bp5-vertical) > .bp5-divider:not(:first-child){\n    margin-left:6px; }\n  .bp5-dark .bp5-control-group:not(.bp5-vertical) > *:not(.bp5-divider){\n    margin-right:0; }\n  .bp5-dark .bp5-control-group:not(.bp5-vertical) > .bp5-button + .bp5-button{\n    margin-left:1px; }\n  .bp5-control-group .bp5-popover-wrapper,\n  .bp5-control-group .bp5-popover-target{\n    border-radius:inherit; }\n  .bp5-control-group > :first-child{\n    border-radius:3px 0 0 3px; }\n  .bp5-control-group > :last-child{\n    border-radius:0 3px 3px 0;\n    margin-right:0; }\n  .bp5-control-group > :only-child{\n    border-radius:3px;\n    margin-right:0; }\n  .bp5-control-group .bp5-input-group .bp5-button{\n    border-radius:3px; }\n  .bp5-control-group .bp5-numeric-input:not(:first-child) .bp5-input-group{\n    border-bottom-left-radius:0;\n    border-top-left-radius:0; }\n  .bp5-control-group.bp5-fill{\n    width:100%; }\n  .bp5-control-group > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-control-group.bp5-fill > *:not(.bp5-fixed){\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-control-group.bp5-vertical{\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:column;\n            flex-direction:column; }\n    .bp5-control-group.bp5-vertical > *{\n      margin-top:-1px; }\n    .bp5-control-group.bp5-vertical > :first-child{\n      border-radius:3px 3px 0 0;\n      margin-top:0; }\n    .bp5-control-group.bp5-vertical > :last-child{\n      border-radius:0 0 3px 3px; }\n.bp5-control{\n  cursor:pointer;\n  display:block;\n  margin-bottom:10px;\n  position:relative;\n  text-transform:none; }\n  .bp5-control input:checked ~ .bp5-control-indicator{\n    background-color:#137cbd;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n  .bp5-control:hover input:checked ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n  .bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n    background:#0e5a8a;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n    background:rgba(19, 124, 189, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-dark .bp5-control input:checked ~ .bp5-control-indicator{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control:hover input:checked ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n    background-color:#0e5a8a;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n    background:rgba(14, 90, 138, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-control:not(.bp5-align-right){\n    padding-left:26px; }\n    .bp5-control:not(.bp5-align-right) .bp5-control-indicator{\n      margin-left:-26px; }\n  .bp5-control.bp5-align-right{\n    padding-right:26px; }\n    .bp5-control.bp5-align-right .bp5-control-indicator{\n      margin-right:-26px; }\n  .bp5-control.bp5-disabled{\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n  .bp5-control.bp5-inline{\n    display:inline-block;\n    margin-right:20px; }\n  .bp5-control input{\n    left:0;\n    opacity:0;\n    position:absolute;\n    top:0;\n    z-index:-1; }\n  .bp5-control .bp5-control-indicator{\n    background-clip:padding-box;\n    background-color:#f5f8fa;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n    border:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    cursor:pointer;\n    display:inline-block;\n    font-size:16px;\n    height:1em;\n    margin-right:10px;\n    margin-top:-3px;\n    position:relative;\n    -webkit-user-select:none;\n       -moz-user-select:none;\n        -ms-user-select:none;\n            user-select:none;\n    vertical-align:middle;\n    width:1em; }\n    .bp5-control .bp5-control-indicator::before{\n      content:\"\";\n      display:block;\n      height:1em;\n      width:1em; }\n  .bp5-control:hover .bp5-control-indicator{\n    background-color:#ebf1f5; }\n  .bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n    background:#d8e1e8;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-control input:disabled ~ .bp5-control-indicator{\n    background:rgba(206, 217, 224, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    cursor:not-allowed; }\n  .bp5-control input:focus ~ .bp5-control-indicator{\n    outline:rgba(19, 124, 189, 0.6) auto 2px;\n    outline-offset:2px;\n    -moz-outline-radius:6px; }\n  .bp5-control.bp5-align-right .bp5-control-indicator{\n    float:right;\n    margin-left:10px;\n    margin-top:1px; }\n  .bp5-control.bp5-large{\n    font-size:16px; }\n    .bp5-control.bp5-large:not(.bp5-align-right){\n      padding-left:30px; }\n      .bp5-control.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n        margin-left:-30px; }\n    .bp5-control.bp5-large.bp5-align-right{\n      padding-right:30px; }\n      .bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n        margin-right:-30px; }\n    .bp5-control.bp5-large .bp5-control-indicator{\n      font-size:20px; }\n    .bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n      margin-top:0; }\n  .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    background-color:#137cbd;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n  .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n  .bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n    background:#0e5a8a;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n    background:rgba(19, 124, 189, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-dark .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n    background-color:#0e5a8a;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n    background:rgba(14, 90, 138, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-control.bp5-checkbox .bp5-control-indicator{\n    border-radius:3px; }\n  .bp5-control.bp5-checkbox input:checked ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 5c-.28 0-.53.11-.71.29L7 9.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29s.53-.11.71-.29l5-5A1.003 1.003 0 0012 5z' fill='white'/%3e%3c/svg%3e\"); }\n  .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 7H5c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1z' fill='white'/%3e%3c/svg%3e\"); }\n  .bp5-control.bp5-radio .bp5-control-indicator{\n    border-radius:50%; }\n  .bp5-control.bp5-radio input:checked ~ .bp5-control-indicator::before{\n    background-image:radial-gradient(#ffffff, #ffffff 28%, transparent 32%); }\n  .bp5-control.bp5-radio input:checked:disabled ~ .bp5-control-indicator::before{\n    opacity:0.5; }\n  .bp5-control.bp5-radio input:focus ~ .bp5-control-indicator{\n    -moz-outline-radius:16px; }\n  .bp5-control.bp5-switch input ~ .bp5-control-indicator{\n    background:rgba(167, 182, 194, 0.5); }\n  .bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n    background:rgba(115, 134, 148, 0.5); }\n  .bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n    background:rgba(92, 112, 128, 0.5); }\n  .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n    background:rgba(206, 217, 224, 0.5); }\n    .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n      background:rgba(255, 255, 255, 0.8); }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:#137cbd; }\n  .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:#106ba3; }\n  .bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n    background:#0e5a8a; }\n  .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background:rgba(19, 124, 189, 0.5); }\n    .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n      background:rgba(255, 255, 255, 0.8); }\n  .bp5-control.bp5-switch:not(.bp5-align-right){\n    padding-left:38px; }\n    .bp5-control.bp5-switch:not(.bp5-align-right) .bp5-control-indicator{\n      margin-left:-38px; }\n  .bp5-control.bp5-switch.bp5-align-right{\n    padding-right:38px; }\n    .bp5-control.bp5-switch.bp5-align-right .bp5-control-indicator{\n      margin-right:-38px; }\n  .bp5-control.bp5-switch .bp5-control-indicator{\n    border:none;\n    border-radius:1.75em;\n    -webkit-box-shadow:none !important;\n            box-shadow:none !important;\n    min-width:1.75em;\n    -webkit-transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    width:auto; }\n    .bp5-control.bp5-switch .bp5-control-indicator::before{\n      background:#ffffff;\n      border-radius:50%;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n      height:calc(1em - 4px);\n      left:0;\n      margin:2px;\n      position:absolute;\n      -webkit-transition:left 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n      transition:left 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n      width:calc(1em - 4px); }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n    left:calc(100% - 1em); }\n  .bp5-control.bp5-switch.bp5-large:not(.bp5-align-right){\n    padding-left:45px; }\n    .bp5-control.bp5-switch.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n      margin-left:-45px; }\n  .bp5-control.bp5-switch.bp5-large.bp5-align-right{\n    padding-right:45px; }\n    .bp5-control.bp5-switch.bp5-large.bp5-align-right .bp5-control-indicator{\n      margin-right:-45px; }\n  .bp5-dark .bp5-control.bp5-switch input ~ .bp5-control-indicator{\n    background:rgba(16, 22, 26, 0.5); }\n  .bp5-dark .bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n    background:rgba(16, 22, 26, 0.7); }\n  .bp5-dark .bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n    background:rgba(16, 22, 26, 0.9); }\n  .bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n    background:rgba(57, 75, 89, 0.5); }\n    .bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n      background:rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:#137cbd; }\n  .bp5-dark .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:#106ba3; }\n  .bp5-dark .bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n    background:#0e5a8a; }\n  .bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background:rgba(14, 90, 138, 0.5); }\n    .bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n      background:rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-switch .bp5-control-indicator::before{\n    background:#394b59;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-control.bp5-switch .bp5-switch-inner-text{\n    font-size:0.7em;\n    text-align:center; }\n  .bp5-control.bp5-switch .bp5-control-indicator-child:first-child{\n    line-height:0;\n    margin-left:0.5em;\n    margin-right:1.2em;\n    visibility:hidden; }\n  .bp5-control.bp5-switch .bp5-control-indicator-child:last-child{\n    line-height:1em;\n    margin-left:1.2em;\n    margin-right:0.5em;\n    visibility:visible; }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:first-child{\n    line-height:1em;\n    visibility:visible; }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:last-child{\n    line-height:0;\n    visibility:hidden; }\n  .bp5-dark .bp5-control{\n    color:#f5f8fa; }\n    .bp5-dark .bp5-control.bp5-disabled{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-control .bp5-control-indicator{\n      background-color:#394b59;\n      background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n      background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-control:hover .bp5-control-indicator{\n      background-color:#30404d; }\n    .bp5-dark .bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n      background:#202b33;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-control input:disabled ~ .bp5-control-indicator{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      cursor:not-allowed; }\n    .bp5-dark .bp5-control.bp5-checkbox input:disabled:checked ~ .bp5-control-indicator, .bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n      color:rgba(167, 182, 194, 0.6); }\n.bp5-file-input{\n  cursor:pointer;\n  display:inline-block;\n  height:30px;\n  position:relative; }\n  .bp5-file-input input{\n    margin:0;\n    min-width:200px;\n    opacity:0; }\n    .bp5-file-input input:disabled + .bp5-file-upload-input,\n    .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n      background:rgba(206, 217, 224, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed;\n      resize:none; }\n      .bp5-file-input input:disabled + .bp5-file-upload-input::after,\n      .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n        background-color:rgba(206, 217, 224, 0.5);\n        background-image:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:rgba(92, 112, 128, 0.6);\n        cursor:not-allowed;\n        outline:none; }\n        .bp5-file-input input:disabled + .bp5-file-upload-input::after.bp5-active, .bp5-file-input input:disabled + .bp5-file-upload-input::after.bp5-active:hover,\n        .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after.bp5-active,\n        .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after.bp5-active:hover{\n          background:rgba(206, 217, 224, 0.7); }\n      .bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input, .bp5-dark\n      .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n        background:rgba(57, 75, 89, 0.5);\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:rgba(167, 182, 194, 0.6); }\n        .bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input::after, .bp5-dark\n        .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n          background-color:rgba(57, 75, 89, 0.5);\n          background-image:none;\n          -webkit-box-shadow:none;\n                  box-shadow:none;\n          color:rgba(167, 182, 194, 0.6); }\n          .bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input::after.bp5-active, .bp5-dark\n          .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after.bp5-active{\n            background:rgba(57, 75, 89, 0.7); }\n  .bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n    color:#182026; }\n  .bp5-dark .bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n    color:#f5f8fa; }\n  .bp5-file-input.bp5-fill{\n    width:100%; }\n  .bp5-file-input.bp5-large,\n  .bp5-large .bp5-file-input{\n    height:40px; }\n  .bp5-file-input .bp5-file-upload-input-custom-text::after{\n    content:attr(bp5-button-text); }\n\n.bp5-file-upload-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  color:#182026;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  -webkit-transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  color:rgba(92, 112, 128, 0.6);\n  left:0;\n  padding-right:80px;\n  position:absolute;\n  right:0;\n  top:0;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-file-upload-input::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input:focus, .bp5-file-upload-input.bp5-active{\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-file-upload-input[type=\"search\"], .bp5-file-upload-input.bp5-round{\n    border-radius:30px;\n    -webkit-box-sizing:border-box;\n            box-sizing:border-box;\n    padding-left:10px; }\n  .bp5-file-upload-input[readonly]{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15); }\n  .bp5-file-upload-input:disabled, .bp5-file-upload-input.bp5-disabled{\n    background:rgba(206, 217, 224, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    resize:none; }\n  .bp5-file-upload-input::after{\n    background-color:#f5f8fa;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    color:#182026;\n    min-height:24px;\n    min-width:24px;\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    border-radius:3px;\n    content:\"Browse\";\n    line-height:24px;\n    margin:3px;\n    position:absolute;\n    right:0;\n    text-align:center;\n    top:0;\n    width:70px; }\n    .bp5-file-upload-input::after:hover{\n      background-clip:padding-box;\n      background-color:#ebf1f5;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n    .bp5-file-upload-input::after:active, .bp5-file-upload-input::after.bp5-active{\n      background-color:#d8e1e8;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-file-upload-input::after:disabled, .bp5-file-upload-input::after.bp5-disabled{\n      background-color:rgba(206, 217, 224, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed;\n      outline:none; }\n      .bp5-file-upload-input::after:disabled.bp5-active, .bp5-file-upload-input::after:disabled.bp5-active:hover, .bp5-file-upload-input::after.bp5-disabled.bp5-active, .bp5-file-upload-input::after.bp5-disabled.bp5-active:hover{\n        background:rgba(206, 217, 224, 0.7); }\n  .bp5-file-upload-input:hover::after{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n  .bp5-file-upload-input:active::after{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-large .bp5-file-upload-input{\n    font-size:16px;\n    height:40px;\n    line-height:40px;\n    padding-right:95px; }\n    .bp5-large .bp5-file-upload-input[type=\"search\"], .bp5-large .bp5-file-upload-input.bp5-round{\n      padding:0 15px; }\n    .bp5-large .bp5-file-upload-input::after{\n      min-height:30px;\n      min-width:30px;\n      line-height:30px;\n      margin:5px;\n      width:85px; }\n  .bp5-dark .bp5-file-upload-input{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa;\n    color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-file-upload-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-file-upload-input:disabled, .bp5-dark .bp5-file-upload-input.bp5-disabled{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::after{\n      background-color:#394b59;\n      background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n      background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n      color:#f5f8fa; }\n      .bp5-dark .bp5-file-upload-input::after:hover, .bp5-dark .bp5-file-upload-input::after:active, .bp5-dark .bp5-file-upload-input::after.bp5-active{\n        color:#f5f8fa; }\n      .bp5-dark .bp5-file-upload-input::after:hover{\n        background-color:#30404d;\n        -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-file-upload-input::after:active, .bp5-dark .bp5-file-upload-input::after.bp5-active{\n        background-color:#202b33;\n        background-image:none;\n        -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n                box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n      .bp5-dark .bp5-file-upload-input::after:disabled, .bp5-dark .bp5-file-upload-input::after.bp5-disabled{\n        background-color:rgba(57, 75, 89, 0.5);\n        background-image:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:rgba(167, 182, 194, 0.6); }\n        .bp5-dark .bp5-file-upload-input::after:disabled.bp5-active, .bp5-dark .bp5-file-upload-input::after.bp5-disabled.bp5-active{\n          background:rgba(57, 75, 89, 0.7); }\n      .bp5-dark .bp5-file-upload-input::after .bp5-button-spinner .bp5-spinner-head{\n        background:rgba(16, 22, 26, 0.5);\n        stroke:#8a9ba8; }\n    .bp5-dark .bp5-file-upload-input:hover::after{\n      background-color:#30404d;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-file-upload-input:active::after{\n      background-color:#202b33;\n      background-image:none;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n.bp5-file-upload-input::after{\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n.bp5-form-group{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin:0 0 15px; }\n  .bp5-form-group label.bp5-label{\n    margin-bottom:5px; }\n  .bp5-form-group .bp5-control{\n    margin-top:7px; }\n  .bp5-form-group .bp5-form-group-sub-label,\n  .bp5-form-group .bp5-form-helper-text{\n    color:#5c7080;\n    font-size:12px; }\n  .bp5-form-group .bp5-form-group-sub-label{\n    margin-bottom:5px; }\n  .bp5-form-group .bp5-form-helper-text{\n    margin-top:5px; }\n  .bp5-form-group.bp5-intent-primary .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n    color:#106ba3; }\n  .bp5-form-group.bp5-intent-success .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n    color:#0d8050; }\n  .bp5-form-group.bp5-intent-warning .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n    color:#bf7326; }\n  .bp5-form-group.bp5-intent-danger .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n    color:#c23030; }\n  .bp5-form-group.bp5-inline{\n    -webkit-box-align:start;\n        -ms-flex-align:start;\n            align-items:flex-start;\n    -webkit-box-orient:horizontal;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:row;\n            flex-direction:row; }\n    .bp5-form-group.bp5-inline.bp5-large label.bp5-label{\n      line-height:40px;\n      margin:0 10px 0 0; }\n    .bp5-form-group.bp5-inline label.bp5-label{\n      line-height:30px;\n      margin:0 10px 0 0; }\n  .bp5-form-group.bp5-disabled .bp5-label,\n  .bp5-form-group.bp5-disabled .bp5-text-muted,\n  .bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-disabled .bp5-form-helper-text{\n    color:rgba(92, 112, 128, 0.6) !important; }\n  .bp5-dark .bp5-form-group.bp5-intent-primary .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n    color:#48aff0; }\n  .bp5-dark .bp5-form-group.bp5-intent-success .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n    color:#3dcc91; }\n  .bp5-dark .bp5-form-group.bp5-intent-warning .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n    color:#ffb366; }\n  .bp5-dark .bp5-form-group.bp5-intent-danger .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n    color:#ff7373; }\n  .bp5-dark .bp5-form-group .bp5-form-helper-text{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-label,\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-text-muted,\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-form-helper-text{\n    color:rgba(167, 182, 194, 0.6) !important; }\n.bp5-input-group{\n  display:block;\n  position:relative; }\n  .bp5-input-group .bp5-input{\n    position:relative;\n    width:100%; }\n    .bp5-input-group .bp5-input:not(:first-child){\n      padding-left:30px; }\n    .bp5-input-group .bp5-input:not(:last-child){\n      padding-right:30px; }\n  .bp5-input-group .bp5-input-action,\n  .bp5-input-group > .bp5-input-left-container,\n  .bp5-input-group > .bp5-button,\n  .bp5-input-group > .bp5-icon{\n    position:absolute;\n    top:0; }\n    .bp5-input-group .bp5-input-action:first-child,\n    .bp5-input-group > .bp5-input-left-container:first-child,\n    .bp5-input-group > .bp5-button:first-child,\n    .bp5-input-group > .bp5-icon:first-child{\n      left:0; }\n    .bp5-input-group .bp5-input-action:last-child,\n    .bp5-input-group > .bp5-input-left-container:last-child,\n    .bp5-input-group > .bp5-button:last-child,\n    .bp5-input-group > .bp5-icon:last-child{\n      right:0; }\n  .bp5-input-group .bp5-button{\n    min-height:24px;\n    min-width:24px;\n    margin:3px;\n    padding:0 7px; }\n    .bp5-input-group .bp5-button:empty{\n      padding:0; }\n  .bp5-input-group > .bp5-input-left-container,\n  .bp5-input-group > .bp5-icon{\n    z-index:1; }\n  .bp5-input-group > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group > .bp5-icon{\n    color:#5c7080; }\n    .bp5-input-group > .bp5-input-left-container > .bp5-icon:empty,\n    .bp5-input-group > .bp5-icon:empty{\n      font-family:\"blueprint-icons-16\", sans-serif;\n      font-size:16px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1;\n      -moz-osx-font-smoothing:grayscale;\n      -webkit-font-smoothing:antialiased; }\n  .bp5-input-group > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group > .bp5-icon,\n  .bp5-input-group .bp5-input-action > .bp5-spinner{\n    margin:7px; }\n  .bp5-input-group .bp5-tag{\n    margin:5px; }\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus),\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n    color:#5c7080; }\n    .bp5-dark .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus), .bp5-dark\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n      color:#a7b6c2; }\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large{\n      color:#5c7080; }\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled,\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled{\n    color:rgba(92, 112, 128, 0.6) !important; }\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-large,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-standard,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-large{\n      color:rgba(92, 112, 128, 0.6) !important; }\n  .bp5-input-group.bp5-disabled{\n    cursor:not-allowed; }\n    .bp5-input-group.bp5-disabled .bp5-icon{\n      color:rgba(92, 112, 128, 0.6); }\n  .bp5-input-group.bp5-large .bp5-button{\n    min-height:30px;\n    min-width:30px;\n    margin:5px; }\n  .bp5-input-group.bp5-large > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group.bp5-large > .bp5-icon,\n  .bp5-input-group.bp5-large .bp5-input-action > .bp5-spinner{\n    margin:12px; }\n  .bp5-input-group.bp5-large .bp5-input{\n    font-size:16px;\n    height:40px;\n    line-height:40px; }\n    .bp5-input-group.bp5-large .bp5-input[type=\"search\"], .bp5-input-group.bp5-large .bp5-input.bp5-round{\n      padding:0 15px; }\n    .bp5-input-group.bp5-large .bp5-input:not(:first-child){\n      padding-left:40px; }\n    .bp5-input-group.bp5-large .bp5-input:not(:last-child){\n      padding-right:40px; }\n  .bp5-input-group.bp5-small .bp5-button{\n    min-height:20px;\n    min-width:20px;\n    margin:2px; }\n  .bp5-input-group.bp5-small .bp5-tag{\n    min-height:20px;\n    min-width:20px;\n    margin:2px; }\n  .bp5-input-group.bp5-small > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group.bp5-small > .bp5-icon,\n  .bp5-input-group.bp5-small .bp5-input-action > .bp5-spinner{\n    margin:4px; }\n  .bp5-input-group.bp5-small .bp5-input{\n    font-size:12px;\n    height:24px;\n    line-height:24px;\n    padding-left:8px;\n    padding-right:8px; }\n    .bp5-input-group.bp5-small .bp5-input[type=\"search\"], .bp5-input-group.bp5-small .bp5-input.bp5-round{\n      padding:0 12px; }\n    .bp5-input-group.bp5-small .bp5-input:not(:first-child){\n      padding-left:24px; }\n    .bp5-input-group.bp5-small .bp5-input:not(:last-child){\n      padding-right:24px; }\n  .bp5-input-group.bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    width:100%; }\n  .bp5-input-group.bp5-round .bp5-button,\n  .bp5-input-group.bp5-round .bp5-input,\n  .bp5-input-group.bp5-round .bp5-tag{\n    border-radius:30px; }\n  .bp5-dark .bp5-input-group .bp5-icon{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-input-group.bp5-disabled .bp5-icon{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-input-group.bp5-intent-primary .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-primary .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-primary .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n              box-shadow:inset 0 0 0 1px #137cbd; }\n    .bp5-input-group.bp5-intent-primary .bp5-input:disabled, .bp5-input-group.bp5-intent-primary .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-primary > .bp5-icon{\n    color:#106ba3; }\n    .bp5-dark .bp5-input-group.bp5-intent-primary > .bp5-icon{\n      color:#48aff0; }\n  .bp5-input-group.bp5-intent-success .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-success .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-success .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n              box-shadow:inset 0 0 0 1px #0f9960; }\n    .bp5-input-group.bp5-intent-success .bp5-input:disabled, .bp5-input-group.bp5-intent-success .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-success > .bp5-icon{\n    color:#0d8050; }\n    .bp5-dark .bp5-input-group.bp5-intent-success > .bp5-icon{\n      color:#3dcc91; }\n  .bp5-input-group.bp5-intent-warning .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-warning .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-warning .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n              box-shadow:inset 0 0 0 1px #d9822b; }\n    .bp5-input-group.bp5-intent-warning .bp5-input:disabled, .bp5-input-group.bp5-intent-warning .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-warning > .bp5-icon{\n    color:#bf7326; }\n    .bp5-dark .bp5-input-group.bp5-intent-warning > .bp5-icon{\n      color:#ffb366; }\n  .bp5-input-group.bp5-intent-danger .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-danger .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-danger .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #db3737;\n              box-shadow:inset 0 0 0 1px #db3737; }\n    .bp5-input-group.bp5-intent-danger .bp5-input:disabled, .bp5-input-group.bp5-intent-danger .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-danger > .bp5-icon{\n    color:#c23030; }\n    .bp5-dark .bp5-input-group.bp5-intent-danger > .bp5-icon{\n      color:#ff7373; }\n.bp5-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  color:#182026;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  -webkit-transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle; }\n  .bp5-input::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input:focus, .bp5-input.bp5-active{\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-input[type=\"search\"], .bp5-input.bp5-round{\n    border-radius:30px;\n    -webkit-box-sizing:border-box;\n            box-sizing:border-box;\n    padding-left:10px; }\n  .bp5-input[readonly]{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15); }\n  .bp5-input:disabled, .bp5-input.bp5-disabled{\n    background:rgba(206, 217, 224, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    resize:none; }\n  .bp5-input.bp5-large{\n    font-size:16px;\n    height:40px;\n    line-height:40px; }\n    .bp5-input.bp5-large[type=\"search\"], .bp5-input.bp5-large.bp5-round{\n      padding:0 15px; }\n  .bp5-input.bp5-small{\n    font-size:12px;\n    height:24px;\n    line-height:24px;\n    padding-left:8px;\n    padding-right:8px; }\n    .bp5-input.bp5-small[type=\"search\"], .bp5-input.bp5-small.bp5-round{\n      padding:0 12px; }\n  .bp5-input.bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    width:100%; }\n  .bp5-dark .bp5-input{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark .bp5-input::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-input:disabled, .bp5-dark .bp5-input.bp5-disabled{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n  .bp5-input.bp5-intent-primary{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-primary:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-primary[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n              box-shadow:inset 0 0 0 1px #137cbd; }\n    .bp5-input.bp5-intent-primary:disabled, .bp5-input.bp5-intent-primary.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-primary{\n      -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-primary:focus{\n        -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-primary[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n                box-shadow:inset 0 0 0 1px #137cbd; }\n      .bp5-dark .bp5-input.bp5-intent-primary:disabled, .bp5-dark .bp5-input.bp5-intent-primary.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input.bp5-intent-success{\n    -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-success:focus{\n      -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-success[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n              box-shadow:inset 0 0 0 1px #0f9960; }\n    .bp5-input.bp5-intent-success:disabled, .bp5-input.bp5-intent-success.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-success{\n      -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-success:focus{\n        -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-success[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n                box-shadow:inset 0 0 0 1px #0f9960; }\n      .bp5-dark .bp5-input.bp5-intent-success:disabled, .bp5-dark .bp5-input.bp5-intent-success.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input.bp5-intent-warning{\n    -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-warning:focus{\n      -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-warning[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n              box-shadow:inset 0 0 0 1px #d9822b; }\n    .bp5-input.bp5-intent-warning:disabled, .bp5-input.bp5-intent-warning.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-warning{\n      -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-warning:focus{\n        -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-warning[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n                box-shadow:inset 0 0 0 1px #d9822b; }\n      .bp5-dark .bp5-input.bp5-intent-warning:disabled, .bp5-dark .bp5-input.bp5-intent-warning.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input.bp5-intent-danger{\n    -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-danger:focus{\n      -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-danger[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #db3737;\n              box-shadow:inset 0 0 0 1px #db3737; }\n    .bp5-input.bp5-intent-danger:disabled, .bp5-input.bp5-intent-danger.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-danger{\n      -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-danger:focus{\n        -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-danger[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #db3737;\n                box-shadow:inset 0 0 0 1px #db3737; }\n      .bp5-dark .bp5-input.bp5-intent-danger:disabled, .bp5-dark .bp5-input.bp5-intent-danger.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input::-ms-clear{\n    display:none; }\ntextarea.bp5-input{\n  max-width:100%;\n  padding:10px; }\n  textarea.bp5-input, textarea.bp5-input.bp5-large, textarea.bp5-input.bp5-small{\n    height:auto;\n    line-height:inherit; }\n  textarea.bp5-input.bp5-small{\n    padding:8px; }\n  .bp5-dark textarea.bp5-input{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark textarea.bp5-input::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark textarea.bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark textarea.bp5-input:disabled, .bp5-dark textarea.bp5-input.bp5-disabled{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\nlabel.bp5-label{\n  display:block;\n  margin-bottom:15px;\n  margin-top:0; }\n  label.bp5-label .bp5-html-select,\n  label.bp5-label .bp5-input,\n  label.bp5-label .bp5-select,\n  label.bp5-label .bp5-slider,\n  label.bp5-label .bp5-popover-wrapper{\n    display:block;\n    margin-top:5px;\n    text-transform:none; }\n  label.bp5-label .bp5-button-group{\n    margin-top:5px; }\n  label.bp5-label .bp5-select select,\n  label.bp5-label .bp5-html-select select{\n    font-weight:400;\n    vertical-align:top;\n    width:100%; }\n  label.bp5-label.bp5-disabled,\n  label.bp5-label.bp5-disabled .bp5-text-muted{\n    color:rgba(92, 112, 128, 0.6); }\n  label.bp5-label.bp5-inline{\n    line-height:30px; }\n    label.bp5-label.bp5-inline .bp5-html-select,\n    label.bp5-label.bp5-inline .bp5-input,\n    label.bp5-label.bp5-inline .bp5-input-group,\n    label.bp5-label.bp5-inline .bp5-select,\n    label.bp5-label.bp5-inline .bp5-popover-wrapper{\n      display:inline-block;\n      margin:0 0 0 5px;\n      vertical-align:top; }\n    label.bp5-label.bp5-inline .bp5-button-group{\n      margin:0 0 0 5px; }\n    label.bp5-label.bp5-inline .bp5-input-group .bp5-input{\n      margin-left:0; }\n    label.bp5-label.bp5-inline.bp5-large{\n      line-height:40px; }\n  label.bp5-label:not(.bp5-inline) .bp5-popover-target{\n    display:block; }\n  .bp5-dark label.bp5-label{\n    color:#f5f8fa; }\n    .bp5-dark label.bp5-label.bp5-disabled,\n    .bp5-dark label.bp5-label.bp5-disabled .bp5-text-muted{\n      color:rgba(167, 182, 194, 0.6); }\n.bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 14px;\n          flex:1 1 14px;\n  min-height:0;\n  padding:0;\n  width:30px; }\n  .bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button:first-child{\n    border-radius:0 3px 0 0; }\n  .bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button:last-child{\n    border-radius:0 0 3px 0; }\n\n.bp5-numeric-input .bp5-button-group.bp5-vertical:first-child > .bp5-button:first-child{\n  border-radius:3px 0 0 0; }\n\n.bp5-numeric-input .bp5-button-group.bp5-vertical:first-child > .bp5-button:last-child{\n  border-radius:0 0 0 3px; }\n\n.bp5-numeric-input.bp5-large .bp5-button-group.bp5-vertical > .bp5-button{\n  width:40px; }\n\nform{\n  display:block; }\n.bp5-html-select select,\n.bp5-select select{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  border:none;\n  border-radius:3px;\n  cursor:pointer;\n  font-size:14px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  background-color:#f5f8fa;\n  background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n  background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n  color:#182026;\n  -moz-appearance:none;\n  -webkit-appearance:none;\n  border-radius:3px;\n  height:30px;\n  padding:0 25px 0 10px;\n  width:100%; }\n  .bp5-html-select select > *, .bp5-select select > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-html-select select > .bp5-fill, .bp5-select select > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-html-select select::before,\n  .bp5-select select::before, .bp5-html-select select > *, .bp5-select select > *{\n    margin-right:7px; }\n  .bp5-html-select select:empty::before,\n  .bp5-select select:empty::before,\n  .bp5-html-select select > :last-child,\n  .bp5-select select > :last-child{\n    margin-right:0; }\n  .bp5-html-select select:hover,\n  .bp5-select select:hover{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n  .bp5-html-select select:active,\n  .bp5-select select:active, .bp5-html-select select.bp5-active,\n  .bp5-select select.bp5-active{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-html-select select:disabled,\n  .bp5-select select:disabled, .bp5-html-select select.bp5-disabled,\n  .bp5-select select.bp5-disabled{\n    background-color:rgba(206, 217, 224, 0.5);\n    background-image:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    outline:none; }\n    .bp5-html-select select:disabled.bp5-active,\n    .bp5-select select:disabled.bp5-active, .bp5-html-select select:disabled.bp5-active:hover,\n    .bp5-select select:disabled.bp5-active:hover, .bp5-html-select select.bp5-disabled.bp5-active,\n    .bp5-select select.bp5-disabled.bp5-active, .bp5-html-select select.bp5-disabled.bp5-active:hover,\n    .bp5-select select.bp5-disabled.bp5-active:hover{\n      background:rgba(206, 217, 224, 0.7); }\n\n.bp5-html-select.bp5-minimal select,\n.bp5-select.bp5-minimal select{\n  background:none;\n  -webkit-box-shadow:none;\n          box-shadow:none; }\n  .bp5-html-select.bp5-minimal select:hover,\n  .bp5-select.bp5-minimal select:hover{\n    background:rgba(167, 182, 194, 0.3);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:#182026;\n    text-decoration:none; }\n  .bp5-html-select.bp5-minimal select:active,\n  .bp5-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal select.bp5-active,\n  .bp5-select.bp5-minimal select.bp5-active{\n    background:rgba(115, 134, 148, 0.3);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:#182026; }\n  .bp5-html-select.bp5-minimal select:disabled,\n  .bp5-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal select:disabled:hover,\n  .bp5-select.bp5-minimal select:disabled:hover, .bp5-html-select.bp5-minimal select.bp5-disabled,\n  .bp5-select.bp5-minimal select.bp5-disabled, .bp5-html-select.bp5-minimal select.bp5-disabled:hover,\n  .bp5-select.bp5-minimal select.bp5-disabled:hover{\n    background:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n    .bp5-html-select.bp5-minimal select:disabled.bp5-active,\n    .bp5-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal select:disabled:hover.bp5-active,\n    .bp5-select.bp5-minimal select:disabled:hover.bp5-active, .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-disabled:hover.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-disabled:hover.bp5-active{\n      background:rgba(115, 134, 148, 0.3); }\n  .bp5-dark .bp5-html-select.bp5-minimal select, .bp5-html-select.bp5-minimal .bp5-dark select,\n  .bp5-dark .bp5-select.bp5-minimal select, .bp5-select.bp5-minimal .bp5-dark select{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:inherit; }\n    .bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n    .bp5-dark .bp5-select.bp5-minimal select:hover, .bp5-select.bp5-minimal .bp5-dark select:hover, .bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n    .bp5-dark .bp5-select.bp5-minimal select:active, .bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n    .bp5-dark .bp5-select.bp5-minimal select:hover, .bp5-select.bp5-minimal .bp5-dark select:hover{\n      background:rgba(138, 155, 168, 0.15); }\n    .bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n    .bp5-dark .bp5-select.bp5-minimal select:active, .bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n      background:rgba(138, 155, 168, 0.3);\n      color:#f5f8fa; }\n    .bp5-dark .bp5-html-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal .bp5-dark select:disabled,\n    .bp5-dark .bp5-select.bp5-minimal select:disabled, .bp5-select.bp5-minimal .bp5-dark select:disabled, .bp5-dark .bp5-html-select.bp5-minimal select:disabled:hover, .bp5-html-select.bp5-minimal .bp5-dark select:disabled:hover,\n    .bp5-dark .bp5-select.bp5-minimal select:disabled:hover, .bp5-select.bp5-minimal .bp5-dark select:disabled:hover, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled:hover,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled:hover{\n      background:none;\n      color:rgba(167, 182, 194, 0.6);\n      cursor:not-allowed; }\n      .bp5-dark .bp5-html-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select:disabled.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select:disabled:hover.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select:disabled:hover.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select:disabled:hover.bp5-active, .bp5-select.bp5-minimal .bp5-dark select:disabled:hover.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled:hover.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled:hover.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled:hover.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled:hover.bp5-active{\n        background:rgba(138, 155, 168, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-primary,\n  .bp5-select.bp5-minimal select.bp5-intent-primary{\n    color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:hover{\n      background:rgba(19, 124, 189, 0.15);\n      color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n      background:rgba(19, 124, 189, 0.3);\n      color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled{\n      background:none;\n      color:rgba(16, 107, 163, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active{\n        background:rgba(19, 124, 189, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n      stroke:#106ba3; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary{\n      color:#48aff0; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.2);\n        color:#48aff0; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#48aff0; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(72, 175, 240, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-success,\n  .bp5-select.bp5-minimal select.bp5-intent-success{\n    color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n    .bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-success:hover{\n      background:rgba(15, 153, 96, 0.15);\n      color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n    .bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n      background:rgba(15, 153, 96, 0.3);\n      color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled{\n      background:none;\n      color:rgba(13, 128, 80, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active{\n        background:rgba(15, 153, 96, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n      stroke:#0d8050; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success{\n      color:#3dcc91; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.2);\n        color:#3dcc91; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#3dcc91; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(61, 204, 145, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-warning,\n  .bp5-select.bp5-minimal select.bp5-intent-warning{\n    color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:hover{\n      background:rgba(217, 130, 43, 0.15);\n      color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n      background:rgba(217, 130, 43, 0.3);\n      color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled{\n      background:none;\n      color:rgba(191, 115, 38, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active{\n        background:rgba(217, 130, 43, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n      stroke:#bf7326; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning{\n      color:#ffb366; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.2);\n        color:#ffb366; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#ffb366; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(255, 179, 102, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-danger,\n  .bp5-select.bp5-minimal select.bp5-intent-danger{\n    color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:hover{\n      background:rgba(219, 55, 55, 0.15);\n      color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n      background:rgba(219, 55, 55, 0.3);\n      color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled{\n      background:none;\n      color:rgba(194, 48, 48, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active{\n        background:rgba(219, 55, 55, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n      stroke:#c23030; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger{\n      color:#ff7373; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.2);\n        color:#ff7373; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#ff7373; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(255, 115, 115, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n\n.bp5-html-select.bp5-large select,\n.bp5-select.bp5-large select{\n  font-size:16px;\n  height:40px;\n  padding-right:35px; }\n\n.bp5-dark .bp5-html-select select, .bp5-dark .bp5-select select{\n  background-color:#394b59;\n  background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n  background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n  color:#f5f8fa; }\n  .bp5-dark .bp5-html-select select:hover, .bp5-dark .bp5-select select:hover, .bp5-dark .bp5-html-select select:active, .bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active, .bp5-dark .bp5-select select.bp5-active{\n    color:#f5f8fa; }\n  .bp5-dark .bp5-html-select select:hover, .bp5-dark .bp5-select select:hover{\n    background-color:#30404d;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-html-select select:active, .bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active, .bp5-dark .bp5-select select.bp5-active{\n    background-color:#202b33;\n    background-image:none;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-html-select select:disabled, .bp5-dark .bp5-select select:disabled, .bp5-dark .bp5-html-select select.bp5-disabled, .bp5-dark .bp5-select select.bp5-disabled{\n    background-color:rgba(57, 75, 89, 0.5);\n    background-image:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-html-select select:disabled.bp5-active, .bp5-dark .bp5-select select:disabled.bp5-active, .bp5-dark .bp5-html-select select.bp5-disabled.bp5-active, .bp5-dark .bp5-select select.bp5-disabled.bp5-active{\n      background:rgba(57, 75, 89, 0.7); }\n  .bp5-dark .bp5-html-select select .bp5-button-spinner .bp5-spinner-head, .bp5-dark .bp5-select select .bp5-button-spinner .bp5-spinner-head{\n    background:rgba(16, 22, 26, 0.5);\n    stroke:#8a9ba8; }\n\n.bp5-html-select select:disabled,\n.bp5-select select:disabled{\n  background-color:rgba(206, 217, 224, 0.5);\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-html-select .bp5-icon,\n.bp5-select .bp5-icon, .bp5-select::after{\n  color:#5c7080;\n  pointer-events:none;\n  position:absolute;\n  right:7px;\n  top:7px; }\n  .bp5-html-select .bp5-disabled.bp5-icon,\n  .bp5-select .bp5-disabled.bp5-icon, .bp5-disabled.bp5-select::after{\n    color:rgba(92, 112, 128, 0.6); }\n.bp5-html-select,\n.bp5-select{\n  display:inline-block;\n  letter-spacing:normal;\n  position:relative;\n  vertical-align:middle; }\n  .bp5-html-select select::-ms-expand,\n  .bp5-select select::-ms-expand{\n    display:none; }\n  .bp5-html-select .bp5-icon,\n  .bp5-select .bp5-icon{\n    color:#5c7080; }\n    .bp5-html-select .bp5-icon:hover,\n    .bp5-select .bp5-icon:hover{\n      color:#182026; }\n    .bp5-dark .bp5-html-select .bp5-icon, .bp5-dark\n    .bp5-select .bp5-icon{\n      color:#a7b6c2; }\n      .bp5-dark .bp5-html-select .bp5-icon:hover, .bp5-dark\n      .bp5-select .bp5-icon:hover{\n        color:#f5f8fa; }\n  .bp5-html-select.bp5-large::after,\n  .bp5-html-select.bp5-large .bp5-icon,\n  .bp5-select.bp5-large::after,\n  .bp5-select.bp5-large .bp5-icon{\n    right:12px;\n    top:12px; }\n  .bp5-html-select.bp5-fill,\n  .bp5-html-select.bp5-fill select,\n  .bp5-select.bp5-fill,\n  .bp5-select.bp5-fill select{\n    width:100%; }\n  .bp5-dark .bp5-html-select option, .bp5-dark\n  .bp5-select option{\n    background-color:#30404d;\n    color:#f5f8fa; }\n  .bp5-dark .bp5-html-select option:disabled, .bp5-dark\n  .bp5-select option:disabled{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-dark .bp5-html-select::after, .bp5-dark\n  .bp5-select::after{\n    color:#a7b6c2; }\n\n.bp5-select::after{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-weight:400;\n  line-height:1;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  content:\"\\f184\"; }\n.bp5-running-text table, table.bp5-html-table{\n  border-spacing:0;\n  font-size:14px; }\n  .bp5-running-text table th, table.bp5-html-table th,\n  .bp5-running-text table td,\n  table.bp5-html-table td{\n    padding:11px;\n    text-align:left;\n    vertical-align:top; }\n  .bp5-running-text table th, table.bp5-html-table th{\n    color:#182026;\n    font-weight:600; }\n\n  .bp5-running-text table td,\n  table.bp5-html-table td{\n    color:#182026; }\n  .bp5-running-text table tbody tr:first-child th, table.bp5-html-table tbody tr:first-child th,\n  .bp5-running-text table tbody tr:first-child td,\n  table.bp5-html-table tbody tr:first-child td,\n  .bp5-running-text table tfoot tr:first-child th,\n  table.bp5-html-table tfoot tr:first-child th,\n  .bp5-running-text table tfoot tr:first-child td,\n  table.bp5-html-table tfoot tr:first-child td{\n    -webkit-box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15);\n            box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15); }\n  .bp5-dark .bp5-running-text table th, .bp5-running-text .bp5-dark table th, .bp5-dark table.bp5-html-table th{\n    color:#f5f8fa; }\n  .bp5-dark .bp5-running-text table td, .bp5-running-text .bp5-dark table td, .bp5-dark table.bp5-html-table td{\n    color:#f5f8fa; }\n  .bp5-dark .bp5-running-text table tbody tr:first-child th, .bp5-running-text .bp5-dark table tbody tr:first-child th, .bp5-dark table.bp5-html-table tbody tr:first-child th,\n  .bp5-dark .bp5-running-text table tbody tr:first-child td,\n  .bp5-running-text .bp5-dark table tbody tr:first-child td,\n  .bp5-dark table.bp5-html-table tbody tr:first-child td,\n  .bp5-dark .bp5-running-text table tfoot tr:first-child th,\n  .bp5-running-text .bp5-dark table tfoot tr:first-child th,\n  .bp5-dark table.bp5-html-table tfoot tr:first-child th,\n  .bp5-dark .bp5-running-text table tfoot tr:first-child td,\n  .bp5-running-text .bp5-dark table tfoot tr:first-child td,\n  .bp5-dark table.bp5-html-table tfoot tr:first-child td{\n    -webkit-box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-condensed th,\ntable.bp5-html-table.bp5-html-table-condensed td, table.bp5-html-table.bp5-small th,\ntable.bp5-html-table.bp5-small td{\n  padding-bottom:6px;\n  padding-top:6px; }\n\ntable.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n  background:rgba(191, 204, 214, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n  -webkit-box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15);\n          box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-bordered tbody tr td,\ntable.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n  -webkit-box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15);\n          box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15); }\n  table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n  table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n    -webkit-box-shadow:inset 1px 1px 0 0 rgba(16, 22, 26, 0.15);\n            box-shadow:inset 1px 1px 0 0 rgba(16, 22, 26, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n  -webkit-box-shadow:none;\n          box-shadow:none; }\n  table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:not(:first-child){\n    -webkit-box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15);\n            box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15); }\n\ntable.bp5-html-table.bp5-interactive tbody tr:hover td{\n  background-color:rgba(191, 204, 214, 0.3);\n  cursor:pointer; }\n\ntable.bp5-html-table.bp5-interactive tbody tr:active td{\n  background-color:rgba(191, 204, 214, 0.4); }\n\n.bp5-dark table.bp5-html-table{ }\n  .bp5-dark table.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n    background:rgba(92, 112, 128, 0.15); }\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n    -webkit-box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15); }\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td,\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n    -webkit-box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15); }\n    .bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n    .bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n      -webkit-box-shadow:inset 1px 1px 0 0 rgba(255, 255, 255, 0.15);\n              box-shadow:inset 1px 1px 0 0 rgba(255, 255, 255, 0.15); }\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n    -webkit-box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15); }\n    .bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:first-child{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-dark table.bp5-html-table.bp5-interactive tbody tr:hover td{\n    background-color:rgba(92, 112, 128, 0.3);\n    cursor:pointer; }\n  .bp5-dark table.bp5-html-table.bp5-interactive tbody tr:active td{\n    background-color:rgba(92, 112, 128, 0.4); }\n.bp5-key-combo{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center; }\n  .bp5-key-combo > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-key-combo > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-key-combo::before,\n  .bp5-key-combo > *{\n    margin-right:5px; }\n  .bp5-key-combo:empty::before,\n  .bp5-key-combo > :last-child{\n    margin-right:0; }\n\n.bp5-hotkey-dialog{\n  padding-bottom:0;\n  top:40px; }\n  .bp5-hotkey-dialog .bp5-dialog-body{\n    margin:0;\n    padding:0; }\n  .bp5-hotkey-dialog .bp5-hotkey-label{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1; }\n\n.bp5-hotkey-column{\n  margin:auto;\n  max-height:80vh;\n  overflow-y:auto;\n  padding:30px; }\n  .bp5-hotkey-column .bp5-heading{\n    margin-bottom:20px; }\n    .bp5-hotkey-column .bp5-heading:not(:first-child){\n      margin-top:40px; }\n\n.bp5-hotkey{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:justify;\n      -ms-flex-pack:justify;\n          justify-content:space-between;\n  margin-left:0;\n  margin-right:0; }\n  .bp5-hotkey:not(:last-child){\n    margin-bottom:10px; }\n.bp5-icon{\n  display:inline-block;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  vertical-align:text-bottom; }\n  .bp5-icon:not(:empty)::before{\n    content:\"\" !important;\n    content:unset !important; }\n  .bp5-icon > svg{\n    display:block; }\n    .bp5-icon > svg:not([fill]){\n      fill:currentColor; }\n\n.bp5-icon.bp5-intent-primary, .bp5-icon-standard.bp5-intent-primary, .bp5-icon-large.bp5-intent-primary{\n  color:#106ba3; }\n  .bp5-dark .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-icon-large.bp5-intent-primary{\n    color:#48aff0; }\n\n.bp5-icon.bp5-intent-success, .bp5-icon-standard.bp5-intent-success, .bp5-icon-large.bp5-intent-success{\n  color:#0d8050; }\n  .bp5-dark .bp5-icon.bp5-intent-success, .bp5-dark .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-icon-large.bp5-intent-success{\n    color:#3dcc91; }\n\n.bp5-icon.bp5-intent-warning, .bp5-icon-standard.bp5-intent-warning, .bp5-icon-large.bp5-intent-warning{\n  color:#bf7326; }\n  .bp5-dark .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-icon-large.bp5-intent-warning{\n    color:#ffb366; }\n\n.bp5-icon.bp5-intent-danger, .bp5-icon-standard.bp5-intent-danger, .bp5-icon-large.bp5-intent-danger{\n  color:#c23030; }\n  .bp5-dark .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-icon-large.bp5-intent-danger{\n    color:#ff7373; }\n\nspan.bp5-icon-standard{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon-large{\n  font-family:\"blueprint-icons-20\", sans-serif;\n  font-size:20px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:20px;\n  line-height:1;\n  width:20px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon:empty{\n  font-family:\"blueprint-icons-20\";\n  font-size:inherit;\n  font-style:normal;\n  font-weight:400;\n  line-height:1;\n}\nspan.bp5-icon:empty::before{\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n}\nspan.bp5-icon:empty.bp5-icon-standard{\n  font-size:16px;\n}\nspan.bp5-icon:empty.bp5-icon-large{\n  font-size:20px;\n}\n\n.bp5-icon-add::before{\n  content:\"\\f109\";\n}\n\n.bp5-icon-add-clip::before{\n  content:\"\\f101\";\n}\n\n.bp5-icon-add-column-left::before{\n  content:\"\\f102\";\n}\n\n.bp5-icon-add-column-right::before{\n  content:\"\\f103\";\n}\n\n.bp5-icon-add-location::before{\n  content:\"\\f104\";\n}\n\n.bp5-icon-add-row-bottom::before{\n  content:\"\\f105\";\n}\n\n.bp5-icon-add-row-top::before{\n  content:\"\\f106\";\n}\n\n.bp5-icon-add-to-artifact::before{\n  content:\"\\f107\";\n}\n\n.bp5-icon-add-to-folder::before{\n  content:\"\\f108\";\n}\n\n.bp5-icon-aimpoints-target::before{\n  content:\"\\f335\";\n}\n\n.bp5-icon-airplane::before{\n  content:\"\\f10a\";\n}\n\n.bp5-icon-align-center::before{\n  content:\"\\f10b\";\n}\n\n.bp5-icon-align-justify::before{\n  content:\"\\f10c\";\n}\n\n.bp5-icon-align-left::before{\n  content:\"\\f10d\";\n}\n\n.bp5-icon-align-right::before{\n  content:\"\\f10e\";\n}\n\n.bp5-icon-alignment-bottom::before{\n  content:\"\\f10f\";\n}\n\n.bp5-icon-alignment-horizontal-center::before{\n  content:\"\\f110\";\n}\n\n.bp5-icon-alignment-left::before{\n  content:\"\\f111\";\n}\n\n.bp5-icon-alignment-right::before{\n  content:\"\\f112\";\n}\n\n.bp5-icon-alignment-top::before{\n  content:\"\\f113\";\n}\n\n.bp5-icon-alignment-vertical-center::before{\n  content:\"\\f114\";\n}\n\n.bp5-icon-ammunition::before{\n  content:\"\\f342\";\n}\n\n.bp5-icon-anchor::before{\n  content:\"\\f330\";\n}\n\n.bp5-icon-annotation::before{\n  content:\"\\f115\";\n}\n\n.bp5-icon-announcement::before{\n  content:\"\\f396\";\n}\n\n.bp5-icon-antenna::before{\n  content:\"\\f116\";\n}\n\n.bp5-icon-app-header::before{\n  content:\"\\f117\";\n}\n\n.bp5-icon-application::before{\n  content:\"\\f118\";\n}\n\n.bp5-icon-applications::before{\n  content:\"\\f119\";\n}\n\n.bp5-icon-archive::before{\n  content:\"\\f11a\";\n}\n\n.bp5-icon-area-of-interest::before{\n  content:\"\\f11b\";\n}\n\n.bp5-icon-array::before{\n  content:\"\\f121\";\n}\n\n.bp5-icon-array-boolean::before{\n  content:\"\\f11c\";\n}\n\n.bp5-icon-array-date::before{\n  content:\"\\f11d\";\n}\n\n.bp5-icon-array-floating-point::before{\n  content:\"\\f32d\";\n}\n\n.bp5-icon-array-numeric::before{\n  content:\"\\f11e\";\n}\n\n.bp5-icon-array-object::before{\n  content:\"\\f394\";\n}\n\n.bp5-icon-array-string::before{\n  content:\"\\f11f\";\n}\n\n.bp5-icon-array-timestamp::before{\n  content:\"\\f120\";\n}\n\n.bp5-icon-arrow-bottom-left::before{\n  content:\"\\f122\";\n}\n\n.bp5-icon-arrow-bottom-right::before{\n  content:\"\\f123\";\n}\n\n.bp5-icon-arrow-down::before{\n  content:\"\\f124\";\n}\n\n.bp5-icon-arrow-left::before{\n  content:\"\\f125\";\n}\n\n.bp5-icon-arrow-right::before{\n  content:\"\\f126\";\n}\n\n.bp5-icon-arrow-top-left::before{\n  content:\"\\f127\";\n}\n\n.bp5-icon-arrow-top-right::before{\n  content:\"\\f128\";\n}\n\n.bp5-icon-arrow-up::before{\n  content:\"\\f129\";\n}\n\n.bp5-icon-arrows-arc::before{\n  content:\"\\f387\";\n}\n\n.bp5-icon-arrows-horizontal::before{\n  content:\"\\f12a\";\n}\n\n.bp5-icon-arrows-vertical::before{\n  content:\"\\f12b\";\n}\n\n.bp5-icon-asterisk::before{\n  content:\"\\f12c\";\n}\n\n.bp5-icon-at::before{\n  content:\"\\f331\";\n}\n\n.bp5-icon-automatic-updates::before{\n  content:\"\\f12d\";\n}\n\n.bp5-icon-axle::before{\n  content:\"\\f338\";\n}\n\n.bp5-icon-backlink::before{\n  content:\"\\f12e\";\n}\n\n.bp5-icon-backward-ten::before{\n  content:\"\\f35c\";\n}\n\n.bp5-icon-badge::before{\n  content:\"\\f12f\";\n}\n\n.bp5-icon-ban-circle::before{\n  content:\"\\f130\";\n}\n\n.bp5-icon-bank-account::before{\n  content:\"\\f131\";\n}\n\n.bp5-icon-barcode::before{\n  content:\"\\f132\";\n}\n\n.bp5-icon-binary-number::before{\n  content:\"\\f357\";\n}\n\n.bp5-icon-blank::before{\n  content:\"\\f133\";\n}\n\n.bp5-icon-block-promote::before{\n  content:\"\\f372\";\n}\n\n.bp5-icon-blocked-person::before{\n  content:\"\\f134\";\n}\n\n.bp5-icon-bold::before{\n  content:\"\\f135\";\n}\n\n.bp5-icon-book::before{\n  content:\"\\f136\";\n}\n\n.bp5-icon-bookmark::before{\n  content:\"\\f137\";\n}\n\n.bp5-icon-box::before{\n  content:\"\\f138\";\n}\n\n.bp5-icon-briefcase::before{\n  content:\"\\f139\";\n}\n\n.bp5-icon-bring-data::before{\n  content:\"\\f13a\";\n}\n\n.bp5-icon-bring-forward::before{\n  content:\"\\f354\";\n}\n\n.bp5-icon-british-pound::before{\n  content:\"\\f386\";\n}\n\n.bp5-icon-bug::before{\n  content:\"\\f32e\";\n}\n\n.bp5-icon-buggy::before{\n  content:\"\\f13b\";\n}\n\n.bp5-icon-build::before{\n  content:\"\\f13c\";\n}\n\n.bp5-icon-bullseye::before{\n  content:\"\\f359\";\n}\n\n.bp5-icon-calculator::before{\n  content:\"\\f13d\";\n}\n\n.bp5-icon-calendar::before{\n  content:\"\\f13e\";\n}\n\n.bp5-icon-camera::before{\n  content:\"\\f13f\";\n}\n\n.bp5-icon-caret-down::before{\n  content:\"\\f140\";\n}\n\n.bp5-icon-caret-left::before{\n  content:\"\\f141\";\n}\n\n.bp5-icon-caret-right::before{\n  content:\"\\f142\";\n}\n\n.bp5-icon-caret-up::before{\n  content:\"\\f143\";\n}\n\n.bp5-icon-cargo-ship::before{\n  content:\"\\f144\";\n}\n\n.bp5-icon-cell-tower::before{\n  content:\"\\f145\";\n}\n\n.bp5-icon-changes::before{\n  content:\"\\f146\";\n}\n\n.bp5-icon-chart::before{\n  content:\"\\f147\";\n}\n\n.bp5-icon-chat::before{\n  content:\"\\f148\";\n}\n\n.bp5-icon-chevron-backward::before{\n  content:\"\\f149\";\n}\n\n.bp5-icon-chevron-down::before{\n  content:\"\\f14a\";\n}\n\n.bp5-icon-chevron-forward::before{\n  content:\"\\f14b\";\n}\n\n.bp5-icon-chevron-left::before{\n  content:\"\\f14c\";\n}\n\n.bp5-icon-chevron-right::before{\n  content:\"\\f14d\";\n}\n\n.bp5-icon-chevron-up::before{\n  content:\"\\f14e\";\n}\n\n.bp5-icon-circle::before{\n  content:\"\\f153\";\n}\n\n.bp5-icon-circle-arrow-down::before{\n  content:\"\\f14f\";\n}\n\n.bp5-icon-circle-arrow-left::before{\n  content:\"\\f150\";\n}\n\n.bp5-icon-circle-arrow-right::before{\n  content:\"\\f151\";\n}\n\n.bp5-icon-circle-arrow-up::before{\n  content:\"\\f152\";\n}\n\n.bp5-icon-citation::before{\n  content:\"\\f154\";\n}\n\n.bp5-icon-clean::before{\n  content:\"\\f155\";\n}\n\n.bp5-icon-clip::before{\n  content:\"\\f156\";\n}\n\n.bp5-icon-clipboard::before{\n  content:\"\\f157\";\n}\n\n.bp5-icon-clipboard-file::before{\n  content:\"\\f35b\";\n}\n\n.bp5-icon-cloud::before{\n  content:\"\\f15a\";\n}\n\n.bp5-icon-cloud-download::before{\n  content:\"\\f158\";\n}\n\n.bp5-icon-cloud-server::before{\n  content:\"\\f35a\";\n}\n\n.bp5-icon-cloud-tick::before{\n  content:\"\\f34e\";\n}\n\n.bp5-icon-cloud-upload::before{\n  content:\"\\f159\";\n}\n\n.bp5-icon-code::before{\n  content:\"\\f15c\";\n}\n\n.bp5-icon-code-block::before{\n  content:\"\\f15b\";\n}\n\n.bp5-icon-cog::before{\n  content:\"\\f15d\";\n}\n\n.bp5-icon-collapse-all::before{\n  content:\"\\f15e\";\n}\n\n.bp5-icon-color-fill::before{\n  content:\"\\f328\";\n}\n\n.bp5-icon-column-layout::before{\n  content:\"\\f15f\";\n}\n\n.bp5-icon-comment::before{\n  content:\"\\f160\";\n}\n\n.bp5-icon-comparison::before{\n  content:\"\\f161\";\n}\n\n.bp5-icon-compass::before{\n  content:\"\\f162\";\n}\n\n.bp5-icon-compressed::before{\n  content:\"\\f163\";\n}\n\n.bp5-icon-confirm::before{\n  content:\"\\f164\";\n}\n\n.bp5-icon-console::before{\n  content:\"\\f165\";\n}\n\n.bp5-icon-construction::before{\n  content:\"\\f395\";\n}\n\n.bp5-icon-contrast::before{\n  content:\"\\f166\";\n}\n\n.bp5-icon-control::before{\n  content:\"\\f167\";\n}\n\n.bp5-icon-credit-card::before{\n  content:\"\\f168\";\n}\n\n.bp5-icon-crop::before{\n  content:\"\\f353\";\n}\n\n.bp5-icon-cross::before{\n  content:\"\\f169\";\n}\n\n.bp5-icon-cross-circle::before{\n  content:\"\\f336\";\n}\n\n.bp5-icon-crown::before{\n  content:\"\\f16a\";\n}\n\n.bp5-icon-css-style::before{\n  content:\"\\f36b\";\n}\n\n.bp5-icon-cube::before{\n  content:\"\\f16d\";\n}\n\n.bp5-icon-cube-add::before{\n  content:\"\\f16b\";\n}\n\n.bp5-icon-cube-edit::before{\n  content:\"\\f383\";\n}\n\n.bp5-icon-cube-remove::before{\n  content:\"\\f16c\";\n}\n\n.bp5-icon-cubes::before{\n  content:\"\\f373\";\n}\n\n.bp5-icon-curly-braces::before{\n  content:\"\\f358\";\n}\n\n.bp5-icon-curved-range-chart::before{\n  content:\"\\f16e\";\n}\n\n.bp5-icon-cut::before{\n  content:\"\\f16f\";\n}\n\n.bp5-icon-cycle::before{\n  content:\"\\f170\";\n}\n\n.bp5-icon-dashboard::before{\n  content:\"\\f171\";\n}\n\n.bp5-icon-data-cloud::before{\n  content:\"\\f391\";\n}\n\n.bp5-icon-data-connection::before{\n  content:\"\\f172\";\n}\n\n.bp5-icon-data-lineage::before{\n  content:\"\\f173\";\n}\n\n.bp5-icon-data-search::before{\n  content:\"\\f36f\";\n}\n\n.bp5-icon-data-sync::before{\n  content:\"\\f36c\";\n}\n\n.bp5-icon-database::before{\n  content:\"\\f174\";\n}\n\n.bp5-icon-delete::before{\n  content:\"\\f175\";\n}\n\n.bp5-icon-delta::before{\n  content:\"\\f176\";\n}\n\n.bp5-icon-derive-column::before{\n  content:\"\\f177\";\n}\n\n.bp5-icon-desktop::before{\n  content:\"\\f178\";\n}\n\n.bp5-icon-detection::before{\n  content:\"\\f341\";\n}\n\n.bp5-icon-diagnosis::before{\n  content:\"\\f179\";\n}\n\n.bp5-icon-diagram-tree::before{\n  content:\"\\f17a\";\n}\n\n.bp5-icon-direction-left::before{\n  content:\"\\f17b\";\n}\n\n.bp5-icon-direction-right::before{\n  content:\"\\f17c\";\n}\n\n.bp5-icon-disable::before{\n  content:\"\\f17d\";\n}\n\n.bp5-icon-divide::before{\n  content:\"\\f327\";\n}\n\n.bp5-icon-document::before{\n  content:\"\\f180\";\n}\n\n.bp5-icon-document-open::before{\n  content:\"\\f17e\";\n}\n\n.bp5-icon-document-share::before{\n  content:\"\\f17f\";\n}\n\n.bp5-icon-dollar::before{\n  content:\"\\f181\";\n}\n\n.bp5-icon-dot::before{\n  content:\"\\f182\";\n}\n\n.bp5-icon-double-caret-horizontal::before{\n  content:\"\\f183\";\n}\n\n.bp5-icon-double-caret-vertical::before{\n  content:\"\\f184\";\n}\n\n.bp5-icon-double-chevron-down::before{\n  content:\"\\f185\";\n}\n\n.bp5-icon-double-chevron-left::before{\n  content:\"\\f186\";\n}\n\n.bp5-icon-double-chevron-right::before{\n  content:\"\\f187\";\n}\n\n.bp5-icon-double-chevron-up::before{\n  content:\"\\f188\";\n}\n\n.bp5-icon-doughnut-chart::before{\n  content:\"\\f189\";\n}\n\n.bp5-icon-download::before{\n  content:\"\\f18a\";\n}\n\n.bp5-icon-drag-handle-horizontal::before{\n  content:\"\\f18b\";\n}\n\n.bp5-icon-drag-handle-vertical::before{\n  content:\"\\f18c\";\n}\n\n.bp5-icon-draw::before{\n  content:\"\\f18d\";\n}\n\n.bp5-icon-drawer-left::before{\n  content:\"\\f18f\";\n}\n\n.bp5-icon-drawer-left-filled::before{\n  content:\"\\f18e\";\n}\n\n.bp5-icon-drawer-right::before{\n  content:\"\\f191\";\n}\n\n.bp5-icon-drawer-right-filled::before{\n  content:\"\\f190\";\n}\n\n.bp5-icon-drive-time::before{\n  content:\"\\f192\";\n}\n\n.bp5-icon-duplicate::before{\n  content:\"\\f193\";\n}\n\n.bp5-icon-edit::before{\n  content:\"\\f194\";\n}\n\n.bp5-icon-eject::before{\n  content:\"\\f195\";\n}\n\n.bp5-icon-emoji::before{\n  content:\"\\f196\";\n}\n\n.bp5-icon-endnote::before{\n  content:\"\\f356\";\n}\n\n.bp5-icon-endorsed::before{\n  content:\"\\f197\";\n}\n\n.bp5-icon-envelope::before{\n  content:\"\\f198\";\n}\n\n.bp5-icon-equals::before{\n  content:\"\\f199\";\n}\n\n.bp5-icon-eraser::before{\n  content:\"\\f19a\";\n}\n\n.bp5-icon-error::before{\n  content:\"\\f19b\";\n}\n\n.bp5-icon-euro::before{\n  content:\"\\f19c\";\n}\n\n.bp5-icon-excavator::before{\n  content:\"\\f36d\";\n}\n\n.bp5-icon-exchange::before{\n  content:\"\\f19d\";\n}\n\n.bp5-icon-exclude-row::before{\n  content:\"\\f19e\";\n}\n\n.bp5-icon-expand-all::before{\n  content:\"\\f19f\";\n}\n\n.bp5-icon-explain::before{\n  content:\"\\f34d\";\n}\n\n.bp5-icon-export::before{\n  content:\"\\f1a0\";\n}\n\n.bp5-icon-eye-off::before{\n  content:\"\\f1a1\";\n}\n\n.bp5-icon-eye-on::before{\n  content:\"\\f1a2\";\n}\n\n.bp5-icon-eye-open::before{\n  content:\"\\f1a3\";\n}\n\n.bp5-icon-fast-backward::before{\n  content:\"\\f1a4\";\n}\n\n.bp5-icon-fast-forward::before{\n  content:\"\\f1a5\";\n}\n\n.bp5-icon-feed::before{\n  content:\"\\f1a7\";\n}\n\n.bp5-icon-feed-subscribed::before{\n  content:\"\\f1a6\";\n}\n\n.bp5-icon-fighter-jet::before{\n  content:\"\\f384\";\n}\n\n.bp5-icon-film::before{\n  content:\"\\f1a8\";\n}\n\n.bp5-icon-filter::before{\n  content:\"\\f1ad\";\n}\n\n.bp5-icon-filter-keep::before{\n  content:\"\\f1a9\";\n}\n\n.bp5-icon-filter-list::before{\n  content:\"\\f1aa\";\n}\n\n.bp5-icon-filter-open::before{\n  content:\"\\f1ab\";\n}\n\n.bp5-icon-filter-remove::before{\n  content:\"\\f1ac\";\n}\n\n.bp5-icon-filter-sort-asc::before{\n  content:\"\\f38e\";\n}\n\n.bp5-icon-filter-sort-desc::before{\n  content:\"\\f38f\";\n}\n\n.bp5-icon-flag::before{\n  content:\"\\f1ae\";\n}\n\n.bp5-icon-flame::before{\n  content:\"\\f1af\";\n}\n\n.bp5-icon-flash::before{\n  content:\"\\f1b0\";\n}\n\n.bp5-icon-floating-point::before{\n  content:\"\\f32c\";\n}\n\n.bp5-icon-floppy-disk::before{\n  content:\"\\f1b1\";\n}\n\n.bp5-icon-flow-branch::before{\n  content:\"\\f1b2\";\n}\n\n.bp5-icon-flow-end::before{\n  content:\"\\f1b3\";\n}\n\n.bp5-icon-flow-linear::before{\n  content:\"\\f1b4\";\n}\n\n.bp5-icon-flow-review::before{\n  content:\"\\f1b6\";\n}\n\n.bp5-icon-flow-review-branch::before{\n  content:\"\\f1b5\";\n}\n\n.bp5-icon-flows::before{\n  content:\"\\f1b7\";\n}\n\n.bp5-icon-folder-close::before{\n  content:\"\\f1b8\";\n}\n\n.bp5-icon-folder-new::before{\n  content:\"\\f1b9\";\n}\n\n.bp5-icon-folder-open::before{\n  content:\"\\f1ba\";\n}\n\n.bp5-icon-folder-shared::before{\n  content:\"\\f1bc\";\n}\n\n.bp5-icon-folder-shared-open::before{\n  content:\"\\f1bb\";\n}\n\n.bp5-icon-follower::before{\n  content:\"\\f1bd\";\n}\n\n.bp5-icon-following::before{\n  content:\"\\f1be\";\n}\n\n.bp5-icon-font::before{\n  content:\"\\f1bf\";\n}\n\n.bp5-icon-fork::before{\n  content:\"\\f1c0\";\n}\n\n.bp5-icon-form::before{\n  content:\"\\f1c1\";\n}\n\n.bp5-icon-forward-ten::before{\n  content:\"\\f35d\";\n}\n\n.bp5-icon-fuel::before{\n  content:\"\\f323\";\n}\n\n.bp5-icon-full-circle::before{\n  content:\"\\f1c2\";\n}\n\n.bp5-icon-full-stacked-chart::before{\n  content:\"\\f1c3\";\n}\n\n.bp5-icon-fullscreen::before{\n  content:\"\\f1c4\";\n}\n\n.bp5-icon-function::before{\n  content:\"\\f1c5\";\n}\n\n.bp5-icon-gantt-chart::before{\n  content:\"\\f1c6\";\n}\n\n.bp5-icon-generate::before{\n  content:\"\\f34c\";\n}\n\n.bp5-icon-geofence::before{\n  content:\"\\f1c7\";\n}\n\n.bp5-icon-geolocation::before{\n  content:\"\\f1c8\";\n}\n\n.bp5-icon-geosearch::before{\n  content:\"\\f1c9\";\n}\n\n.bp5-icon-geotime::before{\n  content:\"\\f344\";\n}\n\n.bp5-icon-git-branch::before{\n  content:\"\\f1ca\";\n}\n\n.bp5-icon-git-commit::before{\n  content:\"\\f1cb\";\n}\n\n.bp5-icon-git-merge::before{\n  content:\"\\f1cc\";\n}\n\n.bp5-icon-git-new-branch::before{\n  content:\"\\f1cd\";\n}\n\n.bp5-icon-git-pull::before{\n  content:\"\\f1ce\";\n}\n\n.bp5-icon-git-push::before{\n  content:\"\\f1cf\";\n}\n\n.bp5-icon-git-repo::before{\n  content:\"\\f1d0\";\n}\n\n.bp5-icon-glass::before{\n  content:\"\\f1d1\";\n}\n\n.bp5-icon-globe::before{\n  content:\"\\f1d3\";\n}\n\n.bp5-icon-globe-network::before{\n  content:\"\\f1d2\";\n}\n\n.bp5-icon-globe-network-add::before{\n  content:\"\\f382\";\n}\n\n.bp5-icon-graph::before{\n  content:\"\\f1d5\";\n}\n\n.bp5-icon-graph-remove::before{\n  content:\"\\f1d4\";\n}\n\n.bp5-icon-greater-than::before{\n  content:\"\\f1d7\";\n}\n\n.bp5-icon-greater-than-or-equal-to::before{\n  content:\"\\f1d6\";\n}\n\n.bp5-icon-grid::before{\n  content:\"\\f1d9\";\n}\n\n.bp5-icon-grid-view::before{\n  content:\"\\f1d8\";\n}\n\n.bp5-icon-group-item::before{\n  content:\"\\f34a\";\n}\n\n.bp5-icon-group-objects::before{\n  content:\"\\f1da\";\n}\n\n.bp5-icon-grouped-bar-chart::before{\n  content:\"\\f1db\";\n}\n\n.bp5-icon-hand::before{\n  content:\"\\f1e0\";\n}\n\n.bp5-icon-hand-down::before{\n  content:\"\\f1dc\";\n}\n\n.bp5-icon-hand-left::before{\n  content:\"\\f1dd\";\n}\n\n.bp5-icon-hand-right::before{\n  content:\"\\f1de\";\n}\n\n.bp5-icon-hand-up::before{\n  content:\"\\f1df\";\n}\n\n.bp5-icon-hat::before{\n  content:\"\\f1e1\";\n}\n\n.bp5-icon-header::before{\n  content:\"\\f1e5\";\n}\n\n.bp5-icon-header-one::before{\n  content:\"\\f1e2\";\n}\n\n.bp5-icon-header-three::before{\n  content:\"\\f1e3\";\n}\n\n.bp5-icon-header-two::before{\n  content:\"\\f1e4\";\n}\n\n.bp5-icon-headset::before{\n  content:\"\\f1e6\";\n}\n\n.bp5-icon-heart::before{\n  content:\"\\f1e8\";\n}\n\n.bp5-icon-heart-broken::before{\n  content:\"\\f1e7\";\n}\n\n.bp5-icon-heat-grid::before{\n  content:\"\\f1e9\";\n}\n\n.bp5-icon-heatmap::before{\n  content:\"\\f1ea\";\n}\n\n.bp5-icon-helicopter::before{\n  content:\"\\f1eb\";\n}\n\n.bp5-icon-help::before{\n  content:\"\\f1ec\";\n}\n\n.bp5-icon-helper-management::before{\n  content:\"\\f1ed\";\n}\n\n.bp5-icon-hexagon::before{\n  content:\"\\f374\";\n}\n\n.bp5-icon-high-priority::before{\n  content:\"\\f1ee\";\n}\n\n.bp5-icon-high-voltage-pole::before{\n  content:\"\\f333\";\n}\n\n.bp5-icon-highlight::before{\n  content:\"\\f1ef\";\n}\n\n.bp5-icon-history::before{\n  content:\"\\f1f0\";\n}\n\n.bp5-icon-home::before{\n  content:\"\\f1f1\";\n}\n\n.bp5-icon-horizontal-bar-chart::before{\n  content:\"\\f1f4\";\n}\n\n.bp5-icon-horizontal-bar-chart-asc::before{\n  content:\"\\f1f2\";\n}\n\n.bp5-icon-horizontal-bar-chart-desc::before{\n  content:\"\\f1f3\";\n}\n\n.bp5-icon-horizontal-distribution::before{\n  content:\"\\f1f5\";\n}\n\n.bp5-icon-horizontal-inbetween::before{\n  content:\"\\f329\";\n}\n\n.bp5-icon-hurricane::before{\n  content:\"\\f1f6\";\n}\n\n.bp5-icon-id-number::before{\n  content:\"\\f1f7\";\n}\n\n.bp5-icon-image-rotate-left::before{\n  content:\"\\f1f8\";\n}\n\n.bp5-icon-image-rotate-right::before{\n  content:\"\\f1f9\";\n}\n\n.bp5-icon-import::before{\n  content:\"\\f1fa\";\n}\n\n.bp5-icon-inbox::before{\n  content:\"\\f1ff\";\n}\n\n.bp5-icon-inbox-filtered::before{\n  content:\"\\f1fb\";\n}\n\n.bp5-icon-inbox-geo::before{\n  content:\"\\f1fc\";\n}\n\n.bp5-icon-inbox-search::before{\n  content:\"\\f1fd\";\n}\n\n.bp5-icon-inbox-update::before{\n  content:\"\\f1fe\";\n}\n\n.bp5-icon-info-sign::before{\n  content:\"\\f200\";\n}\n\n.bp5-icon-inheritance::before{\n  content:\"\\f201\";\n}\n\n.bp5-icon-inherited-group::before{\n  content:\"\\f202\";\n}\n\n.bp5-icon-inner-join::before{\n  content:\"\\f203\";\n}\n\n.bp5-icon-input::before{\n  content:\"\\f34b\";\n}\n\n.bp5-icon-insert::before{\n  content:\"\\f204\";\n}\n\n.bp5-icon-intelligence::before{\n  content:\"\\f337\";\n}\n\n.bp5-icon-intersection::before{\n  content:\"\\f205\";\n}\n\n.bp5-icon-ip-address::before{\n  content:\"\\f206\";\n}\n\n.bp5-icon-issue::before{\n  content:\"\\f209\";\n}\n\n.bp5-icon-issue-closed::before{\n  content:\"\\f207\";\n}\n\n.bp5-icon-issue-new::before{\n  content:\"\\f208\";\n}\n\n.bp5-icon-italic::before{\n  content:\"\\f20a\";\n}\n\n.bp5-icon-join-table::before{\n  content:\"\\f20b\";\n}\n\n.bp5-icon-key::before{\n  content:\"\\f215\";\n}\n\n.bp5-icon-key-backspace::before{\n  content:\"\\f20c\";\n}\n\n.bp5-icon-key-command::before{\n  content:\"\\f20d\";\n}\n\n.bp5-icon-key-control::before{\n  content:\"\\f20e\";\n}\n\n.bp5-icon-key-delete::before{\n  content:\"\\f20f\";\n}\n\n.bp5-icon-key-enter::before{\n  content:\"\\f210\";\n}\n\n.bp5-icon-key-escape::before{\n  content:\"\\f211\";\n}\n\n.bp5-icon-key-option::before{\n  content:\"\\f212\";\n}\n\n.bp5-icon-key-shift::before{\n  content:\"\\f213\";\n}\n\n.bp5-icon-key-tab::before{\n  content:\"\\f214\";\n}\n\n.bp5-icon-known-vehicle::before{\n  content:\"\\f216\";\n}\n\n.bp5-icon-lab-test::before{\n  content:\"\\f217\";\n}\n\n.bp5-icon-label::before{\n  content:\"\\f218\";\n}\n\n.bp5-icon-layer::before{\n  content:\"\\f21a\";\n}\n\n.bp5-icon-layer-outline::before{\n  content:\"\\f219\";\n}\n\n.bp5-icon-layers::before{\n  content:\"\\f21b\";\n}\n\n.bp5-icon-layout::before{\n  content:\"\\f225\";\n}\n\n.bp5-icon-layout-auto::before{\n  content:\"\\f21c\";\n}\n\n.bp5-icon-layout-balloon::before{\n  content:\"\\f21d\";\n}\n\n.bp5-icon-layout-bottom-row-three-tiles::before{\n  content:\"\\f364\";\n}\n\n.bp5-icon-layout-bottom-row-two-tiles::before{\n  content:\"\\f363\";\n}\n\n.bp5-icon-layout-circle::before{\n  content:\"\\f21e\";\n}\n\n.bp5-icon-layout-grid::before{\n  content:\"\\f21f\";\n}\n\n.bp5-icon-layout-group-by::before{\n  content:\"\\f220\";\n}\n\n.bp5-icon-layout-hierarchy::before{\n  content:\"\\f221\";\n}\n\n.bp5-icon-layout-left-column-three-tiles::before{\n  content:\"\\f366\";\n}\n\n.bp5-icon-layout-left-column-two-tiles::before{\n  content:\"\\f365\";\n}\n\n.bp5-icon-layout-linear::before{\n  content:\"\\f222\";\n}\n\n.bp5-icon-layout-right-column-three-tiles::before{\n  content:\"\\f368\";\n}\n\n.bp5-icon-layout-right-column-two-tiles::before{\n  content:\"\\f367\";\n}\n\n.bp5-icon-layout-skew-grid::before{\n  content:\"\\f223\";\n}\n\n.bp5-icon-layout-sorted-clusters::before{\n  content:\"\\f224\";\n}\n\n.bp5-icon-layout-three-columns::before{\n  content:\"\\f361\";\n}\n\n.bp5-icon-layout-three-rows::before{\n  content:\"\\f362\";\n}\n\n.bp5-icon-layout-top-row-three-tiles::before{\n  content:\"\\f36a\";\n}\n\n.bp5-icon-layout-top-row-two-tiles::before{\n  content:\"\\f369\";\n}\n\n.bp5-icon-layout-two-columns::before{\n  content:\"\\f35f\";\n}\n\n.bp5-icon-layout-two-rows::before{\n  content:\"\\f360\";\n}\n\n.bp5-icon-learning::before{\n  content:\"\\f226\";\n}\n\n.bp5-icon-left-join::before{\n  content:\"\\f227\";\n}\n\n.bp5-icon-lengthen-text::before{\n  content:\"\\f33e\";\n}\n\n.bp5-icon-less-than::before{\n  content:\"\\f229\";\n}\n\n.bp5-icon-less-than-or-equal-to::before{\n  content:\"\\f228\";\n}\n\n.bp5-icon-lifesaver::before{\n  content:\"\\f22a\";\n}\n\n.bp5-icon-lightbulb::before{\n  content:\"\\f22b\";\n}\n\n.bp5-icon-lightning::before{\n  content:\"\\f22c\";\n}\n\n.bp5-icon-link::before{\n  content:\"\\f22d\";\n}\n\n.bp5-icon-linked-squares::before{\n  content:\"\\f385\";\n}\n\n.bp5-icon-list::before{\n  content:\"\\f230\";\n}\n\n.bp5-icon-list-columns::before{\n  content:\"\\f22e\";\n}\n\n.bp5-icon-list-detail-view::before{\n  content:\"\\f22f\";\n}\n\n.bp5-icon-locate::before{\n  content:\"\\f231\";\n}\n\n.bp5-icon-lock::before{\n  content:\"\\f232\";\n}\n\n.bp5-icon-locomotive::before{\n  content:\"\\f33b\";\n}\n\n.bp5-icon-log-in::before{\n  content:\"\\f233\";\n}\n\n.bp5-icon-log-out::before{\n  content:\"\\f234\";\n}\n\n.bp5-icon-low-voltage-pole::before{\n  content:\"\\f332\";\n}\n\n.bp5-icon-manual::before{\n  content:\"\\f235\";\n}\n\n.bp5-icon-manually-entered-data::before{\n  content:\"\\f236\";\n}\n\n.bp5-icon-many-to-many::before{\n  content:\"\\f237\";\n}\n\n.bp5-icon-many-to-one::before{\n  content:\"\\f238\";\n}\n\n.bp5-icon-map::before{\n  content:\"\\f23b\";\n}\n\n.bp5-icon-map-create::before{\n  content:\"\\f239\";\n}\n\n.bp5-icon-map-marker::before{\n  content:\"\\f23a\";\n}\n\n.bp5-icon-maximize::before{\n  content:\"\\f23c\";\n}\n\n.bp5-icon-media::before{\n  content:\"\\f23d\";\n}\n\n.bp5-icon-menu::before{\n  content:\"\\f240\";\n}\n\n.bp5-icon-menu-closed::before{\n  content:\"\\f23e\";\n}\n\n.bp5-icon-menu-open::before{\n  content:\"\\f23f\";\n}\n\n.bp5-icon-merge-columns::before{\n  content:\"\\f241\";\n}\n\n.bp5-icon-merge-links::before{\n  content:\"\\f242\";\n}\n\n.bp5-icon-microphone::before{\n  content:\"\\f343\";\n}\n\n.bp5-icon-minimize::before{\n  content:\"\\f243\";\n}\n\n.bp5-icon-minus::before{\n  content:\"\\f244\";\n}\n\n.bp5-icon-mobile-phone::before{\n  content:\"\\f245\";\n}\n\n.bp5-icon-mobile-video::before{\n  content:\"\\f246\";\n}\n\n.bp5-icon-modal::before{\n  content:\"\\f248\";\n}\n\n.bp5-icon-modal-filled::before{\n  content:\"\\f247\";\n}\n\n.bp5-icon-model::before{\n  content:\"\\f33d\";\n}\n\n.bp5-icon-moon::before{\n  content:\"\\f249\";\n}\n\n.bp5-icon-more::before{\n  content:\"\\f24a\";\n}\n\n.bp5-icon-mountain::before{\n  content:\"\\f24b\";\n}\n\n.bp5-icon-move::before{\n  content:\"\\f24c\";\n}\n\n.bp5-icon-mugshot::before{\n  content:\"\\f24d\";\n}\n\n.bp5-icon-multi-select::before{\n  content:\"\\f24e\";\n}\n\n.bp5-icon-music::before{\n  content:\"\\f24f\";\n}\n\n.bp5-icon-nest::before{\n  content:\"\\f250\";\n}\n\n.bp5-icon-new-drawing::before{\n  content:\"\\f251\";\n}\n\n.bp5-icon-new-grid-item::before{\n  content:\"\\f252\";\n}\n\n.bp5-icon-new-layer::before{\n  content:\"\\f253\";\n}\n\n.bp5-icon-new-layers::before{\n  content:\"\\f254\";\n}\n\n.bp5-icon-new-link::before{\n  content:\"\\f255\";\n}\n\n.bp5-icon-new-object::before{\n  content:\"\\f256\";\n}\n\n.bp5-icon-new-person::before{\n  content:\"\\f257\";\n}\n\n.bp5-icon-new-prescription::before{\n  content:\"\\f258\";\n}\n\n.bp5-icon-new-shield::before{\n  content:\"\\f349\";\n}\n\n.bp5-icon-new-text-box::before{\n  content:\"\\f259\";\n}\n\n.bp5-icon-ninja::before{\n  content:\"\\f25a\";\n}\n\n.bp5-icon-not-equal-to::before{\n  content:\"\\f25b\";\n}\n\n.bp5-icon-notifications::before{\n  content:\"\\f25e\";\n}\n\n.bp5-icon-notifications-add::before{\n  content:\"\\f398\";\n}\n\n.bp5-icon-notifications-snooze::before{\n  content:\"\\f25c\";\n}\n\n.bp5-icon-notifications-updated::before{\n  content:\"\\f25d\";\n}\n\n.bp5-icon-numbered-list::before{\n  content:\"\\f25f\";\n}\n\n.bp5-icon-numerical::before{\n  content:\"\\f260\";\n}\n\n.bp5-icon-object-view::before{\n  content:\"\\f390\";\n}\n\n.bp5-icon-office::before{\n  content:\"\\f261\";\n}\n\n.bp5-icon-offline::before{\n  content:\"\\f262\";\n}\n\n.bp5-icon-oil-field::before{\n  content:\"\\f263\";\n}\n\n.bp5-icon-one-column::before{\n  content:\"\\f264\";\n}\n\n.bp5-icon-one-to-many::before{\n  content:\"\\f265\";\n}\n\n.bp5-icon-one-to-one::before{\n  content:\"\\f266\";\n}\n\n.bp5-icon-open-application::before{\n  content:\"\\f32b\";\n}\n\n.bp5-icon-outdated::before{\n  content:\"\\f267\";\n}\n\n.bp5-icon-output::before{\n  content:\"\\f370\";\n}\n\n.bp5-icon-package::before{\n  content:\"\\f375\";\n}\n\n.bp5-icon-page-layout::before{\n  content:\"\\f268\";\n}\n\n.bp5-icon-panel::before{\n  content:\"\\f381\";\n}\n\n.bp5-icon-panel-stats::before{\n  content:\"\\f269\";\n}\n\n.bp5-icon-panel-table::before{\n  content:\"\\f26a\";\n}\n\n.bp5-icon-paperclip::before{\n  content:\"\\f26b\";\n}\n\n.bp5-icon-paragraph::before{\n  content:\"\\f26c\";\n}\n\n.bp5-icon-paste-variable::before{\n  content:\"\\f346\";\n}\n\n.bp5-icon-path::before{\n  content:\"\\f26e\";\n}\n\n.bp5-icon-path-search::before{\n  content:\"\\f26d\";\n}\n\n.bp5-icon-pause::before{\n  content:\"\\f26f\";\n}\n\n.bp5-icon-people::before{\n  content:\"\\f270\";\n}\n\n.bp5-icon-percentage::before{\n  content:\"\\f271\";\n}\n\n.bp5-icon-person::before{\n  content:\"\\f272\";\n}\n\n.bp5-icon-phone::before{\n  content:\"\\f273\";\n}\n\n.bp5-icon-phone-call::before{\n  content:\"\\f347\";\n}\n\n.bp5-icon-phone-forward::before{\n  content:\"\\f348\";\n}\n\n.bp5-icon-phone-search::before{\n  content:\"\\f397\";\n}\n\n.bp5-icon-pie-chart::before{\n  content:\"\\f274\";\n}\n\n.bp5-icon-pill::before{\n  content:\"\\f376\";\n}\n\n.bp5-icon-pin::before{\n  content:\"\\f275\";\n}\n\n.bp5-icon-pivot::before{\n  content:\"\\f277\";\n}\n\n.bp5-icon-pivot-table::before{\n  content:\"\\f276\";\n}\n\n.bp5-icon-play::before{\n  content:\"\\f278\";\n}\n\n.bp5-icon-playbook::before{\n  content:\"\\f324\";\n}\n\n.bp5-icon-plus::before{\n  content:\"\\f279\";\n}\n\n.bp5-icon-polygon-filter::before{\n  content:\"\\f27a\";\n}\n\n.bp5-icon-power::before{\n  content:\"\\f27b\";\n}\n\n.bp5-icon-predictive-analysis::before{\n  content:\"\\f27c\";\n}\n\n.bp5-icon-prescription::before{\n  content:\"\\f27d\";\n}\n\n.bp5-icon-presentation::before{\n  content:\"\\f27e\";\n}\n\n.bp5-icon-print::before{\n  content:\"\\f27f\";\n}\n\n.bp5-icon-projects::before{\n  content:\"\\f280\";\n}\n\n.bp5-icon-properties::before{\n  content:\"\\f281\";\n}\n\n.bp5-icon-property::before{\n  content:\"\\f282\";\n}\n\n.bp5-icon-publish-function::before{\n  content:\"\\f283\";\n}\n\n.bp5-icon-pulse::before{\n  content:\"\\f284\";\n}\n\n.bp5-icon-rain::before{\n  content:\"\\f285\";\n}\n\n.bp5-icon-random::before{\n  content:\"\\f286\";\n}\n\n.bp5-icon-range-ring::before{\n  content:\"\\f371\";\n}\n\n.bp5-icon-record::before{\n  content:\"\\f287\";\n}\n\n.bp5-icon-rect-height::before{\n  content:\"\\f325\";\n}\n\n.bp5-icon-rect-width::before{\n  content:\"\\f326\";\n}\n\n.bp5-icon-rectangle::before{\n  content:\"\\f321\";\n}\n\n.bp5-icon-redo::before{\n  content:\"\\f288\";\n}\n\n.bp5-icon-refresh::before{\n  content:\"\\f289\";\n}\n\n.bp5-icon-regex::before{\n  content:\"\\f32f\";\n}\n\n.bp5-icon-regression-chart::before{\n  content:\"\\f28a\";\n}\n\n.bp5-icon-remove::before{\n  content:\"\\f290\";\n}\n\n.bp5-icon-remove-column::before{\n  content:\"\\f28d\";\n}\n\n.bp5-icon-remove-column-left::before{\n  content:\"\\f28b\";\n}\n\n.bp5-icon-remove-column-right::before{\n  content:\"\\f28c\";\n}\n\n.bp5-icon-remove-row-bottom::before{\n  content:\"\\f28e\";\n}\n\n.bp5-icon-remove-row-top::before{\n  content:\"\\f28f\";\n}\n\n.bp5-icon-repeat::before{\n  content:\"\\f291\";\n}\n\n.bp5-icon-reset::before{\n  content:\"\\f292\";\n}\n\n.bp5-icon-resolve::before{\n  content:\"\\f293\";\n}\n\n.bp5-icon-rig::before{\n  content:\"\\f294\";\n}\n\n.bp5-icon-right-join::before{\n  content:\"\\f295\";\n}\n\n.bp5-icon-ring::before{\n  content:\"\\f296\";\n}\n\n.bp5-icon-rocket::before{\n  content:\"\\f298\";\n}\n\n.bp5-icon-rocket-slant::before{\n  content:\"\\f297\";\n}\n\n.bp5-icon-rotate-ccw::before{\n  content:\"\\f389\";\n}\n\n.bp5-icon-rotate-cw::before{\n  content:\"\\f388\";\n}\n\n.bp5-icon-rotate-document::before{\n  content:\"\\f299\";\n}\n\n.bp5-icon-rotate-page::before{\n  content:\"\\f29a\";\n}\n\n.bp5-icon-route::before{\n  content:\"\\f29b\";\n}\n\n.bp5-icon-run-history::before{\n  content:\"\\f393\";\n}\n\n.bp5-icon-satellite::before{\n  content:\"\\f29c\";\n}\n\n.bp5-icon-saved::before{\n  content:\"\\f29d\";\n}\n\n.bp5-icon-scatter-plot::before{\n  content:\"\\f29e\";\n}\n\n.bp5-icon-search::before{\n  content:\"\\f2a2\";\n}\n\n.bp5-icon-search-around::before{\n  content:\"\\f29f\";\n}\n\n.bp5-icon-search-template::before{\n  content:\"\\f2a0\";\n}\n\n.bp5-icon-search-text::before{\n  content:\"\\f2a1\";\n}\n\n.bp5-icon-segmented-control::before{\n  content:\"\\f2a3\";\n}\n\n.bp5-icon-select::before{\n  content:\"\\f2a4\";\n}\n\n.bp5-icon-selection::before{\n  content:\"\\f2a5\";\n}\n\n.bp5-icon-selection-box::before{\n  content:\"\\f399\";\n}\n\n.bp5-icon-selection-box-add::before{\n  content:\"\\f39a\";\n}\n\n.bp5-icon-selection-box-edit::before{\n  content:\"\\f39b\";\n}\n\n.bp5-icon-send-backward::before{\n  content:\"\\f355\";\n}\n\n.bp5-icon-send-message::before{\n  content:\"\\f2a6\";\n}\n\n.bp5-icon-send-to::before{\n  content:\"\\f2a9\";\n}\n\n.bp5-icon-send-to-graph::before{\n  content:\"\\f2a7\";\n}\n\n.bp5-icon-send-to-map::before{\n  content:\"\\f2a8\";\n}\n\n.bp5-icon-sensor::before{\n  content:\"\\f33c\";\n}\n\n.bp5-icon-series-add::before{\n  content:\"\\f2aa\";\n}\n\n.bp5-icon-series-configuration::before{\n  content:\"\\f2ab\";\n}\n\n.bp5-icon-series-derived::before{\n  content:\"\\f2ac\";\n}\n\n.bp5-icon-series-filtered::before{\n  content:\"\\f2ad\";\n}\n\n.bp5-icon-series-search::before{\n  content:\"\\f2ae\";\n}\n\n.bp5-icon-server::before{\n  content:\"\\f378\";\n}\n\n.bp5-icon-server-install::before{\n  content:\"\\f377\";\n}\n\n.bp5-icon-settings::before{\n  content:\"\\f2af\";\n}\n\n.bp5-icon-shapes::before{\n  content:\"\\f2b0\";\n}\n\n.bp5-icon-share::before{\n  content:\"\\f2b1\";\n}\n\n.bp5-icon-shared-filter::before{\n  content:\"\\f2b2\";\n}\n\n.bp5-icon-shield::before{\n  content:\"\\f2b3\";\n}\n\n.bp5-icon-ship::before{\n  content:\"\\f2b4\";\n}\n\n.bp5-icon-shop::before{\n  content:\"\\f2b5\";\n}\n\n.bp5-icon-shopping-cart::before{\n  content:\"\\f2b6\";\n}\n\n.bp5-icon-shorten-text::before{\n  content:\"\\f33f\";\n}\n\n.bp5-icon-signal-search::before{\n  content:\"\\f2b7\";\n}\n\n.bp5-icon-sim-card::before{\n  content:\"\\f2b8\";\n}\n\n.bp5-icon-slash::before{\n  content:\"\\f2b9\";\n}\n\n.bp5-icon-small-cross::before{\n  content:\"\\f2ba\";\n}\n\n.bp5-icon-small-info-sign::before{\n  content:\"\\f334\";\n}\n\n.bp5-icon-small-minus::before{\n  content:\"\\f2bb\";\n}\n\n.bp5-icon-small-plus::before{\n  content:\"\\f2bc\";\n}\n\n.bp5-icon-small-square::before{\n  content:\"\\f2bd\";\n}\n\n.bp5-icon-small-tick::before{\n  content:\"\\f2be\";\n}\n\n.bp5-icon-snowflake::before{\n  content:\"\\f2bf\";\n}\n\n.bp5-icon-soccer-ball::before{\n  content:\"\\f350\";\n}\n\n.bp5-icon-social-media::before{\n  content:\"\\f2c0\";\n}\n\n.bp5-icon-sort::before{\n  content:\"\\f2c7\";\n}\n\n.bp5-icon-sort-alphabetical::before{\n  content:\"\\f2c2\";\n}\n\n.bp5-icon-sort-alphabetical-desc::before{\n  content:\"\\f2c1\";\n}\n\n.bp5-icon-sort-asc::before{\n  content:\"\\f2c3\";\n}\n\n.bp5-icon-sort-desc::before{\n  content:\"\\f2c4\";\n}\n\n.bp5-icon-sort-numerical::before{\n  content:\"\\f2c6\";\n}\n\n.bp5-icon-sort-numerical-desc::before{\n  content:\"\\f2c5\";\n}\n\n.bp5-icon-spell-check::before{\n  content:\"\\f340\";\n}\n\n.bp5-icon-split-columns::before{\n  content:\"\\f2c8\";\n}\n\n.bp5-icon-sports-stadium::before{\n  content:\"\\f351\";\n}\n\n.bp5-icon-square::before{\n  content:\"\\f2c9\";\n}\n\n.bp5-icon-stacked-chart::before{\n  content:\"\\f2ca\";\n}\n\n.bp5-icon-stadium-geometry::before{\n  content:\"\\f2cb\";\n}\n\n.bp5-icon-star::before{\n  content:\"\\f2cd\";\n}\n\n.bp5-icon-star-empty::before{\n  content:\"\\f2cc\";\n}\n\n.bp5-icon-step-backward::before{\n  content:\"\\f2ce\";\n}\n\n.bp5-icon-step-chart::before{\n  content:\"\\f2cf\";\n}\n\n.bp5-icon-step-forward::before{\n  content:\"\\f2d0\";\n}\n\n.bp5-icon-stop::before{\n  content:\"\\f2d1\";\n}\n\n.bp5-icon-stopwatch::before{\n  content:\"\\f2d2\";\n}\n\n.bp5-icon-strikethrough::before{\n  content:\"\\f2d3\";\n}\n\n.bp5-icon-style::before{\n  content:\"\\f2d4\";\n}\n\n.bp5-icon-subscript::before{\n  content:\"\\f339\";\n}\n\n.bp5-icon-subtract-right-join::before{\n  content:\"\\f392\";\n}\n\n.bp5-icon-superscript::before{\n  content:\"\\f33a\";\n}\n\n.bp5-icon-swap-horizontal::before{\n  content:\"\\f2d5\";\n}\n\n.bp5-icon-swap-vertical::before{\n  content:\"\\f2d6\";\n}\n\n.bp5-icon-switch::before{\n  content:\"\\f2d7\";\n}\n\n.bp5-icon-symbol-circle::before{\n  content:\"\\f2d8\";\n}\n\n.bp5-icon-symbol-cross::before{\n  content:\"\\f2d9\";\n}\n\n.bp5-icon-symbol-diamond::before{\n  content:\"\\f2da\";\n}\n\n.bp5-icon-symbol-rectangle::before{\n  content:\"\\f322\";\n}\n\n.bp5-icon-symbol-square::before{\n  content:\"\\f2db\";\n}\n\n.bp5-icon-symbol-triangle-down::before{\n  content:\"\\f2dc\";\n}\n\n.bp5-icon-symbol-triangle-up::before{\n  content:\"\\f2dd\";\n}\n\n.bp5-icon-syringe::before{\n  content:\"\\f2de\";\n}\n\n.bp5-icon-table-sync::before{\n  content:\"\\f36e\";\n}\n\n.bp5-icon-tag::before{\n  content:\"\\f2df\";\n}\n\n.bp5-icon-tag-add::before{\n  content:\"\\f379\";\n}\n\n.bp5-icon-tag-promote::before{\n  content:\"\\f37a\";\n}\n\n.bp5-icon-tag-refresh::before{\n  content:\"\\f37b\";\n}\n\n.bp5-icon-tag-undo::before{\n  content:\"\\f37c\";\n}\n\n.bp5-icon-tags::before{\n  content:\"\\f37d\";\n}\n\n.bp5-icon-take-action::before{\n  content:\"\\f2e0\";\n}\n\n.bp5-icon-tank::before{\n  content:\"\\f2e1\";\n}\n\n.bp5-icon-target::before{\n  content:\"\\f2e2\";\n}\n\n.bp5-icon-taxi::before{\n  content:\"\\f2e3\";\n}\n\n.bp5-icon-team::before{\n  content:\"\\f352\";\n}\n\n.bp5-icon-temperature::before{\n  content:\"\\f2e4\";\n}\n\n.bp5-icon-text-highlight::before{\n  content:\"\\f2e5\";\n}\n\n.bp5-icon-th::before{\n  content:\"\\f2ea\";\n}\n\n.bp5-icon-th-add::before{\n  content:\"\\f38a\";\n}\n\n.bp5-icon-th-derived::before{\n  content:\"\\f2e6\";\n}\n\n.bp5-icon-th-disconnect::before{\n  content:\"\\f2e7\";\n}\n\n.bp5-icon-th-filtered::before{\n  content:\"\\f2e8\";\n}\n\n.bp5-icon-th-list::before{\n  content:\"\\f2e9\";\n}\n\n.bp5-icon-th-list-add::before{\n  content:\"\\f38b\";\n}\n\n.bp5-icon-th-virtual::before{\n  content:\"\\f38c\";\n}\n\n.bp5-icon-th-virtual-add::before{\n  content:\"\\f38d\";\n}\n\n.bp5-icon-third-party::before{\n  content:\"\\f2eb\";\n}\n\n.bp5-icon-thumbs-down::before{\n  content:\"\\f2ec\";\n}\n\n.bp5-icon-thumbs-up::before{\n  content:\"\\f2ed\";\n}\n\n.bp5-icon-tick::before{\n  content:\"\\f2ef\";\n}\n\n.bp5-icon-tick-circle::before{\n  content:\"\\f2ee\";\n}\n\n.bp5-icon-time::before{\n  content:\"\\f2f0\";\n}\n\n.bp5-icon-timeline-area-chart::before{\n  content:\"\\f2f1\";\n}\n\n.bp5-icon-timeline-bar-chart::before{\n  content:\"\\f2f2\";\n}\n\n.bp5-icon-timeline-events::before{\n  content:\"\\f2f3\";\n}\n\n.bp5-icon-timeline-line-chart::before{\n  content:\"\\f2f4\";\n}\n\n.bp5-icon-tint::before{\n  content:\"\\f2f5\";\n}\n\n.bp5-icon-torch::before{\n  content:\"\\f2f6\";\n}\n\n.bp5-icon-tractor::before{\n  content:\"\\f2f7\";\n}\n\n.bp5-icon-train::before{\n  content:\"\\f2f8\";\n}\n\n.bp5-icon-translate::before{\n  content:\"\\f2f9\";\n}\n\n.bp5-icon-trash::before{\n  content:\"\\f2fa\";\n}\n\n.bp5-icon-tree::before{\n  content:\"\\f2fb\";\n}\n\n.bp5-icon-trending-down::before{\n  content:\"\\f2fc\";\n}\n\n.bp5-icon-trending-up::before{\n  content:\"\\f2fd\";\n}\n\n.bp5-icon-trophy::before{\n  content:\"\\f34f\";\n}\n\n.bp5-icon-truck::before{\n  content:\"\\f2fe\";\n}\n\n.bp5-icon-two-columns::before{\n  content:\"\\f2ff\";\n}\n\n.bp5-icon-unarchive::before{\n  content:\"\\f300\";\n}\n\n.bp5-icon-underline::before{\n  content:\"\\f301\";\n}\n\n.bp5-icon-undo::before{\n  content:\"\\f302\";\n}\n\n.bp5-icon-ungroup-objects::before{\n  content:\"\\f303\";\n}\n\n.bp5-icon-unknown-vehicle::before{\n  content:\"\\f304\";\n}\n\n.bp5-icon-unlink::before{\n  content:\"\\f345\";\n}\n\n.bp5-icon-unlock::before{\n  content:\"\\f305\";\n}\n\n.bp5-icon-unpin::before{\n  content:\"\\f306\";\n}\n\n.bp5-icon-unresolve::before{\n  content:\"\\f307\";\n}\n\n.bp5-icon-updated::before{\n  content:\"\\f308\";\n}\n\n.bp5-icon-upload::before{\n  content:\"\\f309\";\n}\n\n.bp5-icon-user::before{\n  content:\"\\f30a\";\n}\n\n.bp5-icon-variable::before{\n  content:\"\\f30b\";\n}\n\n.bp5-icon-vector::before{\n  content:\"\\f35e\";\n}\n\n.bp5-icon-vertical-bar-chart-asc::before{\n  content:\"\\f30c\";\n}\n\n.bp5-icon-vertical-bar-chart-desc::before{\n  content:\"\\f30d\";\n}\n\n.bp5-icon-vertical-distribution::before{\n  content:\"\\f30e\";\n}\n\n.bp5-icon-vertical-inbetween::before{\n  content:\"\\f32a\";\n}\n\n.bp5-icon-video::before{\n  content:\"\\f30f\";\n}\n\n.bp5-icon-virus::before{\n  content:\"\\f310\";\n}\n\n.bp5-icon-volume-down::before{\n  content:\"\\f311\";\n}\n\n.bp5-icon-volume-off::before{\n  content:\"\\f312\";\n}\n\n.bp5-icon-volume-up::before{\n  content:\"\\f313\";\n}\n\n.bp5-icon-walk::before{\n  content:\"\\f314\";\n}\n\n.bp5-icon-warning-sign::before{\n  content:\"\\f315\";\n}\n\n.bp5-icon-waterfall-chart::before{\n  content:\"\\f316\";\n}\n\n.bp5-icon-waves::before{\n  content:\"\\f317\";\n}\n\n.bp5-icon-widget::before{\n  content:\"\\f31b\";\n}\n\n.bp5-icon-widget-button::before{\n  content:\"\\f318\";\n}\n\n.bp5-icon-widget-footer::before{\n  content:\"\\f319\";\n}\n\n.bp5-icon-widget-header::before{\n  content:\"\\f31a\";\n}\n\n.bp5-icon-wind::before{\n  content:\"\\f31c\";\n}\n\n.bp5-icon-wrench::before{\n  content:\"\\f31d\";\n}\n\n.bp5-icon-wrench-redo::before{\n  content:\"\\f37e\";\n}\n\n.bp5-icon-wrench-snooze::before{\n  content:\"\\f37f\";\n}\n\n.bp5-icon-wrench-time::before{\n  content:\"\\f380\";\n}\n\n.bp5-icon-zoom-in::before{\n  content:\"\\f31e\";\n}\n\n.bp5-icon-zoom-out::before{\n  content:\"\\f31f\";\n}\n\n.bp5-icon-zoom-to-fit::before{\n  content:\"\\f320\";\n}\n\n.bp5-submenu > .bp5-popover-wrapper{\n  display:block; }\n\n.bp5-submenu .bp5-popover-target{\n  display:block; }\n  .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{ }\n\n.bp5-submenu.bp5-popover{\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  padding:0 5px; }\n  .bp5-submenu.bp5-popover > .bp5-popover-content{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-submenu.bp5-popover, .bp5-submenu.bp5-popover.bp5-dark{\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n    .bp5-dark .bp5-submenu.bp5-popover > .bp5-popover-content, .bp5-submenu.bp5-popover.bp5-dark > .bp5-popover-content{\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n.bp5-menu{\n  background:#ffffff;\n  border-radius:3px;\n  color:#182026;\n  list-style:none;\n  margin:0;\n  min-width:180px;\n  padding:5px;\n  text-align:left; }\n\n.bp5-menu-divider{\n  border-top:1px solid rgba(16, 22, 26, 0.15);\n  display:block;\n  margin:5px; }\n  .bp5-dark .bp5-menu-divider{\n    border-top-color:rgba(255, 255, 255, 0.15); }\n\n.bp5-menu-item{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:start;\n      -ms-flex-align:start;\n          align-items:flex-start;\n  border-radius:2px;\n  color:inherit;\n  line-height:20px;\n  padding:5px 7px;\n  text-decoration:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-menu-item > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-menu-item > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-menu-item::before,\n  .bp5-menu-item > *{\n    margin-right:7px; }\n  .bp5-menu-item:empty::before,\n  .bp5-menu-item > :last-child{\n    margin-right:0; }\n  .bp5-menu-item > .bp5-fill{\n    word-break:break-word; }\n  .bp5-menu-item:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n    background-color:rgba(167, 182, 194, 0.3);\n    cursor:pointer;\n    text-decoration:none; }\n  .bp5-menu-item.bp5-disabled{\n    background-color:inherit;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n  .bp5-dark .bp5-menu-item{\n    color:inherit; }\n    .bp5-dark .bp5-menu-item:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n      background-color:rgba(138, 155, 168, 0.15);\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-disabled{\n      background-color:inherit;\n      color:rgba(167, 182, 194, 0.6); }\n  .bp5-menu-item.bp5-intent-primary{\n    color:#106ba3; }\n    .bp5-menu-item.bp5-intent-primary .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-primary::before, .bp5-menu-item.bp5-intent-primary::after,\n    .bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n      color:#106ba3; }\n    .bp5-menu-item.bp5-intent-primary:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-menu-item.bp5-intent-primary.bp5-active{\n      background-color:#137cbd; }\n    .bp5-menu-item.bp5-intent-primary:active{\n      background-color:#106ba3; }\n    .bp5-menu-item.bp5-intent-primary:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-menu-item.bp5-intent-primary:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::before, .bp5-menu-item.bp5-intent-primary:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-primary:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-primary:active, .bp5-menu-item.bp5-intent-primary:active::before, .bp5-menu-item.bp5-intent-primary:active::after,\n    .bp5-menu-item.bp5-intent-primary:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-primary.bp5-active, .bp5-menu-item.bp5-intent-primary.bp5-active::before, .bp5-menu-item.bp5-intent-primary.bp5-active::after,\n    .bp5-menu-item.bp5-intent-primary.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item.bp5-intent-success{\n    color:#0d8050; }\n    .bp5-menu-item.bp5-intent-success .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-success::before, .bp5-menu-item.bp5-intent-success::after,\n    .bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n      color:#0d8050; }\n    .bp5-menu-item.bp5-intent-success:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-menu-item.bp5-intent-success.bp5-active{\n      background-color:#0f9960; }\n    .bp5-menu-item.bp5-intent-success:active{\n      background-color:#0d8050; }\n    .bp5-menu-item.bp5-intent-success:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-menu-item.bp5-intent-success:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::before, .bp5-menu-item.bp5-intent-success:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-success:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-success:active, .bp5-menu-item.bp5-intent-success:active::before, .bp5-menu-item.bp5-intent-success:active::after,\n    .bp5-menu-item.bp5-intent-success:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-success.bp5-active, .bp5-menu-item.bp5-intent-success.bp5-active::before, .bp5-menu-item.bp5-intent-success.bp5-active::after,\n    .bp5-menu-item.bp5-intent-success.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item.bp5-intent-warning{\n    color:#bf7326; }\n    .bp5-menu-item.bp5-intent-warning .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-warning::before, .bp5-menu-item.bp5-intent-warning::after,\n    .bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n      color:#bf7326; }\n    .bp5-menu-item.bp5-intent-warning:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-menu-item.bp5-intent-warning.bp5-active{\n      background-color:#d9822b; }\n    .bp5-menu-item.bp5-intent-warning:active{\n      background-color:#bf7326; }\n    .bp5-menu-item.bp5-intent-warning:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-menu-item.bp5-intent-warning:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::before, .bp5-menu-item.bp5-intent-warning:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-warning:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-warning:active, .bp5-menu-item.bp5-intent-warning:active::before, .bp5-menu-item.bp5-intent-warning:active::after,\n    .bp5-menu-item.bp5-intent-warning:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-warning.bp5-active, .bp5-menu-item.bp5-intent-warning.bp5-active::before, .bp5-menu-item.bp5-intent-warning.bp5-active::after,\n    .bp5-menu-item.bp5-intent-warning.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item.bp5-intent-danger{\n    color:#c23030; }\n    .bp5-menu-item.bp5-intent-danger .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-danger::before, .bp5-menu-item.bp5-intent-danger::after,\n    .bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n      color:#c23030; }\n    .bp5-menu-item.bp5-intent-danger:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-menu-item.bp5-intent-danger.bp5-active{\n      background-color:#db3737; }\n    .bp5-menu-item.bp5-intent-danger:active{\n      background-color:#c23030; }\n    .bp5-menu-item.bp5-intent-danger:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-menu-item.bp5-intent-danger:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::before, .bp5-menu-item.bp5-intent-danger:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-danger:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-danger:active, .bp5-menu-item.bp5-intent-danger:active::before, .bp5-menu-item.bp5-intent-danger:active::after,\n    .bp5-menu-item.bp5-intent-danger:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-danger.bp5-active, .bp5-menu-item.bp5-intent-danger.bp5-active::before, .bp5-menu-item.bp5-intent-danger.bp5-active::after,\n    .bp5-menu-item.bp5-intent-danger.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item::before{\n    font-family:\"blueprint-icons-16\", sans-serif;\n    font-size:16px;\n    font-style:normal;\n    font-weight:400;\n    line-height:1;\n    -moz-osx-font-smoothing:grayscale;\n    -webkit-font-smoothing:antialiased;\n    margin-right:7px; }\n  .bp5-menu-item::before,\n  .bp5-menu-item > .bp5-icon{\n    color:#5c7080;\n    margin-top:2px; }\n  .bp5-menu-item .bp5-menu-item-label{\n    color:#5c7080; }\n  .bp5-menu-item:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n    color:inherit; }\n  .bp5-menu-item.bp5-active, .bp5-menu-item:active{\n    background-color:rgba(115, 134, 148, 0.3); }\n  .bp5-menu-item.bp5-disabled{\n    background-color:inherit !important;\n    color:rgba(92, 112, 128, 0.6) !important;\n    cursor:not-allowed !important;\n    outline:none !important; }\n    .bp5-menu-item.bp5-disabled::before,\n    .bp5-menu-item.bp5-disabled > .bp5-icon,\n    .bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n      color:rgba(92, 112, 128, 0.6) !important; }\n  .bp5-large .bp5-menu-item{\n    font-size:16px;\n    line-height:22px;\n    padding:9px 7px; }\n    .bp5-large .bp5-menu-item .bp5-icon{\n      margin-top:3px; }\n    .bp5-large .bp5-menu-item::before{\n      font-family:\"blueprint-icons-20\", sans-serif;\n      font-size:20px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1;\n      -moz-osx-font-smoothing:grayscale;\n      -webkit-font-smoothing:antialiased;\n      margin-right:10px;\n      margin-top:1px; }\n\nbutton.bp5-menu-item{\n  background:none;\n  border:none;\n  text-align:left;\n  width:100%; }\n.bp5-menu-header{\n  border-top:1px solid rgba(16, 22, 26, 0.15);\n  display:block;\n  margin:5px;\n  cursor:default;\n  padding-left:2px; }\n  .bp5-dark .bp5-menu-header{\n    border-top-color:rgba(255, 255, 255, 0.15); }\n  .bp5-menu-header:first-of-type{\n    border-top:none; }\n  .bp5-menu-header > h6{\n    color:#182026;\n    font-weight:600;\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    line-height:17px;\n    margin:0;\n    padding:10px 7px 0 1px; }\n    .bp5-dark .bp5-menu-header > h6{\n      color:#f5f8fa; }\n  .bp5-menu-header:first-of-type > h6{\n    padding-top:0; }\n  .bp5-large .bp5-menu-header > h6{\n    font-size:18px;\n    padding-bottom:5px;\n    padding-top:15px; }\n  .bp5-large .bp5-menu-header:first-of-type > h6{\n    padding-top:0; }\n\n.bp5-dark .bp5-menu{\n  background:#30404d;\n  color:#f5f8fa; }\n\n.bp5-dark .bp5-menu-item{ }\n  .bp5-dark .bp5-menu-item.bp5-intent-primary{\n    color:#48aff0; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary::before, .bp5-dark .bp5-menu-item.bp5-intent-primary::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n      color:#48aff0; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active{\n      background-color:#137cbd; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:active{\n      background-color:#106ba3; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-primary:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-primary:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-primary:active, .bp5-dark .bp5-menu-item.bp5-intent-primary:active::before, .bp5-dark .bp5-menu-item.bp5-intent-primary:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item.bp5-intent-success{\n    color:#3dcc91; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success::before, .bp5-dark .bp5-menu-item.bp5-intent-success::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n      color:#3dcc91; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active{\n      background-color:#0f9960; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success:active{\n      background-color:#0d8050; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-success:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-success:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-success:active, .bp5-dark .bp5-menu-item.bp5-intent-success:active::before, .bp5-dark .bp5-menu-item.bp5-intent-success:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item.bp5-intent-warning{\n    color:#ffb366; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning::before, .bp5-dark .bp5-menu-item.bp5-intent-warning::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n      color:#ffb366; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active{\n      background-color:#d9822b; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:active{\n      background-color:#bf7326; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-warning:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-warning:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-warning:active, .bp5-dark .bp5-menu-item.bp5-intent-warning:active::before, .bp5-dark .bp5-menu-item.bp5-intent-warning:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item.bp5-intent-danger{\n    color:#ff7373; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger::before, .bp5-dark .bp5-menu-item.bp5-intent-danger::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n      color:#ff7373; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active{\n      background-color:#db3737; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:active{\n      background-color:#c23030; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-danger:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-danger:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-danger:active, .bp5-dark .bp5-menu-item.bp5-intent-danger:active::before, .bp5-dark .bp5-menu-item.bp5-intent-danger:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item::before,\n  .bp5-dark .bp5-menu-item > .bp5-icon{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-menu-item .bp5-menu-item-label{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-menu-item.bp5-active, .bp5-dark .bp5-menu-item:active{\n    background-color:rgba(138, 155, 168, 0.3); }\n  .bp5-dark .bp5-menu-item.bp5-disabled{\n    color:rgba(167, 182, 194, 0.6) !important; }\n    .bp5-dark .bp5-menu-item.bp5-disabled::before,\n    .bp5-dark .bp5-menu-item.bp5-disabled > .bp5-icon,\n    .bp5-dark .bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n      color:rgba(167, 182, 194, 0.6) !important; }\n\n.bp5-dark .bp5-menu-divider,\n.bp5-dark .bp5-menu-header{\n  border-color:rgba(255, 255, 255, 0.15); }\n\n.bp5-dark .bp5-menu-header > h6{\n  color:#f5f8fa; }\n\n.bp5-label .bp5-menu{\n  margin-top:5px; }\n.bp5-navbar{\n  background-color:#ffffff;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n  height:50px;\n  padding:0 15px;\n  position:relative;\n  width:100%;\n  z-index:10; }\n  .bp5-navbar.bp5-dark,\n  .bp5-dark .bp5-navbar{\n    background-color:#394b59; }\n  .bp5-navbar.bp5-dark{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-navbar{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-navbar.bp5-fixed-top{\n    left:0;\n    position:fixed;\n    right:0;\n    top:0; }\n\n.bp5-navbar-heading{\n  font-size:16px;\n  margin-right:15px; }\n\n.bp5-navbar-group{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:50px; }\n  .bp5-navbar-group.bp5-align-left{\n    float:left; }\n  .bp5-navbar-group.bp5-align-right{\n    float:right; }\n\n.bp5-navbar-divider{\n  border-left:1px solid rgba(16, 22, 26, 0.15);\n  height:20px;\n  margin:0 10px; }\n  .bp5-dark .bp5-navbar-divider{\n    border-left-color:rgba(255, 255, 255, 0.15); }\n.bp5-non-ideal-state{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  height:100%;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  text-align:center;\n  width:100%; }\n  .bp5-non-ideal-state > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-non-ideal-state > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-non-ideal-state::before,\n  .bp5-non-ideal-state > *{\n    margin-bottom:20px; }\n  .bp5-non-ideal-state:empty::before,\n  .bp5-non-ideal-state > :last-child{\n    margin-bottom:0; }\n  .bp5-non-ideal-state > *{\n    max-width:400px; }\n\n.bp5-non-ideal-state-visual{\n  color:rgba(92, 112, 128, 0.6);\n  font-size:60px; }\n  .bp5-dark .bp5-non-ideal-state-visual{\n    color:rgba(167, 182, 194, 0.6); }\n\n.bp5-overflow-list{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-wrap:nowrap;\n      flex-wrap:nowrap;\n  min-width:0; }\n\n.bp5-overflow-list-spacer{\n  -ms-flex-negative:1;\n      flex-shrink:1;\n  width:1px; }\nbody.bp5-overlay-open{\n  overflow:hidden; }\n\n.bp5-overlay{\n  bottom:0;\n  left:0;\n  position:static;\n  right:0;\n  top:0;\n  z-index:20; }\n  .bp5-overlay:not(.bp5-overlay-open){\n    pointer-events:none; }\n  .bp5-overlay.bp5-overlay-container{\n    overflow:hidden;\n    position:fixed; }\n    .bp5-overlay.bp5-overlay-container.bp5-overlay-inline{\n      position:absolute; }\n  .bp5-overlay.bp5-overlay-scroll-container{\n    overflow:auto;\n    position:fixed; }\n    .bp5-overlay.bp5-overlay-scroll-container.bp5-overlay-inline{\n      position:absolute; }\n  .bp5-overlay.bp5-overlay-inline{\n    display:inline;\n    overflow:visible; }\n\n.bp5-overlay-content{\n  position:fixed;\n  z-index:20; }\n  .bp5-overlay-inline .bp5-overlay-content,\n  .bp5-overlay-scroll-container .bp5-overlay-content{\n    position:absolute; }\n\n.bp5-overlay-backdrop{\n  bottom:0;\n  left:0;\n  position:fixed;\n  right:0;\n  top:0;\n  opacity:1;\n  background-color:rgba(16, 22, 26, 0.7);\n  overflow:auto;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  z-index:20; }\n  .bp5-overlay-backdrop.bp5-overlay-enter, .bp5-overlay-backdrop.bp5-overlay-appear{\n    opacity:0; }\n  .bp5-overlay-backdrop.bp5-overlay-enter-active, .bp5-overlay-backdrop.bp5-overlay-appear-active{\n    opacity:1;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:200ms;\n            transition-duration:200ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-overlay-backdrop.bp5-overlay-exit{\n    opacity:1; }\n  .bp5-overlay-backdrop.bp5-overlay-exit-active{\n    opacity:0;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:200ms;\n            transition-duration:200ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-overlay-backdrop:focus{\n    outline:none; }\n  .bp5-overlay-inline .bp5-overlay-backdrop{\n    position:absolute; }\n.bp5-panel-stack{\n  overflow:hidden;\n  position:relative; }\n\n.bp5-panel-stack-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  -webkit-box-shadow:0 1px rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-negative:0;\n      flex-shrink:0;\n  height:30px;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack-header{\n    -webkit-box-shadow:0 1px rgba(255, 255, 255, 0.15);\n            box-shadow:0 1px rgba(255, 255, 255, 0.15); }\n  .bp5-panel-stack-header > span{\n    -webkit-box-align:stretch;\n        -ms-flex-align:stretch;\n            align-items:stretch;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-flex:1;\n        -ms-flex:1;\n            flex:1; }\n  .bp5-panel-stack-header .bp5-heading{\n    margin:0 5px; }\n\n.bp5-button.bp5-panel-stack-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap; }\n  .bp5-button.bp5-panel-stack-header-back .bp5-icon{\n    margin:0 2px; }\n\n.bp5-panel-stack-view{\n  bottom:0;\n  left:0;\n  position:absolute;\n  right:0;\n  top:0;\n  background-color:#ffffff;\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack-view{\n    background-color:#30404d; }\n  .bp5-panel-stack-view:nth-last-child(n + 4){\n    display:none; }\n\n.bp5-panel-stack-push .bp5-panel-stack-enter, .bp5-panel-stack-push .bp5-panel-stack-appear{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0; }\n\n.bp5-panel-stack-push .bp5-panel-stack-enter-active, .bp5-panel-stack-push .bp5-panel-stack-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack-push .bp5-panel-stack-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack-push .bp5-panel-stack-exit-active{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-enter, .bp5-panel-stack-pop .bp5-panel-stack-appear{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-enter-active, .bp5-panel-stack-pop .bp5-panel-stack-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-exit-active{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n.bp5-panel-stack2{\n  overflow:hidden;\n  position:relative; }\n\n.bp5-panel-stack2-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  -webkit-box-shadow:0 1px rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-negative:0;\n      flex-shrink:0;\n  height:30px;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack2-header{\n    -webkit-box-shadow:0 1px rgba(255, 255, 255, 0.15);\n            box-shadow:0 1px rgba(255, 255, 255, 0.15); }\n  .bp5-panel-stack2-header > span{\n    -webkit-box-align:stretch;\n        -ms-flex-align:stretch;\n            align-items:stretch;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-flex:1;\n        -ms-flex:1;\n            flex:1; }\n  .bp5-panel-stack2-header .bp5-heading{\n    margin:0 5px; }\n\n.bp5-button.bp5-panel-stack2-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap; }\n  .bp5-button.bp5-panel-stack2-header-back .bp5-icon{\n    margin:0 2px; }\n\n.bp5-panel-stack2-view{\n  bottom:0;\n  left:0;\n  position:absolute;\n  right:0;\n  top:0;\n  background-color:#ffffff;\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack2-view{\n    background-color:#30404d; }\n  .bp5-panel-stack2-view:nth-last-child(n + 4){\n    display:none; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-enter, .bp5-panel-stack2-push .bp5-panel-stack2-appear{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-enter-active, .bp5-panel-stack2-push .bp5-panel-stack2-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-exit-active{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter, .bp5-panel-stack2-pop .bp5-panel-stack2-appear{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter-active, .bp5-panel-stack2-pop .bp5-panel-stack2-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit-active{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n.bp5-popover{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  -webkit-transform:scale(1);\n          transform:scale(1);\n  border-radius:3px;\n  display:inline-block;\n  z-index:20; }\n  .bp5-popover .bp5-popover-arrow{\n    height:30px;\n    position:absolute;\n    width:30px; }\n    .bp5-popover .bp5-popover-arrow::before{\n      height:20px;\n      margin:5px;\n      width:20px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-popover{\n    margin-bottom:17px;\n    margin-top:-17px; }\n    .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-popover > .bp5-popover-arrow{\n      bottom:-11px; }\n      .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(-90deg);\n                transform:rotate(-90deg); }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-popover{\n    margin-left:17px; }\n    .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-popover > .bp5-popover-arrow{\n      left:-11px; }\n      .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(0);\n                transform:rotate(0); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-popover{\n    margin-top:17px; }\n    .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-popover > .bp5-popover-arrow{\n      top:-11px; }\n      .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(90deg);\n                transform:rotate(90deg); }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-popover{\n    margin-left:-17px;\n    margin-right:17px; }\n    .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-popover > .bp5-popover-arrow{\n      right:-11px; }\n      .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(180deg);\n                transform:rotate(180deg); }\n  .bp5-tether-element-attached-middle > .bp5-popover > .bp5-popover-arrow{\n    top:50%;\n    -webkit-transform:translateY(-50%);\n            transform:translateY(-50%); }\n  .bp5-tether-element-attached-center > .bp5-popover > .bp5-popover-arrow{\n    right:50%;\n    -webkit-transform:translateX(50%);\n            transform:translateX(50%); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-top > .bp5-popover > .bp5-popover-arrow{\n    top:-0.3934px; }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-right > .bp5-popover > .bp5-popover-arrow{\n    right:-0.3934px; }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-left > .bp5-popover > .bp5-popover-arrow{\n    left:-0.3934px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-bottom > .bp5-popover > .bp5-popover-arrow{\n    bottom:-0.3934px; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-left > .bp5-popover{\n    -webkit-transform-origin:top left;\n            transform-origin:top left; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-center > .bp5-popover{\n    -webkit-transform-origin:top center;\n            transform-origin:top center; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-right > .bp5-popover{\n    -webkit-transform-origin:top right;\n            transform-origin:top right; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-left > .bp5-popover{\n    -webkit-transform-origin:center left;\n            transform-origin:center left; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-center > .bp5-popover{\n    -webkit-transform-origin:center center;\n            transform-origin:center center; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-right > .bp5-popover{\n    -webkit-transform-origin:center right;\n            transform-origin:center right; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-left > .bp5-popover{\n    -webkit-transform-origin:bottom left;\n            transform-origin:bottom left; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-center > .bp5-popover{\n    -webkit-transform-origin:bottom center;\n            transform-origin:bottom center; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-right > .bp5-popover{\n    -webkit-transform-origin:bottom right;\n            transform-origin:bottom right; }\n  .bp5-popover .bp5-popover-content{\n    background:#ffffff; }\n  .bp5-popover .bp5-popover-content,\n  .bp5-popover .bp5-heading{\n    color:inherit; }\n  .bp5-popover .bp5-popover-arrow::before{\n    -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2);\n            box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-popover .bp5-popover-arrow-border{\n    fill:#10161a;\n    fill-opacity:0.1; }\n  .bp5-popover .bp5-popover-arrow-fill{\n    fill:#ffffff; }\n  .bp5-popover-enter > .bp5-popover, .bp5-popover-appear > .bp5-popover{\n    -webkit-transform:scale(0.3);\n            transform:scale(0.3); }\n  .bp5-popover-enter-active > .bp5-popover, .bp5-popover-appear-active > .bp5-popover{\n    -webkit-transform:scale(1);\n            transform:scale(1);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-popover-exit > .bp5-popover{\n    -webkit-transform:scale(1);\n            transform:scale(1); }\n  .bp5-popover-exit-active > .bp5-popover{\n    -webkit-transform:scale(0.3);\n            transform:scale(0.3);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-popover .bp5-popover-content{\n    border-radius:3px;\n    position:relative; }\n  .bp5-popover.bp5-popover-content-sizing .bp5-popover-content{\n    max-width:350px;\n    padding:20px; }\n  .bp5-popover-target + .bp5-overlay .bp5-popover.bp5-popover-content-sizing{\n    width:350px; }\n  .bp5-popover.bp5-minimal{\n    margin:0 !important; }\n    .bp5-popover.bp5-minimal .bp5-popover-arrow{\n      display:none; }\n    .bp5-popover.bp5-minimal.bp5-popover{\n      -webkit-transform:scale(1);\n              transform:scale(1); }\n      .bp5-popover-enter > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1); }\n      .bp5-popover-enter-active > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear-active > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1);\n        -webkit-transition-delay:0;\n                transition-delay:0;\n        -webkit-transition-duration:100ms;\n                transition-duration:100ms;\n        -webkit-transition-property:-webkit-transform;\n        transition-property:-webkit-transform;\n        transition-property:transform;\n        transition-property:transform, -webkit-transform;\n        -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n                transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n      .bp5-popover-exit > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1); }\n      .bp5-popover-exit-active > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1);\n        -webkit-transition-delay:0;\n                transition-delay:0;\n        -webkit-transition-duration:100ms;\n                transition-duration:100ms;\n        -webkit-transition-property:-webkit-transform;\n        transition-property:-webkit-transform;\n        transition-property:transform;\n        transition-property:transform, -webkit-transform;\n        -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n                transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-popover.bp5-dark,\n  .bp5-dark .bp5-popover{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n    .bp5-popover.bp5-dark .bp5-popover-content,\n    .bp5-dark .bp5-popover .bp5-popover-content{\n      background:#30404d; }\n    .bp5-popover.bp5-dark .bp5-popover-content,\n    .bp5-popover.bp5-dark .bp5-heading,\n    .bp5-dark .bp5-popover .bp5-popover-content,\n    .bp5-dark .bp5-popover .bp5-heading{\n      color:inherit; }\n    .bp5-popover.bp5-dark .bp5-popover-arrow::before,\n    .bp5-dark .bp5-popover .bp5-popover-arrow::before{\n      -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4);\n              box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4); }\n    .bp5-popover.bp5-dark .bp5-popover-arrow-border,\n    .bp5-dark .bp5-popover .bp5-popover-arrow-border{\n      fill:#10161a;\n      fill-opacity:0.2; }\n    .bp5-popover.bp5-dark .bp5-popover-arrow-fill,\n    .bp5-dark .bp5-popover .bp5-popover-arrow-fill{\n      fill:#30404d; }\n\n.bp5-popover-arrow::before{\n  border-radius:2px;\n  content:\"\";\n  display:block;\n  position:absolute;\n  -webkit-transform:rotate(45deg);\n          transform:rotate(45deg); }\n\n.bp5-tether-pinned .bp5-popover-arrow{\n  display:none; }\n\n.bp5-popover-backdrop{\n  background:rgba(255, 255, 255, 0); }\n\n.bp5-transition-container{\n  opacity:1;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  z-index:20; }\n  .bp5-transition-container.bp5-popover-enter, .bp5-transition-container.bp5-popover-appear{\n    opacity:0; }\n  .bp5-transition-container.bp5-popover-enter-active, .bp5-transition-container.bp5-popover-appear-active{\n    opacity:1;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-transition-container.bp5-popover-exit{\n    opacity:1; }\n  .bp5-transition-container.bp5-popover-exit-active{\n    opacity:0;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-transition-container:focus{\n    outline:none; }\n  .bp5-transition-container.bp5-popover-leave .bp5-popover-content{\n    pointer-events:none; }\n  .bp5-transition-container[data-x-out-of-boundaries]{\n    display:none; }\n\nspan.bp5-popover-target{\n  display:inline-block; }\n\n.bp5-popover-wrapper.bp5-fill{\n  width:100%; }\n\n.bp5-portal{\n  left:0;\n  position:absolute;\n  right:0;\n  top:0; }\n@-webkit-keyframes linear-progress-bar-stripes{\n  from{\n    background-position:0 0; }\n  to{\n    background-position:30px 0; } }\n@keyframes linear-progress-bar-stripes{\n  from{\n    background-position:0 0; }\n  to{\n    background-position:30px 0; } }\n\n.bp5-progress-bar{\n  background:rgba(92, 112, 128, 0.2);\n  border-radius:40px;\n  display:block;\n  height:8px;\n  overflow:hidden;\n  position:relative;\n  width:100%; }\n  .bp5-progress-bar .bp5-progress-meter{\n    background:linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%);\n    background-color:rgba(92, 112, 128, 0.8);\n    background-size:30px 30px;\n    border-radius:40px;\n    height:100%;\n    position:absolute;\n    -webkit-transition:width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    width:100%; }\n  .bp5-progress-bar:not(.bp5-no-animation):not(.bp5-no-stripes) .bp5-progress-meter{\n    animation:linear-progress-bar-stripes 300ms linear infinite reverse; }\n  .bp5-progress-bar.bp5-no-stripes .bp5-progress-meter{\n    background-image:none; }\n\n.bp5-dark .bp5-progress-bar{\n  background:rgba(16, 22, 26, 0.5); }\n  .bp5-dark .bp5-progress-bar .bp5-progress-meter{\n    background-color:#8a9ba8; }\n\n.bp5-progress-bar.bp5-intent-primary .bp5-progress-meter{\n  background-color:#137cbd; }\n\n.bp5-progress-bar.bp5-intent-success .bp5-progress-meter{\n  background-color:#0f9960; }\n\n.bp5-progress-bar.bp5-intent-warning .bp5-progress-meter{\n  background-color:#d9822b; }\n\n.bp5-progress-bar.bp5-intent-danger .bp5-progress-meter{\n  background-color:#db3737; }\n@-webkit-keyframes skeleton-glow{\n  from{\n    background:rgba(206, 217, 224, 0.2);\n    border-color:rgba(206, 217, 224, 0.2); }\n  to{\n    background:rgba(92, 112, 128, 0.2);\n    border-color:rgba(92, 112, 128, 0.2); } }\n@keyframes skeleton-glow{\n  from{\n    background:rgba(206, 217, 224, 0.2);\n    border-color:rgba(206, 217, 224, 0.2); }\n  to{\n    background:rgba(92, 112, 128, 0.2);\n    border-color:rgba(92, 112, 128, 0.2); } }\n.bp5-skeleton{\n  -webkit-animation:1000ms linear infinite alternate skeleton-glow;\n          animation:1000ms linear infinite alternate skeleton-glow;\n  background:rgba(206, 217, 224, 0.2);\n  background-clip:padding-box !important;\n  border-color:rgba(206, 217, 224, 0.2) !important;\n  border-radius:2px;\n  -webkit-box-shadow:none !important;\n          box-shadow:none !important;\n  color:transparent !important;\n  cursor:default;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-skeleton::before, .bp5-skeleton::after,\n  .bp5-skeleton *{\n    visibility:hidden !important; }\n.bp5-slider{\n  height:40px;\n  min-width:150px;\n  width:100%;\n  cursor:default;\n  outline:none;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-slider:hover{\n    cursor:pointer; }\n  .bp5-slider:active{\n    cursor:-webkit-grabbing;\n    cursor:grabbing; }\n  .bp5-slider.bp5-disabled{\n    cursor:not-allowed;\n    opacity:0.5; }\n  .bp5-slider.bp5-slider-unlabeled{\n    height:16px; }\n\n.bp5-slider-track,\n.bp5-slider-progress{\n  height:6px;\n  left:0;\n  right:0;\n  top:5px;\n  position:absolute; }\n\n.bp5-slider-track{\n  border-radius:3px;\n  overflow:hidden; }\n\n.bp5-slider-progress{\n  background:rgba(92, 112, 128, 0.2); }\n  .bp5-dark .bp5-slider-progress{\n    background:rgba(16, 22, 26, 0.5); }\n  .bp5-slider-progress.bp5-intent-primary{\n    background-color:#137cbd; }\n  .bp5-slider-progress.bp5-intent-success{\n    background-color:#0f9960; }\n  .bp5-slider-progress.bp5-intent-warning{\n    background-color:#d9822b; }\n  .bp5-slider-progress.bp5-intent-danger{\n    background-color:#db3737; }\n\n.bp5-slider-handle{\n  background-color:#f5f8fa;\n  background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n  background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n  color:#182026;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n  cursor:pointer;\n  height:16px;\n  left:0;\n  position:absolute;\n  top:0;\n  width:16px; }\n  .bp5-slider-handle:hover{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n  .bp5-slider-handle:active, .bp5-slider-handle.bp5-active{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-slider-handle:disabled, .bp5-slider-handle.bp5-disabled{\n    background-color:rgba(206, 217, 224, 0.5);\n    background-image:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    outline:none; }\n    .bp5-slider-handle:disabled.bp5-active, .bp5-slider-handle:disabled.bp5-active:hover, .bp5-slider-handle.bp5-disabled.bp5-active, .bp5-slider-handle.bp5-disabled.bp5-active:hover{\n      background:rgba(206, 217, 224, 0.7); }\n  .bp5-slider-handle:focus{\n    z-index:1; }\n  .bp5-slider-handle:hover{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n    cursor:-webkit-grab;\n    cursor:grab;\n    z-index:2; }\n  .bp5-slider-handle.bp5-active{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 1px rgba(16, 22, 26, 0.1);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 1px rgba(16, 22, 26, 0.1);\n    cursor:-webkit-grabbing;\n    cursor:grabbing; }\n  .bp5-disabled .bp5-slider-handle{\n    background:#bfccd6;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    pointer-events:none; }\n  .bp5-dark .bp5-slider-handle{\n    background-color:#394b59;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark .bp5-slider-handle:hover, .bp5-dark .bp5-slider-handle:active, .bp5-dark .bp5-slider-handle.bp5-active{\n      color:#f5f8fa; }\n    .bp5-dark .bp5-slider-handle:hover{\n      background-color:#30404d;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-slider-handle:active, .bp5-dark .bp5-slider-handle.bp5-active{\n      background-color:#202b33;\n      background-image:none;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-slider-handle:disabled, .bp5-dark .bp5-slider-handle.bp5-disabled{\n      background-color:rgba(57, 75, 89, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n      .bp5-dark .bp5-slider-handle:disabled.bp5-active, .bp5-dark .bp5-slider-handle.bp5-disabled.bp5-active{\n        background:rgba(57, 75, 89, 0.7); }\n    .bp5-dark .bp5-slider-handle .bp5-button-spinner .bp5-spinner-head{\n      background:rgba(16, 22, 26, 0.5);\n      stroke:#8a9ba8; }\n    .bp5-dark .bp5-slider-handle, .bp5-dark .bp5-slider-handle:hover{\n      background-color:#394b59; }\n    .bp5-dark .bp5-slider-handle.bp5-active{\n      background-color:#293742; }\n  .bp5-dark .bp5-disabled .bp5-slider-handle{\n    background:#5c7080;\n    border-color:#5c7080;\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-slider-handle .bp5-slider-label{\n    background:#394b59;\n    border-radius:3px;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n    color:#f5f8fa;\n    margin-left:8px; }\n    .bp5-dark .bp5-slider-handle .bp5-slider-label{\n      background:#e1e8ed;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n      color:#394b59; }\n    .bp5-disabled .bp5-slider-handle .bp5-slider-label{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-slider-handle.bp5-start, .bp5-slider-handle.bp5-end{\n    width:8px; }\n  .bp5-slider-handle.bp5-start{\n    border-bottom-right-radius:0;\n    border-top-right-radius:0; }\n  .bp5-slider-handle.bp5-end{\n    border-bottom-left-radius:0;\n    border-top-left-radius:0;\n    margin-left:8px; }\n    .bp5-slider-handle.bp5-end .bp5-slider-label{\n      margin-left:0; }\n\n.bp5-slider-label{\n  -webkit-transform:translate(-50%, 20px);\n          transform:translate(-50%, 20px);\n  display:inline-block;\n  font-size:12px;\n  line-height:1;\n  padding:2px 5px;\n  position:absolute;\n  vertical-align:top; }\n\n.bp5-slider.bp5-vertical{\n  height:150px;\n  min-width:40px;\n  width:40px; }\n  .bp5-slider.bp5-vertical .bp5-slider-track,\n  .bp5-slider.bp5-vertical .bp5-slider-progress{\n    bottom:0;\n    height:auto;\n    left:5px;\n    top:0;\n    width:6px; }\n  .bp5-slider.bp5-vertical .bp5-slider-progress{\n    top:auto; }\n  .bp5-slider.bp5-vertical .bp5-slider-label{\n    -webkit-transform:translate(20px, 50%);\n            transform:translate(20px, 50%); }\n  .bp5-slider.bp5-vertical .bp5-slider-handle{\n    top:auto; }\n    .bp5-slider.bp5-vertical .bp5-slider-handle .bp5-slider-label{\n      margin-left:0;\n      margin-top:-8px; }\n    .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end, .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n      height:8px;\n      margin-left:0;\n      width:16px; }\n    .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n      border-bottom-right-radius:3px;\n      border-top-left-radius:0; }\n      .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start .bp5-slider-label{\n        -webkit-transform:translate(20px);\n                transform:translate(20px); }\n    .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end{\n      border-bottom-left-radius:0;\n      border-bottom-right-radius:0;\n      border-top-left-radius:3px;\n      margin-bottom:8px; }\n@-webkit-keyframes pt-spinner-animation{\n  from{\n    -webkit-transform:rotate(0deg);\n            transform:rotate(0deg); }\n  to{\n    -webkit-transform:rotate(360deg);\n            transform:rotate(360deg); } }\n@keyframes pt-spinner-animation{\n  from{\n    -webkit-transform:rotate(0deg);\n            transform:rotate(0deg); }\n  to{\n    -webkit-transform:rotate(360deg);\n            transform:rotate(360deg); } }\n\n.bp5-spinner{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  overflow:visible;\n  vertical-align:middle; }\n  .bp5-spinner svg{\n    display:block; }\n  .bp5-spinner path{\n    fill-opacity:0; }\n  .bp5-spinner .bp5-spinner-head{\n    stroke:rgba(92, 112, 128, 0.8);\n    stroke-linecap:round;\n    -webkit-transform-origin:center;\n            transform-origin:center;\n    -webkit-transition:stroke-dashoffset 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:stroke-dashoffset 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-spinner .bp5-spinner-track{\n    stroke:rgba(92, 112, 128, 0.2); }\n\n.bp5-spinner-animation{\n  -webkit-animation:pt-spinner-animation 500ms linear infinite;\n          animation:pt-spinner-animation 500ms linear infinite; }\n  .bp5-no-spin > .bp5-spinner-animation{\n    -webkit-animation:none;\n            animation:none; }\n\n.bp5-dark .bp5-spinner .bp5-spinner-head{\n  stroke:#8a9ba8; }\n\n.bp5-dark .bp5-spinner .bp5-spinner-track{\n  stroke:rgba(16, 22, 26, 0.5); }\n\n.bp5-spinner.bp5-intent-primary .bp5-spinner-head{\n  stroke:#137cbd; }\n\n.bp5-spinner.bp5-intent-success .bp5-spinner-head{\n  stroke:#0f9960; }\n\n.bp5-spinner.bp5-intent-warning .bp5-spinner-head{\n  stroke:#d9822b; }\n\n.bp5-spinner.bp5-intent-danger .bp5-spinner-head{\n  stroke:#db3737; }\n.bp5-tabs.bp5-vertical{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex; }\n  .bp5-tabs.bp5-vertical > .bp5-tab-list{\n    -webkit-box-align:start;\n        -ms-flex-align:start;\n            align-items:flex-start;\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:column;\n            flex-direction:column; }\n    .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab{\n      border-radius:3px;\n      padding:0 10px;\n      width:100%; }\n      .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab[aria-selected=\"true\"]{\n        background-color:rgba(19, 124, 189, 0.2);\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n    .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n      background-color:rgba(19, 124, 189, 0.2);\n      border-radius:3px;\n      bottom:0;\n      height:auto;\n      left:0;\n      right:0;\n      top:0; }\n  .bp5-tabs.bp5-vertical > .bp5-tab-panel{\n    margin-top:0;\n    padding-left:20px; }\n\n.bp5-tab-list{\n  -webkit-box-align:end;\n      -ms-flex-align:end;\n          align-items:flex-end;\n  border:none;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  list-style:none;\n  margin:0;\n  padding:0;\n  position:relative; }\n  .bp5-tab-list > *:not(:last-child){\n    margin-right:20px; }\n\n.bp5-tab{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  color:#182026;\n  cursor:pointer;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  font-size:14px;\n  line-height:30px;\n  max-width:100%;\n  position:relative;\n  vertical-align:top; }\n  .bp5-tab a{\n    color:inherit;\n    display:block;\n    text-decoration:none; }\n  .bp5-tab-indicator-wrapper ~ .bp5-tab{\n    background-color:transparent !important;\n    -webkit-box-shadow:none !important;\n            box-shadow:none !important; }\n  .bp5-tab[aria-disabled=\"true\"]{\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n  .bp5-tab[aria-selected=\"true\"]{\n    border-radius:0;\n    -webkit-box-shadow:inset 0 -3px 0 #106ba3;\n            box-shadow:inset 0 -3px 0 #106ba3; }\n  .bp5-tab[aria-selected=\"true\"], .bp5-tab:not([aria-disabled=\"true\"]):hover{\n    color:#106ba3; }\n  .bp5-tab:focus{\n    -moz-outline-radius:0; }\n  .bp5-large > .bp5-tab{\n    font-size:16px;\n    line-height:40px; }\n\n.bp5-tab-panel{\n  margin-top:20px; }\n  .bp5-tab-panel[aria-hidden=\"true\"]{\n    display:none; }\n\n.bp5-tab-indicator-wrapper{\n  left:0;\n  pointer-events:none;\n  position:absolute;\n  top:0;\n  -webkit-transform:translateX(0), translateY(0);\n          transform:translateX(0), translateY(0);\n  -webkit-transition:height, width, -webkit-transform;\n  transition:height, width, -webkit-transform;\n  transition:height, transform, width;\n  transition:height, transform, width, -webkit-transform;\n  -webkit-transition-duration:200ms;\n          transition-duration:200ms;\n  -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n          transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n    background-color:#106ba3;\n    bottom:0;\n    height:3px;\n    left:0;\n    position:absolute;\n    right:0; }\n  .bp5-tab-indicator-wrapper.bp5-no-animation{\n    -webkit-transition:none;\n    transition:none; }\n\n.bp5-dark .bp5-tab{\n  color:#f5f8fa; }\n  .bp5-dark .bp5-tab[aria-disabled=\"true\"]{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-dark .bp5-tab[aria-selected=\"true\"]{\n    -webkit-box-shadow:inset 0 -3px 0 #48aff0;\n            box-shadow:inset 0 -3px 0 #48aff0; }\n  .bp5-dark .bp5-tab[aria-selected=\"true\"], .bp5-dark .bp5-tab:not([aria-disabled=\"true\"]):hover{\n    color:#48aff0; }\n\n.bp5-dark .bp5-tab-indicator{\n  background-color:#48aff0; }\n\n.bp5-flex-expander{\n  -webkit-box-flex:1;\n      -ms-flex:1 1;\n          flex:1 1; }\n.bp5-tag{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background-color:#5c7080;\n  border:none;\n  border-radius:3px;\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  color:#f5f8fa;\n  font-size:12px;\n  line-height:16px;\n  max-width:100%;\n  min-height:20px;\n  min-width:20px;\n  padding:2px 6px;\n  position:relative; }\n  .bp5-tag.bp5-interactive{\n    cursor:pointer; }\n    .bp5-tag.bp5-interactive:hover{\n      background-color:rgba(92, 112, 128, 0.85); }\n    .bp5-tag.bp5-interactive.bp5-active, .bp5-tag.bp5-interactive:active{\n      background-color:rgba(92, 112, 128, 0.7); }\n  .bp5-tag > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-tag > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-tag::before,\n  .bp5-tag > *{\n    margin-right:4px; }\n  .bp5-tag:empty::before,\n  .bp5-tag > :last-child{\n    margin-right:0; }\n  .bp5-tag:focus{\n    outline:rgba(19, 124, 189, 0.6) auto 2px;\n    outline-offset:0;\n    -moz-outline-radius:6px; }\n  .bp5-tag.bp5-round{\n    border-radius:30px;\n    padding-left:8px;\n    padding-right:8px; }\n  .bp5-dark .bp5-tag{\n    background-color:#bfccd6;\n    color:#182026; }\n    .bp5-dark .bp5-tag.bp5-interactive{\n      cursor:pointer; }\n      .bp5-dark .bp5-tag.bp5-interactive:hover{\n        background-color:rgba(191, 204, 214, 0.85); }\n      .bp5-dark .bp5-tag.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-interactive:active{\n        background-color:rgba(191, 204, 214, 0.7); }\n    .bp5-dark .bp5-tag > .bp5-icon, .bp5-dark .bp5-tag .bp5-icon-standard, .bp5-dark .bp5-tag .bp5-icon-large{\n      fill:currentColor; }\n  .bp5-tag > .bp5-icon, .bp5-tag .bp5-icon-standard, .bp5-tag .bp5-icon-large{\n    fill:#ffffff; }\n  .bp5-tag.bp5-large,\n  .bp5-large .bp5-tag{\n    font-size:14px;\n    line-height:20px;\n    min-height:30px;\n    min-width:30px;\n    padding:5px 10px; }\n    .bp5-tag.bp5-large::before,\n    .bp5-tag.bp5-large > *,\n    .bp5-large .bp5-tag::before,\n    .bp5-large .bp5-tag > *{\n      margin-right:7px; }\n    .bp5-tag.bp5-large:empty::before,\n    .bp5-tag.bp5-large > :last-child,\n    .bp5-large .bp5-tag:empty::before,\n    .bp5-large .bp5-tag > :last-child{\n      margin-right:0; }\n    .bp5-tag.bp5-large.bp5-round,\n    .bp5-large .bp5-tag.bp5-round{\n      padding-left:12px;\n      padding-right:12px; }\n  .bp5-tag.bp5-intent-primary{\n    background:#137cbd;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-primary.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-primary.bp5-interactive:hover{\n        background-color:rgba(19, 124, 189, 0.85); }\n      .bp5-tag.bp5-intent-primary.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-primary.bp5-interactive:active{\n        background-color:rgba(19, 124, 189, 0.7); }\n  .bp5-tag.bp5-intent-success{\n    background:#0f9960;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-success.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-success.bp5-interactive:hover{\n        background-color:rgba(15, 153, 96, 0.85); }\n      .bp5-tag.bp5-intent-success.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-success.bp5-interactive:active{\n        background-color:rgba(15, 153, 96, 0.7); }\n  .bp5-tag.bp5-intent-warning{\n    background:#d9822b;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-warning.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-warning.bp5-interactive:hover{\n        background-color:rgba(217, 130, 43, 0.85); }\n      .bp5-tag.bp5-intent-warning.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-warning.bp5-interactive:active{\n        background-color:rgba(217, 130, 43, 0.7); }\n  .bp5-tag.bp5-intent-danger{\n    background:#db3737;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-danger.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-danger.bp5-interactive:hover{\n        background-color:rgba(219, 55, 55, 0.85); }\n      .bp5-tag.bp5-intent-danger.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-danger.bp5-interactive:active{\n        background-color:rgba(219, 55, 55, 0.7); }\n  .bp5-tag.bp5-fill{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    width:100%; }\n  .bp5-tag.bp5-minimal > .bp5-icon, .bp5-tag.bp5-minimal .bp5-icon-standard, .bp5-tag.bp5-minimal .bp5-icon-large{\n    fill:#5c7080; }\n  .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]){\n    background-color:rgba(138, 155, 168, 0.2);\n    color:#182026; }\n    .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:hover{\n        background-color:rgba(92, 112, 128, 0.3); }\n      .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive.bp5-active, .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:active{\n        background-color:rgba(92, 112, 128, 0.4); }\n    .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]){\n      color:#f5f8fa; }\n      .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:hover{\n          background-color:rgba(191, 204, 214, 0.3); }\n        .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:active{\n          background-color:rgba(191, 204, 214, 0.4); }\n      .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]) > .bp5-icon, .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]) .bp5-icon-standard, .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]) .bp5-icon-large{\n        fill:#a7b6c2; }\n  .bp5-tag.bp5-minimal.bp5-intent-primary{\n    background-color:rgba(19, 124, 189, 0.15);\n    color:#106ba3; }\n    .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n        background-color:rgba(19, 124, 189, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active{\n        background-color:rgba(19, 124, 189, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-primary > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-large{\n      fill:#137cbd; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary{\n      background-color:rgba(19, 124, 189, 0.25);\n      color:#48aff0; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n          background-color:rgba(19, 124, 189, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active{\n          background-color:rgba(19, 124, 189, 0.45); }\n  .bp5-tag.bp5-minimal.bp5-intent-success{\n    background-color:rgba(15, 153, 96, 0.15);\n    color:#0d8050; }\n    .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n        background-color:rgba(15, 153, 96, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active{\n        background-color:rgba(15, 153, 96, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-success > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-large{\n      fill:#0f9960; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success{\n      background-color:rgba(15, 153, 96, 0.25);\n      color:#3dcc91; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n          background-color:rgba(15, 153, 96, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active{\n          background-color:rgba(15, 153, 96, 0.45); }\n  .bp5-tag.bp5-minimal.bp5-intent-warning{\n    background-color:rgba(217, 130, 43, 0.15);\n    color:#bf7326; }\n    .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n        background-color:rgba(217, 130, 43, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active{\n        background-color:rgba(217, 130, 43, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-warning > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-large{\n      fill:#d9822b; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning{\n      background-color:rgba(217, 130, 43, 0.25);\n      color:#ffb366; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n          background-color:rgba(217, 130, 43, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active{\n          background-color:rgba(217, 130, 43, 0.45); }\n  .bp5-tag.bp5-minimal.bp5-intent-danger{\n    background-color:rgba(219, 55, 55, 0.15);\n    color:#c23030; }\n    .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n        background-color:rgba(219, 55, 55, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active{\n        background-color:rgba(219, 55, 55, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-danger > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-large{\n      fill:#db3737; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger{\n      background-color:rgba(219, 55, 55, 0.25);\n      color:#ff7373; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n          background-color:rgba(219, 55, 55, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active{\n          background-color:rgba(219, 55, 55, 0.45); }\n\n.bp5-tag-remove{\n  background:none;\n  border:none;\n  color:inherit;\n  cursor:pointer;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  margin-bottom:-2px;\n  margin-right:-6px !important;\n  margin-top:-2px;\n  opacity:0.5;\n  padding:2px;\n  padding-left:0; }\n  .bp5-tag-remove:hover{\n    background:none;\n    opacity:0.8;\n    text-decoration:none; }\n  .bp5-tag-remove:active{\n    opacity:1; }\n  .bp5-tag-remove:empty::before{\n    font-family:\"blueprint-icons-16\", sans-serif;\n    font-size:16px;\n    font-style:normal;\n    font-weight:400;\n    line-height:1;\n    -moz-osx-font-smoothing:grayscale;\n    -webkit-font-smoothing:antialiased;\n    content:\"\\f2ba\"; }\n  .bp5-large .bp5-tag-remove{\n    margin-right:-10px !important;\n    padding:0 5px 0 0; }\n    .bp5-large .bp5-tag-remove:empty::before{\n      font-family:\"blueprint-icons-20\", sans-serif;\n      font-size:20px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1; }\n.bp5-tag-input{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:start;\n      -ms-flex-align:start;\n          align-items:flex-start;\n  cursor:text;\n  height:auto;\n  line-height:inherit;\n  min-height:30px;\n  padding-left:5px;\n  padding-right:0; }\n  .bp5-tag-input > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-tag-input > .bp5-tag-input-values{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-tag-input .bp5-tag-input-icon{\n    color:#5c7080;\n    margin-left:2px;\n    margin-right:7px;\n    margin-top:7px; }\n  .bp5-tag-input .bp5-tag-input-values{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-orient:horizontal;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:row;\n            flex-direction:row;\n    -webkit-box-align:center;\n        -ms-flex-align:center;\n            align-items:center;\n    -ms-flex-item-align:stretch;\n        align-self:stretch;\n    -ms-flex-wrap:wrap;\n        flex-wrap:wrap;\n    margin-right:7px;\n    margin-top:5px;\n    min-width:0; }\n    .bp5-tag-input .bp5-tag-input-values > *{\n      -webkit-box-flex:0;\n          -ms-flex-positive:0;\n              flex-grow:0;\n      -ms-flex-negative:0;\n          flex-shrink:0; }\n    .bp5-tag-input .bp5-tag-input-values > .bp5-fill{\n      -webkit-box-flex:1;\n          -ms-flex-positive:1;\n              flex-grow:1;\n      -ms-flex-negative:1;\n          flex-shrink:1; }\n    .bp5-tag-input .bp5-tag-input-values::before,\n    .bp5-tag-input .bp5-tag-input-values > *{\n      margin-right:5px; }\n    .bp5-tag-input .bp5-tag-input-values:empty::before,\n    .bp5-tag-input .bp5-tag-input-values > :last-child{\n      margin-right:0; }\n    .bp5-tag-input .bp5-tag-input-values:first-child .bp5-input-ghost:first-child{\n      padding-left:5px; }\n    .bp5-tag-input .bp5-tag-input-values > *{\n      margin-bottom:5px; }\n  .bp5-tag-input .bp5-tag{\n    overflow-wrap:break-word; }\n    .bp5-tag-input .bp5-tag.bp5-active{\n      outline:rgba(19, 124, 189, 0.6) auto 2px;\n      outline-offset:0;\n      -moz-outline-radius:6px; }\n  .bp5-tag-input .bp5-input-ghost{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    line-height:20px;\n    width:80px; }\n    .bp5-tag-input .bp5-input-ghost:disabled, .bp5-tag-input .bp5-input-ghost.bp5-disabled{\n      cursor:not-allowed; }\n  .bp5-tag-input .bp5-button,\n  .bp5-tag-input .bp5-spinner{\n    margin:3px;\n    margin-left:0; }\n  .bp5-tag-input .bp5-button{\n    min-height:24px;\n    min-width:24px;\n    padding:0 7px; }\n  .bp5-tag-input.bp5-large{\n    height:auto;\n    min-height:40px; }\n    .bp5-tag-input.bp5-large::before,\n    .bp5-tag-input.bp5-large > *{\n      margin-right:10px; }\n    .bp5-tag-input.bp5-large:empty::before,\n    .bp5-tag-input.bp5-large > :last-child{\n      margin-right:0; }\n    .bp5-tag-input.bp5-large .bp5-tag-input-icon{\n      margin-left:5px;\n      margin-top:10px; }\n    .bp5-tag-input.bp5-large .bp5-input-ghost{\n      line-height:30px; }\n    .bp5-tag-input.bp5-large .bp5-button{\n      min-height:30px;\n      min-width:30px;\n      padding:5px 10px;\n      margin:5px;\n      margin-left:0; }\n    .bp5-tag-input.bp5-large .bp5-spinner{\n      margin:8px;\n      margin-left:0; }\n  .bp5-tag-input.bp5-active{\n    background-color:#ffffff;\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-primary{\n      -webkit-box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-success{\n      -webkit-box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-warning{\n      -webkit-box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-danger{\n      -webkit-box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-tag-input .bp5-tag-input-icon, .bp5-tag-input.bp5-dark .bp5-tag-input-icon{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-tag-input .bp5-input-ghost, .bp5-tag-input.bp5-dark .bp5-input-ghost{\n    color:#f5f8fa; }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::-webkit-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::-moz-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost:-ms-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::-ms-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n  .bp5-dark .bp5-tag-input.bp5-active, .bp5-tag-input.bp5-dark.bp5-active{\n    background-color:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-primary, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-primary{\n      -webkit-box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-success, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-success{\n      -webkit-box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-warning, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-warning{\n      -webkit-box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-danger, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-danger{\n      -webkit-box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-input-ghost{\n  background:none;\n  border:none;\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  padding:0; }\n  .bp5-input-ghost::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost:focus{\n    outline:none !important; }\n.bp5-toast{\n  -webkit-box-align:start;\n      -ms-flex-align:start;\n          align-items:flex-start;\n  background-color:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  margin:20px 0 0;\n  max-width:500px;\n  min-width:300px;\n  pointer-events:all;\n  position:relative !important; }\n  .bp5-toast.bp5-toast-enter, .bp5-toast.bp5-toast-appear{\n    -webkit-transform:translateY(-40px);\n            transform:translateY(-40px); }\n  .bp5-toast.bp5-toast-enter-active, .bp5-toast.bp5-toast-appear-active{\n    -webkit-transform:translateY(0);\n            transform:translateY(0);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-toast.bp5-toast-enter ~ .bp5-toast, .bp5-toast.bp5-toast-appear ~ .bp5-toast{\n    -webkit-transform:translateY(-40px);\n            transform:translateY(-40px); }\n  .bp5-toast.bp5-toast-enter-active ~ .bp5-toast, .bp5-toast.bp5-toast-appear-active ~ .bp5-toast{\n    -webkit-transform:translateY(0);\n            transform:translateY(0);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-toast.bp5-toast-exit{\n    opacity:1;\n    -webkit-filter:blur(0);\n            filter:blur(0); }\n  .bp5-toast.bp5-toast-exit-active{\n    opacity:0;\n    -webkit-filter:blur(10px);\n            filter:blur(10px);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:opacity, -webkit-filter;\n    transition-property:opacity, -webkit-filter;\n    transition-property:opacity, filter;\n    transition-property:opacity, filter, -webkit-filter;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-toast.bp5-toast-exit ~ .bp5-toast{\n    -webkit-transform:translateY(0);\n            transform:translateY(0); }\n  .bp5-toast.bp5-toast-exit-active ~ .bp5-toast{\n    -webkit-transform:translateY(-40px);\n            transform:translateY(-40px);\n    -webkit-transition-delay:50ms;\n            transition-delay:50ms;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-toast .bp5-button-group{\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    padding:5px;\n    padding-left:0; }\n  .bp5-toast > .bp5-icon{\n    color:#5c7080;\n    margin:12px;\n    margin-right:0; }\n  .bp5-toast.bp5-dark,\n  .bp5-dark .bp5-toast{\n    background-color:#394b59;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n    .bp5-toast.bp5-dark > .bp5-icon,\n    .bp5-dark .bp5-toast > .bp5-icon{\n      color:#a7b6c2; }\n  .bp5-toast[class*=\"bp5-intent-\"] a{\n    color:rgba(255, 255, 255, 0.7); }\n    .bp5-toast[class*=\"bp5-intent-\"] a:hover{\n      color:#ffffff; }\n  .bp5-toast[class*=\"bp5-intent-\"] > .bp5-icon{\n    color:#ffffff; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button, .bp5-toast[class*=\"bp5-intent-\"] .bp5-button::before,\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button .bp5-icon, .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:active{\n    color:rgba(255, 255, 255, 0.7) !important; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:focus{\n    outline-color:rgba(255, 255, 255, 0.5); }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:hover{\n    background-color:rgba(255, 255, 255, 0.15) !important;\n    color:#ffffff !important; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:active{\n    background-color:rgba(255, 255, 255, 0.3) !important;\n    color:#ffffff !important; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button::after{\n    background:rgba(255, 255, 255, 0.3) !important; }\n  .bp5-toast.bp5-intent-primary{\n    background-color:#137cbd;\n    color:#ffffff; }\n  .bp5-toast.bp5-intent-success{\n    background-color:#0f9960;\n    color:#ffffff; }\n  .bp5-toast.bp5-intent-warning{\n    background-color:#d9822b;\n    color:#ffffff; }\n  .bp5-toast.bp5-intent-danger{\n    background-color:#db3737;\n    color:#ffffff; }\n\n.bp5-toast-message{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  padding:11px;\n  word-break:break-word; }\n\n.bp5-toast-container{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box !important;\n  display:-ms-flexbox !important;\n  display:flex !important;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  left:0;\n  overflow:hidden;\n  padding:0 20px 20px;\n  pointer-events:none;\n  right:0;\n  z-index:40; }\n  .bp5-toast-container.bp5-toast-container-in-portal{\n    position:fixed; }\n  .bp5-toast-container.bp5-toast-container-inline{\n    position:absolute; }\n  .bp5-toast-container.bp5-toast-container-top{\n    top:0; }\n  .bp5-toast-container.bp5-toast-container-bottom{\n    bottom:0;\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:reverse;\n        -ms-flex-direction:column-reverse;\n            flex-direction:column-reverse;\n    top:auto; }\n  .bp5-toast-container.bp5-toast-container-left{\n    -webkit-box-align:start;\n        -ms-flex-align:start;\n            align-items:flex-start; }\n  .bp5-toast-container.bp5-toast-container-right{\n    -webkit-box-align:end;\n        -ms-flex-align:end;\n            align-items:flex-end; }\n\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active),\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active) ~ .bp5-toast, .bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active),\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active) ~ .bp5-toast,\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-exit-active ~ .bp5-toast,\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-leave-active ~ .bp5-toast{\n  -webkit-transform:translateY(60px);\n          transform:translateY(60px); }\n.bp5-tooltip{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  -webkit-transform:scale(1);\n          transform:scale(1); }\n  .bp5-tooltip .bp5-popover-arrow{\n    height:22px;\n    position:absolute;\n    width:22px; }\n    .bp5-tooltip .bp5-popover-arrow::before{\n      height:14px;\n      margin:4px;\n      width:14px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-tooltip{\n    margin-bottom:11px;\n    margin-top:-11px; }\n    .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-tooltip > .bp5-popover-arrow{\n      bottom:-8px; }\n      .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(-90deg);\n                transform:rotate(-90deg); }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-tooltip{\n    margin-left:11px; }\n    .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-tooltip > .bp5-popover-arrow{\n      left:-8px; }\n      .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(0);\n                transform:rotate(0); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-tooltip{\n    margin-top:11px; }\n    .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-tooltip > .bp5-popover-arrow{\n      top:-8px; }\n      .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(90deg);\n                transform:rotate(90deg); }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-tooltip{\n    margin-left:-11px;\n    margin-right:11px; }\n    .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-tooltip > .bp5-popover-arrow{\n      right:-8px; }\n      .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(180deg);\n                transform:rotate(180deg); }\n  .bp5-tether-element-attached-middle > .bp5-tooltip > .bp5-popover-arrow{\n    top:50%;\n    -webkit-transform:translateY(-50%);\n            transform:translateY(-50%); }\n  .bp5-tether-element-attached-center > .bp5-tooltip > .bp5-popover-arrow{\n    right:50%;\n    -webkit-transform:translateX(50%);\n            transform:translateX(50%); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-top > .bp5-tooltip > .bp5-popover-arrow{\n    top:-0.22183px; }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-right > .bp5-tooltip > .bp5-popover-arrow{\n    right:-0.22183px; }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-left > .bp5-tooltip > .bp5-popover-arrow{\n    left:-0.22183px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-bottom > .bp5-tooltip > .bp5-popover-arrow{\n    bottom:-0.22183px; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-left > .bp5-tooltip{\n    -webkit-transform-origin:top left;\n            transform-origin:top left; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-center > .bp5-tooltip{\n    -webkit-transform-origin:top center;\n            transform-origin:top center; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-right > .bp5-tooltip{\n    -webkit-transform-origin:top right;\n            transform-origin:top right; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-left > .bp5-tooltip{\n    -webkit-transform-origin:center left;\n            transform-origin:center left; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-center > .bp5-tooltip{\n    -webkit-transform-origin:center center;\n            transform-origin:center center; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-right > .bp5-tooltip{\n    -webkit-transform-origin:center right;\n            transform-origin:center right; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-left > .bp5-tooltip{\n    -webkit-transform-origin:bottom left;\n            transform-origin:bottom left; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-center > .bp5-tooltip{\n    -webkit-transform-origin:bottom center;\n            transform-origin:bottom center; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-right > .bp5-tooltip{\n    -webkit-transform-origin:bottom right;\n            transform-origin:bottom right; }\n  .bp5-tooltip .bp5-popover-content{\n    background:#394b59; }\n  .bp5-tooltip .bp5-popover-content,\n  .bp5-tooltip .bp5-heading{\n    color:#f5f8fa; }\n  .bp5-tooltip .bp5-popover-arrow::before{\n    -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2);\n            box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-tooltip .bp5-popover-arrow-border{\n    fill:#10161a;\n    fill-opacity:0.1; }\n  .bp5-tooltip .bp5-popover-arrow-fill{\n    fill:#394b59; }\n  .bp5-popover-enter > .bp5-tooltip, .bp5-popover-appear > .bp5-tooltip{\n    -webkit-transform:scale(0.8);\n            transform:scale(0.8); }\n  .bp5-popover-enter-active > .bp5-tooltip, .bp5-popover-appear-active > .bp5-tooltip{\n    -webkit-transform:scale(1);\n            transform:scale(1);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-popover-exit > .bp5-tooltip{\n    -webkit-transform:scale(1);\n            transform:scale(1); }\n  .bp5-popover-exit-active > .bp5-tooltip{\n    -webkit-transform:scale(0.8);\n            transform:scale(0.8);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-tooltip .bp5-popover-content{\n    padding:10px 12px; }\n  .bp5-tooltip.bp5-dark,\n  .bp5-dark .bp5-tooltip{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n    .bp5-tooltip.bp5-dark .bp5-popover-content,\n    .bp5-dark .bp5-tooltip .bp5-popover-content{\n      background:#e1e8ed; }\n    .bp5-tooltip.bp5-dark .bp5-popover-content,\n    .bp5-tooltip.bp5-dark .bp5-heading,\n    .bp5-dark .bp5-tooltip .bp5-popover-content,\n    .bp5-dark .bp5-tooltip .bp5-heading{\n      color:#394b59; }\n    .bp5-tooltip.bp5-dark .bp5-popover-arrow::before,\n    .bp5-dark .bp5-tooltip .bp5-popover-arrow::before{\n      -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4);\n              box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4); }\n    .bp5-tooltip.bp5-dark .bp5-popover-arrow-border,\n    .bp5-dark .bp5-tooltip .bp5-popover-arrow-border{\n      fill:#10161a;\n      fill-opacity:0.2; }\n    .bp5-tooltip.bp5-dark .bp5-popover-arrow-fill,\n    .bp5-dark .bp5-tooltip .bp5-popover-arrow-fill{\n      fill:#e1e8ed; }\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-content{\n    background:#137cbd;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill{\n    fill:#137cbd; }\n  .bp5-tooltip.bp5-intent-success .bp5-popover-content{\n    background:#0f9960;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill{\n    fill:#0f9960; }\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-content{\n    background:#d9822b;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill{\n    fill:#d9822b; }\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-content{\n    background:#db3737;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill{\n    fill:#db3737; }\n\n.bp5-tooltip-indicator{\n  border-bottom:dotted 1px;\n  cursor:help; }\n.bp5-tree .bp5-icon, .bp5-tree .bp5-icon-standard, .bp5-tree .bp5-icon-large{\n  color:#5c7080; }\n  .bp5-tree .bp5-icon.bp5-intent-primary, .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-tree .bp5-icon-large.bp5-intent-primary{\n    color:#137cbd; }\n  .bp5-tree .bp5-icon.bp5-intent-success, .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-tree .bp5-icon-large.bp5-intent-success{\n    color:#0f9960; }\n  .bp5-tree .bp5-icon.bp5-intent-warning, .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-tree .bp5-icon-large.bp5-intent-warning{\n    color:#d9822b; }\n  .bp5-tree .bp5-icon.bp5-intent-danger, .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-tree .bp5-icon-large.bp5-intent-danger{\n    color:#db3737; }\n\n.bp5-tree-node-list{\n  list-style:none;\n  margin:0;\n  padding-left:0; }\n\n.bp5-tree-root{\n  background-color:transparent;\n  cursor:default;\n  padding-left:0;\n  position:relative; }\n\n.bp5-tree-node-content-0{\n  padding-left:0px; }\n\n.bp5-tree-node-content-1{\n  padding-left:23px; }\n\n.bp5-tree-node-content-2{\n  padding-left:46px; }\n\n.bp5-tree-node-content-3{\n  padding-left:69px; }\n\n.bp5-tree-node-content-4{\n  padding-left:92px; }\n\n.bp5-tree-node-content-5{\n  padding-left:115px; }\n\n.bp5-tree-node-content-6{\n  padding-left:138px; }\n\n.bp5-tree-node-content-7{\n  padding-left:161px; }\n\n.bp5-tree-node-content-8{\n  padding-left:184px; }\n\n.bp5-tree-node-content-9{\n  padding-left:207px; }\n\n.bp5-tree-node-content-10{\n  padding-left:230px; }\n\n.bp5-tree-node-content-11{\n  padding-left:253px; }\n\n.bp5-tree-node-content-12{\n  padding-left:276px; }\n\n.bp5-tree-node-content-13{\n  padding-left:299px; }\n\n.bp5-tree-node-content-14{\n  padding-left:322px; }\n\n.bp5-tree-node-content-15{\n  padding-left:345px; }\n\n.bp5-tree-node-content-16{\n  padding-left:368px; }\n\n.bp5-tree-node-content-17{\n  padding-left:391px; }\n\n.bp5-tree-node-content-18{\n  padding-left:414px; }\n\n.bp5-tree-node-content-19{\n  padding-left:437px; }\n\n.bp5-tree-node-content-20{\n  padding-left:460px; }\n\n.bp5-tree-node-content{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:30px;\n  padding-right:5px;\n  width:100%; }\n  .bp5-tree-node-content:hover{\n    background-color:rgba(191, 204, 214, 0.4); }\n\n.bp5-tree-node-caret,\n.bp5-tree-node-caret-none{\n  min-width:30px; }\n\n.bp5-tree-node-caret{\n  color:#5c7080;\n  cursor:pointer;\n  padding:7px;\n  -webkit-transform:rotate(0deg);\n          transform:rotate(0deg);\n  -webkit-transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-tree-node-caret:hover{\n    color:#182026; }\n  .bp5-dark .bp5-tree-node-caret{\n    color:#a7b6c2; }\n    .bp5-dark .bp5-tree-node-caret:hover{\n      color:#f5f8fa; }\n  .bp5-tree-node-caret.bp5-tree-node-caret-open{\n    -webkit-transform:rotate(90deg);\n            transform:rotate(90deg); }\n  .bp5-tree-node-caret.bp5-icon-standard::before{\n    content:\"\\f14d\"; }\n\n.bp5-tree-node-icon{\n  margin-right:7px;\n  position:relative; }\n\n.bp5-tree-node-label{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-tree-node-label span{\n    display:inline; }\n\n.bp5-tree-node-secondary-label{\n  padding:0 5px;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-tree-node-secondary-label .bp5-popover-wrapper,\n  .bp5-tree-node-secondary-label .bp5-popover-target{\n    -webkit-box-align:center;\n        -ms-flex-align:center;\n            align-items:center;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex; }\n\n.bp5-tree-node.bp5-disabled .bp5-tree-node-content{\n  background-color:inherit;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-tree-node.bp5-disabled .bp5-tree-node-caret,\n.bp5-tree-node.bp5-disabled .bp5-tree-node-icon{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#137cbd; }\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content,\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-standard, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-large{\n    color:#ffffff; }\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret::before{\n    color:rgba(255, 255, 255, 0.7); }\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret:hover::before{\n    color:#ffffff; }\n\n.bp5-dark .bp5-tree-node-content:hover{\n  background-color:rgba(92, 112, 128, 0.3); }\n\n.bp5-dark .bp5-tree .bp5-icon, .bp5-dark .bp5-tree .bp5-icon-standard, .bp5-dark .bp5-tree .bp5-icon-large{\n  color:#a7b6c2; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-primary{\n    color:#137cbd; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-success{\n    color:#0f9960; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-warning{\n    color:#d9822b; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-danger{\n    color:#db3737; }\n\n.bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#137cbd; }\n/*# sourceMappingURL=blueprint.css.map */"
  },
  {
    "path": "www/dist-dev/static/blueprint5.css",
    "content": "@charset \"UTF-8\";\nhtml{\n  box-sizing:border-box;\n}\n\n*,\n*::before,\n*::after{\n  box-sizing:inherit;\n}\n\nbody{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none;\n  color:#1c2127;\n  font-family:-apple-system, \"BlinkMacSystemFont\", \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Open Sans\", \"Helvetica Neue\", \"blueprint-icons-16\", sans-serif;\n}\n\np{\n  margin-bottom:10px;\n  margin-top:0;\n}\n\nsmall{\n  font-size:12px;\n}\n\nstrong{\n  font-weight:600;\n}\n\n::-moz-selection{\n  background:rgba(125, 188, 255, 0.6);\n}\n\n::selection{\n  background:rgba(125, 188, 255, 0.6);\n}\n\n.bp5-icon{\n  display:inline-block;\n  flex:0 0 auto;\n  vertical-align:text-bottom;\n}\n.bp5-icon:not(:empty)::before{\n  content:\"\" !important;\n  content:unset !important;\n}\n.bp5-icon > svg{\n  display:block;\n}\n.bp5-icon > svg:not([fill]){\n  fill:currentcolor;\n}\n.bp5-icon.bp5-icon-muted svg{\n  fill-opacity:0.15;\n  overflow:visible;\n}\n.bp5-icon.bp5-icon-muted svg path{\n  stroke:#8f99a8;\n  stroke-opacity:0.5;\n  stroke-width:0.5px;\n}\n.bp5-dark .bp5-icon .bp5-icon-muted svg{\n  fill-opacity:0.2;\n}\n\nspan.bp5-icon-standard{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon-large{\n  font-family:\"blueprint-icons-20\", sans-serif;\n  font-size:20px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:20px;\n  line-height:1;\n  width:20px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon:empty{\n  font-family:\"blueprint-icons-20\";\n  font-size:inherit;\n  font-style:normal;\n  font-weight:400;\n  line-height:1;\n}\nspan.bp5-icon:empty::before{\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n}\nspan.bp5-icon:empty.bp5-icon-standard{\n  font-size:16px;\n}\nspan.bp5-icon:empty.bp5-icon-large{\n  font-size:20px;\n}\n\n.bp5-icon-add::before{\n  content:\"\\f109\";\n}\n\n.bp5-icon-add-clip::before{\n  content:\"\\f101\";\n}\n\n.bp5-icon-add-column-left::before{\n  content:\"\\f102\";\n}\n\n.bp5-icon-add-column-right::before{\n  content:\"\\f103\";\n}\n\n.bp5-icon-add-location::before{\n  content:\"\\f104\";\n}\n\n.bp5-icon-add-row-bottom::before{\n  content:\"\\f105\";\n}\n\n.bp5-icon-add-row-top::before{\n  content:\"\\f106\";\n}\n\n.bp5-icon-add-to-artifact::before{\n  content:\"\\f107\";\n}\n\n.bp5-icon-add-to-folder::before{\n  content:\"\\f108\";\n}\n\n.bp5-icon-aimpoints-target::before{\n  content:\"\\f335\";\n}\n\n.bp5-icon-airplane::before{\n  content:\"\\f10a\";\n}\n\n.bp5-icon-align-center::before{\n  content:\"\\f10b\";\n}\n\n.bp5-icon-align-justify::before{\n  content:\"\\f10c\";\n}\n\n.bp5-icon-align-left::before{\n  content:\"\\f10d\";\n}\n\n.bp5-icon-align-right::before{\n  content:\"\\f10e\";\n}\n\n.bp5-icon-alignment-bottom::before{\n  content:\"\\f10f\";\n}\n\n.bp5-icon-alignment-horizontal-center::before{\n  content:\"\\f110\";\n}\n\n.bp5-icon-alignment-left::before{\n  content:\"\\f111\";\n}\n\n.bp5-icon-alignment-right::before{\n  content:\"\\f112\";\n}\n\n.bp5-icon-alignment-top::before{\n  content:\"\\f113\";\n}\n\n.bp5-icon-alignment-vertical-center::before{\n  content:\"\\f114\";\n}\n\n.bp5-icon-ammunition::before{\n  content:\"\\f342\";\n}\n\n.bp5-icon-anchor::before{\n  content:\"\\f330\";\n}\n\n.bp5-icon-annotation::before{\n  content:\"\\f115\";\n}\n\n.bp5-icon-announcement::before{\n  content:\"\\f396\";\n}\n\n.bp5-icon-antenna::before{\n  content:\"\\f116\";\n}\n\n.bp5-icon-app-header::before{\n  content:\"\\f117\";\n}\n\n.bp5-icon-application::before{\n  content:\"\\f118\";\n}\n\n.bp5-icon-applications::before{\n  content:\"\\f119\";\n}\n\n.bp5-icon-archive::before{\n  content:\"\\f11a\";\n}\n\n.bp5-icon-area-of-interest::before{\n  content:\"\\f11b\";\n}\n\n.bp5-icon-array::before{\n  content:\"\\f121\";\n}\n\n.bp5-icon-array-boolean::before{\n  content:\"\\f11c\";\n}\n\n.bp5-icon-array-date::before{\n  content:\"\\f11d\";\n}\n\n.bp5-icon-array-floating-point::before{\n  content:\"\\f32d\";\n}\n\n.bp5-icon-array-numeric::before{\n  content:\"\\f11e\";\n}\n\n.bp5-icon-array-object::before{\n  content:\"\\f394\";\n}\n\n.bp5-icon-array-string::before{\n  content:\"\\f11f\";\n}\n\n.bp5-icon-array-timestamp::before{\n  content:\"\\f120\";\n}\n\n.bp5-icon-arrow-bottom-left::before{\n  content:\"\\f122\";\n}\n\n.bp5-icon-arrow-bottom-right::before{\n  content:\"\\f123\";\n}\n\n.bp5-icon-arrow-down::before{\n  content:\"\\f124\";\n}\n\n.bp5-icon-arrow-left::before{\n  content:\"\\f125\";\n}\n\n.bp5-icon-arrow-right::before{\n  content:\"\\f126\";\n}\n\n.bp5-icon-arrow-top-left::before{\n  content:\"\\f127\";\n}\n\n.bp5-icon-arrow-top-right::before{\n  content:\"\\f128\";\n}\n\n.bp5-icon-arrow-up::before{\n  content:\"\\f129\";\n}\n\n.bp5-icon-arrows-arc::before{\n  content:\"\\f387\";\n}\n\n.bp5-icon-arrows-horizontal::before{\n  content:\"\\f12a\";\n}\n\n.bp5-icon-arrows-vertical::before{\n  content:\"\\f12b\";\n}\n\n.bp5-icon-asterisk::before{\n  content:\"\\f12c\";\n}\n\n.bp5-icon-at::before{\n  content:\"\\f331\";\n}\n\n.bp5-icon-automatic-updates::before{\n  content:\"\\f12d\";\n}\n\n.bp5-icon-axle::before{\n  content:\"\\f338\";\n}\n\n.bp5-icon-backlink::before{\n  content:\"\\f12e\";\n}\n\n.bp5-icon-backward-ten::before{\n  content:\"\\f35c\";\n}\n\n.bp5-icon-badge::before{\n  content:\"\\f12f\";\n}\n\n.bp5-icon-ban-circle::before{\n  content:\"\\f130\";\n}\n\n.bp5-icon-bank-account::before{\n  content:\"\\f131\";\n}\n\n.bp5-icon-barcode::before{\n  content:\"\\f132\";\n}\n\n.bp5-icon-binary-number::before{\n  content:\"\\f357\";\n}\n\n.bp5-icon-blank::before{\n  content:\"\\f133\";\n}\n\n.bp5-icon-block-promote::before{\n  content:\"\\f372\";\n}\n\n.bp5-icon-blocked-person::before{\n  content:\"\\f134\";\n}\n\n.bp5-icon-bold::before{\n  content:\"\\f135\";\n}\n\n.bp5-icon-book::before{\n  content:\"\\f136\";\n}\n\n.bp5-icon-bookmark::before{\n  content:\"\\f137\";\n}\n\n.bp5-icon-box::before{\n  content:\"\\f138\";\n}\n\n.bp5-icon-briefcase::before{\n  content:\"\\f139\";\n}\n\n.bp5-icon-bring-data::before{\n  content:\"\\f13a\";\n}\n\n.bp5-icon-bring-forward::before{\n  content:\"\\f354\";\n}\n\n.bp5-icon-british-pound::before{\n  content:\"\\f386\";\n}\n\n.bp5-icon-bug::before{\n  content:\"\\f32e\";\n}\n\n.bp5-icon-buggy::before{\n  content:\"\\f13b\";\n}\n\n.bp5-icon-build::before{\n  content:\"\\f13c\";\n}\n\n.bp5-icon-bullseye::before{\n  content:\"\\f359\";\n}\n\n.bp5-icon-calculator::before{\n  content:\"\\f13d\";\n}\n\n.bp5-icon-calendar::before{\n  content:\"\\f13e\";\n}\n\n.bp5-icon-camera::before{\n  content:\"\\f13f\";\n}\n\n.bp5-icon-caret-down::before{\n  content:\"\\f140\";\n}\n\n.bp5-icon-caret-left::before{\n  content:\"\\f141\";\n}\n\n.bp5-icon-caret-right::before{\n  content:\"\\f142\";\n}\n\n.bp5-icon-caret-up::before{\n  content:\"\\f143\";\n}\n\n.bp5-icon-cargo-ship::before{\n  content:\"\\f144\";\n}\n\n.bp5-icon-cell-tower::before{\n  content:\"\\f145\";\n}\n\n.bp5-icon-changes::before{\n  content:\"\\f146\";\n}\n\n.bp5-icon-chart::before{\n  content:\"\\f147\";\n}\n\n.bp5-icon-chat::before{\n  content:\"\\f148\";\n}\n\n.bp5-icon-chevron-backward::before{\n  content:\"\\f149\";\n}\n\n.bp5-icon-chevron-down::before{\n  content:\"\\f14a\";\n}\n\n.bp5-icon-chevron-forward::before{\n  content:\"\\f14b\";\n}\n\n.bp5-icon-chevron-left::before{\n  content:\"\\f14c\";\n}\n\n.bp5-icon-chevron-right::before{\n  content:\"\\f14d\";\n}\n\n.bp5-icon-chevron-up::before{\n  content:\"\\f14e\";\n}\n\n.bp5-icon-circle::before{\n  content:\"\\f153\";\n}\n\n.bp5-icon-circle-arrow-down::before{\n  content:\"\\f14f\";\n}\n\n.bp5-icon-circle-arrow-left::before{\n  content:\"\\f150\";\n}\n\n.bp5-icon-circle-arrow-right::before{\n  content:\"\\f151\";\n}\n\n.bp5-icon-circle-arrow-up::before{\n  content:\"\\f152\";\n}\n\n.bp5-icon-citation::before{\n  content:\"\\f154\";\n}\n\n.bp5-icon-clean::before{\n  content:\"\\f155\";\n}\n\n.bp5-icon-clip::before{\n  content:\"\\f156\";\n}\n\n.bp5-icon-clipboard::before{\n  content:\"\\f157\";\n}\n\n.bp5-icon-clipboard-file::before{\n  content:\"\\f35b\";\n}\n\n.bp5-icon-cloud::before{\n  content:\"\\f15a\";\n}\n\n.bp5-icon-cloud-download::before{\n  content:\"\\f158\";\n}\n\n.bp5-icon-cloud-server::before{\n  content:\"\\f35a\";\n}\n\n.bp5-icon-cloud-tick::before{\n  content:\"\\f34e\";\n}\n\n.bp5-icon-cloud-upload::before{\n  content:\"\\f159\";\n}\n\n.bp5-icon-code::before{\n  content:\"\\f15c\";\n}\n\n.bp5-icon-code-block::before{\n  content:\"\\f15b\";\n}\n\n.bp5-icon-cog::before{\n  content:\"\\f15d\";\n}\n\n.bp5-icon-collapse-all::before{\n  content:\"\\f15e\";\n}\n\n.bp5-icon-color-fill::before{\n  content:\"\\f328\";\n}\n\n.bp5-icon-column-layout::before{\n  content:\"\\f15f\";\n}\n\n.bp5-icon-comment::before{\n  content:\"\\f160\";\n}\n\n.bp5-icon-comparison::before{\n  content:\"\\f161\";\n}\n\n.bp5-icon-compass::before{\n  content:\"\\f162\";\n}\n\n.bp5-icon-compressed::before{\n  content:\"\\f163\";\n}\n\n.bp5-icon-confirm::before{\n  content:\"\\f164\";\n}\n\n.bp5-icon-console::before{\n  content:\"\\f165\";\n}\n\n.bp5-icon-construction::before{\n  content:\"\\f395\";\n}\n\n.bp5-icon-contrast::before{\n  content:\"\\f166\";\n}\n\n.bp5-icon-control::before{\n  content:\"\\f167\";\n}\n\n.bp5-icon-credit-card::before{\n  content:\"\\f168\";\n}\n\n.bp5-icon-crop::before{\n  content:\"\\f353\";\n}\n\n.bp5-icon-cross::before{\n  content:\"\\f169\";\n}\n\n.bp5-icon-cross-circle::before{\n  content:\"\\f336\";\n}\n\n.bp5-icon-crown::before{\n  content:\"\\f16a\";\n}\n\n.bp5-icon-css-style::before{\n  content:\"\\f36b\";\n}\n\n.bp5-icon-cube::before{\n  content:\"\\f16d\";\n}\n\n.bp5-icon-cube-add::before{\n  content:\"\\f16b\";\n}\n\n.bp5-icon-cube-edit::before{\n  content:\"\\f383\";\n}\n\n.bp5-icon-cube-remove::before{\n  content:\"\\f16c\";\n}\n\n.bp5-icon-cubes::before{\n  content:\"\\f373\";\n}\n\n.bp5-icon-curly-braces::before{\n  content:\"\\f358\";\n}\n\n.bp5-icon-curved-range-chart::before{\n  content:\"\\f16e\";\n}\n\n.bp5-icon-cut::before{\n  content:\"\\f16f\";\n}\n\n.bp5-icon-cycle::before{\n  content:\"\\f170\";\n}\n\n.bp5-icon-dashboard::before{\n  content:\"\\f171\";\n}\n\n.bp5-icon-data-cloud::before{\n  content:\"\\f391\";\n}\n\n.bp5-icon-data-connection::before{\n  content:\"\\f172\";\n}\n\n.bp5-icon-data-lineage::before{\n  content:\"\\f173\";\n}\n\n.bp5-icon-data-search::before{\n  content:\"\\f36f\";\n}\n\n.bp5-icon-data-sync::before{\n  content:\"\\f36c\";\n}\n\n.bp5-icon-database::before{\n  content:\"\\f174\";\n}\n\n.bp5-icon-delete::before{\n  content:\"\\f175\";\n}\n\n.bp5-icon-delta::before{\n  content:\"\\f176\";\n}\n\n.bp5-icon-derive-column::before{\n  content:\"\\f177\";\n}\n\n.bp5-icon-desktop::before{\n  content:\"\\f178\";\n}\n\n.bp5-icon-detection::before{\n  content:\"\\f341\";\n}\n\n.bp5-icon-diagnosis::before{\n  content:\"\\f179\";\n}\n\n.bp5-icon-diagram-tree::before{\n  content:\"\\f17a\";\n}\n\n.bp5-icon-direction-left::before{\n  content:\"\\f17b\";\n}\n\n.bp5-icon-direction-right::before{\n  content:\"\\f17c\";\n}\n\n.bp5-icon-disable::before{\n  content:\"\\f17d\";\n}\n\n.bp5-icon-divide::before{\n  content:\"\\f327\";\n}\n\n.bp5-icon-document::before{\n  content:\"\\f180\";\n}\n\n.bp5-icon-document-open::before{\n  content:\"\\f17e\";\n}\n\n.bp5-icon-document-share::before{\n  content:\"\\f17f\";\n}\n\n.bp5-icon-dollar::before{\n  content:\"\\f181\";\n}\n\n.bp5-icon-dot::before{\n  content:\"\\f182\";\n}\n\n.bp5-icon-double-caret-horizontal::before{\n  content:\"\\f183\";\n}\n\n.bp5-icon-double-caret-vertical::before{\n  content:\"\\f184\";\n}\n\n.bp5-icon-double-chevron-down::before{\n  content:\"\\f185\";\n}\n\n.bp5-icon-double-chevron-left::before{\n  content:\"\\f186\";\n}\n\n.bp5-icon-double-chevron-right::before{\n  content:\"\\f187\";\n}\n\n.bp5-icon-double-chevron-up::before{\n  content:\"\\f188\";\n}\n\n.bp5-icon-doughnut-chart::before{\n  content:\"\\f189\";\n}\n\n.bp5-icon-download::before{\n  content:\"\\f18a\";\n}\n\n.bp5-icon-drag-handle-horizontal::before{\n  content:\"\\f18b\";\n}\n\n.bp5-icon-drag-handle-vertical::before{\n  content:\"\\f18c\";\n}\n\n.bp5-icon-draw::before{\n  content:\"\\f18d\";\n}\n\n.bp5-icon-drawer-left::before{\n  content:\"\\f18f\";\n}\n\n.bp5-icon-drawer-left-filled::before{\n  content:\"\\f18e\";\n}\n\n.bp5-icon-drawer-right::before{\n  content:\"\\f191\";\n}\n\n.bp5-icon-drawer-right-filled::before{\n  content:\"\\f190\";\n}\n\n.bp5-icon-drive-time::before{\n  content:\"\\f192\";\n}\n\n.bp5-icon-duplicate::before{\n  content:\"\\f193\";\n}\n\n.bp5-icon-edit::before{\n  content:\"\\f194\";\n}\n\n.bp5-icon-eject::before{\n  content:\"\\f195\";\n}\n\n.bp5-icon-emoji::before{\n  content:\"\\f196\";\n}\n\n.bp5-icon-endnote::before{\n  content:\"\\f356\";\n}\n\n.bp5-icon-endorsed::before{\n  content:\"\\f197\";\n}\n\n.bp5-icon-envelope::before{\n  content:\"\\f198\";\n}\n\n.bp5-icon-equals::before{\n  content:\"\\f199\";\n}\n\n.bp5-icon-eraser::before{\n  content:\"\\f19a\";\n}\n\n.bp5-icon-error::before{\n  content:\"\\f19b\";\n}\n\n.bp5-icon-euro::before{\n  content:\"\\f19c\";\n}\n\n.bp5-icon-excavator::before{\n  content:\"\\f36d\";\n}\n\n.bp5-icon-exchange::before{\n  content:\"\\f19d\";\n}\n\n.bp5-icon-exclude-row::before{\n  content:\"\\f19e\";\n}\n\n.bp5-icon-expand-all::before{\n  content:\"\\f19f\";\n}\n\n.bp5-icon-explain::before{\n  content:\"\\f34d\";\n}\n\n.bp5-icon-export::before{\n  content:\"\\f1a0\";\n}\n\n.bp5-icon-eye-off::before{\n  content:\"\\f1a1\";\n}\n\n.bp5-icon-eye-on::before{\n  content:\"\\f1a2\";\n}\n\n.bp5-icon-eye-open::before{\n  content:\"\\f1a3\";\n}\n\n.bp5-icon-fast-backward::before{\n  content:\"\\f1a4\";\n}\n\n.bp5-icon-fast-forward::before{\n  content:\"\\f1a5\";\n}\n\n.bp5-icon-feed::before{\n  content:\"\\f1a7\";\n}\n\n.bp5-icon-feed-subscribed::before{\n  content:\"\\f1a6\";\n}\n\n.bp5-icon-fighter-jet::before{\n  content:\"\\f384\";\n}\n\n.bp5-icon-film::before{\n  content:\"\\f1a8\";\n}\n\n.bp5-icon-filter::before{\n  content:\"\\f1ad\";\n}\n\n.bp5-icon-filter-keep::before{\n  content:\"\\f1a9\";\n}\n\n.bp5-icon-filter-list::before{\n  content:\"\\f1aa\";\n}\n\n.bp5-icon-filter-open::before{\n  content:\"\\f1ab\";\n}\n\n.bp5-icon-filter-remove::before{\n  content:\"\\f1ac\";\n}\n\n.bp5-icon-filter-sort-asc::before{\n  content:\"\\f38e\";\n}\n\n.bp5-icon-filter-sort-desc::before{\n  content:\"\\f38f\";\n}\n\n.bp5-icon-flag::before{\n  content:\"\\f1ae\";\n}\n\n.bp5-icon-flame::before{\n  content:\"\\f1af\";\n}\n\n.bp5-icon-flash::before{\n  content:\"\\f1b0\";\n}\n\n.bp5-icon-floating-point::before{\n  content:\"\\f32c\";\n}\n\n.bp5-icon-floppy-disk::before{\n  content:\"\\f1b1\";\n}\n\n.bp5-icon-flow-branch::before{\n  content:\"\\f1b2\";\n}\n\n.bp5-icon-flow-end::before{\n  content:\"\\f1b3\";\n}\n\n.bp5-icon-flow-linear::before{\n  content:\"\\f1b4\";\n}\n\n.bp5-icon-flow-review::before{\n  content:\"\\f1b6\";\n}\n\n.bp5-icon-flow-review-branch::before{\n  content:\"\\f1b5\";\n}\n\n.bp5-icon-flows::before{\n  content:\"\\f1b7\";\n}\n\n.bp5-icon-folder-close::before{\n  content:\"\\f1b8\";\n}\n\n.bp5-icon-folder-new::before{\n  content:\"\\f1b9\";\n}\n\n.bp5-icon-folder-open::before{\n  content:\"\\f1ba\";\n}\n\n.bp5-icon-folder-shared::before{\n  content:\"\\f1bc\";\n}\n\n.bp5-icon-folder-shared-open::before{\n  content:\"\\f1bb\";\n}\n\n.bp5-icon-follower::before{\n  content:\"\\f1bd\";\n}\n\n.bp5-icon-following::before{\n  content:\"\\f1be\";\n}\n\n.bp5-icon-font::before{\n  content:\"\\f1bf\";\n}\n\n.bp5-icon-fork::before{\n  content:\"\\f1c0\";\n}\n\n.bp5-icon-form::before{\n  content:\"\\f1c1\";\n}\n\n.bp5-icon-forward-ten::before{\n  content:\"\\f35d\";\n}\n\n.bp5-icon-fuel::before{\n  content:\"\\f323\";\n}\n\n.bp5-icon-full-circle::before{\n  content:\"\\f1c2\";\n}\n\n.bp5-icon-full-stacked-chart::before{\n  content:\"\\f1c3\";\n}\n\n.bp5-icon-fullscreen::before{\n  content:\"\\f1c4\";\n}\n\n.bp5-icon-function::before{\n  content:\"\\f1c5\";\n}\n\n.bp5-icon-gantt-chart::before{\n  content:\"\\f1c6\";\n}\n\n.bp5-icon-generate::before{\n  content:\"\\f34c\";\n}\n\n.bp5-icon-geofence::before{\n  content:\"\\f1c7\";\n}\n\n.bp5-icon-geolocation::before{\n  content:\"\\f1c8\";\n}\n\n.bp5-icon-geosearch::before{\n  content:\"\\f1c9\";\n}\n\n.bp5-icon-geotime::before{\n  content:\"\\f344\";\n}\n\n.bp5-icon-git-branch::before{\n  content:\"\\f1ca\";\n}\n\n.bp5-icon-git-commit::before{\n  content:\"\\f1cb\";\n}\n\n.bp5-icon-git-merge::before{\n  content:\"\\f1cc\";\n}\n\n.bp5-icon-git-new-branch::before{\n  content:\"\\f1cd\";\n}\n\n.bp5-icon-git-pull::before{\n  content:\"\\f1ce\";\n}\n\n.bp5-icon-git-push::before{\n  content:\"\\f1cf\";\n}\n\n.bp5-icon-git-repo::before{\n  content:\"\\f1d0\";\n}\n\n.bp5-icon-glass::before{\n  content:\"\\f1d1\";\n}\n\n.bp5-icon-globe::before{\n  content:\"\\f1d3\";\n}\n\n.bp5-icon-globe-network::before{\n  content:\"\\f1d2\";\n}\n\n.bp5-icon-globe-network-add::before{\n  content:\"\\f382\";\n}\n\n.bp5-icon-graph::before{\n  content:\"\\f1d5\";\n}\n\n.bp5-icon-graph-remove::before{\n  content:\"\\f1d4\";\n}\n\n.bp5-icon-greater-than::before{\n  content:\"\\f1d7\";\n}\n\n.bp5-icon-greater-than-or-equal-to::before{\n  content:\"\\f1d6\";\n}\n\n.bp5-icon-grid::before{\n  content:\"\\f1d9\";\n}\n\n.bp5-icon-grid-view::before{\n  content:\"\\f1d8\";\n}\n\n.bp5-icon-group-item::before{\n  content:\"\\f34a\";\n}\n\n.bp5-icon-group-objects::before{\n  content:\"\\f1da\";\n}\n\n.bp5-icon-grouped-bar-chart::before{\n  content:\"\\f1db\";\n}\n\n.bp5-icon-hand::before{\n  content:\"\\f1e0\";\n}\n\n.bp5-icon-hand-down::before{\n  content:\"\\f1dc\";\n}\n\n.bp5-icon-hand-left::before{\n  content:\"\\f1dd\";\n}\n\n.bp5-icon-hand-right::before{\n  content:\"\\f1de\";\n}\n\n.bp5-icon-hand-up::before{\n  content:\"\\f1df\";\n}\n\n.bp5-icon-hat::before{\n  content:\"\\f1e1\";\n}\n\n.bp5-icon-header::before{\n  content:\"\\f1e5\";\n}\n\n.bp5-icon-header-one::before{\n  content:\"\\f1e2\";\n}\n\n.bp5-icon-header-three::before{\n  content:\"\\f1e3\";\n}\n\n.bp5-icon-header-two::before{\n  content:\"\\f1e4\";\n}\n\n.bp5-icon-headset::before{\n  content:\"\\f1e6\";\n}\n\n.bp5-icon-heart::before{\n  content:\"\\f1e8\";\n}\n\n.bp5-icon-heart-broken::before{\n  content:\"\\f1e7\";\n}\n\n.bp5-icon-heat-grid::before{\n  content:\"\\f1e9\";\n}\n\n.bp5-icon-heatmap::before{\n  content:\"\\f1ea\";\n}\n\n.bp5-icon-helicopter::before{\n  content:\"\\f1eb\";\n}\n\n.bp5-icon-help::before{\n  content:\"\\f1ec\";\n}\n\n.bp5-icon-helper-management::before{\n  content:\"\\f1ed\";\n}\n\n.bp5-icon-hexagon::before{\n  content:\"\\f374\";\n}\n\n.bp5-icon-high-priority::before{\n  content:\"\\f1ee\";\n}\n\n.bp5-icon-high-voltage-pole::before{\n  content:\"\\f333\";\n}\n\n.bp5-icon-highlight::before{\n  content:\"\\f1ef\";\n}\n\n.bp5-icon-history::before{\n  content:\"\\f1f0\";\n}\n\n.bp5-icon-home::before{\n  content:\"\\f1f1\";\n}\n\n.bp5-icon-horizontal-bar-chart::before{\n  content:\"\\f1f4\";\n}\n\n.bp5-icon-horizontal-bar-chart-asc::before{\n  content:\"\\f1f2\";\n}\n\n.bp5-icon-horizontal-bar-chart-desc::before{\n  content:\"\\f1f3\";\n}\n\n.bp5-icon-horizontal-distribution::before{\n  content:\"\\f1f5\";\n}\n\n.bp5-icon-horizontal-inbetween::before{\n  content:\"\\f329\";\n}\n\n.bp5-icon-hurricane::before{\n  content:\"\\f1f6\";\n}\n\n.bp5-icon-id-number::before{\n  content:\"\\f1f7\";\n}\n\n.bp5-icon-image-rotate-left::before{\n  content:\"\\f1f8\";\n}\n\n.bp5-icon-image-rotate-right::before{\n  content:\"\\f1f9\";\n}\n\n.bp5-icon-import::before{\n  content:\"\\f1fa\";\n}\n\n.bp5-icon-inbox::before{\n  content:\"\\f1ff\";\n}\n\n.bp5-icon-inbox-filtered::before{\n  content:\"\\f1fb\";\n}\n\n.bp5-icon-inbox-geo::before{\n  content:\"\\f1fc\";\n}\n\n.bp5-icon-inbox-search::before{\n  content:\"\\f1fd\";\n}\n\n.bp5-icon-inbox-update::before{\n  content:\"\\f1fe\";\n}\n\n.bp5-icon-info-sign::before{\n  content:\"\\f200\";\n}\n\n.bp5-icon-inheritance::before{\n  content:\"\\f201\";\n}\n\n.bp5-icon-inherited-group::before{\n  content:\"\\f202\";\n}\n\n.bp5-icon-inner-join::before{\n  content:\"\\f203\";\n}\n\n.bp5-icon-input::before{\n  content:\"\\f34b\";\n}\n\n.bp5-icon-insert::before{\n  content:\"\\f204\";\n}\n\n.bp5-icon-intelligence::before{\n  content:\"\\f337\";\n}\n\n.bp5-icon-intersection::before{\n  content:\"\\f205\";\n}\n\n.bp5-icon-ip-address::before{\n  content:\"\\f206\";\n}\n\n.bp5-icon-issue::before{\n  content:\"\\f209\";\n}\n\n.bp5-icon-issue-closed::before{\n  content:\"\\f207\";\n}\n\n.bp5-icon-issue-new::before{\n  content:\"\\f208\";\n}\n\n.bp5-icon-italic::before{\n  content:\"\\f20a\";\n}\n\n.bp5-icon-join-table::before{\n  content:\"\\f20b\";\n}\n\n.bp5-icon-key::before{\n  content:\"\\f215\";\n}\n\n.bp5-icon-key-backspace::before{\n  content:\"\\f20c\";\n}\n\n.bp5-icon-key-command::before{\n  content:\"\\f20d\";\n}\n\n.bp5-icon-key-control::before{\n  content:\"\\f20e\";\n}\n\n.bp5-icon-key-delete::before{\n  content:\"\\f20f\";\n}\n\n.bp5-icon-key-enter::before{\n  content:\"\\f210\";\n}\n\n.bp5-icon-key-escape::before{\n  content:\"\\f211\";\n}\n\n.bp5-icon-key-option::before{\n  content:\"\\f212\";\n}\n\n.bp5-icon-key-shift::before{\n  content:\"\\f213\";\n}\n\n.bp5-icon-key-tab::before{\n  content:\"\\f214\";\n}\n\n.bp5-icon-known-vehicle::before{\n  content:\"\\f216\";\n}\n\n.bp5-icon-lab-test::before{\n  content:\"\\f217\";\n}\n\n.bp5-icon-label::before{\n  content:\"\\f218\";\n}\n\n.bp5-icon-layer::before{\n  content:\"\\f21a\";\n}\n\n.bp5-icon-layer-outline::before{\n  content:\"\\f219\";\n}\n\n.bp5-icon-layers::before{\n  content:\"\\f21b\";\n}\n\n.bp5-icon-layout::before{\n  content:\"\\f225\";\n}\n\n.bp5-icon-layout-auto::before{\n  content:\"\\f21c\";\n}\n\n.bp5-icon-layout-balloon::before{\n  content:\"\\f21d\";\n}\n\n.bp5-icon-layout-bottom-row-three-tiles::before{\n  content:\"\\f364\";\n}\n\n.bp5-icon-layout-bottom-row-two-tiles::before{\n  content:\"\\f363\";\n}\n\n.bp5-icon-layout-circle::before{\n  content:\"\\f21e\";\n}\n\n.bp5-icon-layout-grid::before{\n  content:\"\\f21f\";\n}\n\n.bp5-icon-layout-group-by::before{\n  content:\"\\f220\";\n}\n\n.bp5-icon-layout-hierarchy::before{\n  content:\"\\f221\";\n}\n\n.bp5-icon-layout-left-column-three-tiles::before{\n  content:\"\\f366\";\n}\n\n.bp5-icon-layout-left-column-two-tiles::before{\n  content:\"\\f365\";\n}\n\n.bp5-icon-layout-linear::before{\n  content:\"\\f222\";\n}\n\n.bp5-icon-layout-right-column-three-tiles::before{\n  content:\"\\f368\";\n}\n\n.bp5-icon-layout-right-column-two-tiles::before{\n  content:\"\\f367\";\n}\n\n.bp5-icon-layout-skew-grid::before{\n  content:\"\\f223\";\n}\n\n.bp5-icon-layout-sorted-clusters::before{\n  content:\"\\f224\";\n}\n\n.bp5-icon-layout-three-columns::before{\n  content:\"\\f361\";\n}\n\n.bp5-icon-layout-three-rows::before{\n  content:\"\\f362\";\n}\n\n.bp5-icon-layout-top-row-three-tiles::before{\n  content:\"\\f36a\";\n}\n\n.bp5-icon-layout-top-row-two-tiles::before{\n  content:\"\\f369\";\n}\n\n.bp5-icon-layout-two-columns::before{\n  content:\"\\f35f\";\n}\n\n.bp5-icon-layout-two-rows::before{\n  content:\"\\f360\";\n}\n\n.bp5-icon-learning::before{\n  content:\"\\f226\";\n}\n\n.bp5-icon-left-join::before{\n  content:\"\\f227\";\n}\n\n.bp5-icon-lengthen-text::before{\n  content:\"\\f33e\";\n}\n\n.bp5-icon-less-than::before{\n  content:\"\\f229\";\n}\n\n.bp5-icon-less-than-or-equal-to::before{\n  content:\"\\f228\";\n}\n\n.bp5-icon-lifesaver::before{\n  content:\"\\f22a\";\n}\n\n.bp5-icon-lightbulb::before{\n  content:\"\\f22b\";\n}\n\n.bp5-icon-lightning::before{\n  content:\"\\f22c\";\n}\n\n.bp5-icon-link::before{\n  content:\"\\f22d\";\n}\n\n.bp5-icon-linked-squares::before{\n  content:\"\\f385\";\n}\n\n.bp5-icon-list::before{\n  content:\"\\f230\";\n}\n\n.bp5-icon-list-columns::before{\n  content:\"\\f22e\";\n}\n\n.bp5-icon-list-detail-view::before{\n  content:\"\\f22f\";\n}\n\n.bp5-icon-locate::before{\n  content:\"\\f231\";\n}\n\n.bp5-icon-lock::before{\n  content:\"\\f232\";\n}\n\n.bp5-icon-locomotive::before{\n  content:\"\\f33b\";\n}\n\n.bp5-icon-log-in::before{\n  content:\"\\f233\";\n}\n\n.bp5-icon-log-out::before{\n  content:\"\\f234\";\n}\n\n.bp5-icon-low-voltage-pole::before{\n  content:\"\\f332\";\n}\n\n.bp5-icon-manual::before{\n  content:\"\\f235\";\n}\n\n.bp5-icon-manually-entered-data::before{\n  content:\"\\f236\";\n}\n\n.bp5-icon-many-to-many::before{\n  content:\"\\f237\";\n}\n\n.bp5-icon-many-to-one::before{\n  content:\"\\f238\";\n}\n\n.bp5-icon-map::before{\n  content:\"\\f23b\";\n}\n\n.bp5-icon-map-create::before{\n  content:\"\\f239\";\n}\n\n.bp5-icon-map-marker::before{\n  content:\"\\f23a\";\n}\n\n.bp5-icon-maximize::before{\n  content:\"\\f23c\";\n}\n\n.bp5-icon-media::before{\n  content:\"\\f23d\";\n}\n\n.bp5-icon-menu::before{\n  content:\"\\f240\";\n}\n\n.bp5-icon-menu-closed::before{\n  content:\"\\f23e\";\n}\n\n.bp5-icon-menu-open::before{\n  content:\"\\f23f\";\n}\n\n.bp5-icon-merge-columns::before{\n  content:\"\\f241\";\n}\n\n.bp5-icon-merge-links::before{\n  content:\"\\f242\";\n}\n\n.bp5-icon-microphone::before{\n  content:\"\\f343\";\n}\n\n.bp5-icon-minimize::before{\n  content:\"\\f243\";\n}\n\n.bp5-icon-minus::before{\n  content:\"\\f244\";\n}\n\n.bp5-icon-mobile-phone::before{\n  content:\"\\f245\";\n}\n\n.bp5-icon-mobile-video::before{\n  content:\"\\f246\";\n}\n\n.bp5-icon-modal::before{\n  content:\"\\f248\";\n}\n\n.bp5-icon-modal-filled::before{\n  content:\"\\f247\";\n}\n\n.bp5-icon-model::before{\n  content:\"\\f33d\";\n}\n\n.bp5-icon-moon::before{\n  content:\"\\f249\";\n}\n\n.bp5-icon-more::before{\n  content:\"\\f24a\";\n}\n\n.bp5-icon-mountain::before{\n  content:\"\\f24b\";\n}\n\n.bp5-icon-move::before{\n  content:\"\\f24c\";\n}\n\n.bp5-icon-mugshot::before{\n  content:\"\\f24d\";\n}\n\n.bp5-icon-multi-select::before{\n  content:\"\\f24e\";\n}\n\n.bp5-icon-music::before{\n  content:\"\\f24f\";\n}\n\n.bp5-icon-nest::before{\n  content:\"\\f250\";\n}\n\n.bp5-icon-new-drawing::before{\n  content:\"\\f251\";\n}\n\n.bp5-icon-new-grid-item::before{\n  content:\"\\f252\";\n}\n\n.bp5-icon-new-layer::before{\n  content:\"\\f253\";\n}\n\n.bp5-icon-new-layers::before{\n  content:\"\\f254\";\n}\n\n.bp5-icon-new-link::before{\n  content:\"\\f255\";\n}\n\n.bp5-icon-new-object::before{\n  content:\"\\f256\";\n}\n\n.bp5-icon-new-person::before{\n  content:\"\\f257\";\n}\n\n.bp5-icon-new-prescription::before{\n  content:\"\\f258\";\n}\n\n.bp5-icon-new-shield::before{\n  content:\"\\f349\";\n}\n\n.bp5-icon-new-text-box::before{\n  content:\"\\f259\";\n}\n\n.bp5-icon-ninja::before{\n  content:\"\\f25a\";\n}\n\n.bp5-icon-not-equal-to::before{\n  content:\"\\f25b\";\n}\n\n.bp5-icon-notifications::before{\n  content:\"\\f25e\";\n}\n\n.bp5-icon-notifications-add::before{\n  content:\"\\f398\";\n}\n\n.bp5-icon-notifications-snooze::before{\n  content:\"\\f25c\";\n}\n\n.bp5-icon-notifications-updated::before{\n  content:\"\\f25d\";\n}\n\n.bp5-icon-numbered-list::before{\n  content:\"\\f25f\";\n}\n\n.bp5-icon-numerical::before{\n  content:\"\\f260\";\n}\n\n.bp5-icon-object-view::before{\n  content:\"\\f390\";\n}\n\n.bp5-icon-office::before{\n  content:\"\\f261\";\n}\n\n.bp5-icon-offline::before{\n  content:\"\\f262\";\n}\n\n.bp5-icon-oil-field::before{\n  content:\"\\f263\";\n}\n\n.bp5-icon-one-column::before{\n  content:\"\\f264\";\n}\n\n.bp5-icon-one-to-many::before{\n  content:\"\\f265\";\n}\n\n.bp5-icon-one-to-one::before{\n  content:\"\\f266\";\n}\n\n.bp5-icon-open-application::before{\n  content:\"\\f32b\";\n}\n\n.bp5-icon-outdated::before{\n  content:\"\\f267\";\n}\n\n.bp5-icon-output::before{\n  content:\"\\f370\";\n}\n\n.bp5-icon-package::before{\n  content:\"\\f375\";\n}\n\n.bp5-icon-page-layout::before{\n  content:\"\\f268\";\n}\n\n.bp5-icon-panel::before{\n  content:\"\\f381\";\n}\n\n.bp5-icon-panel-stats::before{\n  content:\"\\f269\";\n}\n\n.bp5-icon-panel-table::before{\n  content:\"\\f26a\";\n}\n\n.bp5-icon-paperclip::before{\n  content:\"\\f26b\";\n}\n\n.bp5-icon-paragraph::before{\n  content:\"\\f26c\";\n}\n\n.bp5-icon-paste-variable::before{\n  content:\"\\f346\";\n}\n\n.bp5-icon-path::before{\n  content:\"\\f26e\";\n}\n\n.bp5-icon-path-search::before{\n  content:\"\\f26d\";\n}\n\n.bp5-icon-pause::before{\n  content:\"\\f26f\";\n}\n\n.bp5-icon-people::before{\n  content:\"\\f270\";\n}\n\n.bp5-icon-percentage::before{\n  content:\"\\f271\";\n}\n\n.bp5-icon-person::before{\n  content:\"\\f272\";\n}\n\n.bp5-icon-phone::before{\n  content:\"\\f273\";\n}\n\n.bp5-icon-phone-call::before{\n  content:\"\\f347\";\n}\n\n.bp5-icon-phone-forward::before{\n  content:\"\\f348\";\n}\n\n.bp5-icon-phone-search::before{\n  content:\"\\f397\";\n}\n\n.bp5-icon-pie-chart::before{\n  content:\"\\f274\";\n}\n\n.bp5-icon-pill::before{\n  content:\"\\f376\";\n}\n\n.bp5-icon-pin::before{\n  content:\"\\f275\";\n}\n\n.bp5-icon-pivot::before{\n  content:\"\\f277\";\n}\n\n.bp5-icon-pivot-table::before{\n  content:\"\\f276\";\n}\n\n.bp5-icon-play::before{\n  content:\"\\f278\";\n}\n\n.bp5-icon-playbook::before{\n  content:\"\\f324\";\n}\n\n.bp5-icon-plus::before{\n  content:\"\\f279\";\n}\n\n.bp5-icon-polygon-filter::before{\n  content:\"\\f27a\";\n}\n\n.bp5-icon-power::before{\n  content:\"\\f27b\";\n}\n\n.bp5-icon-predictive-analysis::before{\n  content:\"\\f27c\";\n}\n\n.bp5-icon-prescription::before{\n  content:\"\\f27d\";\n}\n\n.bp5-icon-presentation::before{\n  content:\"\\f27e\";\n}\n\n.bp5-icon-print::before{\n  content:\"\\f27f\";\n}\n\n.bp5-icon-projects::before{\n  content:\"\\f280\";\n}\n\n.bp5-icon-properties::before{\n  content:\"\\f281\";\n}\n\n.bp5-icon-property::before{\n  content:\"\\f282\";\n}\n\n.bp5-icon-publish-function::before{\n  content:\"\\f283\";\n}\n\n.bp5-icon-pulse::before{\n  content:\"\\f284\";\n}\n\n.bp5-icon-rain::before{\n  content:\"\\f285\";\n}\n\n.bp5-icon-random::before{\n  content:\"\\f286\";\n}\n\n.bp5-icon-range-ring::before{\n  content:\"\\f371\";\n}\n\n.bp5-icon-record::before{\n  content:\"\\f287\";\n}\n\n.bp5-icon-rect-height::before{\n  content:\"\\f325\";\n}\n\n.bp5-icon-rect-width::before{\n  content:\"\\f326\";\n}\n\n.bp5-icon-rectangle::before{\n  content:\"\\f321\";\n}\n\n.bp5-icon-redo::before{\n  content:\"\\f288\";\n}\n\n.bp5-icon-refresh::before{\n  content:\"\\f289\";\n}\n\n.bp5-icon-regex::before{\n  content:\"\\f32f\";\n}\n\n.bp5-icon-regression-chart::before{\n  content:\"\\f28a\";\n}\n\n.bp5-icon-remove::before{\n  content:\"\\f290\";\n}\n\n.bp5-icon-remove-column::before{\n  content:\"\\f28d\";\n}\n\n.bp5-icon-remove-column-left::before{\n  content:\"\\f28b\";\n}\n\n.bp5-icon-remove-column-right::before{\n  content:\"\\f28c\";\n}\n\n.bp5-icon-remove-row-bottom::before{\n  content:\"\\f28e\";\n}\n\n.bp5-icon-remove-row-top::before{\n  content:\"\\f28f\";\n}\n\n.bp5-icon-repeat::before{\n  content:\"\\f291\";\n}\n\n.bp5-icon-reset::before{\n  content:\"\\f292\";\n}\n\n.bp5-icon-resolve::before{\n  content:\"\\f293\";\n}\n\n.bp5-icon-rig::before{\n  content:\"\\f294\";\n}\n\n.bp5-icon-right-join::before{\n  content:\"\\f295\";\n}\n\n.bp5-icon-ring::before{\n  content:\"\\f296\";\n}\n\n.bp5-icon-rocket::before{\n  content:\"\\f298\";\n}\n\n.bp5-icon-rocket-slant::before{\n  content:\"\\f297\";\n}\n\n.bp5-icon-rotate-ccw::before{\n  content:\"\\f389\";\n}\n\n.bp5-icon-rotate-cw::before{\n  content:\"\\f388\";\n}\n\n.bp5-icon-rotate-document::before{\n  content:\"\\f299\";\n}\n\n.bp5-icon-rotate-page::before{\n  content:\"\\f29a\";\n}\n\n.bp5-icon-route::before{\n  content:\"\\f29b\";\n}\n\n.bp5-icon-run-history::before{\n  content:\"\\f393\";\n}\n\n.bp5-icon-satellite::before{\n  content:\"\\f29c\";\n}\n\n.bp5-icon-saved::before{\n  content:\"\\f29d\";\n}\n\n.bp5-icon-scatter-plot::before{\n  content:\"\\f29e\";\n}\n\n.bp5-icon-search::before{\n  content:\"\\f2a2\";\n}\n\n.bp5-icon-search-around::before{\n  content:\"\\f29f\";\n}\n\n.bp5-icon-search-template::before{\n  content:\"\\f2a0\";\n}\n\n.bp5-icon-search-text::before{\n  content:\"\\f2a1\";\n}\n\n.bp5-icon-segmented-control::before{\n  content:\"\\f2a3\";\n}\n\n.bp5-icon-select::before{\n  content:\"\\f2a4\";\n}\n\n.bp5-icon-selection::before{\n  content:\"\\f2a5\";\n}\n\n.bp5-icon-selection-box::before{\n  content:\"\\f399\";\n}\n\n.bp5-icon-selection-box-add::before{\n  content:\"\\f39a\";\n}\n\n.bp5-icon-selection-box-edit::before{\n  content:\"\\f39b\";\n}\n\n.bp5-icon-send-backward::before{\n  content:\"\\f355\";\n}\n\n.bp5-icon-send-message::before{\n  content:\"\\f2a6\";\n}\n\n.bp5-icon-send-to::before{\n  content:\"\\f2a9\";\n}\n\n.bp5-icon-send-to-graph::before{\n  content:\"\\f2a7\";\n}\n\n.bp5-icon-send-to-map::before{\n  content:\"\\f2a8\";\n}\n\n.bp5-icon-sensor::before{\n  content:\"\\f33c\";\n}\n\n.bp5-icon-series-add::before{\n  content:\"\\f2aa\";\n}\n\n.bp5-icon-series-configuration::before{\n  content:\"\\f2ab\";\n}\n\n.bp5-icon-series-derived::before{\n  content:\"\\f2ac\";\n}\n\n.bp5-icon-series-filtered::before{\n  content:\"\\f2ad\";\n}\n\n.bp5-icon-series-search::before{\n  content:\"\\f2ae\";\n}\n\n.bp5-icon-server::before{\n  content:\"\\f378\";\n}\n\n.bp5-icon-server-install::before{\n  content:\"\\f377\";\n}\n\n.bp5-icon-settings::before{\n  content:\"\\f2af\";\n}\n\n.bp5-icon-shapes::before{\n  content:\"\\f2b0\";\n}\n\n.bp5-icon-share::before{\n  content:\"\\f2b1\";\n}\n\n.bp5-icon-shared-filter::before{\n  content:\"\\f2b2\";\n}\n\n.bp5-icon-shield::before{\n  content:\"\\f2b3\";\n}\n\n.bp5-icon-ship::before{\n  content:\"\\f2b4\";\n}\n\n.bp5-icon-shop::before{\n  content:\"\\f2b5\";\n}\n\n.bp5-icon-shopping-cart::before{\n  content:\"\\f2b6\";\n}\n\n.bp5-icon-shorten-text::before{\n  content:\"\\f33f\";\n}\n\n.bp5-icon-signal-search::before{\n  content:\"\\f2b7\";\n}\n\n.bp5-icon-sim-card::before{\n  content:\"\\f2b8\";\n}\n\n.bp5-icon-slash::before{\n  content:\"\\f2b9\";\n}\n\n.bp5-icon-small-cross::before{\n  content:\"\\f2ba\";\n}\n\n.bp5-icon-small-info-sign::before{\n  content:\"\\f334\";\n}\n\n.bp5-icon-small-minus::before{\n  content:\"\\f2bb\";\n}\n\n.bp5-icon-small-plus::before{\n  content:\"\\f2bc\";\n}\n\n.bp5-icon-small-square::before{\n  content:\"\\f2bd\";\n}\n\n.bp5-icon-small-tick::before{\n  content:\"\\f2be\";\n}\n\n.bp5-icon-snowflake::before{\n  content:\"\\f2bf\";\n}\n\n.bp5-icon-soccer-ball::before{\n  content:\"\\f350\";\n}\n\n.bp5-icon-social-media::before{\n  content:\"\\f2c0\";\n}\n\n.bp5-icon-sort::before{\n  content:\"\\f2c7\";\n}\n\n.bp5-icon-sort-alphabetical::before{\n  content:\"\\f2c2\";\n}\n\n.bp5-icon-sort-alphabetical-desc::before{\n  content:\"\\f2c1\";\n}\n\n.bp5-icon-sort-asc::before{\n  content:\"\\f2c3\";\n}\n\n.bp5-icon-sort-desc::before{\n  content:\"\\f2c4\";\n}\n\n.bp5-icon-sort-numerical::before{\n  content:\"\\f2c6\";\n}\n\n.bp5-icon-sort-numerical-desc::before{\n  content:\"\\f2c5\";\n}\n\n.bp5-icon-spell-check::before{\n  content:\"\\f340\";\n}\n\n.bp5-icon-split-columns::before{\n  content:\"\\f2c8\";\n}\n\n.bp5-icon-sports-stadium::before{\n  content:\"\\f351\";\n}\n\n.bp5-icon-square::before{\n  content:\"\\f2c9\";\n}\n\n.bp5-icon-stacked-chart::before{\n  content:\"\\f2ca\";\n}\n\n.bp5-icon-stadium-geometry::before{\n  content:\"\\f2cb\";\n}\n\n.bp5-icon-star::before{\n  content:\"\\f2cd\";\n}\n\n.bp5-icon-star-empty::before{\n  content:\"\\f2cc\";\n}\n\n.bp5-icon-step-backward::before{\n  content:\"\\f2ce\";\n}\n\n.bp5-icon-step-chart::before{\n  content:\"\\f2cf\";\n}\n\n.bp5-icon-step-forward::before{\n  content:\"\\f2d0\";\n}\n\n.bp5-icon-stop::before{\n  content:\"\\f2d1\";\n}\n\n.bp5-icon-stopwatch::before{\n  content:\"\\f2d2\";\n}\n\n.bp5-icon-strikethrough::before{\n  content:\"\\f2d3\";\n}\n\n.bp5-icon-style::before{\n  content:\"\\f2d4\";\n}\n\n.bp5-icon-subscript::before{\n  content:\"\\f339\";\n}\n\n.bp5-icon-subtract-right-join::before{\n  content:\"\\f392\";\n}\n\n.bp5-icon-superscript::before{\n  content:\"\\f33a\";\n}\n\n.bp5-icon-swap-horizontal::before{\n  content:\"\\f2d5\";\n}\n\n.bp5-icon-swap-vertical::before{\n  content:\"\\f2d6\";\n}\n\n.bp5-icon-switch::before{\n  content:\"\\f2d7\";\n}\n\n.bp5-icon-symbol-circle::before{\n  content:\"\\f2d8\";\n}\n\n.bp5-icon-symbol-cross::before{\n  content:\"\\f2d9\";\n}\n\n.bp5-icon-symbol-diamond::before{\n  content:\"\\f2da\";\n}\n\n.bp5-icon-symbol-rectangle::before{\n  content:\"\\f322\";\n}\n\n.bp5-icon-symbol-square::before{\n  content:\"\\f2db\";\n}\n\n.bp5-icon-symbol-triangle-down::before{\n  content:\"\\f2dc\";\n}\n\n.bp5-icon-symbol-triangle-up::before{\n  content:\"\\f2dd\";\n}\n\n.bp5-icon-syringe::before{\n  content:\"\\f2de\";\n}\n\n.bp5-icon-table-sync::before{\n  content:\"\\f36e\";\n}\n\n.bp5-icon-tag::before{\n  content:\"\\f2df\";\n}\n\n.bp5-icon-tag-add::before{\n  content:\"\\f379\";\n}\n\n.bp5-icon-tag-promote::before{\n  content:\"\\f37a\";\n}\n\n.bp5-icon-tag-refresh::before{\n  content:\"\\f37b\";\n}\n\n.bp5-icon-tag-undo::before{\n  content:\"\\f37c\";\n}\n\n.bp5-icon-tags::before{\n  content:\"\\f37d\";\n}\n\n.bp5-icon-take-action::before{\n  content:\"\\f2e0\";\n}\n\n.bp5-icon-tank::before{\n  content:\"\\f2e1\";\n}\n\n.bp5-icon-target::before{\n  content:\"\\f2e2\";\n}\n\n.bp5-icon-taxi::before{\n  content:\"\\f2e3\";\n}\n\n.bp5-icon-team::before{\n  content:\"\\f352\";\n}\n\n.bp5-icon-temperature::before{\n  content:\"\\f2e4\";\n}\n\n.bp5-icon-text-highlight::before{\n  content:\"\\f2e5\";\n}\n\n.bp5-icon-th::before{\n  content:\"\\f2ea\";\n}\n\n.bp5-icon-th-add::before{\n  content:\"\\f38a\";\n}\n\n.bp5-icon-th-derived::before{\n  content:\"\\f2e6\";\n}\n\n.bp5-icon-th-disconnect::before{\n  content:\"\\f2e7\";\n}\n\n.bp5-icon-th-filtered::before{\n  content:\"\\f2e8\";\n}\n\n.bp5-icon-th-list::before{\n  content:\"\\f2e9\";\n}\n\n.bp5-icon-th-list-add::before{\n  content:\"\\f38b\";\n}\n\n.bp5-icon-th-virtual::before{\n  content:\"\\f38c\";\n}\n\n.bp5-icon-th-virtual-add::before{\n  content:\"\\f38d\";\n}\n\n.bp5-icon-third-party::before{\n  content:\"\\f2eb\";\n}\n\n.bp5-icon-thumbs-down::before{\n  content:\"\\f2ec\";\n}\n\n.bp5-icon-thumbs-up::before{\n  content:\"\\f2ed\";\n}\n\n.bp5-icon-tick::before{\n  content:\"\\f2ef\";\n}\n\n.bp5-icon-tick-circle::before{\n  content:\"\\f2ee\";\n}\n\n.bp5-icon-time::before{\n  content:\"\\f2f0\";\n}\n\n.bp5-icon-timeline-area-chart::before{\n  content:\"\\f2f1\";\n}\n\n.bp5-icon-timeline-bar-chart::before{\n  content:\"\\f2f2\";\n}\n\n.bp5-icon-timeline-events::before{\n  content:\"\\f2f3\";\n}\n\n.bp5-icon-timeline-line-chart::before{\n  content:\"\\f2f4\";\n}\n\n.bp5-icon-tint::before{\n  content:\"\\f2f5\";\n}\n\n.bp5-icon-torch::before{\n  content:\"\\f2f6\";\n}\n\n.bp5-icon-tractor::before{\n  content:\"\\f2f7\";\n}\n\n.bp5-icon-train::before{\n  content:\"\\f2f8\";\n}\n\n.bp5-icon-translate::before{\n  content:\"\\f2f9\";\n}\n\n.bp5-icon-trash::before{\n  content:\"\\f2fa\";\n}\n\n.bp5-icon-tree::before{\n  content:\"\\f2fb\";\n}\n\n.bp5-icon-trending-down::before{\n  content:\"\\f2fc\";\n}\n\n.bp5-icon-trending-up::before{\n  content:\"\\f2fd\";\n}\n\n.bp5-icon-trophy::before{\n  content:\"\\f34f\";\n}\n\n.bp5-icon-truck::before{\n  content:\"\\f2fe\";\n}\n\n.bp5-icon-two-columns::before{\n  content:\"\\f2ff\";\n}\n\n.bp5-icon-unarchive::before{\n  content:\"\\f300\";\n}\n\n.bp5-icon-underline::before{\n  content:\"\\f301\";\n}\n\n.bp5-icon-undo::before{\n  content:\"\\f302\";\n}\n\n.bp5-icon-ungroup-objects::before{\n  content:\"\\f303\";\n}\n\n.bp5-icon-unknown-vehicle::before{\n  content:\"\\f304\";\n}\n\n.bp5-icon-unlink::before{\n  content:\"\\f345\";\n}\n\n.bp5-icon-unlock::before{\n  content:\"\\f305\";\n}\n\n.bp5-icon-unpin::before{\n  content:\"\\f306\";\n}\n\n.bp5-icon-unresolve::before{\n  content:\"\\f307\";\n}\n\n.bp5-icon-updated::before{\n  content:\"\\f308\";\n}\n\n.bp5-icon-upload::before{\n  content:\"\\f309\";\n}\n\n.bp5-icon-user::before{\n  content:\"\\f30a\";\n}\n\n.bp5-icon-variable::before{\n  content:\"\\f30b\";\n}\n\n.bp5-icon-vector::before{\n  content:\"\\f35e\";\n}\n\n.bp5-icon-vertical-bar-chart-asc::before{\n  content:\"\\f30c\";\n}\n\n.bp5-icon-vertical-bar-chart-desc::before{\n  content:\"\\f30d\";\n}\n\n.bp5-icon-vertical-distribution::before{\n  content:\"\\f30e\";\n}\n\n.bp5-icon-vertical-inbetween::before{\n  content:\"\\f32a\";\n}\n\n.bp5-icon-video::before{\n  content:\"\\f30f\";\n}\n\n.bp5-icon-virus::before{\n  content:\"\\f310\";\n}\n\n.bp5-icon-volume-down::before{\n  content:\"\\f311\";\n}\n\n.bp5-icon-volume-off::before{\n  content:\"\\f312\";\n}\n\n.bp5-icon-volume-up::before{\n  content:\"\\f313\";\n}\n\n.bp5-icon-walk::before{\n  content:\"\\f314\";\n}\n\n.bp5-icon-warning-sign::before{\n  content:\"\\f315\";\n}\n\n.bp5-icon-waterfall-chart::before{\n  content:\"\\f316\";\n}\n\n.bp5-icon-waves::before{\n  content:\"\\f317\";\n}\n\n.bp5-icon-widget::before{\n  content:\"\\f31b\";\n}\n\n.bp5-icon-widget-button::before{\n  content:\"\\f318\";\n}\n\n.bp5-icon-widget-footer::before{\n  content:\"\\f319\";\n}\n\n.bp5-icon-widget-header::before{\n  content:\"\\f31a\";\n}\n\n.bp5-icon-wind::before{\n  content:\"\\f31c\";\n}\n\n.bp5-icon-wrench::before{\n  content:\"\\f31d\";\n}\n\n.bp5-icon-wrench-redo::before{\n  content:\"\\f37e\";\n}\n\n.bp5-icon-wrench-snooze::before{\n  content:\"\\f37f\";\n}\n\n.bp5-icon-wrench-time::before{\n  content:\"\\f380\";\n}\n\n.bp5-icon-zoom-in::before{\n  content:\"\\f31e\";\n}\n\n.bp5-icon-zoom-out::before{\n  content:\"\\f31f\";\n}\n\n.bp5-icon-zoom-to-fit::before{\n  content:\"\\f320\";\n}\n.bp5-running-text table, table.bp5-html-table{\n  border-spacing:0;\n  font-size:14px;\n}\n.bp5-running-text table th, table.bp5-html-table th,\n.bp5-running-text table td,\ntable.bp5-html-table td{\n  padding:11px;\n  text-align:left;\n  vertical-align:top;\n}\n.bp5-running-text table th, table.bp5-html-table th{\n  color:#1c2127;\n  font-weight:600;\n}\n.bp5-running-text table td, table.bp5-html-table td{\n  color:#1c2127;\n}\n.bp5-running-text table tbody tr:first-child th, table.bp5-html-table tbody tr:first-child th,\n.bp5-running-text table tbody tr:first-child td,\ntable.bp5-html-table tbody tr:first-child td,\n.bp5-running-text table tfoot tr:first-child th,\ntable.bp5-html-table tfoot tr:first-child th,\n.bp5-running-text table tfoot tr:first-child td,\ntable.bp5-html-table tfoot tr:first-child td{\n  box-shadow:inset 0 1px 0 0 rgba(17, 20, 24, 0.15);\n}\n.bp5-dark .bp5-running-text table th, .bp5-running-text .bp5-dark table th, .bp5-dark table.bp5-html-table th{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-running-text table td, .bp5-running-text .bp5-dark table td, .bp5-dark table.bp5-html-table td{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-running-text table tbody tr:first-child th, .bp5-running-text .bp5-dark table tbody tr:first-child th, .bp5-dark table.bp5-html-table tbody tr:first-child th,\n.bp5-dark .bp5-running-text table tbody tr:first-child td,\n.bp5-running-text .bp5-dark table tbody tr:first-child td,\n.bp5-dark table.bp5-html-table tbody tr:first-child td,\n.bp5-dark .bp5-running-text table tfoot tr:first-child th,\n.bp5-running-text .bp5-dark table tfoot tr:first-child th,\n.bp5-dark table.bp5-html-table tfoot tr:first-child th,\n.bp5-dark .bp5-running-text table tfoot tr:first-child td,\n.bp5-running-text .bp5-dark table tfoot tr:first-child td,\n.bp5-dark table.bp5-html-table tfoot tr:first-child td{\n  box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.2);\n}\n\ntable.bp5-html-table.bp5-compact th,\ntable.bp5-html-table.bp5-compact td{\n  padding-bottom:6px;\n  padding-top:6px;\n}\ntable.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n  background:rgba(143, 153, 168, 0.15);\n}\ntable.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n  box-shadow:inset 1px 0 0 0 rgba(17, 20, 24, 0.15);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n    border-left:1px solid buttonborder;\n  }\n}\ntable.bp5-html-table.bp5-html-table-bordered tbody tr td,\ntable.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n  box-shadow:inset 0 1px 0 0 rgba(17, 20, 24, 0.15);\n}\ntable.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\ntable.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n  box-shadow:inset 1px 1px 0 0 rgba(17, 20, 24, 0.15);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n  table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n    border-left:1px solid buttonborder;\n    border-top:1px solid buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-html-table-bordered tbody tr td,\n  table.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n    border-top:1px solid buttonborder;\n  }\n}\ntable.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n  box-shadow:none;\n}\ntable.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:not(:first-child){\n  box-shadow:inset 1px 0 0 0 rgba(17, 20, 24, 0.15);\n}\ntable.bp5-html-table.bp5-interactive tbody tr:hover td{\n  background-color:rgba(143, 153, 168, 0.3);\n  cursor:pointer;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-interactive tbody tr:hover td{\n    background-color:highlight;\n  }\n}\ntable.bp5-html-table.bp5-interactive tbody tr:active td{\n  background-color:rgba(143, 153, 168, 0.35);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  table.bp5-html-table.bp5-interactive tbody tr:active td{\n    background-color:highlight;\n  }\n}\n.bp5-dark table.bp5-html-table{\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n  background:rgba(95, 107, 124, 0.15);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n  box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td,\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n  box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n  box-shadow:inset 1px 1px 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n  box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:first-child{\n  box-shadow:none;\n}\n.bp5-dark table.bp5-html-table.bp5-interactive tbody tr:hover td{\n  background-color:rgba(95, 107, 124, 0.3);\n  cursor:pointer;\n}\n.bp5-dark table.bp5-html-table.bp5-interactive tbody tr:active td{\n  background-color:rgba(95, 107, 124, 0.4);\n}\n\n.bp5-text-muted{\n  color:#5f6b7c;\n}\n\n.bp5-text-disabled{\n  color:rgba(95, 107, 124, 0.6);\n}\n\n.bp5-running-text hr{\n  border-color:rgba(17, 20, 24, 0.15);\n}\n\na{\n  color:#215db0;\n}\na:hover{\n  color:#215db0;\n}\na .bp5-icon, a .bp5-icon-standard, a .bp5-icon-large{\n  color:inherit;\n}\na code{\n  color:inherit;\n}\n\n.bp5-code,\n.bp5-running-text code{\n  background:rgba(255, 255, 255, 0.7);\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color:#5f6b7c;\n}\na > .bp5-code,\na > .bp5-running-text code{\n  color:#2d72d2;\n}\n\n.bp5-code-block,\n.bp5-running-text pre{\n  background:rgba(255, 255, 255, 0.7);\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n  color:#1c2127;\n}\n.bp5-code-block > code,\n.bp5-running-text pre > code{\n  background:none;\n  box-shadow:none;\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-code-block,\n  .bp5-running-text pre{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n\n.bp5-key,\n.bp5-running-text kbd{\n  background:#ffffff;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  color:#5f6b7c;\n}\n\n.bp5-icon.bp5-intent-primary, .bp5-icon-standard.bp5-intent-primary, .bp5-icon-large.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-icon.bp5-intent-success, .bp5-icon-standard.bp5-intent-success, .bp5-icon-large.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-icon.bp5-intent-warning, .bp5-icon-standard.bp5-intent-warning, .bp5-icon-large.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-icon.bp5-intent-danger, .bp5-icon-standard.bp5-intent-danger, .bp5-icon-large.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-heading{\n  color:#1c2127;\n  font-weight:600;\n  margin:0 0 10px;\n  padding:0;\n}\n.bp5-dark .bp5-heading{\n  color:#f6f7f9;\n}\n.bp5-heading.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-heading.bp5-text-muted{\n  color:#abb3bf;\n}\n\n.bp5-running-text h1, h1.bp5-heading{\n  font-size:36px;\n  line-height:40px;\n}\n\n.bp5-running-text h2, h2.bp5-heading{\n  font-size:28px;\n  line-height:32px;\n}\n\n.bp5-running-text h3, h3.bp5-heading{\n  font-size:22px;\n  line-height:25px;\n}\n\n.bp5-running-text h4, h4.bp5-heading{\n  font-size:18px;\n  line-height:21px;\n}\n\n.bp5-running-text h5, h5.bp5-heading{\n  font-size:16px;\n  line-height:19px;\n}\n\n.bp5-running-text h6, h6.bp5-heading{\n  font-size:14px;\n  line-height:16px;\n}\n.bp5-ui-text{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none;\n}\n\n.bp5-monospace-text{\n  font-family:monospace;\n  text-transform:none;\n}\n\n.bp5-text-overflow-ellipsis{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n}\n.bp5-running-text{\n  font-size:14px;\n  line-height:1.5;\n}\n.bp5-running-text h1{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h1{\n  color:#f6f7f9;\n}\n.bp5-running-text h1.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h1.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h2{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h2{\n  color:#f6f7f9;\n}\n.bp5-running-text h2.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h2.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h3{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h3{\n  color:#f6f7f9;\n}\n.bp5-running-text h3.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h3.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h4{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h4{\n  color:#f6f7f9;\n}\n.bp5-running-text h4.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h4.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h5{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h5{\n  color:#f6f7f9;\n}\n.bp5-running-text h5.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h5.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text h6{\n  color:#1c2127;\n  font-weight:600;\n  margin-bottom:20px;\n  margin-top:40px;\n}\n.bp5-dark .bp5-running-text h6{\n  color:#f6f7f9;\n}\n.bp5-running-text h6.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-running-text h6.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-running-text hr{\n  border:none;\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n  margin:20px 0;\n}\n.bp5-running-text p{\n  margin:0 0 10px;\n  padding:0;\n}\n.bp5-text-large{\n  font-size:16px;\n}\n\n.bp5-text-small{\n  font-size:12px;\n}\na{\n  text-decoration:none;\n}\na:hover{\n  cursor:pointer;\n  text-decoration:underline;\n}\n.bp5-code, .bp5-running-text code{\n  font-family:monospace;\n  text-transform:none;\n  border-radius:2px;\n  font-size:smaller;\n  padding:2px 5px;\n}\n\n.bp5-code-block, .bp5-running-text pre{\n  font-family:monospace;\n  text-transform:none;\n  border-radius:2px;\n  display:block;\n  font-size:13px;\n  line-height:1.4;\n  margin:10px 0;\n  padding:13px 15px 12px;\n  word-break:break-all;\n  word-wrap:break-word;\n}\n.bp5-code-block > code, .bp5-running-text pre > code{\n  font-size:inherit;\n  padding:0;\n}\n\n.bp5-key, .bp5-running-text kbd{\n  align-items:center;\n  border-radius:2px;\n  display:inline-flex;\n  font-family:inherit;\n  font-size:12px;\n  height:24px;\n  justify-content:center;\n  line-height:24px;\n  min-width:24px;\n  padding:2px 4px;\n  vertical-align:middle;\n}\n.bp5-key .bp5-icon, .bp5-running-text kbd .bp5-icon, .bp5-key .bp5-icon-standard, .bp5-running-text kbd .bp5-icon-standard, .bp5-key .bp5-icon-large, .bp5-running-text kbd .bp5-icon-large{\n  margin-right:5px;\n}\n.bp5-blockquote, .bp5-running-text blockquote{\n  border-left:solid 4px rgba(171, 179, 191, 0.5);\n  margin:0 0 10px;\n  padding:0 20px;\n}\n.bp5-dark .bp5-blockquote, .bp5-dark .bp5-running-text blockquote, .bp5-running-text .bp5-dark blockquote{\n  border-color:rgba(115, 128, 145, 0.5);\n}\n.bp5-list, .bp5-running-text ul,\n.bp5-running-text ol{\n  margin:10px 0;\n  padding-left:30px;\n}\n.bp5-list li:not(:last-child), .bp5-running-text ul li:not(:last-child),\n.bp5-running-text ol li:not(:last-child){\n  margin-bottom:5px;\n}\n.bp5-list ol, .bp5-running-text ul ol,\n.bp5-running-text ol ol,\n.bp5-list ul,\n.bp5-running-text ul ul,\n.bp5-running-text ol ul{\n  margin-top:5px;\n}\n\n.bp5-list-unstyled{\n  list-style:none;\n  margin:0;\n  padding:0;\n}\n.bp5-list-unstyled li{\n  padding:0;\n}\n.bp5-rtl{\n  text-align:right;\n}\n.bp5-dark{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-text-disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-running-text hr{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-dark a{\n  color:#8abbff;\n}\n.bp5-dark a:hover{\n  color:#8abbff;\n}\n.bp5-dark a .bp5-icon, .bp5-dark a .bp5-icon-standard, .bp5-dark a .bp5-icon-large{\n  color:inherit;\n}\n.bp5-dark a code{\n  color:inherit;\n}\n.bp5-dark .bp5-code,\n.bp5-dark .bp5-running-text code{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n  color:#abb3bf;\n}\na > .bp5-dark .bp5-code,\na > .bp5-dark .bp5-running-text code{\n  color:inherit;\n}\n.bp5-dark .bp5-code-block,\n.bp5-dark .bp5-running-text pre{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-code-block > code,\n.bp5-dark .bp5-running-text pre > code{\n  background:none;\n  box-shadow:none;\n  color:inherit;\n}\n.bp5-dark .bp5-key,\n.bp5-dark .bp5-running-text kbd{\n  background:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n  color:#abb3bf;\n}\n.bp5-dark .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-icon-large.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-icon.bp5-intent-success, .bp5-dark .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-icon-large.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-icon-large.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-icon-large.bp5-intent-danger{\n  color:#fa999c;\n}\n\n:focus{\n  outline:rgba(33, 93, 176, 0.752) solid 2px;\n  outline-offset:2px;\n  -moz-outline-radius:6px;\n}\n.bp5-dark :focus{\n  outline-color:rgba(138, 187, 255, 0.752);\n}\n\n.bp5-focus-disabled :focus:not(.bp5-focus-style-manager-ignore *){\n  outline:none !important;\n}\n.bp5-focus-disabled :focus:not(.bp5-focus-style-manager-ignore *) ~ .bp5-control-indicator{\n  outline:none !important;\n}\n\n.bp5-dark{\n  color-scheme:dark;\n}\n\n.bp5-alert{\n  max-width:400px;\n  padding:20px;\n}\n\n.bp5-alert-body{\n  display:flex;\n}\n.bp5-alert-body .bp5-icon{\n  font-size:40px;\n  margin-right:20px;\n  margin-top:0;\n}\n\n.bp5-alert-contents{\n  word-break:break-word;\n}\n\n.bp5-alert-footer{\n  display:flex;\n  flex-direction:row-reverse;\n  margin-top:10px;\n}\n.bp5-alert-footer .bp5-button{\n  margin-left:10px;\n}\n.bp5-breadcrumbs{\n  align-items:center;\n  cursor:default;\n  display:flex;\n  flex-wrap:wrap;\n  height:30px;\n  list-style:none;\n  margin:0;\n  padding:0;\n}\n.bp5-breadcrumbs > li{\n  align-items:center;\n  display:flex;\n}\n.bp5-breadcrumbs > li::after{\n  background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 00-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='%235f6b7c'/%3e%3c/svg%3e\");\n  content:\"\";\n  display:block;\n  height:16px;\n  margin:0 5px;\n  width:16px;\n}\n.bp5-breadcrumbs > li:last-of-type::after{\n  display:none;\n}\n\n.bp5-breadcrumb,\n.bp5-breadcrumb-current,\n.bp5-breadcrumbs-collapsed{\n  align-items:center;\n  display:inline-flex;\n  font-size:16px;\n}\n\n.bp5-breadcrumb,\n.bp5-breadcrumbs-collapsed{\n  color:#5f6b7c;\n}\n\n.bp5-breadcrumb:hover{\n  text-decoration:none;\n}\n.bp5-breadcrumb.bp5-disabled{\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-breadcrumb .bp5-icon{\n  margin-right:5px;\n}\n\n.bp5-breadcrumb-current{\n  color:inherit;\n  font-weight:600;\n}\n.bp5-breadcrumb-current .bp5-input{\n  font-size:inherit;\n  font-weight:inherit;\n  vertical-align:baseline;\n}\n\n.bp5-breadcrumbs-collapsed{\n  background:rgba(143, 153, 168, 0.15);\n  border:none;\n  border-radius:2px;\n  cursor:pointer;\n  margin-right:2px;\n  padding:1px 5px;\n  vertical-align:text-bottom;\n}\n.bp5-breadcrumbs-collapsed::before{\n  background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cg fill='%235f6b7c'%3e%3cpath d='M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z'/%3e%3c/g%3e%3c/svg%3e\") center no-repeat;\n  content:\"\";\n  display:block;\n  height:16px;\n  width:16px;\n}\n.bp5-breadcrumbs-collapsed:hover{\n  background:rgba(143, 153, 168, 0.3);\n  color:#1c2127;\n  text-decoration:none;\n}\n\n.bp5-dark .bp5-breadcrumb,\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-breadcrumbs > li::after{\n  background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 00-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='%23abb3bf'/%3e%3c/svg%3e\");\n  color:#abb3bf;\n}\n.bp5-dark .bp5-breadcrumb.bp5-disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-breadcrumb-current{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  background:rgba(143, 153, 168, 0.2);\n}\n.bp5-dark .bp5-breadcrumbs-collapsed::before{\n  background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cg fill='%23abb3bf'%3e%3cpath d='M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z'/%3e%3c/g%3e%3c/svg%3e\") center no-repeat;\n}\n.bp5-dark .bp5-breadcrumbs-collapsed:hover{\n  background:rgba(143, 153, 168, 0.3);\n  color:#f6f7f9;\n}\n.bp5-button{\n  display:inline-flex;\n  flex-direction:row;\n  align-items:center;\n  border:none;\n  border-radius:2px;\n  cursor:pointer;\n  font-size:14px;\n  justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  min-height:30px;\n  min-width:30px;\n}\n.bp5-button > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-button > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-button::before,\n.bp5-button > *{\n  margin-right:7px;\n}\n.bp5-button:empty::before,\n.bp5-button > :last-child{\n  margin-right:0;\n}\n.bp5-button:empty{\n  padding:0 !important;\n}\n.bp5-button:disabled, .bp5-button.bp5-disabled{\n  cursor:not-allowed;\n}\n.bp5-button.bp5-fill{\n  display:flex;\n  width:100%;\n}\n.bp5-button.bp5-align-right, .bp5-align-right .bp5-button{\n  text-align:right;\n}\n.bp5-button.bp5-align-left, .bp5-align-left .bp5-button{\n  text-align:left;\n}\n.bp5-button:not([class*=bp5-intent-]){\n  background-color:#f6f7f9;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#1c2127;\n}\n.bp5-button:not([class*=bp5-intent-]):hover{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button:not([class*=bp5-intent-]):active, .bp5-button:not([class*=bp5-intent-]).bp5-active{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button:not([class*=bp5-intent-]):active, .bp5-button:not([class*=bp5-intent-]).bp5-active{\n    background:highlight;\n  }\n}\n.bp5-button:not([class*=bp5-intent-]):disabled, .bp5-button:not([class*=bp5-intent-]).bp5-disabled{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  outline:none;\n}\n.bp5-button:not([class*=bp5-intent-]):disabled.bp5-active, .bp5-button:not([class*=bp5-intent-]):disabled.bp5-active:hover, .bp5-button:not([class*=bp5-intent-]).bp5-disabled.bp5-active, .bp5-button:not([class*=bp5-intent-]).bp5-disabled.bp5-active:hover{\n  background:rgba(211, 216, 222, 0.7);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button:not([class*=bp5-intent-]){\n    border:1px solid buttonborder;\n  }\n}\n.bp5-button.bp5-intent-primary{\n  background-color:#2d72d2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-primary:hover, .bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-primary:hover{\n  background-color:#215db0;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n  background-color:#184a90;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-primary:disabled, .bp5-button.bp5-intent-primary.bp5-disabled{\n  background-color:rgba(45, 114, 210, 0.5);\n  border-color:transparent;\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-primary:disabled, .bp5-button.bp5-intent-primary.bp5-disabled{\n    border-color:graytext;\n    color:graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-primary{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-button.bp5-intent-success{\n  background-color:#238551;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-success:hover, .bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-success:hover{\n  background-color:#1c6e42;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n  background-color:#165a36;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-success:disabled, .bp5-button.bp5-intent-success.bp5-disabled{\n  background-color:rgba(35, 133, 81, 0.5);\n  border-color:transparent;\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-success:disabled, .bp5-button.bp5-intent-success.bp5-disabled{\n    border-color:graytext;\n    color:graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-success{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-button.bp5-intent-warning{\n  background-color:#c87619;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-warning:hover, .bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-warning:hover{\n  background-color:#935610;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n  background-color:#77450d;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-warning:disabled, .bp5-button.bp5-intent-warning.bp5-disabled{\n  background-color:rgba(200, 118, 25, 0.5);\n  border-color:transparent;\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-warning:disabled, .bp5-button.bp5-intent-warning.bp5-disabled{\n    border-color:graytext;\n    color:graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-warning{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-button.bp5-intent-danger{\n  background-color:#cd4246;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-danger:hover, .bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n  color:#ffffff;\n}\n.bp5-button.bp5-intent-danger:hover{\n  background-color:#ac2f33;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n  background-color:#8e292c;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-danger:disabled, .bp5-button.bp5-intent-danger.bp5-disabled{\n  background-color:rgba(205, 66, 70, 0.5);\n  border-color:transparent;\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-danger:disabled, .bp5-button.bp5-intent-danger.bp5-disabled{\n    border-color:graytext;\n    color:graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-danger{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-button[class*=bp5-intent-] .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ffffff;\n}\n.bp5-button.bp5-large, .bp5-large .bp5-button{\n  min-height:40px;\n  min-width:40px;\n  font-size:16px;\n  padding:5px 15px;\n}\n.bp5-button.bp5-large::before,\n.bp5-button.bp5-large > *, .bp5-large .bp5-button::before,\n.bp5-large .bp5-button > *{\n  margin-right:10px;\n}\n.bp5-button.bp5-large:empty::before,\n.bp5-button.bp5-large > :last-child, .bp5-large .bp5-button:empty::before,\n.bp5-large .bp5-button > :last-child{\n  margin-right:0;\n}\n.bp5-button.bp5-small, .bp5-small .bp5-button{\n  min-height:24px;\n  min-width:24px;\n  padding:0 7px;\n}\n.bp5-button.bp5-loading{\n  position:relative;\n}\n.bp5-button.bp5-loading[class*=bp5-icon-]::before{\n  visibility:hidden;\n}\n.bp5-button.bp5-loading .bp5-button-spinner{\n  margin:0;\n  position:absolute;\n}\n.bp5-button.bp5-loading > :not(.bp5-button-spinner){\n  visibility:hidden;\n}\n.bp5-button[class*=bp5-icon-]::before{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  color:#5f6b7c;\n}\n.bp5-button .bp5-icon, .bp5-button .bp5-icon-standard, .bp5-button .bp5-icon-large{\n  color:#5f6b7c;\n}\n.bp5-button .bp5-icon.bp5-align-right, .bp5-button .bp5-icon-standard.bp5-align-right, .bp5-button .bp5-icon-large.bp5-align-right{\n  margin-left:7px;\n}\n.bp5-button .bp5-icon:first-child:last-child,\n.bp5-button .bp5-spinner + .bp5-icon:last-child{\n  margin:0 -7px;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]){\n  background-color:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):hover, .bp5-dark .bp5-button:not([class*=bp5-intent-]):active, .bp5-dark .bp5-button:not([class*=bp5-intent-]).bp5-active{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):hover{\n  background-color:#252a31;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):active, .bp5-dark .bp5-button:not([class*=bp5-intent-]).bp5-active{\n  background-color:#1c2127;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):disabled, .bp5-dark .bp5-button:not([class*=bp5-intent-]).bp5-disabled{\n  background-color:rgba(47, 52, 60, 0.15);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]):disabled.bp5-active, .bp5-dark .bp5-button:not([class*=bp5-intent-]).bp5-disabled.bp5-active{\n  background:rgba(47, 52, 60, 0.7);\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]) .bp5-button-spinner .bp5-spinner-head{\n  background:rgba(17, 20, 24, 0.5);\n  stroke:#8f99a8;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-])[class*=bp5-icon-]::before{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-button:not([class*=bp5-intent-]) .bp5-icon:not([class*=bp5-intent-]), .bp5-dark .bp5-button:not([class*=bp5-intent-]) .bp5-icon-standard:not([class*=bp5-intent-]), .bp5-dark .bp5-button:not([class*=bp5-intent-]) .bp5-icon-large:not([class*=bp5-intent-]){\n  color:#abb3bf;\n}\n.bp5-dark .bp5-button[class*=bp5-intent-]{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-button[class*=bp5-intent-]:hover{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-button[class*=bp5-intent-]:active, .bp5-dark .bp5-button[class*=bp5-intent-].bp5-active{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-button[class*=bp5-intent-]:disabled, .bp5-dark .bp5-button[class*=bp5-intent-].bp5-disabled{\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-button[class*=bp5-intent-] .bp5-button-spinner .bp5-spinner-head{\n  stroke:#8f99a8;\n}\n.bp5-button:disabled::before,\n.bp5-button:disabled .bp5-icon, .bp5-button:disabled .bp5-icon-standard, .bp5-button:disabled .bp5-icon-large, .bp5-button.bp5-disabled::before,\n.bp5-button.bp5-disabled .bp5-icon, .bp5-button.bp5-disabled .bp5-icon-standard, .bp5-button.bp5-disabled .bp5-icon-large, .bp5-button[class*=bp5-intent-]::before,\n.bp5-button[class*=bp5-intent-] .bp5-icon, .bp5-button[class*=bp5-intent-] .bp5-icon-standard, .bp5-button[class*=bp5-intent-] .bp5-icon-large{\n  color:inherit !important;\n}\n.bp5-button.bp5-minimal{\n  background:none;\n  box-shadow:none;\n}\n.bp5-button.bp5-minimal:hover{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  color:#1c2127;\n  text-decoration:none;\n}\n.bp5-button.bp5-minimal:active, .bp5-button.bp5-minimal.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:none;\n  color:#1c2127;\n}\n.bp5-button.bp5-minimal:disabled, .bp5-button.bp5-minimal:disabled:hover, .bp5-button.bp5-minimal.bp5-disabled, .bp5-button.bp5-minimal.bp5-disabled:hover{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-button.bp5-minimal:disabled.bp5-active, .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-minimal{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button.bp5-minimal:hover, .bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button.bp5-minimal:hover{\n  background:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-minimal:disabled, .bp5-dark .bp5-button.bp5-minimal:disabled:hover, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-button.bp5-minimal:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:hover, .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#215db0;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.15);\n  color:#215db0;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(33, 93, 176, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n  stroke:#215db0;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:hover, .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#1c6e42;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.15);\n  color:#1c6e42;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(28, 110, 66, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n  stroke:#1c6e42;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:hover, .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#935610;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.15);\n  color:#935610;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(147, 86, 16, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n  stroke:#935610;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:hover, .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ac2f33;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.15);\n  color:#ac2f33;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(172, 47, 51, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ac2f33;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-outlined{\n  background:none;\n  box-shadow:none;\n  border:1px solid rgba(28, 33, 39, 0.2);\n  box-sizing:border-box;\n}\n.bp5-button.bp5-outlined:hover{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  color:#1c2127;\n  text-decoration:none;\n}\n.bp5-button.bp5-outlined:active, .bp5-button.bp5-outlined.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:none;\n  color:#1c2127;\n}\n.bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined.bp5-disabled:hover{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-button.bp5-outlined:disabled.bp5-active, .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-outlined{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button.bp5-outlined:hover, .bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button.bp5-outlined:hover{\n  background:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-button.bp5-outlined:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:hover, .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#215db0;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.15);\n  color:#215db0;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(33, 93, 176, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n  stroke:#215db0;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:hover, .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#1c6e42;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.15);\n  color:#1c6e42;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(28, 110, 66, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n  stroke:#1c6e42;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:hover, .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#935610;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.15);\n  color:#935610;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(147, 86, 16, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n  stroke:#935610;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:hover, .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ac2f33;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.15);\n  color:#ac2f33;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(172, 47, 51, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ac2f33;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled:hover{\n  border-color:rgba(95, 107, 124, 0.1);\n}\n.bp5-dark .bp5-button.bp5-outlined{\n  border-color:rgba(255, 255, 255, 0.4);\n}\n.bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary{\n  border-color:rgba(33, 93, 176, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n  border-color:rgba(33, 93, 176, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n  border-color:rgba(138, 187, 255, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n  border-color:rgba(138, 187, 255, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-success{\n  border-color:rgba(28, 110, 66, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n  border-color:rgba(28, 110, 66, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n  border-color:rgba(114, 202, 155, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n  border-color:rgba(114, 202, 155, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning{\n  border-color:rgba(147, 86, 16, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n  border-color:rgba(147, 86, 16, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n  border-color:rgba(251, 179, 96, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n  border-color:rgba(251, 179, 96, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger{\n  border-color:rgba(172, 47, 51, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n  border-color:rgba(172, 47, 51, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n  border-color:rgba(250, 153, 156, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n  border-color:rgba(250, 153, 156, 0.2);\n}\n.bp5-button.bp5-intent-warning{\n  background:#fbb360;\n  color:#1c2127;\n}\n.bp5-button.bp5-intent-warning:not(.bp5-disabled).bp5-icon > svg{\n  fill:rgba(28, 33, 39, 0.7);\n}\n.bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined):hover{\n  background:#ec9a3c;\n  color:#1c2127;\n}\n.bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined):active, .bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined).bp5-active{\n  background:#c87619;\n  color:#1c2127;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined):active, .bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(.bp5-outlined).bp5-active{\n    background:highlight;\n  }\n}\n.bp5-button.bp5-intent-warning:disabled, .bp5-button.bp5-intent-warning.bp5-disabled{\n  background:rgba(200, 118, 25, 0.5);\n  color:rgba(28, 33, 39, 0.35);\n}\n.bp5-dark .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-intent-warning.bp5-disabled{\n  color:rgba(28, 33, 39, 0.6);\n}\n.bp5-button.bp5-intent-warning.bp5-minimal, .bp5-button.bp5-intent-warning.bp5-outlined{\n  background:none;\n}\n.bp5-dark .bp5-button.bp5-intent-warning.bp5-minimal:not(.bp5-disabled).bp5-icon > svg, .bp5-dark .bp5-button.bp5-intent-warning.bp5-outlined:not(.bp5-disabled).bp5-icon > svg{\n  fill:#fbb360;\n}\n\na.bp5-button{\n  text-align:center;\n  text-decoration:none;\n  transition:none;\n}\na.bp5-button, a.bp5-button:hover, a.bp5-button:active{\n  color:#1c2127;\n}\na.bp5-button.bp5-disabled{\n  color:rgba(95, 107, 124, 0.6);\n}\n\n.bp5-button-text{\n  flex:0 1 auto;\n}\n\n.bp5-button.bp5-align-left .bp5-button-text, .bp5-button.bp5-align-right .bp5-button-text,\n.bp5-button-group.bp5-align-left .bp5-button-text,\n.bp5-button-group.bp5-align-right .bp5-button-text{\n  flex:1 1 auto;\n}\n.bp5-button-group{\n  display:inline-flex;\n}\n.bp5-button-group .bp5-button{\n  flex:0 0 auto;\n  position:relative;\n  z-index:4;\n}\n.bp5-button-group .bp5-button:focus{\n  z-index:5;\n}\n.bp5-button-group .bp5-button:hover{\n  z-index:6;\n}\n.bp5-button-group .bp5-button:active, .bp5-button-group .bp5-button.bp5-active{\n  z-index:7;\n}\n.bp5-button-group .bp5-button:disabled, .bp5-button-group .bp5-button.bp5-disabled{\n  z-index:3;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]{\n  z-index:9;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]:focus{\n  z-index:10;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]:hover{\n  z-index:11;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]:active, .bp5-button-group .bp5-button[class*=bp5-intent-].bp5-active{\n  z-index:12;\n}\n.bp5-button-group .bp5-button[class*=bp5-intent-]:disabled, .bp5-button-group .bp5-button[class*=bp5-intent-].bp5-disabled{\n  z-index:8;\n}\n.bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:first-child) .bp5-button,\n.bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:first-child), .bp5-button-group.bp5-outlined > .bp5-popover-wrapper:not(:first-child) .bp5-button,\n.bp5-button-group.bp5-outlined > .bp5-button:not(:first-child){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n.bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:last-child), .bp5-button-group.bp5-outlined > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n.bp5-button-group.bp5-outlined > .bp5-button:not(:last-child){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n}\n.bp5-button-group:not(.bp5-minimal):not(.bp5-outlined) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n.bp5-button-group:not(.bp5-minimal):not(.bp5-outlined) > .bp5-button:not(:last-child){\n  margin-right:-1px;\n}\n.bp5-button-group.bp5-minimal .bp5-button, .bp5-button-group.bp5-outlined .bp5-button{\n  background:none;\n  box-shadow:none;\n}\n.bp5-button-group.bp5-minimal .bp5-button:hover, .bp5-button-group.bp5-outlined .bp5-button:hover{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  color:#1c2127;\n  text-decoration:none;\n}\n.bp5-button-group.bp5-minimal .bp5-button:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-active, .bp5-button-group.bp5-outlined .bp5-button:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:none;\n  color:#1c2127;\n}\n.bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover, .bp5-button-group.bp5-outlined .bp5-button:disabled, .bp5-button-group.bp5-outlined .bp5-button:disabled:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled:hover{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active, .bp5-button-group.bp5-outlined .bp5-button:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button:disabled:hover.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:hover{\n  background:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:disabled:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled:hover{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button:disabled:hover.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-disabled:hover.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#215db0;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.15);\n  color:#215db0;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(33, 93, 176, 0.5);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n  stroke:#215db0;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#1c6e42;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.15);\n  color:#1c6e42;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(28, 110, 66, 0.5);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success .bp5-button-spinner .bp5-spinner-head, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n  stroke:#1c6e42;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#935610;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.15);\n  color:#935610;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(147, 86, 16, 0.5);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n  stroke:#935610;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ac2f33;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.15);\n  color:#ac2f33;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:disabled, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(172, 47, 51, 0.5);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head, .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ac2f33;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:disabled, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-outlined .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-button-group.bp5-minimal:not(:first-child), .bp5-button-group.bp5-outlined:not(:first-child){\n    border-bottom-left-radius:0;\n    border-left:none;\n    border-top-left-radius:0;\n  }\n  .bp5-button-group.bp5-minimal:not(:last-child), .bp5-button-group.bp5-outlined:not(:last-child){\n    border-bottom-right-radius:0;\n    border-top-right-radius:0;\n    margin-right:-1px;\n  }\n}\n.bp5-button-group.bp5-outlined > .bp5-button{\n  border:1px solid rgba(28, 33, 39, 0.2);\n  box-sizing:border-box;\n}\n.bp5-button-group.bp5-outlined > .bp5-button:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-disabled, .bp5-button-group.bp5-outlined > .bp5-button:disabled:hover, .bp5-button-group.bp5-outlined > .bp5-button.bp5-disabled:hover{\n  border-color:rgba(95, 107, 124, 0.1);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button{\n  border-color:rgba(255, 255, 255, 0.4);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button:disabled:hover, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-disabled:hover{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary{\n  border-color:rgba(33, 93, 176, 0.6);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary.bp5-disabled{\n  border-color:rgba(33, 93, 176, 0.2);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary{\n  border-color:rgba(138, 187, 255, 0.6);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-primary.bp5-disabled{\n  border-color:rgba(138, 187, 255, 0.2);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success{\n  border-color:rgba(28, 110, 66, 0.6);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success.bp5-disabled{\n  border-color:rgba(28, 110, 66, 0.2);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success{\n  border-color:rgba(114, 202, 155, 0.6);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-success.bp5-disabled{\n  border-color:rgba(114, 202, 155, 0.2);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning{\n  border-color:rgba(147, 86, 16, 0.6);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning.bp5-disabled{\n  border-color:rgba(147, 86, 16, 0.2);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning{\n  border-color:rgba(251, 179, 96, 0.6);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-warning.bp5-disabled{\n  border-color:rgba(251, 179, 96, 0.2);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger{\n  border-color:rgba(172, 47, 51, 0.6);\n}\n.bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger:disabled, .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger.bp5-disabled{\n  border-color:rgba(172, 47, 51, 0.2);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger{\n  border-color:rgba(250, 153, 156, 0.6);\n}\n.bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger:disabled, .bp5-dark .bp5-button-group.bp5-outlined > .bp5-button.bp5-intent-danger.bp5-disabled{\n  border-color:rgba(250, 153, 156, 0.2);\n}\n.bp5-button-group.bp5-outlined:not(.bp5-vertical) > .bp5-button:not(:last-child){\n  border-right:none;\n}\n.bp5-button-group .bp5-popover-wrapper,\n.bp5-button-group .bp5-popover-target{\n  display:flex;\n  flex:1 1 auto;\n}\n.bp5-button-group.bp5-fill{\n  display:flex;\n  width:100%;\n}\n.bp5-button-group .bp5-button.bp5-fill, .bp5-button-group.bp5-fill .bp5-button:not(.bp5-fixed){\n  flex:1 1 auto;\n}\n.bp5-button-group.bp5-vertical{\n  align-items:stretch;\n  flex-direction:column;\n  vertical-align:top;\n}\n.bp5-button-group.bp5-vertical.bp5-fill{\n  height:100%;\n  width:unset;\n}\n.bp5-button-group.bp5-vertical .bp5-button{\n  margin-right:0 !important;\n  width:100%;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:first-child .bp5-button,\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:first-child, .bp5-button-group.bp5-vertical.bp5-outlined > .bp5-popover-wrapper:first-child .bp5-button,\n.bp5-button-group.bp5-vertical.bp5-outlined > .bp5-button:first-child{\n  border-radius:2px 2px 0 0;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:last-child .bp5-button,\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:last-child, .bp5-button-group.bp5-vertical.bp5-outlined > .bp5-popover-wrapper:last-child .bp5-button,\n.bp5-button-group.bp5-vertical.bp5-outlined > .bp5-button:last-child{\n  border-radius:0 0 2px 2px;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal):not(.bp5-outlined) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n.bp5-button-group.bp5-vertical:not(.bp5-minimal):not(.bp5-outlined) > .bp5-button:not(:last-child){\n  margin-bottom:-1px;\n}\n.bp5-button-group.bp5-vertical.bp5-outlined > .bp5-button:not(:last-child){\n  border-bottom:none;\n}\n.bp5-button-group.bp5-align-left .bp5-button{\n  text-align:left;\n}\n.bp5-callout{\n  font-size:14px;\n  line-height:1.5;\n  border-radius:2px;\n  padding:15px;\n  position:relative;\n  width:100%;\n}\n.bp5-callout:not(.bp5-minimal){\n  background-color:rgba(143, 153, 168, 0.15);\n}\n.bp5-callout[class*=bp5-icon-]{\n  padding-left:38px;\n}\n.bp5-callout[class*=bp5-icon-]::before{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  color:#5f6b7c;\n  left:15px;\n  position:absolute;\n  top:17px;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-callout-icon{\n  padding-left:38px;\n}\n.bp5-callout.bp5-callout-icon > .bp5-icon:first-child{\n  color:#5f6b7c;\n  left:15px;\n  position:absolute;\n  top:17px;\n}\n.bp5-callout .bp5-heading{\n  line-height:16px;\n  margin-bottom:0;\n  margin-top:2px;\n}\n.bp5-callout.bp5-callout-has-body-content .bp5-heading{\n  margin-bottom:5px;\n}\n.bp5-callout.bp5-compact{\n  padding:10px;\n}\n.bp5-callout.bp5-compact.bp5-callout-icon{\n  padding-left:33px;\n}\n.bp5-callout.bp5-compact.bp5-callout-icon > .bp5-icon:first-child{\n  left:10px;\n  top:12px;\n}\n.bp5-dark .bp5-callout:not(.bp5-minimal){\n  background-color:rgba(143, 153, 168, 0.2);\n}\n.bp5-dark .bp5-callout[class*=bp5-icon-]::before, .bp5-dark .bp5-callout.bp5-callout-icon > .bp5-icon:first-child{\n  color:#abb3bf;\n}\n.bp5-callout.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-callout.bp5-intent-primary:not(.bp5-minimal){\n  background-color:rgba(45, 114, 210, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout.bp5-intent-primary{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-primary[class*=bp5-icon-]::before,\n.bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-primary .bp5-heading{\n  color:#215db0;\n}\n.bp5-dark .bp5-callout.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-callout.bp5-intent-primary:not(.bp5-minimal){\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-dark .bp5-callout.bp5-intent-primary[class*=bp5-icon-]::before,\n.bp5-dark .bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-primary .bp5-heading{\n  color:#8abbff;\n}\n.bp5-callout.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-callout.bp5-intent-success:not(.bp5-minimal){\n  background-color:rgba(35, 133, 81, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout.bp5-intent-success{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-success[class*=bp5-icon-]::before,\n.bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-success .bp5-heading{\n  color:#1c6e42;\n}\n.bp5-dark .bp5-callout.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-callout.bp5-intent-success:not(.bp5-minimal){\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-dark .bp5-callout.bp5-intent-success[class*=bp5-icon-]::before,\n.bp5-dark .bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-success .bp5-heading{\n  color:#72ca9b;\n}\n.bp5-callout.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-callout.bp5-intent-warning:not(.bp5-minimal){\n  background-color:rgba(200, 118, 25, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout.bp5-intent-warning{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-warning[class*=bp5-icon-]::before,\n.bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-warning .bp5-heading{\n  color:#935610;\n}\n.bp5-dark .bp5-callout.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-callout.bp5-intent-warning:not(.bp5-minimal){\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-dark .bp5-callout.bp5-intent-warning[class*=bp5-icon-]::before,\n.bp5-dark .bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-warning .bp5-heading{\n  color:#fbb360;\n}\n.bp5-callout.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-callout.bp5-intent-danger:not(.bp5-minimal){\n  background-color:rgba(205, 66, 70, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-callout.bp5-intent-danger{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-danger[class*=bp5-icon-]::before,\n.bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-danger .bp5-heading{\n  color:#ac2f33;\n}\n.bp5-dark .bp5-callout.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-callout.bp5-intent-danger:not(.bp5-minimal){\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-dark .bp5-callout.bp5-intent-danger[class*=bp5-icon-]::before,\n.bp5-dark .bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-danger .bp5-heading{\n  color:#fa999c;\n}\n.bp5-callout.bp5-intent-primary a{\n  text-decoration:underline;\n}\n.bp5-callout.bp5-intent-primary a:hover{\n  color:#184a90;\n}\n.bp5-dark .bp5-callout.bp5-intent-primary a:hover{\n  color:#99c4ff;\n}\n.bp5-running-text .bp5-callout{\n  margin:20px 0;\n}\n.bp5-card{\n  background-color:#ffffff;\n  border-radius:2px;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.15);\n  padding:20px;\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-card.bp5-dark, .bp5-dark .bp5-card{\n  background-color:#252a31;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-card{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n\n.bp5-elevation-0{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-elevation-0.bp5-dark, .bp5-dark .bp5-elevation-0{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-0{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-1{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-1.bp5-dark, .bp5-dark .bp5-elevation-1{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-1{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-2{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2), 0 2px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-2.bp5-dark, .bp5-dark .bp5-elevation-2{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px rgba(17, 20, 24, 0.4), 0 2px 6px rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-2{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-3{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-3.bp5-dark, .bp5-dark .bp5-elevation-3{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-3{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-4{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 4px 8px rgba(17, 20, 24, 0.2), 0 18px 46px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-4.bp5-dark, .bp5-dark .bp5-elevation-4{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 4px 8px rgba(17, 20, 24, 0.4), 0 18px 46px 6px rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-elevation-4{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-card.bp5-compact{\n  padding:15px;\n}\n\n.bp5-card.bp5-interactive:hover{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  cursor:pointer;\n}\n.bp5-card.bp5-interactive:hover.bp5-dark, .bp5-dark .bp5-card.bp5-interactive:hover{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-card.bp5-interactive.bp5-selected{\n  box-shadow:0 0 0 3px rgba(76, 144, 240, 0.2), 0 0 0 1px #4c90f0;\n}\n.bp5-card.bp5-interactive.bp5-selected.bp5-dark, .bp5-dark .bp5-card.bp5-interactive.bp5-selected{\n  box-shadow:0 0 0 3px rgba(138, 187, 255, 0.4), 0 0 0 1px #8abbff;\n}\n.bp5-card.bp5-interactive:active{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  transition-duration:0;\n}\n.bp5-card.bp5-interactive:active.bp5-dark, .bp5-dark .bp5-card.bp5-interactive:active{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n\n.bp5-card-list{\n  overflow:auto;\n  padding:0;\n  width:100%;\n}\n.bp5-card-list > .bp5-card{\n  align-items:center;\n  border-radius:0;\n  box-shadow:none;\n  display:flex;\n  min-height:51px;\n  padding:10px 20px;\n}\n.bp5-card-list > .bp5-card.bp5-interactive:hover, .bp5-card-list > .bp5-card.bp5-interactive:active{\n  background-color:#f6f7f9;\n  box-shadow:none;\n}\n.bp5-dark .bp5-card-list > .bp5-card.bp5-interactive:hover, .bp5-dark .bp5-card-list > .bp5-card.bp5-interactive:active{\n  background-color:#2f343c;\n}\n.bp5-card-list > .bp5-card.bp5-selected{\n  background-color:#edeff2;\n  box-shadow:none;\n}\n.bp5-dark .bp5-card-list > .bp5-card.bp5-selected{\n  background-color:#383e47;\n  box-shadow:none;\n}\n.bp5-card-list > .bp5-card:not(:last-child){\n  border-bottom:1px solid rgba(17, 20, 24, 0.1);\n}\n.bp5-dark .bp5-card-list > .bp5-card:not(:last-child){\n  border-color:rgba(255, 255, 255, 0.1);\n}\n.bp5-card-list.bp5-compact{\n  padding:0;\n}\n.bp5-card-list.bp5-compact > .bp5-card{\n  min-height:45px;\n  padding:7px 15px;\n}\n.bp5-dark .bp5-card-list{\n  padding:1px;\n}\n.bp5-card-list:not(.bp5-card-list-bordered){\n  border-radius:0;\n  box-shadow:none;\n}\n.bp5-dark .bp5-card-list:not(.bp5-card-list-bordered){\n  margin:1px;\n  width:calc(100% - 2px);\n}\n\n.bp5-collapse{\n  height:0;\n  overflow-y:hidden;\n  transition:height 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-collapse .bp5-collapse-body{\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-collapse .bp5-collapse-body[aria-hidden=true]{\n  display:none;\n}\n\n.bp5-context-menu-virtual-target{\n  position:fixed;\n}\n\n.bp5-card.bp5-control-card, .bp5-card-list > .bp5-card.bp5-control-card{\n  min-height:auto;\n  padding:0;\n}\n\n.bp5-control-card .bp5-control.bp5-control.bp5-control{\n  align-items:flex-start;\n  display:flex;\n  gap:10px;\n  margin:0;\n  padding:20px;\n  width:100%;\n}\n.bp5-control-card .bp5-control.bp5-control.bp5-control.bp5-align-left{\n  flex-direction:row;\n  justify-content:flex-start;\n}\n.bp5-control-card .bp5-control.bp5-control.bp5-control.bp5-align-right{\n  flex-direction:row-reverse;\n  justify-content:space-between;\n}\n.bp5-card-list .bp5-control-card .bp5-control.bp5-control.bp5-control{\n  padding:20px;\n}\n.bp5-card-list.bp5-compact .bp5-control-card .bp5-control.bp5-control.bp5-control{\n  padding:15px;\n}\n.bp5-control-card .bp5-control.bp5-control.bp5-control .bp5-control-indicator{\n  margin:0;\n}\n.bp5-control-card.bp5-compact .bp5-control.bp5-control.bp5-control{\n  padding:15px;\n}\n\n.bp5-divider{\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n  border-right:1px solid rgba(17, 20, 24, 0.15);\n  margin:5px;\n}\n.bp5-dark .bp5-divider{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-dialog-container{\n  opacity:1;\n  transform:scale(1);\n  align-items:center;\n  display:flex;\n  justify-content:center;\n  min-height:100%;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  width:100%;\n}\n.bp5-dialog-container.bp5-overlay-enter > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear > .bp5-dialog{\n  opacity:0;\n  transform:scale(0.5);\n}\n.bp5-dialog-container.bp5-overlay-enter-active > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear-active > .bp5-dialog{\n  opacity:1;\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:opacity, transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-dialog-container.bp5-overlay-exit > .bp5-dialog{\n  opacity:1;\n  transform:scale(1);\n}\n.bp5-dialog-container.bp5-overlay-exit-active > .bp5-dialog{\n  opacity:0;\n  transform:scale(0.5);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:opacity, transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n\n.bp5-dialog{\n  background:#f6f7f9;\n  border-radius:4px;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  display:flex;\n  flex-direction:column;\n  margin:30px 0;\n  pointer-events:all;\n  -webkit-user-select:text;\n     -moz-user-select:text;\n      -ms-user-select:text;\n          user-select:text;\n  width:500px;\n}\n.bp5-dialog:focus{\n  outline:0;\n}\n.bp5-dialog.bp5-dark, .bp5-dark .bp5-dialog{\n  background:#1c2127;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n  color:#f6f7f9;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dialog{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-dialog-header{\n  align-items:center;\n  background:#ffffff;\n  border-radius:4px 4px 0 0;\n  box-shadow:0 1px 0 rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex:0 0 auto;\n  min-height:40px;\n  padding:5px;\n  padding-left:15px;\n  z-index:0;\n}\n.bp5-dialog-header .bp5-icon-large,\n.bp5-dialog-header .bp5-icon{\n  flex:0 0 auto;\n  margin-left:-3px;\n  margin-right:7.5px;\n}\n.bp5-dialog-header .bp5-icon-large:not([class*=bp5-intent]),\n.bp5-dialog-header .bp5-icon:not([class*=bp5-intent]){\n  color:#5f6b7c;\n}\n.bp5-dialog-header .bp5-heading{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  flex:1 1 auto;\n  line-height:inherit;\n  margin:0;\n}\n.bp5-dialog-header .bp5-heading:last-child{\n  margin-right:15px;\n}\n.bp5-dark .bp5-dialog-header{\n  background:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-dialog-header :not([class*=bp5-intent]).bp5-icon-large, .bp5-dark .bp5-dialog-header :not([class*=bp5-intent]).bp5-icon{\n  color:#abb3bf;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dialog-header{\n    border-bottom:1px solid buttonborder;\n  }\n}\n\n.bp5-dialog-body{\n  flex:1 1 auto;\n  margin:15px;\n}\n\n.bp5-dialog-body-scroll-container{\n  margin:0;\n  max-height:70vh;\n  overflow:auto;\n  padding:15px;\n}\n\n.bp5-dialog-footer{\n  flex:0 0 auto;\n  margin:15px;\n}\n\n.bp5-dialog-footer-fixed{\n  align-items:center;\n  background-color:#ffffff;\n  border-radius:0 0 4px 4px;\n  border-top:1px solid rgba(17, 20, 24, 0.15);\n  display:flex;\n  gap:15px;\n  justify-content:space-between;\n  margin:0;\n  padding:10px 10px 10px 15px;\n}\n.bp5-dark .bp5-dialog-footer-fixed{\n  background:#383e47;\n  border-top:1px solid rgba(255, 255, 255, 0.2);\n}\n\n.bp5-dialog-footer-main-section{\n  flex:1 1 auto;\n}\n\n.bp5-dialog-footer-actions{\n  display:flex;\n  justify-content:flex-end;\n}\n.bp5-dialog-footer-actions .bp5-button{\n  margin-left:10px;\n}\n\n.bp5-multistep-dialog-panels{\n  display:flex;\n}\n.bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:first-child{\n  border-radius:4px 0 0 0;\n}\n.bp5-multistep-dialog-panels:first-child .bp5-multistep-dialog-right-panel{\n  border-top-right-radius:4px;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels{\n  flex-direction:column;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:first-child{\n  border-radius:4px 0 0 0;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:last-child{\n  border-radius:0 4px 0 0;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-multistep-dialog-left-panel{\n  flex-direction:row;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-dialog-step-container{\n  flex-grow:1;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-dialog-step-container:not(:first-child){\n  border-left:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-dark .bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-dialog-step-container{\n  border-color:rgba(17, 20, 24, 0.4);\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-multistep-dialog-right-panel{\n  border-left:none;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-multistep-dialog-right-panel,\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels .bp5-multistep-dialog-footer{\n  border-radius:0 0 4px 4px;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels{\n  flex-direction:row-reverse;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels:first-child .bp5-multistep-dialog-right-panel{\n  border-radius:4px 0 0 4px;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:first-child{\n  border-radius:0 4px 0 0;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels .bp5-multistep-dialog-left-panel{\n  border-radius:0 0 4px;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels .bp5-multistep-dialog-right-panel{\n  border-left:none;\n  border-radius:4px 0 0 4px;\n  border-right:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-dark .bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels .bp5-multistep-dialog-right-panel{\n  border-color:rgba(17, 20, 24, 0.4);\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels .bp5-dialog-footer{\n  border-bottom-left-radius:0;\n}\n\n.bp5-multistep-dialog-left-panel{\n  display:flex;\n  flex:1;\n  flex-direction:column;\n}\n.bp5-dark .bp5-multistep-dialog-left-panel{\n  background:#252a31;\n  border-bottom:1px solid rgba(255, 255, 255, 0.2);\n  border-bottom-left-radius:4px;\n  border-left:1px solid rgba(255, 255, 255, 0.2);\n}\n\n.bp5-multistep-dialog-right-panel{\n  background-color:#f6f7f9;\n  border-left:1px solid rgba(17, 20, 24, 0.15);\n  border-radius:0 0 4px;\n  flex:3;\n  min-width:0;\n}\n.bp5-dark .bp5-multistep-dialog-right-panel{\n  background-color:#2f343c;\n  border-bottom:1px solid rgba(255, 255, 255, 0.2);\n  border-bottom-right-radius:4px;\n  border-left:1px solid rgba(255, 255, 255, 0.2);\n  border-right:1px solid rgba(255, 255, 255, 0.2);\n}\n\n.bp5-dialog-step-container{\n  background-color:#f6f7f9;\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-dark .bp5-dialog-step-container{\n  background:#2f343c;\n  border-bottom:1px solid rgba(255, 255, 255, 0.2);\n}\n.bp5-dialog-step-container.bp5-dialog-step-viewed{\n  background-color:#ffffff;\n}\n.bp5-dark .bp5-dialog-step-container.bp5-dialog-step-viewed{\n  background:#383e47;\n}\n\n.bp5-dialog-step{\n  align-items:center;\n  border-radius:4px;\n  cursor:not-allowed;\n  display:flex;\n  margin:4px;\n  padding:6px 14px;\n}\n.bp5-dark .bp5-dialog-step{\n  background:#2f343c;\n}\n.bp5-dialog-step-viewed .bp5-dialog-step{\n  background-color:#ffffff;\n  cursor:pointer;\n}\n.bp5-dark .bp5-dialog-step-viewed .bp5-dialog-step{\n  background:#383e47;\n}\n.bp5-dialog-step:hover{\n  background-color:#f6f7f9;\n}\n.bp5-dark .bp5-dialog-step:hover{\n  background:#2f343c;\n}\n\n.bp5-dialog-step-icon{\n  align-items:center;\n  background-color:rgba(95, 107, 124, 0.6);\n  border-radius:50%;\n  color:#ffffff;\n  display:flex;\n  height:25px;\n  justify-content:center;\n  width:25px;\n}\n.bp5-dark .bp5-dialog-step-icon{\n  background-color:rgba(171, 179, 191, 0.6);\n}\n.bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-icon{\n  background-color:#2d72d2;\n}\n.bp5-dialog-step-viewed .bp5-dialog-step-icon{\n  background-color:#8f99a8;\n}\n\n.bp5-dialog-step-title{\n  color:rgba(95, 107, 124, 0.6);\n  flex:1;\n  padding-left:10px;\n}\n.bp5-dark .bp5-dialog-step-title{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-title{\n  color:#2d72d2;\n}\n.bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n  color:#1c2127;\n}\n.bp5-dark .bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n  color:#f6f7f9;\n}\n\n.bp5-drawer{\n  background:#ffffff;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 4px 8px rgba(17, 20, 24, 0.2), 0 18px 46px 6px rgba(17, 20, 24, 0.2);\n  display:flex;\n  flex-direction:column;\n  margin:0;\n  padding:0;\n}\n.bp5-drawer:focus{\n  outline:0;\n}\n.bp5-drawer.bp5-position-top{\n  height:50%;\n  left:0;\n  right:0;\n  top:0;\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-enter, .bp5-drawer.bp5-position-top.bp5-overlay-appear{\n  transform:translateY(-100%);\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-enter-active, .bp5-drawer.bp5-position-top.bp5-overlay-appear-active{\n  transform:translateY(0);\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-exit{\n  transform:translateY(0);\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-exit-active{\n  transform:translateY(-100%);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer.bp5-position-top{\n    border-bottom:1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-position-bottom{\n  bottom:0;\n  height:50%;\n  left:0;\n  right:0;\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-enter, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear{\n  transform:translateY(100%);\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-enter-active, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear-active{\n  transform:translateY(0);\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-exit{\n  transform:translateY(0);\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-exit-active{\n  transform:translateY(100%);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer.bp5-position-bottom{\n    border-top:1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-position-left{\n  bottom:0;\n  left:0;\n  top:0;\n  width:50%;\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-enter, .bp5-drawer.bp5-position-left.bp5-overlay-appear{\n  transform:translateX(-100%);\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-enter-active, .bp5-drawer.bp5-position-left.bp5-overlay-appear-active{\n  transform:translateX(0);\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-exit{\n  transform:translateX(0);\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-exit-active{\n  transform:translateX(-100%);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer.bp5-position-left{\n    border-right:1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-position-right{\n  bottom:0;\n  right:0;\n  top:0;\n  width:50%;\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-enter, .bp5-drawer.bp5-position-right.bp5-overlay-appear{\n  transform:translateX(100%);\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-enter-active, .bp5-drawer.bp5-position-right.bp5-overlay-appear-active{\n  transform:translateX(0);\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-exit{\n  transform:translateX(0);\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-exit-active{\n  transform:translateX(100%);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer.bp5-position-right{\n    border-left:1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-dark, .bp5-dark .bp5-drawer{\n  background:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n  color:#f6f7f9;\n}\n\n.bp5-drawer-header{\n  align-items:center;\n  border-radius:0;\n  box-shadow:0 1px 0 rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex:0 0 auto;\n  min-height:40px;\n  padding:5px;\n  padding-left:20px;\n  position:relative;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-drawer-header{\n    border-bottom:1px solid buttonborder;\n  }\n}\n.bp5-drawer-header .bp5-icon-large,\n.bp5-drawer-header .bp5-icon{\n  color:#5f6b7c;\n  flex:0 0 auto;\n  margin-right:10px;\n}\n.bp5-drawer-header .bp5-heading{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  flex:1 1 auto;\n  line-height:inherit;\n  margin:0;\n}\n.bp5-drawer-header .bp5-heading:last-child{\n  margin-right:20px;\n}\n.bp5-dark .bp5-drawer-header{\n  box-shadow:0 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-drawer-header .bp5-icon-large,\n.bp5-dark .bp5-drawer-header .bp5-icon{\n  color:#abb3bf;\n}\n\n.bp5-drawer-body{\n  flex:1 1 auto;\n  line-height:18px;\n  overflow:auto;\n}\n\n.bp5-drawer-footer{\n  box-shadow:inset 0 1px 0 rgba(17, 20, 24, 0.15);\n  flex:0 0 auto;\n  padding:10px 20px;\n  position:relative;\n}\n.bp5-dark .bp5-drawer-footer{\n  box-shadow:inset 0 1px 0 rgba(17, 20, 24, 0.4);\n}\n\n.bp5-editable-text{\n  cursor:text;\n  display:inline-block;\n  max-width:100%;\n  position:relative;\n  vertical-align:top;\n  white-space:nowrap;\n}\n.bp5-editable-text::before{\n  inset:-2px;\n  position:absolute;\n  border-radius:2px;\n  content:\"\";\n  transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-editable-text:hover::before{\n  box-shadow:0 0 0 0 rgba(33, 93, 176, 0), 0 0 0 0 rgba(33, 93, 176, 0), inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-editable-text.bp5-editable-text-editing::before{\n  background-color:#ffffff;\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-editable-text:not(.bp5-disabled)::before{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input::-moz-placeholder{\n  color:#2d72d2;\n}\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input:-ms-input-placeholder{\n  color:#2d72d2;\n}\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input::placeholder{\n  color:#2d72d2;\n}\n.bp5-editable-text.bp5-intent-primary:hover::before{\n  box-shadow:0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0), inset 0 0 0 1px rgba(45, 114, 210, 0.4);\n}\n.bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(45, 114, 210, 0.752), 0 0 0 1px rgba(45, 114, 210, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input::-moz-placeholder{\n  color:#238551;\n}\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input:-ms-input-placeholder{\n  color:#238551;\n}\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input::placeholder{\n  color:#238551;\n}\n.bp5-editable-text.bp5-intent-success:hover::before{\n  box-shadow:0 0 0 0 rgba(35, 133, 81, 0), 0 0 0 0 rgba(35, 133, 81, 0), inset 0 0 0 1px rgba(35, 133, 81, 0.4);\n}\n.bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(35, 133, 81, 0.752), 0 0 0 1px rgba(35, 133, 81, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input::-moz-placeholder{\n  color:#c87619;\n}\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input:-ms-input-placeholder{\n  color:#c87619;\n}\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input::placeholder{\n  color:#c87619;\n}\n.bp5-editable-text.bp5-intent-warning:hover::before{\n  box-shadow:0 0 0 0 rgba(200, 118, 25, 0), 0 0 0 0 rgba(200, 118, 25, 0), inset 0 0 0 1px rgba(200, 118, 25, 0.4);\n}\n.bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(200, 118, 25, 0.752), 0 0 0 1px rgba(200, 118, 25, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input::-moz-placeholder{\n  color:#cd4246;\n}\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input:-ms-input-placeholder{\n  color:#cd4246;\n}\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input::placeholder{\n  color:#cd4246;\n}\n.bp5-editable-text.bp5-intent-danger:hover::before{\n  box-shadow:0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0), inset 0 0 0 1px rgba(205, 66, 70, 0.4);\n}\n.bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(205, 66, 70, 0.752), 0 0 0 1px rgba(205, 66, 70, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-editable-text:hover::before{\n  box-shadow:0 0 0 0 rgba(138, 187, 255, 0), 0 0 0 0 rgba(138, 187, 255, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-editable-text.bp5-editable-text-editing::before{\n  background-color:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-editable-text.bp5-disabled::before{\n  box-shadow:none;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input::-moz-placeholder{\n  color:#8abbff;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input:-ms-input-placeholder{\n  color:#8abbff;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input,\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input::placeholder{\n  color:#8abbff;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary:hover::before{\n  box-shadow:0 0 0 0 rgba(138, 187, 255, 0), 0 0 0 0 rgba(138, 187, 255, 0), inset 0 0 0 1px rgba(138, 187, 255, 0.4);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-input::-moz-placeholder{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-input:-ms-input-placeholder{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-input,\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-input::placeholder{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success:hover::before{\n  box-shadow:0 0 0 0 rgba(114, 202, 155, 0), 0 0 0 0 rgba(114, 202, 155, 0), inset 0 0 0 1px rgba(114, 202, 155, 0.4);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(114, 202, 155, 0.752), 0 0 0 1px rgba(114, 202, 155, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input::-moz-placeholder{\n  color:#fbb360;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input:-ms-input-placeholder{\n  color:#fbb360;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input,\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input::placeholder{\n  color:#fbb360;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning:hover::before{\n  box-shadow:0 0 0 0 rgba(251, 179, 96, 0), 0 0 0 0 rgba(251, 179, 96, 0), inset 0 0 0 1px rgba(251, 179, 96, 0.4);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(251, 179, 96, 0.752), 0 0 0 1px rgba(251, 179, 96, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input::-moz-placeholder{\n  color:#fa999c;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input:-ms-input-placeholder{\n  color:#fa999c;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input,\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input::placeholder{\n  color:#fa999c;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger:hover::before{\n  box-shadow:0 0 0 0 rgba(250, 153, 156, 0), 0 0 0 0 rgba(250, 153, 156, 0), inset 0 0 0 1px rgba(250, 153, 156, 0.4);\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n  box-shadow:inset 0 0 0 1px rgba(250, 153, 156, 0.752), 0 0 0 1px rgba(250, 153, 156, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-editable-text.bp5-disabled::before{\n  box-shadow:none !important;\n}\n\n.bp5-editable-text-input,\n.bp5-editable-text-content{\n  color:inherit;\n  display:inherit;\n  font:inherit;\n  letter-spacing:inherit;\n  max-width:inherit;\n  min-width:inherit;\n  position:relative;\n  resize:none;\n  text-transform:inherit;\n  vertical-align:top;\n}\n\n.bp5-editable-text-input{\n  background:none;\n  border:none;\n  box-shadow:none;\n  padding:0;\n  white-space:pre-wrap;\n  width:100%;\n}\n.bp5-editable-text-input::-moz-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-editable-text-input:-ms-input-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-editable-text-input::placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-editable-text-input:focus{\n  outline:none;\n}\n.bp5-editable-text-input::-ms-clear{\n  display:none;\n}\n\n.bp5-editable-text-content{\n  overflow:hidden;\n  padding-right:2px;\n  text-overflow:ellipsis;\n  white-space:pre;\n}\n.bp5-editable-text-editing > .bp5-editable-text-content{\n  left:0;\n  position:absolute;\n  visibility:hidden;\n}\n.bp5-editable-text-placeholder > .bp5-editable-text-content{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-editable-text-placeholder > .bp5-editable-text-content{\n  color:#abb3bf;\n}\n\n.bp5-editable-text.bp5-multiline{\n  display:block;\n}\n.bp5-editable-text.bp5-multiline .bp5-editable-text-content{\n  overflow:auto;\n  white-space:pre-wrap;\n  word-wrap:break-word;\n}\n\n.bp5-entity-title{\n  align-items:center;\n  display:flex;\n  gap:7px;\n  min-width:0;\n}\n.bp5-entity-title.bp5-fill{\n  width:100%;\n}\n.bp5-entity-title-icon-container.bp5-entity-title-has-subtitle{\n  align-self:flex-start;\n}\n.bp5-entity-title-icon-container:not(.bp5-entity-title-has-subtitle){\n  align-items:center;\n  display:flex;\n}\n.bp5-entity-title-text{\n  display:flex;\n  flex-direction:column;\n}\n.bp5-fill .bp5-entity-title-text{\n  flex-grow:1;\n}\n.bp5-entity-title-title-and-tags{\n  align-items:center;\n  display:flex;\n  flex-direction:row;\n  gap:5px;\n}\n.bp5-entity-title-tags-container{\n  display:flex;\n  gap:2px;\n  margin-left:5px;\n}\n.bp5-entity-title-title{\n  margin-bottom:0;\n  min-width:0;\n  overflow-wrap:break-word;\n}\n.bp5-fill .bp5-entity-title-title{\n  flex-grow:1;\n}\n.bp5-entity-title-subtitle{\n  font-size:12px;\n  margin-top:2px;\n}\n.bp5-entity-title-ellipsize, .bp5-entity-title-ellipsize .bp5-entity-title-text{\n  overflow:hidden;\n}\n.bp5-entity-title-heading-h1 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:40px;\n}\n.bp5-entity-title-heading-h2 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:32px;\n}\n.bp5-entity-title-heading-h3 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:25px;\n}\n.bp5-entity-title-heading-h4 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:21px;\n}\n.bp5-entity-title-heading-h5 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:19px;\n}\n.bp5-entity-title-heading-h6 .bp5-icon-container{\n  align-items:center;\n  display:flex;\n  height:16px;\n}\n.bp5-entity-title-heading-h1, .bp5-entity-title-heading-h2, .bp5-entity-title-heading-h3{\n  gap:15px;\n}\n.bp5-entity-title-heading-h1 .bp5-entity-title-status-tag, .bp5-entity-title-heading-h2 .bp5-entity-title-status-tag, .bp5-entity-title-heading-h3 .bp5-entity-title-status-tag{\n  margin-left:10px;\n}\n.bp5-entity-title-heading-h1 .bp5-entity-title-subtitle, .bp5-entity-title-heading-h2 .bp5-entity-title-subtitle, .bp5-entity-title-heading-h3 .bp5-entity-title-subtitle{\n  font-size:14px;\n}\n.bp5-entity-title-heading-h4, .bp5-entity-title-heading-h5, .bp5-entity-title-heading-h6{\n  gap:10px;\n}\n.bp5-entity-title-heading-h4 .bp5-entity-title-subtitle, .bp5-entity-title-heading-h5 .bp5-entity-title-subtitle, .bp5-entity-title-heading-h6 .bp5-entity-title-subtitle{\n  font-size:12px;\n}\n.bp5-control-group{\n  transform:translateZ(0);\n  display:flex;\n  flex-direction:row;\n  align-items:stretch;\n}\n.bp5-control-group > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-control-group > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-control-group .bp5-button,\n.bp5-control-group .bp5-html-select,\n.bp5-control-group .bp5-input,\n.bp5-control-group .bp5-select{\n  position:relative;\n}\n.bp5-control-group .bp5-input{\n  z-index:2;\n}\n.bp5-control-group .bp5-input:focus{\n  z-index:14;\n}\n.bp5-control-group .bp5-input[class*=bp5-intent]{\n  z-index:13;\n}\n.bp5-control-group .bp5-input[class*=bp5-intent]:focus{\n  z-index:15;\n}\n.bp5-control-group .bp5-input[readonly], .bp5-control-group .bp5-input:disabled, .bp5-control-group .bp5-input.bp5-disabled{\n  z-index:1;\n}\n.bp5-control-group .bp5-input-group[class*=bp5-intent] .bp5-input{\n  z-index:13;\n}\n.bp5-control-group .bp5-input-group[class*=bp5-intent] .bp5-input:focus{\n  z-index:15;\n}\n.bp5-control-group .bp5-button,\n.bp5-control-group .bp5-html-select select,\n.bp5-control-group .bp5-select select{\n  transform:translateZ(0);\n  z-index:4;\n}\n.bp5-control-group .bp5-button:focus,\n.bp5-control-group .bp5-html-select select:focus,\n.bp5-control-group .bp5-select select:focus{\n  z-index:5;\n}\n.bp5-control-group .bp5-button:hover,\n.bp5-control-group .bp5-html-select select:hover,\n.bp5-control-group .bp5-select select:hover{\n  z-index:6;\n}\n.bp5-control-group .bp5-button:active,\n.bp5-control-group .bp5-html-select select:active,\n.bp5-control-group .bp5-select select:active{\n  z-index:7;\n}\n.bp5-control-group .bp5-button[readonly], .bp5-control-group .bp5-button:disabled, .bp5-control-group .bp5-button.bp5-disabled,\n.bp5-control-group .bp5-html-select select[readonly],\n.bp5-control-group .bp5-html-select select:disabled,\n.bp5-control-group .bp5-html-select select.bp5-disabled,\n.bp5-control-group .bp5-select select[readonly],\n.bp5-control-group .bp5-select select:disabled,\n.bp5-control-group .bp5-select select.bp5-disabled{\n  z-index:3;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent],\n.bp5-control-group .bp5-html-select select[class*=bp5-intent],\n.bp5-control-group .bp5-select select[class*=bp5-intent]{\n  z-index:9;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent]:focus,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent]:focus,\n.bp5-control-group .bp5-select select[class*=bp5-intent]:focus{\n  z-index:10;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent]:hover,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent]:hover,\n.bp5-control-group .bp5-select select[class*=bp5-intent]:hover{\n  z-index:11;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent]:active,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent]:active,\n.bp5-control-group .bp5-select select[class*=bp5-intent]:active{\n  z-index:12;\n}\n.bp5-control-group .bp5-button[class*=bp5-intent][readonly], .bp5-control-group .bp5-button[class*=bp5-intent]:disabled, .bp5-control-group .bp5-button[class*=bp5-intent].bp5-disabled,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent][readonly],\n.bp5-control-group .bp5-html-select select[class*=bp5-intent]:disabled,\n.bp5-control-group .bp5-html-select select[class*=bp5-intent].bp5-disabled,\n.bp5-control-group .bp5-select select[class*=bp5-intent][readonly],\n.bp5-control-group .bp5-select select[class*=bp5-intent]:disabled,\n.bp5-control-group .bp5-select select[class*=bp5-intent].bp5-disabled{\n  z-index:8;\n}\n.bp5-control-group .bp5-input-group > .bp5-icon,\n.bp5-control-group .bp5-input-group > .bp5-button,\n.bp5-control-group .bp5-input-group > .bp5-input-left-container,\n.bp5-control-group .bp5-input-group > .bp5-input-action{\n  z-index:16;\n}\n.bp5-control-group .bp5-select::after,\n.bp5-control-group .bp5-html-select::after,\n.bp5-control-group .bp5-select > .bp5-icon,\n.bp5-control-group .bp5-html-select > .bp5-icon{\n  z-index:17;\n}\n.bp5-control-group .bp5-html-select:focus-within,\n.bp5-control-group .bp5-select:focus-within{\n  z-index:5;\n}\n.bp5-control-group:not(.bp5-vertical) > :not(:last-child){\n  margin-right:2px;\n}\n.bp5-control-group .bp5-numeric-input:not(:first-child) .bp5-input-group{\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-control-group.bp5-fill{\n  width:100%;\n}\n.bp5-control-group > .bp5-fill{\n  flex:1 1 auto;\n}\n.bp5-control-group.bp5-fill > *:not(.bp5-fixed){\n  flex:1 1 auto;\n}\n.bp5-control-group.bp5-vertical{\n  flex-direction:column;\n}\n.bp5-control-group.bp5-vertical > :not(:last-child){\n  margin-bottom:2px;\n}\n\n.bp5-control{\n  cursor:pointer;\n  display:block;\n  margin-bottom:10px;\n  position:relative;\n  text-transform:none;\n}\n.bp5-control input:checked ~ .bp5-control-indicator{\n  background-color:#2d72d2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control input:checked ~ .bp5-control-indicator{\n    background-color:highlight;\n    border:1px solid highlight;\n  }\n}\n.bp5-control:hover input:checked ~ .bp5-control-indicator{\n  background-color:#215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control:hover input:checked ~ .bp5-control-indicator{\n    background-color:highlight;\n  }\n}\n.bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n  background:#184a90;\n}\n.bp5-control input:disabled:checked ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n    background-color:graytext;\n    border-color:graytext;\n  }\n}\n.bp5-dark .bp5-control input:checked ~ .bp5-control-indicator{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-control input:checked ~ .bp5-control-indicator{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-dark .bp5-control:hover input:checked ~ .bp5-control-indicator{\n  background-color:#215db0;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark .bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n  background-color:#184a90;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n.bp5-control:not(.bp5-align-right){\n  padding-inline-start:26px;\n}\n.bp5-control:not(.bp5-align-right) .bp5-control-indicator{\n  margin-inline-start:-26px;\n}\n.bp5-control.bp5-align-right{\n  padding-right:26px;\n}\n.bp5-control.bp5-align-right .bp5-control-indicator{\n  margin-right:-26px;\n}\n[dir=rtl] .bp5-control.bp5-align-left{\n  padding-inline-start:0;\n  padding-left:26px;\n}\n[dir=rtl] .bp5-control.bp5-align-left .bp5-control-indicator{\n  margin-left:-26px;\n}\n.bp5-control.bp5-disabled{\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-control.bp5-inline{\n  display:inline-block;\n  margin-inline-end:20px;\n}\n.bp5-control input{\n  left:0;\n  opacity:0;\n  position:absolute;\n  top:0;\n  z-index:-1;\n}\n.bp5-control .bp5-control-indicator{\n  background-clip:padding-box;\n  background-color:transparent;\n  border:none;\n  box-shadow:inset 0 0 0 1px #738091;\n  cursor:pointer;\n  display:inline-block;\n  font-size:16px;\n  height:1em;\n  margin-inline-end:10px;\n  margin-top:-3px;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  vertical-align:middle;\n  width:1em;\n}\n.bp5-control .bp5-control-indicator::before{\n  content:\"\";\n  display:block;\n  height:1em;\n  width:1em;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control .bp5-control-indicator{\n    border:1px solid buttonborder;\n  }\n  .bp5-control .bp5-control-indicator::before{\n    margin-left:-1px;\n    margin-top:-1px;\n  }\n}\n.bp5-control:hover .bp5-control-indicator{\n  background-color:rgba(143, 153, 168, 0.15);\n}\n.bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:inset 0 0 0 1px #738091;\n}\n.bp5-control input:disabled ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  cursor:not-allowed;\n}\n.bp5-control input:focus ~ .bp5-control-indicator{\n  outline:rgba(33, 93, 176, 0.752) solid 2px;\n  outline-offset:2px;\n  -moz-outline-radius:6px;\n}\n.bp5-dark .bp5-control input:focus ~ .bp5-control-indicator{\n  outline-color:rgba(138, 187, 255, 0.752);\n}\n.bp5-control.bp5-align-right .bp5-control-indicator{\n  float:right;\n  margin-left:10px;\n  margin-top:1px;\n}\n[dir=rtl] .bp5-control.bp5-align-left .bp5-control-indicator{\n  float:left;\n  margin-top:1px;\n}\n.bp5-control.bp5-large{\n  font-size:16px;\n}\n.bp5-control.bp5-large:not(.bp5-align-right){\n  padding-inline-start:30px;\n}\n.bp5-control.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n  margin-inline-start:-30px;\n}\n.bp5-control.bp5-large.bp5-align-right{\n  padding-right:30px;\n}\n.bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n  margin-right:-30px;\n}\n[dir=rtl] .bp5-control.bp5-large.bp5-align-left{\n  padding-inline-start:0;\n  padding-left:30px;\n}\n[dir=rtl] .bp5-control.bp5-large.bp5-align-left .bp5-control-indicator{\n  margin-left:-30px;\n}\n.bp5-control.bp5-large .bp5-control-indicator{\n  font-size:20px;\n}\n.bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n  margin-top:0;\n}\n[dir=rtl] .bp5-control.bp5-large.bp5-align-left .bp5-control-indicator{\n  margin-top:0;\n}\n.bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n  background-color:#2d72d2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    background-color:highlight;\n    border:1px solid highlight;\n  }\n}\n.bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n  background-color:#215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n    background-color:highlight;\n  }\n}\n.bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n  background:#184a90;\n}\n.bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n    background-color:graytext;\n    border-color:graytext;\n  }\n}\n.bp5-dark .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-dark .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n  background-color:#215db0;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark .bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n  background-color:#184a90;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  box-shadow:none;\n  color:rgba(255, 255, 255, 0.6);\n}\n.bp5-control.bp5-checkbox .bp5-control-indicator{\n  border-radius:2px;\n}\n.bp5-control.bp5-checkbox input:checked ~ .bp5-control-indicator::before{\n  background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 5c-.28 0-.53.11-.71.29L7 9.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29s.53-.11.71-.29l5-5A1.003 1.003 0 0012 5z' fill='white'/%3e%3c/svg%3e\");\n}\n.bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator::before{\n  background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 7H5c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1z' fill='white'/%3e%3c/svg%3e\");\n}\n.bp5-control.bp5-checkbox input:disabled ~ .bp5-control-indicator::before{\n  opacity:0.5;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-checkbox input:checked:not(:disabled) ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 5c-.28 0-.53.11-.71.29L7 9.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29s.53-.11.71-.29l5-5A1.003 1.003 0 0012 5z' fill='%23111418'/%3e%3c/svg%3e\");\n  }\n  .bp5-control.bp5-checkbox input:indeterminate:not(:disabled) ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 7H5c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1z' fill='%23111418'/%3e%3c/svg%3e\");\n  }\n  .bp5-control.bp5-checkbox input:disabled ~ .bp5-control-indicator{\n    border-color:graytext;\n  }\n}\n.bp5-control.bp5-radio .bp5-control-indicator{\n  border-radius:50%;\n}\n.bp5-control.bp5-radio input:checked ~ .bp5-control-indicator::before{\n  background-image:radial-gradient(#ffffff, #ffffff 28%, transparent 32%);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-radio input:checked ~ .bp5-control-indicator::before{\n    background:highlight;\n    height:12px;\n    margin-left:1px;\n    margin-top:1px;\n    width:12px;\n  }\n}\n.bp5-control.bp5-radio input:checked:disabled ~ .bp5-control-indicator::before{\n  opacity:0.5;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-radio input:checked:disabled ~ .bp5-control-indicator::before{\n    background:graytext;\n  }\n}\n.bp5-control.bp5-radio input:focus ~ .bp5-control-indicator{\n  -moz-outline-radius:16px;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-radio input:disabled ~ .bp5-control-indicator{\n    border-color:graytext;\n  }\n}\n.bp5-control.bp5-switch input ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.3);\n  color:#1c2127;\n}\n.bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.4);\n}\n.bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.5);\n}\n.bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.15);\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n  background:rgba(255, 255, 255, 0.8);\n  box-shadow:none;\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n  background:#2d72d2;\n  color:#ffffff;\n}\n.bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n  background:#215db0;\n}\n.bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n  background:#184a90;\n}\n.bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  color:rgba(255, 255, 255, 0.6);\n}\n.bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n  background:rgba(255, 255, 255, 0.5);\n  box-shadow:none;\n}\n.bp5-control.bp5-switch:not(.bp5-align-right){\n  padding-inline-start:38px;\n}\n.bp5-control.bp5-switch:not(.bp5-align-right) .bp5-control-indicator{\n  margin-inline-start:-38px;\n}\n.bp5-control.bp5-switch.bp5-align-right{\n  padding-right:38px;\n}\n.bp5-control.bp5-switch.bp5-align-right .bp5-control-indicator{\n  margin-right:-38px;\n}\n[dir=rtl] .bp5-control.bp5-switch.bp5-align-left{\n  padding-inline-start:0;\n  padding-left:38px;\n}\n[dir=rtl] .bp5-control.bp5-switch.bp5-align-left .bp5-control-indicator{\n  margin-left:-38px;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:highlight;\n    border:1px solid buttonborder;\n  }\n  .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background-color:graytext;\n  }\n  .bp5-control.bp5-switch input:not(:checked):disabled ~ .bp5-control-indicator{\n    border-color:graytext;\n  }\n  .bp5-control.bp5-switch input:not(:checked):disabled ~ .bp5-control-indicator::before{\n    border-color:graytext;\n  }\n  .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:highlight;\n  }\n}\n.bp5-control.bp5-switch .bp5-control-indicator{\n  border:none;\n  border-radius:1.75em;\n  box-shadow:none !important;\n  min-width:1.75em;\n  transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width:auto;\n}\n.bp5-control.bp5-switch .bp5-control-indicator::before{\n  background:#ffffff;\n  border-radius:50%;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.5);\n  height:calc(1em - 4px);\n  left:0;\n  margin:2px;\n  position:absolute;\n  transition:left 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width:calc(1em - 4px);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-switch .bp5-control-indicator::before{\n    border:1px solid buttonborder;\n    margin-top:1px;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-control.bp5-switch .bp5-control-indicator{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n  left:calc(100% - 1em);\n}\n.bp5-control.bp5-switch.bp5-large:not(.bp5-align-right){\n  padding-inline-start:45px;\n}\n.bp5-control.bp5-switch.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n  margin-inline-start:-45px;\n}\n.bp5-control.bp5-switch.bp5-large.bp5-align-right{\n  padding-right:45px;\n}\n.bp5-control.bp5-switch.bp5-large.bp5-align-right .bp5-control-indicator{\n  margin-right:-45px;\n}\n[dir=rtl] .bp5-control.bp5-switch.bp5-large.bp5-align-left{\n  padding-inline-start:0;\n  padding-left:45px;\n}\n[dir=rtl] .bp5-control.bp5-switch.bp5-large.bp5-align-left .bp5-control-indicator{\n  margin-left:-45px;\n}\n.bp5-dark .bp5-control.bp5-switch input ~ .bp5-control-indicator{\n  background:rgba(17, 20, 24, 0.5);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n  background:rgba(17, 20, 24, 0.8);\n}\n.bp5-dark .bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n  background:rgba(17, 20, 24, 0.9);\n}\n.bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.15);\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n  background:rgba(171, 179, 191, 0.5);\n  box-shadow:none;\n}\n.bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n  background:#2d72d2;\n  color:#ffffff;\n}\n.bp5-dark .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n  background:#215db0;\n}\n.bp5-dark .bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n  background:#184a90;\n}\n.bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n  background:rgba(255, 255, 255, 0.3);\n  box-shadow:none;\n}\n.bp5-dark .bp5-control.bp5-switch .bp5-control-indicator::before{\n  background:#abb3bf;\n}\n.bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n  background:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:highlight;\n    border:1px solid buttonborder;\n  }\n  .bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background-color:graytext;\n  }\n  .bp5-dark .bp5-control.bp5-switch input:not(:checked):disabled ~ .bp5-control-indicator{\n    border-color:graytext;\n  }\n  .bp5-dark .bp5-control.bp5-switch input:not(:checked):disabled ~ .bp5-control-indicator::before{\n    border-color:graytext;\n  }\n  .bp5-dark .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:highlight;\n  }\n}\n.bp5-control.bp5-switch .bp5-switch-inner-text{\n  font-size:0.7em;\n  text-align:center;\n}\n.bp5-control.bp5-switch .bp5-control-indicator-child:first-child{\n  line-height:0;\n  margin-left:0.5em;\n  margin-right:1.2em;\n  visibility:hidden;\n}\n.bp5-control.bp5-switch .bp5-control-indicator-child:last-child{\n  line-height:1em;\n  margin-left:1.2em;\n  margin-right:0.5em;\n  visibility:visible;\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:first-child{\n  line-height:1em;\n  visibility:visible;\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:last-child{\n  line-height:0;\n  visibility:hidden;\n}\n.bp5-dark .bp5-control{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-control.bp5-disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-control .bp5-control-indicator{\n  background-color:transparent;\n  box-shadow:inset 0 0 0 1px #8f99a8;\n}\n.bp5-dark .bp5-control:hover .bp5-control-indicator{\n  background-color:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:inset 0 0 0 1px #8f99a8;\n}\n.bp5-dark .bp5-control input:disabled ~ .bp5-control-indicator{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-control.bp5-checkbox input:disabled:checked ~ .bp5-control-indicator, .bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n  background:rgba(45, 114, 210, 0.5);\n}\n.bp5-file-input{\n  cursor:pointer;\n  display:inline-block;\n  height:30px;\n  position:relative;\n}\n.bp5-file-input input{\n  margin:0;\n  min-width:200px;\n  opacity:0;\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n  background:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  resize:none;\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input::-moz-placeholder, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::-moz-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input:-ms-input-placeholder, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input:-ms-input-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input::placeholder, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input::after, .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  outline:none;\n}\n.bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input, .bp5-dark .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n  background:rgba(64, 72, 84, 0.5);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input::after, .bp5-dark .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n  background-color:rgba(47, 52, 60, 0.15);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-file-input input:focus + .bp5-file-upload-input{\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-file-input input:focus + .bp5-file-upload-input{\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n  color:#1c2127;\n}\n.bp5-dark .bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n  color:#f6f7f9;\n}\n.bp5-file-input.bp5-fill{\n  width:100%;\n}\n.bp5-file-input.bp5-large, .bp5-large .bp5-file-input{\n  height:40px;\n}\n.bp5-file-input.bp5-small, .bp5-small .bp5-file-input{\n  height:24px;\n}\n.bp5-file-input .bp5-file-upload-input-custom-text::after{\n  content:attr(bp5-button-text);\n}\n\n.bp5-file-upload-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:2px;\n  box-shadow:0 0 0 0 rgba(33, 93, 176, 0), 0 0 0 0 rgba(33, 93, 176, 0), inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n  color:#1c2127;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  color:rgba(95, 107, 124, 0.6);\n  left:0;\n  padding-right:80px;\n  position:absolute;\n  right:0;\n  top:0;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-file-upload-input::-moz-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-file-upload-input:-ms-input-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-file-upload-input::placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-file-upload-input:focus, .bp5-file-upload-input.bp5-active{\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-file-upload-input[type=search], .bp5-file-upload-input.bp5-round{\n  border-radius:30px;\n  box-sizing:border-box;\n  padding-left:10px;\n}\n.bp5-file-upload-input[readonly]{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-file-upload-input:disabled, .bp5-file-upload-input.bp5-disabled{\n  background:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  resize:none;\n}\n.bp5-file-upload-input:disabled::-moz-placeholder, .bp5-file-upload-input.bp5-disabled::-moz-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-upload-input:disabled:-ms-input-placeholder, .bp5-file-upload-input.bp5-disabled:-ms-input-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-file-upload-input:disabled::placeholder, .bp5-file-upload-input.bp5-disabled::placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-file-upload-input{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-file-upload-input::after{\n  background-color:#f6f7f9;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#1c2127;\n  min-height:24px;\n  min-width:24px;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  border-radius:2px;\n  content:\"Browse\";\n  line-height:24px;\n  margin:3px;\n  position:absolute;\n  right:0;\n  text-align:center;\n  top:0;\n  width:70px;\n}\n.bp5-file-upload-input:hover::after{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-file-upload-input:active::after{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-file-upload-input:active::after{\n    background:highlight;\n  }\n}\n.bp5-large .bp5-file-upload-input{\n  font-size:16px;\n  height:40px;\n  line-height:40px;\n  padding-right:95px;\n}\n.bp5-large .bp5-file-upload-input[type=search], .bp5-large .bp5-file-upload-input.bp5-round{\n  padding:0 15px;\n}\n.bp5-large .bp5-file-upload-input::after{\n  min-height:30px;\n  min-width:30px;\n  line-height:30px;\n  margin:5px;\n  width:85px;\n}\n.bp5-small .bp5-file-upload-input{\n  font-size:12px;\n  height:24px;\n  line-height:24px;\n  padding-left:8px;\n  padding-right:8px;\n  padding-right:65px;\n}\n.bp5-small .bp5-file-upload-input[type=search], .bp5-small .bp5-file-upload-input.bp5-round{\n  padding:0 12px;\n}\n.bp5-small .bp5-file-upload-input::after{\n  min-height:20px;\n  min-width:20px;\n  line-height:20px;\n  margin:2px;\n  width:55px;\n}\n.bp5-dark .bp5-file-upload-input{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:0 0 0 0 rgba(138, 187, 255, 0), 0 0 0 0 rgba(138, 187, 255, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n  color:#f6f7f9;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-file-upload-input::-moz-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-file-upload-input:-ms-input-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-file-upload-input::placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-file-upload-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752);\n}\n.bp5-dark .bp5-file-upload-input[readonly]{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-file-upload-input:disabled, .bp5-dark .bp5-file-upload-input.bp5-disabled{\n  background:rgba(64, 72, 84, 0.5);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-file-upload-input::after{\n  background-color:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-file-upload-input:hover::after{\n  background-color:#252a31;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-file-upload-input:active::after{\n  background-color:#1c2127;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-file-upload-input::after{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n}\n.bp5-form-group{\n  display:flex;\n  flex-direction:column;\n  margin:0 0 15px;\n}\n.bp5-form-group label.bp5-label{\n  margin-bottom:5px;\n}\n.bp5-form-group .bp5-control{\n  margin-top:7px;\n}\n.bp5-form-group .bp5-form-group-sub-label,\n.bp5-form-group .bp5-form-helper-text{\n  color:#5f6b7c;\n  font-size:12px;\n}\n.bp5-form-group .bp5-form-group-sub-label{\n  margin-bottom:5px;\n}\n.bp5-form-group .bp5-form-helper-text{\n  margin-top:5px;\n}\n.bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n  color:#215db0;\n}\n.bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n  color:#1c6e42;\n}\n.bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n  color:#935610;\n}\n.bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n  color:#ac2f33;\n}\n.bp5-form-group.bp5-fill{\n  width:100%;\n}\n.bp5-form-group.bp5-inline{\n  align-items:flex-start;\n  flex-direction:row;\n}\n.bp5-form-group.bp5-inline.bp5-large label.bp5-label{\n  line-height:40px;\n  margin:0 10px 0 0;\n}\n.bp5-form-group.bp5-inline label.bp5-label{\n  line-height:30px;\n  margin:0 10px 0 0;\n}\n.bp5-form-group.bp5-disabled .bp5-label,\n.bp5-form-group.bp5-disabled .bp5-text-muted,\n.bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n.bp5-form-group.bp5-disabled .bp5-form-helper-text{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-dark .bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n  color:#8abbff;\n}\n.bp5-dark .bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n  color:#fbb360;\n}\n.bp5-dark .bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n  color:#fa999c;\n}\n.bp5-dark .bp5-form-group .bp5-form-group-sub-label,\n.bp5-dark .bp5-form-group .bp5-form-helper-text{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-label,\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-text-muted,\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-form-helper-text{\n  color:rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-input-group{\n  display:block;\n  position:relative;\n}\n.bp5-input-group .bp5-input{\n  position:relative;\n  width:100%;\n}\n.bp5-input-group .bp5-input:not(:first-child){\n  padding-left:30px;\n}\n.bp5-input-group .bp5-input:not(:last-child){\n  padding-right:30px;\n}\n.bp5-input-group .bp5-input-action,\n.bp5-input-group > .bp5-input-left-container,\n.bp5-input-group > .bp5-button,\n.bp5-input-group > .bp5-icon{\n  position:absolute;\n  top:0;\n}\n.bp5-input-group .bp5-input-action:first-child,\n.bp5-input-group > .bp5-input-left-container:first-child,\n.bp5-input-group > .bp5-button:first-child,\n.bp5-input-group > .bp5-icon:first-child{\n  left:0;\n}\n.bp5-input-group .bp5-input-action:last-child,\n.bp5-input-group > .bp5-input-left-container:last-child,\n.bp5-input-group > .bp5-button:last-child,\n.bp5-input-group > .bp5-icon:last-child{\n  right:0;\n}\n.bp5-input-group .bp5-button{\n  min-height:24px;\n  min-width:24px;\n  margin:3px;\n  padding:0 7px;\n}\n.bp5-input-group .bp5-button:empty{\n  padding:0;\n}\n.bp5-input-group > .bp5-input-left-container,\n.bp5-input-group > .bp5-icon{\n  z-index:1;\n}\n.bp5-input-group > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group > .bp5-icon{\n  color:#5f6b7c;\n}\n.bp5-input-group > .bp5-input-left-container > .bp5-icon:empty,\n.bp5-input-group > .bp5-icon:empty{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n}\n.bp5-input-group > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group > .bp5-icon,\n.bp5-input-group .bp5-input-action > .bp5-spinner{\n  margin:7px;\n}\n.bp5-input-group .bp5-tag{\n  margin:5px;\n}\n.bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus),\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus),\n.bp5-dark .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n  color:#abb3bf;\n}\n.bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large{\n  color:#5f6b7c;\n}\n.bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-large,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-standard,\n.bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-large{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-input-group.bp5-disabled{\n  cursor:not-allowed;\n}\n.bp5-input-group.bp5-disabled .bp5-icon{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-input-group.bp5-large .bp5-button{\n  min-height:30px;\n  min-width:30px;\n  margin:5px;\n}\n.bp5-input-group.bp5-large > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group.bp5-large > .bp5-icon,\n.bp5-input-group.bp5-large .bp5-input-action > .bp5-spinner{\n  margin:12px;\n}\n.bp5-input-group.bp5-large .bp5-input{\n  font-size:16px;\n  height:40px;\n  line-height:40px;\n}\n.bp5-input-group.bp5-large .bp5-input[type=search], .bp5-input-group.bp5-large .bp5-input.bp5-round{\n  padding:0 15px;\n}\n.bp5-input-group.bp5-large .bp5-input:not(:first-child){\n  padding-left:40px;\n}\n.bp5-input-group.bp5-large .bp5-input:not(:last-child){\n  padding-right:40px;\n}\n.bp5-input-group.bp5-small .bp5-button{\n  min-height:20px;\n  min-width:20px;\n  margin:2px;\n}\n.bp5-input-group.bp5-small .bp5-tag{\n  min-height:20px;\n  min-width:20px;\n  margin:2px;\n}\n.bp5-input-group.bp5-small > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group.bp5-small > .bp5-icon,\n.bp5-input-group.bp5-small .bp5-input-action > .bp5-spinner{\n  margin:4px;\n}\n.bp5-input-group.bp5-small .bp5-input{\n  font-size:12px;\n  height:24px;\n  line-height:24px;\n  padding-left:8px;\n  padding-right:8px;\n}\n.bp5-input-group.bp5-small .bp5-input[type=search], .bp5-input-group.bp5-small .bp5-input.bp5-round{\n  padding:0 12px;\n}\n.bp5-input-group.bp5-small .bp5-input:not(:first-child){\n  padding-left:24px;\n}\n.bp5-input-group.bp5-small .bp5-input:not(:last-child){\n  padding-right:24px;\n}\n.bp5-input-group.bp5-fill{\n  flex:1 1 auto;\n  width:100%;\n}\n.bp5-input-group.bp5-round .bp5-button,\n.bp5-input-group.bp5-round .bp5-input,\n.bp5-input-group.bp5-round .bp5-tag{\n  border-radius:30px;\n}\n.bp5-dark .bp5-input-group .bp5-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-input-group.bp5-disabled .bp5-icon{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-input-group.bp5-intent-primary .bp5-input{\n  box-shadow:0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0), inset 0 0 0 1px #2d72d2, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-primary .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(45, 114, 210, 0.752), 0 0 0 2px rgba(45, 114, 210, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-primary .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #2d72d2;\n}\n.bp5-input-group.bp5-intent-primary .bp5-input:disabled, .bp5-input-group.bp5-intent-primary .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input{\n  box-shadow:0 0 0 0 rgba(76, 144, 240, 0), 0 0 0 0 rgba(76, 144, 240, 0), inset 0 0 0 1px #4c90f0, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(76, 144, 240, 0.752), 0 0 0 2px rgba(76, 144, 240, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #4c90f0;\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input:disabled, .bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input-group.bp5-intent-primary > .bp5-icon{\n  color:#215db0;\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary > .bp5-icon{\n  color:#8abbff;\n}\n.bp5-input-group.bp5-intent-success .bp5-input{\n  box-shadow:0 0 0 0 rgba(35, 133, 81, 0), 0 0 0 0 rgba(35, 133, 81, 0), inset 0 0 0 1px #238551, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-success .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(35, 133, 81, 0.752), 0 0 0 2px rgba(35, 133, 81, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-success .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #238551;\n}\n.bp5-input-group.bp5-intent-success .bp5-input:disabled, .bp5-input-group.bp5-intent-success .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input{\n  box-shadow:0 0 0 0 rgba(50, 164, 103, 0), 0 0 0 0 rgba(50, 164, 103, 0), inset 0 0 0 1px #32a467, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(50, 164, 103, 0.752), 0 0 0 2px rgba(50, 164, 103, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #32a467;\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input:disabled, .bp5-dark .bp5-input-group.bp5-intent-success .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input-group.bp5-intent-success > .bp5-icon{\n  color:#1c6e42;\n}\n.bp5-dark .bp5-input-group.bp5-intent-success > .bp5-icon{\n  color:#72ca9b;\n}\n.bp5-input-group.bp5-intent-warning .bp5-input{\n  box-shadow:0 0 0 0 rgba(200, 118, 25, 0), 0 0 0 0 rgba(200, 118, 25, 0), inset 0 0 0 1px #c87619, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-warning .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(200, 118, 25, 0.752), 0 0 0 2px rgba(200, 118, 25, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-warning .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #c87619;\n}\n.bp5-input-group.bp5-intent-warning .bp5-input:disabled, .bp5-input-group.bp5-intent-warning .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input{\n  box-shadow:0 0 0 0 rgba(236, 154, 60, 0), 0 0 0 0 rgba(236, 154, 60, 0), inset 0 0 0 1px #ec9a3c, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(236, 154, 60, 0.752), 0 0 0 2px rgba(236, 154, 60, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #ec9a3c;\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input:disabled, .bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input-group.bp5-intent-warning > .bp5-icon{\n  color:#935610;\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning > .bp5-icon{\n  color:#fbb360;\n}\n.bp5-input-group.bp5-intent-danger .bp5-input{\n  box-shadow:0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0), inset 0 0 0 1px #cd4246, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-danger .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(205, 66, 70, 0.752), 0 0 0 2px rgba(205, 66, 70, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-danger .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #cd4246;\n}\n.bp5-input-group.bp5-intent-danger .bp5-input:disabled, .bp5-input-group.bp5-intent-danger .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input{\n  box-shadow:0 0 0 0 rgba(231, 106, 110, 0), 0 0 0 0 rgba(231, 106, 110, 0), inset 0 0 0 1px #e76a6e, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(231, 106, 110, 0.752), 0 0 0 2px rgba(231, 106, 110, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px #e76a6e;\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input:disabled, .bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input-group.bp5-intent-danger > .bp5-icon{\n  color:#ac2f33;\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger > .bp5-icon{\n  color:#fa999c;\n}\n.bp5-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:2px;\n  box-shadow:0 0 0 0 rgba(33, 93, 176, 0), 0 0 0 0 rgba(33, 93, 176, 0), inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n  color:#1c2127;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle;\n}\n.bp5-input::-moz-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input:-ms-input-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input::placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input:focus, .bp5-input.bp5-active{\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input[type=search], .bp5-input.bp5-round{\n  border-radius:30px;\n  box-sizing:border-box;\n  padding-left:10px;\n}\n.bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-input:disabled, .bp5-input.bp5-disabled{\n  background:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  resize:none;\n}\n.bp5-input:disabled::-moz-placeholder, .bp5-input.bp5-disabled::-moz-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-input:disabled:-ms-input-placeholder, .bp5-input.bp5-disabled:-ms-input-placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-input:disabled::placeholder, .bp5-input.bp5-disabled::placeholder{\n  color:rgba(95, 107, 124, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-input{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-input.bp5-large{\n  font-size:16px;\n  height:40px;\n  line-height:40px;\n}\n.bp5-input.bp5-large[type=search], .bp5-input.bp5-large.bp5-round{\n  padding:0 15px;\n}\n.bp5-input.bp5-small{\n  font-size:12px;\n  height:24px;\n  line-height:24px;\n  padding-left:8px;\n  padding-right:8px;\n}\n.bp5-input.bp5-small[type=search], .bp5-input.bp5-small.bp5-round{\n  padding:0 12px;\n}\n.bp5-input.bp5-fill{\n  flex:1 1 auto;\n  width:100%;\n}\n.bp5-dark .bp5-input{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:0 0 0 0 rgba(138, 187, 255, 0), 0 0 0 0 rgba(138, 187, 255, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-input::-moz-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-input:-ms-input-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-input::placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-input:focus{\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752);\n}\n.bp5-dark .bp5-input[readonly]{\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-input:disabled, .bp5-dark .bp5-input.bp5-disabled{\n  background:rgba(64, 72, 84, 0.5);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-input.bp5-intent-primary{\n  box-shadow:0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0), inset 0 0 0 1px #2d72d2, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-primary:focus{\n  box-shadow:inset 0 0 0 1px rgba(45, 114, 210, 0.752), 0 0 0 2px rgba(45, 114, 210, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-primary[readonly]{\n  box-shadow:inset 0 0 0 1px #2d72d2;\n}\n.bp5-input.bp5-intent-primary:disabled, .bp5-input.bp5-intent-primary.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input.bp5-intent-primary{\n  box-shadow:0 0 0 0 rgba(76, 144, 240, 0), 0 0 0 0 rgba(76, 144, 240, 0), inset 0 0 0 1px #4c90f0, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-primary:focus{\n  box-shadow:inset 0 0 0 1px rgba(76, 144, 240, 0.752), 0 0 0 2px rgba(76, 144, 240, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-primary[readonly]{\n  box-shadow:inset 0 0 0 1px #4c90f0;\n}\n.bp5-dark .bp5-input.bp5-intent-primary:disabled, .bp5-dark .bp5-input.bp5-intent-primary.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input.bp5-intent-success{\n  box-shadow:0 0 0 0 rgba(35, 133, 81, 0), 0 0 0 0 rgba(35, 133, 81, 0), inset 0 0 0 1px #238551, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-success:focus{\n  box-shadow:inset 0 0 0 1px rgba(35, 133, 81, 0.752), 0 0 0 2px rgba(35, 133, 81, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-success[readonly]{\n  box-shadow:inset 0 0 0 1px #238551;\n}\n.bp5-input.bp5-intent-success:disabled, .bp5-input.bp5-intent-success.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input.bp5-intent-success{\n  box-shadow:0 0 0 0 rgba(50, 164, 103, 0), 0 0 0 0 rgba(50, 164, 103, 0), inset 0 0 0 1px #32a467, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-success:focus{\n  box-shadow:inset 0 0 0 1px rgba(50, 164, 103, 0.752), 0 0 0 2px rgba(50, 164, 103, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-success[readonly]{\n  box-shadow:inset 0 0 0 1px #32a467;\n}\n.bp5-dark .bp5-input.bp5-intent-success:disabled, .bp5-dark .bp5-input.bp5-intent-success.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input.bp5-intent-warning{\n  box-shadow:0 0 0 0 rgba(200, 118, 25, 0), 0 0 0 0 rgba(200, 118, 25, 0), inset 0 0 0 1px #c87619, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-warning:focus{\n  box-shadow:inset 0 0 0 1px rgba(200, 118, 25, 0.752), 0 0 0 2px rgba(200, 118, 25, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-warning[readonly]{\n  box-shadow:inset 0 0 0 1px #c87619;\n}\n.bp5-input.bp5-intent-warning:disabled, .bp5-input.bp5-intent-warning.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input.bp5-intent-warning{\n  box-shadow:0 0 0 0 rgba(236, 154, 60, 0), 0 0 0 0 rgba(236, 154, 60, 0), inset 0 0 0 1px #ec9a3c, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-warning:focus{\n  box-shadow:inset 0 0 0 1px rgba(236, 154, 60, 0.752), 0 0 0 2px rgba(236, 154, 60, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-warning[readonly]{\n  box-shadow:inset 0 0 0 1px #ec9a3c;\n}\n.bp5-dark .bp5-input.bp5-intent-warning:disabled, .bp5-dark .bp5-input.bp5-intent-warning.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input.bp5-intent-danger{\n  box-shadow:0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0), inset 0 0 0 1px #cd4246, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-danger:focus{\n  box-shadow:inset 0 0 0 1px rgba(205, 66, 70, 0.752), 0 0 0 2px rgba(205, 66, 70, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-danger[readonly]{\n  box-shadow:inset 0 0 0 1px #cd4246;\n}\n.bp5-input.bp5-intent-danger:disabled, .bp5-input.bp5-intent-danger.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-dark .bp5-input.bp5-intent-danger{\n  box-shadow:0 0 0 0 rgba(231, 106, 110, 0), 0 0 0 0 rgba(231, 106, 110, 0), inset 0 0 0 1px #e76a6e, inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-danger:focus{\n  box-shadow:inset 0 0 0 1px rgba(231, 106, 110, 0.752), 0 0 0 2px rgba(231, 106, 110, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-danger[readonly]{\n  box-shadow:inset 0 0 0 1px #e76a6e;\n}\n.bp5-dark .bp5-input.bp5-intent-danger:disabled, .bp5-dark .bp5-input.bp5-intent-danger.bp5-disabled{\n  box-shadow:none;\n}\n.bp5-input::-ms-clear{\n  display:none;\n}\n\n@supports (-webkit-touch-callout: none){\n  input.bp5-input:disabled, input.bp5-input.bp5-disabled{\n    opacity:1;\n    -webkit-text-fill-color:rgba(95, 107, 124, 0.6);\n  }\n  .bp5-dark input.bp5-input:disabled, .bp5-dark input.bp5-input.bp5-disabled{\n    -webkit-text-fill-color:rgba(171, 179, 191, 0.6);\n  }\n}\ntextarea.bp5-input{\n  max-width:100%;\n  padding:10px;\n}\ntextarea.bp5-input, textarea.bp5-input.bp5-large, textarea.bp5-input.bp5-small{\n  height:auto;\n  line-height:inherit;\n}\ntextarea.bp5-input.bp5-small{\n  padding:8px;\n}\n\n.bp5-text-area.bp5-text-area-auto-resize{\n  resize:horizontal;\n}\nlabel.bp5-label{\n  display:block;\n  margin-bottom:15px;\n  margin-top:0;\n}\nlabel.bp5-label .bp5-html-select,\nlabel.bp5-label .bp5-input,\nlabel.bp5-label .bp5-select,\nlabel.bp5-label .bp5-slider,\nlabel.bp5-label .bp5-popover-wrapper{\n  display:block;\n  margin-top:5px;\n  text-transform:none;\n}\nlabel.bp5-label .bp5-button-group{\n  margin-top:5px;\n}\nlabel.bp5-label .bp5-select select,\nlabel.bp5-label .bp5-html-select select{\n  font-weight:400;\n  vertical-align:top;\n  width:100%;\n}\nlabel.bp5-label .bp5-control-group{\n  margin-top:5px;\n}\nlabel.bp5-label .bp5-control-group > .bp5-button-group,\nlabel.bp5-label .bp5-control-group > .bp5-html-select,\nlabel.bp5-label .bp5-control-group > .bp5-input,\nlabel.bp5-label .bp5-control-group > .bp5-select,\nlabel.bp5-label .bp5-control-group > .bp5-slider,\nlabel.bp5-label .bp5-control-group > .bp5-popover-wrapper{\n  margin-top:0;\n}\nlabel.bp5-label.bp5-disabled,\nlabel.bp5-label.bp5-disabled .bp5-text-muted{\n  color:rgba(95, 107, 124, 0.6);\n}\nlabel.bp5-label.bp5-inline{\n  line-height:30px;\n}\nlabel.bp5-label.bp5-inline .bp5-html-select,\nlabel.bp5-label.bp5-inline .bp5-input,\nlabel.bp5-label.bp5-inline .bp5-input-group,\nlabel.bp5-label.bp5-inline .bp5-select,\nlabel.bp5-label.bp5-inline .bp5-popover-wrapper{\n  display:inline-block;\n  margin:0 0 0 5px;\n  vertical-align:top;\n}\nlabel.bp5-label.bp5-inline .bp5-button-group{\n  margin:0 0 0 5px;\n}\nlabel.bp5-label.bp5-inline .bp5-input-group .bp5-input{\n  margin-left:0;\n}\nlabel.bp5-label.bp5-inline.bp5-large{\n  line-height:40px;\n}\nlabel.bp5-label.bp5-inline .bp5-control-group{\n  margin:0 0 0 5px;\n}\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-button-group,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-html-select,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-input,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-select,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-slider,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-popover-wrapper{\n  margin:0;\n}\nlabel.bp5-label:not(.bp5-inline) .bp5-popover-target{\n  display:block;\n}\n.bp5-dark label.bp5-label{\n  color:#f6f7f9;\n}\n.bp5-dark label.bp5-label.bp5-disabled,\n.bp5-dark label.bp5-label.bp5-disabled .bp5-text-muted{\n  color:rgba(171, 179, 191, 0.6);\n}\n\n.bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button{\n  flex:1 1 11px;\n  min-height:0;\n  padding:0;\n  width:24px;\n}\n.bp5-numeric-input.bp5-large .bp5-button-group.bp5-vertical > .bp5-button{\n  width:40px;\n}\n.bp5-numeric-input.bp5-small .bp5-button-group.bp5-vertical > .bp5-button{\n  width:24px;\n}\n\nform{\n  display:block;\n}\n\n.bp5-html-select select,\n.bp5-select select{\n  display:inline-flex;\n  flex-direction:row;\n  align-items:center;\n  border:none;\n  border-radius:2px;\n  cursor:pointer;\n  font-size:14px;\n  justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  background-color:#f6f7f9;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#1c2127;\n  -moz-appearance:none;\n  -webkit-appearance:none;\n  border-radius:2px;\n  height:30px;\n  padding:0 30px 0 10px;\n  width:100%;\n}\n.bp5-html-select select > *,\n.bp5-select select > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-html-select select > .bp5-fill,\n.bp5-select select > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-html-select select::before,\n.bp5-select select::before,\n.bp5-html-select select > *,\n.bp5-select select > *{\n  margin-right:7px;\n}\n.bp5-html-select select:empty::before,\n.bp5-select select:empty::before,\n.bp5-html-select select > :last-child,\n.bp5-select select > :last-child{\n  margin-right:0;\n}\n.bp5-html-select select:hover,\n.bp5-select select:hover{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-html-select select:active,\n.bp5-select select:active, .bp5-html-select select.bp5-active,\n.bp5-select select.bp5-active{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-html-select select:active,\n  .bp5-select select:active, .bp5-html-select select.bp5-active,\n  .bp5-select select.bp5-active{\n    background:highlight;\n  }\n}\n.bp5-html-select select:disabled,\n.bp5-select select:disabled, .bp5-html-select select.bp5-disabled,\n.bp5-select select.bp5-disabled{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  outline:none;\n}\n.bp5-html-select select:disabled.bp5-active,\n.bp5-select select:disabled.bp5-active, .bp5-html-select select.bp5-disabled.bp5-active,\n.bp5-select select.bp5-disabled.bp5-active{\n  background:rgba(211, 216, 222, 0.7);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-html-select select,\n  .bp5-select select{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-html-select.bp5-minimal select,\n.bp5-select.bp5-minimal select{\n  background:none;\n  box-shadow:none;\n}\n.bp5-html-select.bp5-minimal select:hover,\n.bp5-select.bp5-minimal select:hover{\n  background:rgba(143, 153, 168, 0.15);\n  box-shadow:none;\n  color:#1c2127;\n  text-decoration:none;\n}\n.bp5-html-select.bp5-minimal select:active,\n.bp5-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal select.bp5-active,\n.bp5-select.bp5-minimal select.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n  box-shadow:none;\n  color:#1c2127;\n}\n.bp5-html-select.bp5-minimal select:disabled,\n.bp5-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal select.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-disabled{\n  background:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-html-select.bp5-minimal select:disabled.bp5-active,\n.bp5-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-disabled.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select, .bp5-html-select.bp5-minimal .bp5-dark select,\n.bp5-dark .bp5-select.bp5-minimal select,\n.bp5-select.bp5-minimal .bp5-dark select{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n.bp5-dark .bp5-select.bp5-minimal select:hover,\n.bp5-select.bp5-minimal .bp5-dark select:hover, .bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n.bp5-dark .bp5-select.bp5-minimal select:active,\n.bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ffffff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n.bp5-dark .bp5-select.bp5-minimal select:hover,\n.bp5-select.bp5-minimal .bp5-dark select:hover{\n  background:rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n.bp5-dark .bp5-select.bp5-minimal select:active,\n.bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal .bp5-dark select:disabled,\n.bp5-dark .bp5-select.bp5-minimal select:disabled,\n.bp5-select.bp5-minimal .bp5-dark select:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-disabled{\n  background:none;\n  color:rgba(171, 179, 191, 0.6);\n  cursor:not-allowed;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active{\n  background:rgba(143, 153, 168, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary,\n.bp5-select.bp5-minimal select.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#215db0;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-select.bp5-minimal select.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.15);\n  color:#215db0;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(33, 93, 176, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head,\n.bp5-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n  stroke:#215db0;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover{\n  background:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled{\n  background:none;\n  color:rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active{\n  background:rgba(45, 114, 210, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success,\n.bp5-select.bp5-minimal select.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#1c6e42;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-select.bp5-minimal select.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.15);\n  color:#1c6e42;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(28, 110, 66, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head,\n.bp5-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n  stroke:#1c6e42;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover{\n  background:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled{\n  background:none;\n  color:rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active{\n  background:rgba(35, 133, 81, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning,\n.bp5-select.bp5-minimal select.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#935610;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-select.bp5-minimal select.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.15);\n  color:#935610;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(147, 86, 16, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head,\n.bp5-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n  stroke:#935610;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover{\n  background:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled{\n  background:none;\n  color:rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active{\n  background:rgba(200, 118, 25, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger,\n.bp5-select.bp5-minimal select.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n  background:none;\n  box-shadow:none;\n  color:#ac2f33;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-select.bp5-minimal select.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.15);\n  color:#ac2f33;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(172, 47, 51, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head,\n.bp5-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n  stroke:#ac2f33;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover{\n  background:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled{\n  background:none;\n  color:rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active{\n  background:rgba(205, 66, 70, 0.3);\n}\n\n.bp5-html-select.bp5-large select,\n.bp5-select.bp5-large select{\n  font-size:16px;\n  height:40px;\n  padding-right:35px;\n}\n\n.bp5-dark .bp5-html-select select,\n.bp5-dark .bp5-select select{\n  background-color:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-html-select select:hover,\n.bp5-dark .bp5-select select:hover, .bp5-dark .bp5-html-select select:active,\n.bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active,\n.bp5-dark .bp5-select select.bp5-active{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-html-select select:hover,\n.bp5-dark .bp5-select select:hover{\n  background-color:#252a31;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-html-select select:active,\n.bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active,\n.bp5-dark .bp5-select select.bp5-active{\n  background-color:#1c2127;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-html-select select:disabled,\n.bp5-dark .bp5-select select:disabled, .bp5-dark .bp5-html-select select.bp5-disabled,\n.bp5-dark .bp5-select select.bp5-disabled{\n  background-color:rgba(47, 52, 60, 0.15);\n  box-shadow:none;\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-html-select select:disabled.bp5-active,\n.bp5-dark .bp5-select select:disabled.bp5-active, .bp5-dark .bp5-html-select select.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select select.bp5-disabled.bp5-active{\n  background:rgba(47, 52, 60, 0.7);\n}\n.bp5-dark .bp5-html-select select .bp5-button-spinner .bp5-spinner-head,\n.bp5-dark .bp5-select select .bp5-button-spinner .bp5-spinner-head{\n  background:rgba(17, 20, 24, 0.5);\n  stroke:#8f99a8;\n}\n\n.bp5-html-select select:disabled,\n.bp5-select select:disabled{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n\n.bp5-html-select .bp5-icon,\n.bp5-select .bp5-icon, .bp5-select::after{\n  color:#5f6b7c;\n  pointer-events:none;\n  position:absolute;\n  right:10px;\n  top:7px;\n}\n.bp5-html-select .bp5-disabled.bp5-icon,\n.bp5-select .bp5-disabled.bp5-icon, .bp5-disabled.bp5-select::after{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-html-select,\n.bp5-select{\n  display:inline-block;\n  letter-spacing:normal;\n  position:relative;\n  vertical-align:middle;\n}\n.bp5-html-select .bp5-icon,\n.bp5-select .bp5-icon{\n  color:#5f6b7c;\n}\n.bp5-html-select .bp5-icon:hover,\n.bp5-select .bp5-icon:hover{\n  color:#1c2127;\n}\n.bp5-dark .bp5-html-select .bp5-icon,\n.bp5-dark .bp5-select .bp5-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-html-select .bp5-icon:hover,\n.bp5-dark .bp5-select .bp5-icon:hover{\n  color:#f6f7f9;\n}\n.bp5-html-select.bp5-large::after,\n.bp5-html-select.bp5-large .bp5-icon,\n.bp5-select.bp5-large::after,\n.bp5-select.bp5-large .bp5-icon{\n  right:12px;\n  top:12px;\n}\n.bp5-html-select.bp5-fill,\n.bp5-html-select.bp5-fill select,\n.bp5-select.bp5-fill,\n.bp5-select.bp5-fill select{\n  width:100%;\n}\n.bp5-dark .bp5-html-select option,\n.bp5-dark .bp5-select option{\n  background-color:#2f343c;\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-html-select option:disabled,\n.bp5-dark .bp5-select option:disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-html-select::after,\n.bp5-dark .bp5-select::after{\n  color:#abb3bf;\n}\n\n.bp5-select::after{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  content:\"\\f184\";\n}\n\n.bp5-key-combo{\n  align-items:center;\n}\n.bp5-key-combo:not(.bp5-minimal){\n  display:flex;\n  flex-direction:row;\n}\n.bp5-key-combo:not(.bp5-minimal) > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-key-combo:not(.bp5-minimal) > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-key-combo:not(.bp5-minimal)::before,\n.bp5-key-combo:not(.bp5-minimal) > *{\n  margin-right:5px;\n}\n.bp5-key-combo:not(.bp5-minimal):empty::before,\n.bp5-key-combo:not(.bp5-minimal) > :last-child{\n  margin-right:0;\n}\n.bp5-key-combo.bp5-minimal{\n  display:flex;\n  flex-direction:row;\n}\n.bp5-key-combo.bp5-minimal > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-key-combo.bp5-minimal > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n\n.bp5-hotkey-dialog{\n  padding-bottom:0;\n  top:40px;\n}\n.bp5-hotkey-dialog .bp5-dialog-body{\n  margin:0;\n  padding:0;\n}\n.bp5-hotkey-dialog .bp5-hotkey-label{\n  flex-grow:1;\n}\n\n.bp5-hotkey-column{\n  margin:auto;\n  padding:30px;\n}\n.bp5-hotkey-column .bp5-heading{\n  margin-bottom:20px;\n}\n.bp5-hotkey-column .bp5-heading:not(:first-child){\n  margin-top:40px;\n}\n\n.bp5-hotkey{\n  align-items:center;\n  display:flex;\n  justify-content:space-between;\n  margin-left:0;\n  margin-right:0;\n}\n.bp5-hotkey:not(:last-child){\n  margin-bottom:10px;\n}\n\n.bp5-submenu > .bp5-popover-wrapper{\n  display:block;\n}\n.bp5-submenu .bp5-popover-target{\n  display:block;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item:not([class*=bp5-intent-]){\n  background-color:rgba(143, 153, 168, 0.15);\n  color:inherit;\n  cursor:pointer;\n  text-decoration:none;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item:not([class*=bp5-intent-]){\n  color:inherit;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item:not([class*=bp5-intent-]) .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item:not([class*=bp5-intent-]) .bp5-submenu-icon{\n  color:#abb3bf;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-], .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active{\n  background-color:rgba(45, 114, 210, 0.1);\n  color:#215db0;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-label, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-label, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-selected-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-selected-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-selected-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-submenu-icon{\n  color:#215db0;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.1);\n  color:#1c6e42;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.1);\n  color:#935610;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.1);\n  color:#ac2f33;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger .bp5-submenu-icon, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger::before,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger .bp5-submenu-icon{\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-], .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active{\n    background-color:highlight;\n  }\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-], .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-] .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active .bp5-submenu-icon{\n  color:#8abbff;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-success .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-success .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-success .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-warning .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-warning .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-warning .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-].bp5-intent-danger .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover.bp5-intent-danger .bp5-submenu-icon, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger::before,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active.bp5-intent-danger .bp5-submenu-icon{\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-], .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item[class*=bp5-intent-]:active{\n    background-color:highlight;\n  }\n}\n.bp5-submenu.bp5-popover{\n  box-shadow:none;\n  padding:0 5px;\n}\n.bp5-submenu.bp5-popover > .bp5-popover-content{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-submenu.bp5-popover, .bp5-submenu.bp5-popover.bp5-dark{\n  box-shadow:none;\n}\n.bp5-dark .bp5-submenu.bp5-popover > .bp5-popover-content, .bp5-submenu.bp5-popover.bp5-dark > .bp5-popover-content{\n  box-shadow:0 0 0 1px hsl(215, 3%, 38%), inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-menu{\n  background:#ffffff;\n  border-radius:2px;\n  color:#1c2127;\n  list-style:none;\n  margin:0;\n  min-width:180px;\n  padding:5px;\n  text-align:left;\n}\n\n.bp5-menu-divider{\n  border-top:1px solid rgba(17, 20, 24, 0.15);\n  display:block;\n  margin:5px -5px;\n}\n.bp5-dark .bp5-menu-divider{\n  border-top-color:rgba(255, 255, 255, 0.2);\n}\n\n.bp5-menu-item{\n  display:flex;\n  flex-direction:row;\n  align-items:flex-start;\n  border-radius:2px;\n  color:inherit;\n  line-height:20px;\n  padding:5px 7px;\n  text-decoration:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-menu-item > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-menu-item > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-menu-item::before,\n.bp5-menu-item > *{\n  margin-right:7px;\n}\n.bp5-menu-item:empty::before,\n.bp5-menu-item > :last-child{\n  margin-right:0;\n}\n.bp5-menu-item > .bp5-fill{\n  word-break:break-word;\n}\n.bp5-menu-item .bp5-menu-item-icon{\n  display:flex;\n  flex-direction:column;\n  height:20px;\n  justify-content:center;\n}\n.bp5-menu-item .bp5-menu-item-label{\n  color:#5f6b7c;\n}\n.bp5-menu-item::before,\n.bp5-menu-item .bp5-menu-item-icon,\n.bp5-menu-item .bp5-menu-item-selected-icon,\n.bp5-menu-item .bp5-submenu-icon{\n  color:#5f6b7c;\n}\n.bp5-menu-item::before,\n.bp5-menu-item .bp5-submenu-icon{\n  margin-top:2px;\n}\n.bp5-menu-item:hover{\n  background-color:rgba(143, 153, 168, 0.15);\n  color:inherit;\n  cursor:pointer;\n  text-decoration:none;\n}\n.bp5-menu-item:active{\n  background-color:rgba(143, 153, 168, 0.3);\n}\n.bp5-menu-item:active .bp5-menu-item-label{\n  color:#1c2127;\n}\n.bp5-menu-item.bp5-active{\n  background-color:rgba(45, 114, 210, 0.1);\n  color:#215db0;\n}\n.bp5-menu-item.bp5-active .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-active::before,\n.bp5-menu-item.bp5-active .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-active .bp5-submenu-icon{\n  color:#215db0;\n}\n.bp5-menu-item.bp5-active.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.1);\n  color:#1c6e42;\n}\n.bp5-menu-item.bp5-active.bp5-intent-success::before,\n.bp5-menu-item.bp5-active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active.bp5-intent-success .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-menu-item.bp5-active.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.1);\n  color:#935610;\n}\n.bp5-menu-item.bp5-active.bp5-intent-warning::before,\n.bp5-menu-item.bp5-active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active.bp5-intent-warning .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-menu-item.bp5-active.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.1);\n  color:#ac2f33;\n}\n.bp5-menu-item.bp5-active.bp5-intent-danger::before,\n.bp5-menu-item.bp5-active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active.bp5-intent-danger .bp5-submenu-icon{\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-menu-item.bp5-active{\n    background-color:highlight;\n  }\n}\n.bp5-menu-item.bp5-menu-item-is-selectable{\n  padding-left:20px;\n}\n.bp5-menu-item.bp5-menu-item-is-selectable.bp5-selected{\n  padding-left:0;\n}\n.bp5-menu-item.bp5-menu-item-is-selectable .bp5-menu-item-selected-icon{\n  align-self:center;\n  margin:0 2px;\n}\n.bp5-menu-item.bp5-disabled{\n  background-color:inherit !important;\n  color:rgba(95, 107, 124, 0.6) !important;\n  cursor:not-allowed !important;\n  outline:none !important;\n}\n.bp5-menu-item.bp5-disabled::before,\n.bp5-menu-item.bp5-disabled .bp5-menu-item-icon,\n.bp5-menu-item.bp5-disabled .bp5-submenu-icon{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n  color:rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-menu-item.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-menu-item.bp5-intent-primary::before,\n.bp5-menu-item.bp5-intent-primary .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-primary .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-primary .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-intent-primary:hover{\n  background-color:rgba(45, 114, 210, 0.1);\n}\n.bp5-menu-item.bp5-intent-primary:active, .bp5-menu-item.bp5-intent-primary.bp5-active{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#184a90;\n}\n.bp5-menu-item.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-menu-item.bp5-intent-success::before,\n.bp5-menu-item.bp5-intent-success .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-success .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-success .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-intent-success:hover{\n  background-color:rgba(35, 133, 81, 0.1);\n}\n.bp5-menu-item.bp5-intent-success:active, .bp5-menu-item.bp5-intent-success.bp5-active{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#165a36;\n}\n.bp5-menu-item.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-menu-item.bp5-intent-warning::before,\n.bp5-menu-item.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-warning .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-warning .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-intent-warning:hover{\n  background-color:rgba(200, 118, 25, 0.1);\n}\n.bp5-menu-item.bp5-intent-warning:active, .bp5-menu-item.bp5-intent-warning.bp5-active{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#77450d;\n}\n.bp5-menu-item.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-menu-item.bp5-intent-danger::before,\n.bp5-menu-item.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-danger .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-danger .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-menu-item.bp5-intent-danger:hover{\n  background-color:rgba(205, 66, 70, 0.1);\n}\n.bp5-menu-item.bp5-intent-danger:active, .bp5-menu-item.bp5-intent-danger.bp5-active{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#8e292c;\n}\n.bp5-menu-item::before{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  margin-right:7px;\n}\n.bp5-large .bp5-menu-item{\n  font-size:16px;\n  line-height:22px;\n  padding-bottom:9px;\n  padding-top:9px;\n}\n.bp5-large .bp5-menu-item .bp5-menu-item-icon{\n  height:22px;\n}\n.bp5-large .bp5-menu-item::before,\n.bp5-large .bp5-menu-item .bp5-submenu-icon{\n  margin-top:3px;\n}\n.bp5-large .bp5-menu-item::before{\n  font-family:\"blueprint-icons-20\", sans-serif;\n  font-size:20px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:20px;\n  line-height:1;\n  width:20px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  margin-right:10px;\n}\n.bp5-small .bp5-menu-item{\n  padding-bottom:2px;\n  padding-top:2px;\n}\n\nbutton.bp5-menu-item{\n  background:none;\n  border:none;\n  text-align:left;\n  width:100%;\n}\n.bp5-menu-header{\n  border-top:1px solid rgba(17, 20, 24, 0.15);\n  display:block;\n  margin:5px -5px;\n  cursor:default;\n  padding-left:2px;\n}\n.bp5-dark .bp5-menu-header{\n  border-top-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-menu-header:first-of-type{\n  border-top:none;\n}\n.bp5-menu-header > h6{\n  color:#1c2127;\n  font-weight:600;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  line-height:17px;\n  margin:0;\n  padding:10px 7px 0 6px;\n}\n.bp5-dark .bp5-menu-header > h6{\n  color:#f6f7f9;\n}\n.bp5-menu-header > h6.bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-menu-header > h6.bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-menu-header:first-of-type > h6{\n  padding-top:0;\n}\n.bp5-large .bp5-menu-header > h6{\n  font-size:18px;\n  padding-bottom:5px;\n  padding-top:15px;\n}\n.bp5-large .bp5-menu-header:first-of-type > h6{\n  padding-top:0;\n}\n\n.bp5-dark .bp5-menu{\n  background:#2f343c;\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-menu-item{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item .bp5-menu-item-label{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-menu-item::before,\n.bp5-dark .bp5-menu-item .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item .bp5-submenu-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-menu-item:hover{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item:hover .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item:hover .bp5-submenu-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-menu-item:active{\n  background-color:rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-menu-item:active .bp5-menu-item-label{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-menu-item.bp5-active{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-menu-item.bp5-active::before,\n.bp5-dark .bp5-menu-item.bp5-active .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-active .bp5-submenu-icon{\n  color:#8abbff;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success::before,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning::before,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning .bp5-submenu-icon{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger::before,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger .bp5-submenu-icon{\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-menu-item.bp5-active{\n    background-color:highlight;\n  }\n}\n.bp5-dark .bp5-menu-item.bp5-disabled{\n  color:rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-dark .bp5-menu-item.bp5-disabled::before,\n.bp5-dark .bp5-menu-item.bp5-disabled .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-disabled .bp5-submenu-icon{\n  color:rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-dark .bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n  color:rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary::before,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary:hover{\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary:active, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active{\n  background-color:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success::before,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success:hover{\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success:active, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active{\n  background-color:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning::before,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning:hover{\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning:active, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active{\n  background-color:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger::before,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n  color:inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger:hover{\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger:active, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active{\n  background-color:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-menu-divider,\n.bp5-dark .bp5-menu-header{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-menu-header > h6{\n  color:#f6f7f9;\n}\n\n.bp5-label .bp5-menu{\n  margin-top:5px;\n}\n.bp5-navbar{\n  background-color:#ffffff;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  height:50px;\n  padding:0 15px;\n  position:relative;\n  width:100%;\n  z-index:10;\n}\n.bp5-navbar.bp5-dark, .bp5-dark .bp5-navbar{\n  background-color:#252a31;\n}\n.bp5-navbar.bp5-dark{\n  box-shadow:inset inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-navbar{\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-navbar.bp5-fixed-top{\n  left:0;\n  position:fixed;\n  right:0;\n  top:0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-navbar{\n    border:1px solid buttonborder;\n  }\n}\n\n.bp5-navbar-heading{\n  font-size:16px;\n  margin-right:15px;\n}\n\n.bp5-navbar-group{\n  align-items:center;\n  display:flex;\n  height:50px;\n}\n.bp5-navbar-group.bp5-align-left{\n  float:left;\n}\n.bp5-navbar-group.bp5-align-right{\n  float:right;\n}\n\n.bp5-navbar-divider{\n  border-left:1px solid rgba(17, 20, 24, 0.15);\n  height:20px;\n  margin:0 10px;\n}\n.bp5-dark .bp5-navbar-divider{\n  border-left-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-non-ideal-state{\n  display:flex;\n  flex-direction:column;\n  align-items:center;\n  color:#5f6b7c;\n  height:100%;\n  justify-content:center;\n  text-align:center;\n  width:100%;\n}\n.bp5-non-ideal-state > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-non-ideal-state > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-non-ideal-state::before,\n.bp5-non-ideal-state > *{\n  margin-bottom:20px;\n}\n.bp5-non-ideal-state:empty::before,\n.bp5-non-ideal-state > :last-child{\n  margin-bottom:0;\n}\n.bp5-non-ideal-state > *{\n  max-width:400px;\n}\n.bp5-non-ideal-state .bp5-heading{\n  color:#5f6b7c;\n  line-height:20px;\n  margin-bottom:10px;\n}\n.bp5-non-ideal-state .bp5-heading:only-child{\n  margin-bottom:0;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal{\n  display:flex;\n  flex-direction:row;\n  text-align:left;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal::before,\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > *{\n  margin-right:20px;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal:empty::before,\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > :last-child{\n  margin-right:0;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > *{\n  margin-bottom:0;\n}\n.bp5-dark .bp5-non-ideal-state{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-non-ideal-state .bp5-heading{\n  color:#abb3bf;\n}\n\n.bp5-non-ideal-state-visual{\n  color:#8f99a8;\n}\n\n.bp5-overflow-list{\n  display:flex;\n  flex-wrap:nowrap;\n  min-width:0;\n}\n\n.bp5-overflow-list-spacer{\n  flex-shrink:1;\n  width:1px;\n}\n\nbody.bp5-overlay-open{\n  overflow:hidden;\n}\n\n.bp5-overlay{\n  inset:0;\n  position:static;\n  z-index:20;\n}\n.bp5-overlay:not(.bp5-overlay-open){\n  pointer-events:none;\n}\n.bp5-overlay.bp5-overlay-container{\n  overflow:hidden;\n  position:fixed;\n}\n.bp5-overlay.bp5-overlay-container.bp5-overlay-inline{\n  position:absolute;\n}\n.bp5-overlay.bp5-overlay-scroll-container{\n  overflow:auto;\n  position:fixed;\n}\n.bp5-overlay.bp5-overlay-scroll-container.bp5-overlay-inline{\n  position:absolute;\n}\n.bp5-overlay.bp5-overlay-inline{\n  display:inline;\n  overflow:visible;\n}\n\n.bp5-overlay-content{\n  position:fixed;\n  z-index:20;\n}\n.bp5-overlay-inline .bp5-overlay-content, .bp5-overlay-scroll-container .bp5-overlay-content{\n  position:absolute;\n}\n\n.bp5-overlay-backdrop{\n  inset:0;\n  position:fixed;\n  opacity:1;\n  background-color:rgba(17, 20, 24, 0.7);\n  overflow:auto;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  z-index:20;\n}\n.bp5-overlay-backdrop.bp5-overlay-enter, .bp5-overlay-backdrop.bp5-overlay-appear{\n  opacity:0;\n}\n.bp5-overlay-backdrop.bp5-overlay-enter-active, .bp5-overlay-backdrop.bp5-overlay-appear-active{\n  opacity:1;\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:opacity;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-overlay-backdrop.bp5-overlay-exit{\n  opacity:1;\n}\n.bp5-overlay-backdrop.bp5-overlay-exit-active{\n  opacity:0;\n  transition-delay:0;\n  transition-duration:200ms;\n  transition-property:opacity;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-overlay-backdrop:focus{\n  outline:none;\n}\n.bp5-overlay-inline .bp5-overlay-backdrop{\n  position:absolute;\n}\n\n.bp5-panel-stack{\n  overflow:hidden;\n  position:relative;\n}\n\n.bp5-panel-stack-header{\n  align-items:center;\n  box-shadow:0 1px rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex-shrink:0;\n  height:30px;\n  z-index:1;\n}\n.bp5-dark .bp5-panel-stack-header{\n  box-shadow:0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-panel-stack-header > span{\n  align-items:stretch;\n  display:flex;\n  flex:1;\n}\n.bp5-panel-stack-header .bp5-heading{\n  margin:0 5px;\n}\n\n.bp5-button.bp5-panel-stack-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap;\n}\n.bp5-button.bp5-panel-stack-header-back .bp5-icon{\n  margin:0 2px;\n}\n\n.bp5-panel-stack-view{\n  inset:0;\n  position:absolute;\n  background-color:#ffffff;\n  border-right:1px solid rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1;\n}\n.bp5-dark .bp5-panel-stack-view{\n  background-color:#252a31;\n}\n.bp5-panel-stack-view:nth-last-child(n+4){\n  display:none;\n}\n\n.bp5-panel-stack-push .bp5-panel-stack-enter, .bp5-panel-stack-push .bp5-panel-stack-appear{\n  transform:translateX(100%);\n  opacity:0;\n}\n.bp5-panel-stack-push .bp5-panel-stack-enter-active, .bp5-panel-stack-push .bp5-panel-stack-appear-active{\n  transform:translate(0%);\n  opacity:1;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n.bp5-panel-stack-push .bp5-panel-stack-exit{\n  transform:translate(0%);\n  opacity:1;\n}\n.bp5-panel-stack-push .bp5-panel-stack-exit-active{\n  transform:translateX(-50%);\n  opacity:0;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n\n.bp5-panel-stack-pop .bp5-panel-stack-enter, .bp5-panel-stack-pop .bp5-panel-stack-appear{\n  transform:translateX(-50%);\n  opacity:0;\n}\n.bp5-panel-stack-pop .bp5-panel-stack-enter-active, .bp5-panel-stack-pop .bp5-panel-stack-appear-active{\n  transform:translate(0%);\n  opacity:1;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n.bp5-panel-stack-pop .bp5-panel-stack-exit{\n  transform:translate(0%);\n  opacity:1;\n}\n.bp5-panel-stack-pop .bp5-panel-stack-exit-active{\n  transform:translateX(100%);\n  opacity:0;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n\n.bp5-panel-stack2{\n  overflow:hidden;\n  position:relative;\n}\n\n.bp5-panel-stack2-header{\n  align-items:center;\n  box-shadow:0 1px rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex-shrink:0;\n  height:30px;\n  z-index:1;\n}\n.bp5-dark .bp5-panel-stack2-header{\n  box-shadow:0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-panel-stack2-header > span{\n  align-items:stretch;\n  display:flex;\n  flex:1;\n}\n.bp5-panel-stack2-header .bp5-heading{\n  margin:0 5px;\n}\n\n.bp5-button.bp5-panel-stack2-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap;\n}\n.bp5-button.bp5-panel-stack2-header-back .bp5-icon{\n  margin:0 2px;\n}\n\n.bp5-panel-stack2-view{\n  inset:0;\n  position:absolute;\n  background-color:#ffffff;\n  border-right:1px solid rgba(17, 20, 24, 0.15);\n  display:flex;\n  flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1;\n}\n.bp5-dark .bp5-panel-stack2-view{\n  background-color:#252a31;\n}\n.bp5-panel-stack2-view:nth-last-child(n+4){\n  display:none;\n}\n\n.bp5-panel-stack2-push .bp5-panel-stack2-enter, .bp5-panel-stack2-push .bp5-panel-stack2-appear{\n  transform:translateX(100%);\n  opacity:0;\n}\n.bp5-panel-stack2-push .bp5-panel-stack2-enter-active, .bp5-panel-stack2-push .bp5-panel-stack2-appear-active{\n  transform:translate(0%);\n  opacity:1;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n.bp5-panel-stack2-push .bp5-panel-stack2-exit{\n  transform:translate(0%);\n  opacity:1;\n}\n.bp5-panel-stack2-push .bp5-panel-stack2-exit-active{\n  transform:translateX(-50%);\n  opacity:0;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter, .bp5-panel-stack2-pop .bp5-panel-stack2-appear{\n  transform:translateX(-50%);\n  opacity:0;\n}\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter-active, .bp5-panel-stack2-pop .bp5-panel-stack2-appear-active{\n  transform:translate(0%);\n  opacity:1;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit{\n  transform:translate(0%);\n  opacity:1;\n}\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit-active{\n  transform:translateX(100%);\n  opacity:0;\n  transition-delay:0;\n  transition-duration:400ms;\n  transition-property:transform, opacity;\n  transition-timing-function:ease;\n}\n\n.bp5-button-group:not(.bp5-minimal) > .bp5-popover-target:not(:first-child) .bp5-button{\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n}\n.bp5-button-group:not(.bp5-minimal) > .bp5-popover-target:not(:last-child) .bp5-button{\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n  margin-right:-1px;\n}\n.bp5-button-group .bp5-popover-target{\n  display:flex;\n  flex:1 1 auto;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-target:first-child .bp5-button{\n  border-radius:2px 2px 0 0;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-target:last-child .bp5-button{\n  border-radius:0 0 2px 2px;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-target:not(:last-child) .bp5-button{\n  margin-bottom:-1px;\n}\n\n.bp5-control-group .bp5-popover-target{\n  border-radius:inherit;\n}\n\nlabel.bp5-label .bp5-popover-target{\n  display:block;\n  margin-top:5px;\n  text-transform:none;\n}\n\n.bp5-submenu .bp5-popover-target{\n  display:block;\n}\n.bp5-submenu.bp5-popover{\n  box-shadow:none;\n  padding:0 5px;\n}\n.bp5-submenu.bp5-popover > .bp5-popover-content{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-submenu.bp5-popover, .bp5-submenu.bp5-popover.bp5-dark{\n  box-shadow:none;\n}\n.bp5-dark .bp5-submenu.bp5-popover > .bp5-popover-content, .bp5-submenu.bp5-popover.bp5-dark > .bp5-popover-content{\n  box-shadow:0 0 0 1px hsl(215, 3%, 38%), inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n\n.bp5-tree-node-secondary-label .bp5-popover-target{\n  align-items:center;\n  display:flex;\n}\n\n.bp5-popover{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  transform:scale(1);\n  border-radius:2px;\n  display:inline-block;\n  z-index:20;\n}\n.bp5-popover .bp5-popover-arrow{\n  height:30px;\n  position:absolute;\n  width:30px;\n}\n.bp5-popover .bp5-popover-arrow::before{\n  height:20px;\n  margin:5px;\n  width:20px;\n}\n.bp5-popover .bp5-popover-content{\n  background:#ffffff;\n}\n.bp5-popover .bp5-popover-content,\n.bp5-popover .bp5-heading{\n  color:inherit;\n}\n.bp5-popover .bp5-popover-arrow::before{\n  box-shadow:1px 1px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-popover .bp5-popover-arrow-border{\n  fill:#111418;\n  fill-opacity:0.1;\n}\n.bp5-popover .bp5-popover-arrow-fill{\n  fill:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-popover-enter > .bp5-popover, .bp5-popover-appear > .bp5-popover{\n  transform:scale(0.3);\n}\n.bp5-popover-enter-active > .bp5-popover, .bp5-popover-appear-active > .bp5-popover{\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-popover-exit > .bp5-popover{\n  transform:scale(1);\n}\n.bp5-popover-exit-active > .bp5-popover{\n  transform:scale(0.3);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-popover .bp5-popover-content{\n  border-radius:2px;\n  position:relative;\n}\n.bp5-popover.bp5-popover-content-sizing .bp5-popover-content{\n  max-width:350px;\n  padding:20px;\n}\n.bp5-popover-target + .bp5-overlay .bp5-popover.bp5-popover-content-sizing{\n  width:350px;\n}\n.bp5-popover.bp5-minimal{\n  margin:0 !important;\n}\n.bp5-popover.bp5-minimal .bp5-popover-arrow{\n  display:none;\n}\n.bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n}\n.bp5-popover-enter > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear > .bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n}\n.bp5-popover-enter-active > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear-active > .bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-exit > .bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n}\n.bp5-popover-exit-active > .bp5-popover.bp5-minimal.bp5-popover{\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover.bp5-popover-match-target-width{\n  width:100%;\n}\n.bp5-popover.bp5-dark, .bp5-dark .bp5-popover{\n  box-shadow:0 0 0 1px hsl(215, 3%, 38%), inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-popover.bp5-dark .bp5-popover-content, .bp5-dark .bp5-popover .bp5-popover-content{\n  background:#2f343c;\n}\n.bp5-popover.bp5-dark .bp5-popover-content,\n.bp5-popover.bp5-dark .bp5-heading, .bp5-dark .bp5-popover .bp5-popover-content,\n.bp5-dark .bp5-popover .bp5-heading{\n  color:inherit;\n}\n.bp5-popover.bp5-dark .bp5-popover-arrow::before, .bp5-dark .bp5-popover .bp5-popover-arrow::before{\n  box-shadow:0 0 0 1px #777a7e, 1px 1px 6px rgba(17, 20, 24, 0.4);\n}\n.bp5-popover.bp5-dark .bp5-popover-arrow-border, .bp5-dark .bp5-popover .bp5-popover-arrow-border{\n  fill:#111418;\n  fill-opacity:0.2;\n}\n.bp5-popover.bp5-dark .bp5-popover-arrow-fill, .bp5-dark .bp5-popover .bp5-popover-arrow-fill{\n  fill:#2f343c;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover.bp5-dark .bp5-popover-arrow-fill, .bp5-dark .bp5-popover .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover.bp5-dark, .bp5-dark .bp5-popover{\n    border:1px solid buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-popover{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n\n.bp5-popover-arrow::before{\n  border-radius:1px;\n  content:\"\";\n  display:block;\n  position:absolute;\n  transform:rotate(45deg);\n}\n\n.bp5-overlay-backdrop.bp5-popover-backdrop{\n  background:rgba(255, 255, 255, 0);\n}\n\n.bp5-popover-transition-container{\n  opacity:1;\n  display:flex;\n  z-index:20;\n}\n.bp5-popover-transition-container.bp5-popover-enter, .bp5-popover-transition-container.bp5-popover-appear{\n  opacity:0;\n}\n.bp5-popover-transition-container.bp5-popover-enter-active, .bp5-popover-transition-container.bp5-popover-appear-active{\n  opacity:1;\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:opacity;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-transition-container.bp5-popover-exit{\n  opacity:1;\n}\n.bp5-popover-transition-container.bp5-popover-exit-active{\n  opacity:0;\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:opacity;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-transition-container:focus{\n  outline:none;\n}\n.bp5-popover-transition-container.bp5-popover-leave .bp5-popover-content{\n  pointer-events:none;\n}\n\nspan.bp5-popover-target{\n  display:inline-block;\n}\n\n.bp5-popover-target.bp5-fill{\n  width:100%;\n}\n\n.bp5-portal{\n  left:0;\n  position:absolute;\n  right:0;\n  top:0;\n}\n@keyframes linear-progress-bar-stripes{\n  from{\n    background-position:0 0;\n  }\n  to{\n    background-position:30px 0;\n  }\n}\n.bp5-progress-bar{\n  background:rgba(95, 107, 124, 0.2);\n  border-radius:40px;\n  display:block;\n  height:8px;\n  overflow:hidden;\n  position:relative;\n  width:100%;\n}\n.bp5-progress-bar .bp5-progress-meter{\n  background:linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%);\n  background-color:rgba(95, 107, 124, 0.8);\n  background-size:30px 30px;\n  border-radius:40px;\n  height:100%;\n  position:absolute;\n  transition:width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width:100%;\n}\n.bp5-progress-bar:not(.bp5-no-animation):not(.bp5-no-stripes) .bp5-progress-meter{\n  animation:linear-progress-bar-stripes 300ms linear infinite reverse;\n}\n.bp5-progress-bar.bp5-no-stripes .bp5-progress-meter{\n  background-image:none;\n}\n\n.bp5-dark .bp5-progress-bar{\n  background:rgba(17, 20, 24, 0.5);\n}\n.bp5-dark .bp5-progress-bar .bp5-progress-meter{\n  background-color:#8f99a8;\n}\n\n.bp5-progress-bar.bp5-intent-primary .bp5-progress-meter{\n  background-color:#2d72d2;\n}\n\n.bp5-progress-bar.bp5-intent-success .bp5-progress-meter{\n  background-color:#238551;\n}\n\n.bp5-progress-bar.bp5-intent-warning .bp5-progress-meter{\n  background-color:#c87619;\n}\n\n.bp5-progress-bar.bp5-intent-danger .bp5-progress-meter{\n  background-color:#cd4246;\n}\n\n.bp5-section{\n  overflow:hidden;\n  width:100%;\n}\n.bp5-section, .bp5-section.bp5-compact{\n  padding:0;\n}\n.bp5-section-header{\n  align-items:center;\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n  display:flex;\n  gap:20px;\n  justify-content:space-between;\n  min-height:50px;\n  padding:0 20px;\n  position:relative;\n  width:100%;\n}\n.bp5-section-header.bp5-dark, .bp5-dark .bp5-section-header{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-section-header-left{\n  align-items:center;\n  display:flex;\n  gap:10px;\n  padding:10px 0;\n}\n.bp5-section-header-title{\n  margin-bottom:0;\n}\n.bp5-section-header-sub-title{\n  margin-top:2px;\n}\n.bp5-section-header-right{\n  align-items:center;\n  display:flex;\n  gap:10px;\n  margin-left:auto;\n}\n.bp5-section-header-divider{\n  align-self:stretch;\n  margin:15px 0;\n}\n.bp5-section-header.bp5-interactive{\n  cursor:pointer;\n}\n.bp5-section-header.bp5-interactive:hover, .bp5-section-header.bp5-interactive:active{\n  background:#f6f7f9;\n}\n.bp5-section-header.bp5-interactive:hover.bp5-dark, .bp5-dark .bp5-section-header.bp5-interactive:hover, .bp5-section-header.bp5-interactive:active.bp5-dark, .bp5-dark .bp5-section-header.bp5-interactive:active{\n  background:#383e47;\n}\n.bp5-section-card.bp5-padded{\n  padding:20px;\n}\n.bp5-section-card:not(:last-child){\n  border-bottom:1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-section-card:not(:last-child).bp5-dark, .bp5-dark .bp5-section-card:not(:last-child){\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-section.bp5-section-collapsed .bp5-section-header{\n  border:none;\n}\n.bp5-section.bp5-compact .bp5-section-header{\n  min-height:40px;\n  padding:0 15px;\n}\n.bp5-section.bp5-compact .bp5-section-header-left{\n  padding:7px 0;\n}\n.bp5-section.bp5-compact .bp5-section-card.bp5-padded{\n  padding:15px;\n}\n\n.bp5-segmented-control{\n  background-color:#f6f7f9;\n  border-radius:2px;\n  display:flex;\n  gap:3px;\n  padding:3px;\n}\n.bp5-segmented-control.bp5-inline{\n  display:inline-flex;\n}\n.bp5-segmented-control.bp5-fill{\n  width:100%;\n}\n.bp5-segmented-control.bp5-fill > .bp5-button{\n  flex-grow:1;\n}\n.bp5-segmented-control > .bp5-button:not(.bp5-minimal):not(.bp5-intent-primary){\n  background-color:#ffffff;\n}\n.bp5-dark .bp5-segmented-control > .bp5-button:not(.bp5-minimal):not(.bp5-intent-primary){\n  background-color:#404854;\n}\n.bp5-segmented-control > .bp5-button.bp5-minimal{\n  color:#5f6b7c;\n}\n.bp5-dark .bp5-segmented-control > .bp5-button.bp5-minimal{\n  color:#abb3bf;\n}\n.bp5-segmented-control > .bp5-button.bp5-minimal:disabled{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-dark .bp5-segmented-control > .bp5-button.bp5-minimal:disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-segmented-control{\n  background-color:#252a31;\n}\n@keyframes skeleton-glow{\n  from{\n    background:rgba(211, 216, 222, 0.2);\n    border-color:rgba(211, 216, 222, 0.2);\n  }\n  to{\n    background:rgba(95, 107, 124, 0.2);\n    border-color:rgba(95, 107, 124, 0.2);\n  }\n}\n.bp5-skeleton{\n  animation:1000ms linear infinite alternate skeleton-glow;\n  background:rgba(211, 216, 222, 0.2);\n  background-clip:padding-box !important;\n  border-color:rgba(211, 216, 222, 0.2) !important;\n  border-radius:2px;\n  box-shadow:none !important;\n  color:transparent !important;\n  cursor:default;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-skeleton::before, .bp5-skeleton::after,\n.bp5-skeleton *{\n  visibility:hidden !important;\n}\n.bp5-tooltip{\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  transform:scale(1);\n  color:#f6f7f9;\n}\n.bp5-tooltip .bp5-popover-arrow{\n  height:22px;\n  position:absolute;\n  width:22px;\n}\n.bp5-tooltip .bp5-popover-arrow::before{\n  height:14px;\n  margin:4px;\n  width:14px;\n}\n.bp5-tooltip .bp5-popover-content{\n  background:#404854;\n}\n.bp5-tooltip .bp5-popover-content,\n.bp5-tooltip .bp5-heading{\n  color:#f6f7f9;\n}\n.bp5-tooltip .bp5-popover-arrow::before{\n  box-shadow:1px 1px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-tooltip .bp5-popover-arrow-border{\n  fill:#111418;\n  fill-opacity:0.1;\n}\n.bp5-tooltip .bp5-popover-arrow-fill{\n  fill:#404854;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-popover-enter > .bp5-tooltip, .bp5-popover-appear > .bp5-tooltip{\n  transform:scale(0.8);\n}\n.bp5-popover-enter-active > .bp5-tooltip, .bp5-popover-appear-active > .bp5-tooltip{\n  transform:scale(1);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-exit > .bp5-tooltip{\n  transform:scale(1);\n}\n.bp5-popover-exit-active > .bp5-tooltip{\n  transform:scale(0.8);\n  transition-delay:0;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tooltip .bp5-text-muted{\n  color:#abb3bf;\n}\n.bp5-tooltip .bp5-text-disabled{\n  color:rgba(171, 179, 191, 0.6);\n}\n.bp5-tooltip .bp5-running-text hr{\n  border-color:rgba(255, 255, 255, 0.2);\n}\n.bp5-tooltip a{\n  color:#8abbff;\n}\n.bp5-tooltip a:hover{\n  color:#8abbff;\n}\n.bp5-tooltip a .bp5-icon, .bp5-tooltip a .bp5-icon-standard, .bp5-tooltip a .bp5-icon-large{\n  color:inherit;\n}\n.bp5-tooltip a code{\n  color:inherit;\n}\n.bp5-tooltip .bp5-code,\n.bp5-tooltip .bp5-running-text code{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n  color:#abb3bf;\n}\na > .bp5-tooltip .bp5-code,\na > .bp5-tooltip .bp5-running-text code{\n  color:inherit;\n}\n.bp5-tooltip .bp5-code-block,\n.bp5-tooltip .bp5-running-text pre{\n  background:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n  color:#f6f7f9;\n}\n.bp5-tooltip .bp5-code-block > code,\n.bp5-tooltip .bp5-running-text pre > code{\n  background:none;\n  box-shadow:none;\n  color:inherit;\n}\n.bp5-tooltip .bp5-key,\n.bp5-tooltip .bp5-running-text kbd{\n  background:#2f343c;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 1px 1px 0 rgba(17, 20, 24, 0.4);\n  color:#abb3bf;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-primary, .bp5-tooltip .bp5-icon-standard.bp5-intent-primary, .bp5-tooltip .bp5-icon-large.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-success, .bp5-tooltip .bp5-icon-standard.bp5-intent-success, .bp5-tooltip .bp5-icon-large.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-warning, .bp5-tooltip .bp5-icon-standard.bp5-intent-warning, .bp5-tooltip .bp5-icon-large.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-danger, .bp5-tooltip .bp5-icon-standard.bp5-intent-danger, .bp5-tooltip .bp5-icon-large.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-tooltip .bp5-popover-content{\n  padding:10px 12px;\n}\n.bp5-tooltip.bp5-compact .bp5-popover-content{\n  line-height:1rem;\n  padding:5px 7px;\n}\n.bp5-tooltip.bp5-compact .bp5-code{\n  vertical-align:text-bottom;\n}\n.bp5-tooltip.bp5-popover-placement-top .bp5-popover-arrow{\n  transform:translateY(-3px);\n}\n.bp5-tooltip.bp5-popover-placement-left .bp5-popover-arrow{\n  transform:translateX(-3px);\n}\n.bp5-tooltip.bp5-popover-placement-bottom .bp5-popover-arrow{\n  transform:translateY(3px);\n}\n.bp5-tooltip.bp5-popover-placement-right .bp5-popover-arrow{\n  transform:translateX(3px);\n}\n.bp5-tooltip.bp5-dark, .bp5-dark .bp5-tooltip{\n  box-shadow:0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-tooltip.bp5-dark .bp5-popover-content, .bp5-dark .bp5-tooltip .bp5-popover-content{\n  background:#e5e8eb;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-content,\n.bp5-tooltip.bp5-dark .bp5-heading, .bp5-dark .bp5-tooltip .bp5-popover-content,\n.bp5-dark .bp5-tooltip .bp5-heading{\n  color:#404854;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow::before, .bp5-dark .bp5-tooltip .bp5-popover-arrow::before{\n  box-shadow:1px 1px 6px rgba(17, 20, 24, 0.4);\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow-border, .bp5-dark .bp5-tooltip .bp5-popover-arrow-border{\n  fill:#111418;\n  fill-opacity:0.2;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow-fill, .bp5-dark .bp5-tooltip .bp5-popover-arrow-fill{\n  fill:#e5e8eb;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-dark .bp5-popover-arrow-fill, .bp5-dark .bp5-tooltip .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-dark, .bp5-dark .bp5-tooltip{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-tooltip.bp5-dark .bp5-text-muted, .bp5-dark .bp5-tooltip .bp5-text-muted{\n  color:#5f6b7c;\n}\n.bp5-tooltip.bp5-dark .bp5-text-disabled, .bp5-dark .bp5-tooltip .bp5-text-disabled{\n  color:rgba(95, 107, 124, 0.6);\n}\n.bp5-tooltip.bp5-dark .bp5-running-text hr, .bp5-dark .bp5-tooltip .bp5-running-text hr{\n  border-color:rgba(17, 20, 24, 0.15);\n}\n.bp5-tooltip.bp5-dark a, .bp5-dark .bp5-tooltip a{\n  color:#215db0;\n}\n.bp5-tooltip.bp5-dark a:hover, .bp5-dark .bp5-tooltip a:hover{\n  color:#215db0;\n}\n.bp5-tooltip.bp5-dark a .bp5-icon, .bp5-tooltip.bp5-dark a .bp5-icon-standard, .bp5-tooltip.bp5-dark a .bp5-icon-large, .bp5-dark .bp5-tooltip a .bp5-icon, .bp5-dark .bp5-tooltip a .bp5-icon-standard, .bp5-dark .bp5-tooltip a .bp5-icon-large{\n  color:inherit;\n}\n.bp5-tooltip.bp5-dark a code, .bp5-dark .bp5-tooltip a code{\n  color:inherit;\n}\n.bp5-tooltip.bp5-dark .bp5-code,\n.bp5-tooltip.bp5-dark .bp5-running-text code, .bp5-dark .bp5-tooltip .bp5-code,\n.bp5-dark .bp5-tooltip .bp5-running-text code{\n  background:rgba(255, 255, 255, 0.7);\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color:#5f6b7c;\n}\na > .bp5-tooltip.bp5-dark .bp5-code,\na > .bp5-tooltip.bp5-dark .bp5-running-text code, a > .bp5-dark .bp5-tooltip .bp5-code,\na > .bp5-dark .bp5-tooltip .bp5-running-text code{\n  color:#2d72d2;\n}\n.bp5-tooltip.bp5-dark .bp5-code-block,\n.bp5-tooltip.bp5-dark .bp5-running-text pre, .bp5-dark .bp5-tooltip .bp5-code-block,\n.bp5-dark .bp5-tooltip .bp5-running-text pre{\n  background:rgba(255, 255, 255, 0.7);\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n  color:#1c2127;\n}\n.bp5-tooltip.bp5-dark .bp5-code-block > code,\n.bp5-tooltip.bp5-dark .bp5-running-text pre > code, .bp5-dark .bp5-tooltip .bp5-code-block > code,\n.bp5-dark .bp5-tooltip .bp5-running-text pre > code{\n  background:none;\n  box-shadow:none;\n  color:inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-dark .bp5-code-block,\n  .bp5-tooltip.bp5-dark .bp5-running-text pre, .bp5-dark .bp5-tooltip .bp5-code-block,\n  .bp5-dark .bp5-tooltip .bp5-running-text pre{\n    border:1px solid buttonborder;\n    box-shadow:none;\n  }\n}\n.bp5-tooltip.bp5-dark .bp5-key,\n.bp5-tooltip.bp5-dark .bp5-running-text kbd, .bp5-dark .bp5-tooltip .bp5-key,\n.bp5-dark .bp5-tooltip .bp5-running-text kbd{\n  background:#ffffff;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  color:#5f6b7c;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-primary, .bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-primary, .bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-primary, .bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-primary{\n  color:#215db0;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-success, .bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-success, .bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-success, .bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-success, .bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-success{\n  color:#1c6e42;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-warning, .bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-warning, .bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-warning, .bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-warning{\n  color:#935610;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-danger, .bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-danger, .bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-danger, .bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-danger{\n  color:#ac2f33;\n}\n.bp5-tooltip.bp5-intent-primary .bp5-popover-content{\n  background:#2d72d2;\n  color:#ffffff;\n}\n.bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill{\n  fill:#2d72d2;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-success .bp5-popover-content{\n  background:#238551;\n  color:#ffffff;\n}\n.bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill{\n  fill:#238551;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-warning .bp5-popover-content{\n  background:#c87619;\n  color:#ffffff;\n}\n.bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill{\n  fill:#c87619;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-danger .bp5-popover-content{\n  background:#cd4246;\n  color:#ffffff;\n}\n.bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill{\n  fill:#cd4246;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill{\n    fill:buttonborder;\n  }\n}\n\n.bp5-tooltip-indicator{\n  border-bottom:dotted 1px;\n  cursor:help;\n}\n\n.bp5-slider{\n  height:40px;\n  min-width:150px;\n  width:100%;\n  cursor:default;\n  outline:none;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-slider:hover{\n  cursor:pointer;\n}\n.bp5-slider:active{\n  cursor:grabbing;\n}\n.bp5-slider.bp5-disabled{\n  cursor:not-allowed;\n  opacity:0.5;\n}\n.bp5-slider.bp5-slider-unlabeled{\n  height:16px;\n}\n\n.bp5-slider-track,\n.bp5-slider-progress{\n  height:6px;\n  left:0;\n  right:0;\n  top:5px;\n  position:absolute;\n}\n\n.bp5-slider-track{\n  border-radius:2px;\n  overflow:hidden;\n}\n\n.bp5-slider-progress{\n  background:rgba(95, 107, 124, 0.2);\n}\n.bp5-dark .bp5-slider-progress{\n  background:rgba(17, 20, 24, 0.5);\n}\n.bp5-slider-progress.bp5-intent-primary{\n  background-color:#2d72d2;\n}\n.bp5-slider-progress.bp5-intent-success{\n  background-color:#238551;\n}\n.bp5-slider-progress.bp5-intent-warning{\n  background-color:#c87619;\n}\n.bp5-slider-progress.bp5-intent-danger{\n  background-color:#cd4246;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-slider-progress{\n    background:ButtonText;\n  }\n}\n\n.bp5-slider-handle{\n  background-color:#f6f7f9;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.1);\n  color:#1c2127;\n  border-radius:2px;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.5), 0 1px 1px rgba(17, 20, 24, 0.5);\n  cursor:pointer;\n  height:16px;\n  left:0;\n  position:absolute;\n  top:0;\n  width:16px;\n}\n.bp5-slider-handle:hover{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-slider-handle:active, .bp5-slider-handle.bp5-active{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-slider-handle:active, .bp5-slider-handle.bp5-active{\n    background:highlight;\n  }\n}\n.bp5-slider-handle:disabled, .bp5-slider-handle.bp5-disabled{\n  background-color:rgba(211, 216, 222, 0.5);\n  box-shadow:none;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n  outline:none;\n}\n.bp5-slider-handle:disabled.bp5-active, .bp5-slider-handle:disabled.bp5-active:hover, .bp5-slider-handle.bp5-disabled.bp5-active, .bp5-slider-handle.bp5-disabled.bp5-active:hover{\n  background:rgba(211, 216, 222, 0.7);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-slider-handle{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-slider-handle:focus{\n  z-index:1;\n}\n.bp5-slider-handle:hover{\n  background-clip:padding-box;\n  background-color:#edeff2;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.5), 0 1px 2px rgba(17, 20, 24, 0.6);\n  cursor:grab;\n  z-index:2;\n}\n.bp5-slider-handle.bp5-active{\n  background-color:#dce0e5;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 1px 2px rgba(17, 20, 24, 0.2);\n  box-shadow:inset 0 1px 1px rgba(17, 20, 24, 0.1), 0 0 0 1px rgba(17, 20, 24, 0.5), 0 1px 2px rgba(17, 20, 24, 0.2);\n  cursor:grabbing;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-slider-handle.bp5-active{\n    background:highlight;\n  }\n}\n.bp5-disabled .bp5-slider-handle{\n  background:#c5cbd3;\n  box-shadow:none;\n  pointer-events:none;\n}\n.bp5-dark .bp5-slider-handle{\n  background-color:#abb3bf;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-slider-handle:hover{\n  background-color:#8f99a8;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-slider-handle.bp5-active{\n  background-color:#738091;\n  box-shadow:inset 0 1px 1px rgba(17, 20, 24, 0.1), inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-disabled .bp5-slider-handle{\n  background:#5f6b7c;\n  border-color:#5f6b7c;\n  box-shadow:none;\n}\n.bp5-slider-handle .bp5-slider-label{\n  background:#404854;\n  border-radius:2px;\n  box-shadow:0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  color:#f6f7f9;\n  margin-left:8px;\n}\n.bp5-dark .bp5-slider-handle .bp5-slider-label{\n  background:#e5e8eb;\n  box-shadow:0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n  color:#404854;\n}\n.bp5-disabled .bp5-slider-handle .bp5-slider-label{\n  box-shadow:none;\n}\n.bp5-slider-handle.bp5-start, .bp5-slider-handle.bp5-end{\n  width:8px;\n}\n.bp5-slider-handle.bp5-start{\n  border-bottom-right-radius:0;\n  border-top-right-radius:0;\n}\n.bp5-slider-handle.bp5-end{\n  border-bottom-left-radius:0;\n  border-top-left-radius:0;\n  margin-left:8px;\n}\n.bp5-slider-handle.bp5-end .bp5-slider-label{\n  margin-left:0;\n}\n\n.bp5-slider-label{\n  transform:translate(-50%, 20px);\n  display:inline-block;\n  font-size:12px;\n  line-height:1;\n  padding:2px 5px;\n  position:absolute;\n  vertical-align:top;\n}\n\n.bp5-slider.bp5-vertical{\n  height:150px;\n  min-width:40px;\n  width:40px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-track,\n.bp5-slider.bp5-vertical .bp5-slider-progress{\n  bottom:0;\n  height:auto;\n  left:5px;\n  top:0;\n  width:6px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-progress{\n  top:auto;\n}\n.bp5-slider.bp5-vertical .bp5-slider-label{\n  transform:translate(20px, 50%);\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle{\n  top:auto;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle .bp5-slider-label{\n  margin-left:0;\n  margin-top:-8px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end, .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n  height:8px;\n  margin-left:0;\n  width:16px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n  border-bottom-right-radius:2px;\n  border-top-left-radius:0;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start .bp5-slider-label{\n  transform:translate(20px);\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end{\n  border-bottom-left-radius:0;\n  border-bottom-right-radius:0;\n  border-top-left-radius:2px;\n  margin-bottom:8px;\n}\n\n@keyframes pt-spinner-animation{\n  from{\n    transform:rotate(0deg);\n  }\n  to{\n    transform:rotate(360deg);\n  }\n}\n.bp5-spinner{\n  align-items:center;\n  display:flex;\n  justify-content:center;\n  overflow:visible;\n  vertical-align:middle;\n}\n.bp5-spinner svg{\n  display:block;\n}\n.bp5-spinner path{\n  fill-opacity:0;\n}\n.bp5-spinner .bp5-spinner-head{\n  stroke:rgba(95, 107, 124, 0.8);\n  stroke-linecap:round;\n  transform-origin:center;\n  transition:stroke-dashoffset 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-spinner .bp5-spinner-track{\n  stroke:rgba(95, 107, 124, 0.2);\n}\n\n.bp5-spinner-animation{\n  animation:pt-spinner-animation 500ms linear infinite;\n}\n.bp5-no-spin > .bp5-spinner-animation{\n  animation:none;\n}\n\n.bp5-dark .bp5-spinner .bp5-spinner-head{\n  stroke:#8f99a8;\n}\n.bp5-dark .bp5-spinner .bp5-spinner-track{\n  stroke:rgba(17, 20, 24, 0.5);\n}\n\n.bp5-spinner.bp5-intent-primary .bp5-spinner-head{\n  stroke:#2d72d2;\n}\n\n.bp5-spinner.bp5-intent-success .bp5-spinner-head{\n  stroke:#238551;\n}\n\n.bp5-spinner.bp5-intent-warning .bp5-spinner-head{\n  stroke:#c87619;\n}\n\n.bp5-spinner.bp5-intent-danger .bp5-spinner-head{\n  stroke:#cd4246;\n}\n.bp5-tabs:not(.bp5-vertical).bp5-fill{\n  height:100%;\n}\n.bp5-tabs:not(.bp5-vertical).bp5-fill .bp5-tab-list{\n  height:100%;\n}\n\n.bp5-tabs.bp5-vertical{\n  display:flex;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list{\n  align-items:flex-start;\n  flex-direction:column;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab{\n  align-items:center;\n  border-radius:2px;\n  display:flex;\n  padding:0 10px;\n  width:100%;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab[aria-selected=true]{\n  background-color:rgba(45, 114, 210, 0.2);\n  box-shadow:none;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab[aria-selected=true]{\n    background-color:highlight;\n    color:#111418;\n  }\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n  background-color:rgba(45, 114, 210, 0.2);\n  border-radius:2px;\n  height:auto;\n  inset:0;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-panel{\n  margin-top:0;\n  padding-left:20px;\n}\n\n.bp5-tab-list{\n  align-items:flex-end;\n  border:none;\n  -moz-column-gap:20px;\n       column-gap:20px;\n  display:flex;\n  flex:0 0 auto;\n  list-style:none;\n  margin:0;\n  padding:0;\n  position:relative;\n}\n.bp5-tab-list .bp5-tabs:not(.bp5-vertical) > .bp5-flex-expander{\n  margin-right:-20px;\n}\n\n.bp5-tab{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  align-items:center;\n  align-self:stretch;\n  color:#1c2127;\n  cursor:pointer;\n  display:flex;\n  flex:0 0 auto;\n  font-size:14px;\n  line-height:30px;\n  max-width:100%;\n  position:relative;\n  vertical-align:top;\n}\n.bp5-tab a{\n  color:inherit;\n  display:block;\n  text-decoration:none;\n}\n.bp5-tab-indicator-wrapper ~ .bp5-tab{\n  background-color:transparent !important;\n  box-shadow:none !important;\n}\n.bp5-tab[aria-disabled=true]{\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tab[aria-disabled=true]{\n    color:graytext;\n  }\n}\n.bp5-tab[aria-selected=true]{\n  border-radius:0;\n  box-shadow:inset 0 -3px 0 #215db0;\n}\n.bp5-tab[aria-selected=true], .bp5-tab:not([aria-disabled=true]):hover{\n  color:#215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tab[aria-selected=true], .bp5-tab:not([aria-disabled=true]):hover{\n    color:highlight;\n  }\n}\n.bp5-tab:focus{\n  -moz-outline-radius:0;\n}\n.bp5-large > .bp5-tab{\n  font-size:16px;\n  line-height:40px;\n}\n\n.bp5-tab-panel{\n  margin-top:20px;\n}\n.bp5-tab-panel[aria-hidden=true]{\n  display:none;\n}\n\n.bp5-tab-icon{\n  margin-right:7px;\n}\n\n.bp5-tab-tag{\n  margin-left:7px;\n}\n\n.bp5-tab-indicator-wrapper{\n  left:0;\n  pointer-events:none;\n  position:absolute;\n  top:0;\n  transform:translateX(0), translateY(0);\n  transition:height, transform, width;\n  transition-duration:200ms;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tab-indicator-wrapper .bp5-tab-indicator{\n  background-color:#215db0;\n  bottom:0;\n  height:3px;\n  left:0;\n  position:absolute;\n  right:0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n    background-color:highlight;\n  }\n}\n.bp5-tab-indicator-wrapper.bp5-no-animation{\n  transition:none;\n}\n.bp5-tabs:not(.bp5-vertical) .bp5-tab-indicator-wrapper{\n  height:100%;\n}\n\n.bp5-dark .bp5-tab{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-tab[aria-disabled=true]{\n  color:rgba(171, 179, 191, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-tab[aria-disabled=true]{\n    color:graytext;\n  }\n}\n.bp5-dark .bp5-tab[aria-selected=true]{\n  box-shadow:inset 0 -3px 0 #8abbff;\n}\n.bp5-dark .bp5-tab[aria-selected=true], .bp5-dark .bp5-tab:not([aria-disabled=true]):hover{\n  color:#8abbff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-tab[aria-selected=true], .bp5-dark .bp5-tab:not([aria-disabled=true]):hover{\n    color:highlight;\n  }\n}\n.bp5-dark .bp5-tab-indicator{\n  background-color:#8abbff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-dark .bp5-tab-indicator{\n    background-color:highlight;\n  }\n}\n\n.bp5-flex-expander{\n  flex:1 1;\n}\n.bp5-tag{\n  display:inline-flex;\n  flex-direction:row;\n  align-items:center;\n  background-color:#5f6b7c;\n  border:none;\n  border-radius:2px;\n  box-shadow:none;\n  color:#ffffff;\n  font-size:12px;\n  line-height:16px;\n  max-width:100%;\n  min-height:20px;\n  min-width:20px;\n  padding:2px 6px;\n  position:relative;\n}\n.bp5-tag > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-tag > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-tag::before,\n.bp5-tag > *{\n  margin-right:4px;\n}\n.bp5-tag:empty::before,\n.bp5-tag > :last-child{\n  margin-right:0;\n}\n.bp5-tag:focus{\n  outline:rgba(33, 93, 176, 0.752) solid 2px;\n  outline-offset:0;\n  -moz-outline-radius:6px;\n}\n.bp5-dark .bp5-tag:focus{\n  outline-color:rgba(138, 187, 255, 0.752);\n}\n.bp5-tag.bp5-interactive{\n  cursor:pointer;\n}\n.bp5-tag.bp5-interactive:hover{\n  background:#404854;\n}\n.bp5-tag.bp5-interactive:active, .bp5-tag.bp5-interactive.bp5-active{\n  background:#383e47;\n}\n.bp5-tag.bp5-round{\n  border-radius:30px;\n  padding-left:8px;\n  padding-right:8px;\n}\n.bp5-tag > .bp5-icon, .bp5-tag .bp5-icon-standard, .bp5-tag .bp5-icon-large{\n  fill:#ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark){\n  .bp5-tag{\n    border:1px solid buttonborder;\n  }\n}\n.bp5-tag.bp5-large, .bp5-large .bp5-tag{\n  font-size:14px;\n  line-height:20px;\n  min-height:30px;\n  min-width:30px;\n  padding:5px 10px;\n}\n.bp5-tag.bp5-large::before,\n.bp5-tag.bp5-large > *, .bp5-large .bp5-tag::before,\n.bp5-large .bp5-tag > *{\n  margin-right:7px;\n}\n.bp5-tag.bp5-large:empty::before,\n.bp5-tag.bp5-large > :last-child, .bp5-large .bp5-tag:empty::before,\n.bp5-large .bp5-tag > :last-child{\n  margin-right:0;\n}\n.bp5-tag.bp5-large.bp5-round, .bp5-large .bp5-tag.bp5-round{\n  padding-left:12px;\n  padding-right:12px;\n}\n.bp5-tag.bp5-intent-primary{\n  background:#2d72d2;\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-primary.bp5-interactive:hover{\n  background-color:#215db0;\n}\n.bp5-tag.bp5-intent-primary.bp5-interactive:active, .bp5-tag.bp5-intent-primary.bp5-interactive.bp5-active{\n  background-color:#184a90;\n}\n.bp5-tag.bp5-intent-primary .bp5-tag-remove{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-tag.bp5-intent-primary .bp5-tag-remove:hover, .bp5-tag.bp5-intent-primary .bp5-tag-remove:active{\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-success{\n  background:#238551;\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-success.bp5-interactive:hover{\n  background-color:#1c6e42;\n}\n.bp5-tag.bp5-intent-success.bp5-interactive:active, .bp5-tag.bp5-intent-success.bp5-interactive.bp5-active{\n  background-color:#165a36;\n}\n.bp5-tag.bp5-intent-success .bp5-tag-remove{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-tag.bp5-intent-success .bp5-tag-remove:hover, .bp5-tag.bp5-intent-success .bp5-tag-remove:active{\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-warning{\n  background:#fbb360;\n  color:#1c2127;\n}\n.bp5-tag.bp5-intent-warning.bp5-interactive:hover{\n  background-color:#ec9a3c;\n}\n.bp5-tag.bp5-intent-warning.bp5-interactive:active, .bp5-tag.bp5-intent-warning.bp5-interactive.bp5-active{\n  background-color:#c87619;\n}\n.bp5-tag.bp5-intent-warning .bp5-tag-remove{\n  color:rgba(28, 33, 39, 0.7);\n}\n.bp5-tag.bp5-intent-warning .bp5-tag-remove:hover, .bp5-tag.bp5-intent-warning .bp5-tag-remove:active{\n  color:#1c2127;\n}\n.bp5-tag.bp5-intent-danger{\n  background:#cd4246;\n  color:#ffffff;\n}\n.bp5-tag.bp5-intent-danger.bp5-interactive:hover{\n  background-color:#ac2f33;\n}\n.bp5-tag.bp5-intent-danger.bp5-interactive:active, .bp5-tag.bp5-intent-danger.bp5-interactive.bp5-active{\n  background-color:#8e292c;\n}\n.bp5-tag.bp5-intent-danger .bp5-tag-remove{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-tag.bp5-intent-danger .bp5-tag-remove:hover, .bp5-tag.bp5-intent-danger .bp5-tag-remove:active{\n  color:#ffffff;\n}\n.bp5-tag.bp5-fill{\n  display:flex;\n  width:100%;\n}\n.bp5-tag.bp5-minimal > .bp5-icon, .bp5-tag.bp5-minimal .bp5-icon-standard, .bp5-tag.bp5-minimal .bp5-icon-large{\n  fill:#5f6b7c;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]){\n  background-color:rgba(143, 153, 168, 0.15);\n  color:#1c2127;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive{\n  cursor:pointer;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover{\n  background-color:rgba(143, 153, 168, 0.3);\n  color:#111418;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active, .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active{\n  background-color:rgba(143, 153, 168, 0.35);\n  color:#111418;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove{\n  color:#5f6b7c;\n}\n.bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove:hover, .bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove:active{\n  color:#404854;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]){\n  background-color:rgba(143, 153, 168, 0.15);\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive{\n  cursor:pointer;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover{\n  background-color:rgba(143, 153, 168, 0.3);\n  color:#ffffff;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active{\n  background-color:rgba(143, 153, 168, 0.35);\n  color:#ffffff;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-tag-remove:active{\n  color:#d3d8de;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary{\n  background-color:rgba(45, 114, 210, 0.1);\n  color:#215db0;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-large{\n  fill:#215db0;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#184a90;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active, .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active{\n  background-color:rgba(45, 114, 210, 0.3);\n  color:#184a90;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove{\n  color:#215db0;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:hover, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:active{\n  color:#184a90;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.1);\n  color:#1c6e42;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-large{\n  fill:#1c6e42;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#165a36;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active, .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active{\n  background-color:rgba(35, 133, 81, 0.3);\n  color:#165a36;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove{\n  color:#1c6e42;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:hover, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:active{\n  color:#165a36;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.1);\n  color:#935610;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-large{\n  fill:#935610;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#77450d;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active, .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active{\n  background-color:rgba(200, 118, 25, 0.3);\n  color:#77450d;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove{\n  color:#935610;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:hover, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:active{\n  color:#77450d;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.1);\n  color:#ac2f33;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-large{\n  fill:#ac2f33;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#8e292c;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active, .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active{\n  background-color:rgba(205, 66, 70, 0.3);\n  color:#8e292c;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove{\n  color:#ac2f33;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:hover, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:active{\n  color:#8e292c;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary{\n  background-color:rgba(45, 114, 210, 0.2);\n  color:#8abbff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n  background-color:rgba(45, 114, 210, 0.3);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active{\n  background-color:rgba(45, 114, 210, 0.35);\n  color:#99c4ff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove{\n  color:#8abbff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:active{\n  color:#99c4ff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success{\n  background-color:rgba(35, 133, 81, 0.2);\n  color:#72ca9b;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n  background-color:rgba(35, 133, 81, 0.3);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active{\n  background-color:rgba(35, 133, 81, 0.35);\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:active{\n  color:#7cd7a2;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning{\n  background-color:rgba(200, 118, 25, 0.2);\n  color:#fbb360;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n  background-color:rgba(200, 118, 25, 0.3);\n  color:#f5c186;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active{\n  background-color:rgba(200, 118, 25, 0.35);\n  color:#f5c186;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove{\n  color:#fbb360;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:active{\n  color:#f5c186;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger{\n  background-color:rgba(205, 66, 70, 0.2);\n  color:#fa999c;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n  background-color:rgba(205, 66, 70, 0.3);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active{\n  background-color:rgba(205, 66, 70, 0.35);\n  color:#ffa1a4;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove{\n  color:#fa999c;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:hover, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:active{\n  color:#ffa1a4;\n}\n\n.bp5-tag-remove{\n  background:none;\n  border:none;\n  color:rgba(255, 255, 255, 0.7);\n  cursor:pointer;\n  display:flex;\n  margin-bottom:-2px;\n  margin-right:-6px !important;\n  margin-top:-2px;\n  padding:2px;\n  padding-left:0;\n}\n.bp5-tag-remove:hover{\n  background:none;\n  color:inherit;\n  text-decoration:none;\n}\n.bp5-tag-remove .bp5-icon:first-child{\n  color:inherit;\n}\n.bp5-tag-remove:hover, .bp5-tag-remove:active{\n  color:#ffffff;\n}\n.bp5-tag-remove:empty::before{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  content:\"\\f2ba\";\n}\n.bp5-large .bp5-tag-remove{\n  margin-right:-10px !important;\n  padding:0 5px 0 0;\n}\n.bp5-large .bp5-tag-remove:empty::before{\n  font-family:\"blueprint-icons-20\", sans-serif;\n  font-size:20px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:20px;\n  line-height:1;\n  width:20px;\n}\n.bp5-compound-tag{\n  background:none;\n  padding:0;\n}\n.bp5-compound-tag .bp5-compound-tag-left{\n  background-color:#404854;\n}\n.bp5-compound-tag .bp5-compound-tag-right{\n  background-color:#5f6b7c;\n}\n.bp5-compound-tag.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#383e47;\n}\n.bp5-compound-tag.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#404854;\n}\n.bp5-compound-tag.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#2f343c;\n}\n.bp5-compound-tag.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#383e47;\n}\n.bp5-compound-tag .bp5-compound-tag-left,\n.bp5-compound-tag .bp5-compound-tag-right{\n  align-items:center;\n  display:inline-flex;\n  padding:2px 4px;\n}\n.bp5-compound-tag .bp5-compound-tag-left{\n  border-bottom-left-radius:2px;\n  border-top-left-radius:2px;\n  margin-right:0;\n}\n.bp5-compound-tag .bp5-compound-tag-left > .bp5-icon, .bp5-compound-tag .bp5-compound-tag-left .bp5-icon-standard, .bp5-compound-tag .bp5-compound-tag-left .bp5-icon-large{\n  margin-right:4px;\n}\n.bp5-compound-tag .bp5-compound-tag-right{\n  border-bottom-right-radius:2px;\n  border-top-right-radius:2px;\n  flex-grow:1;\n  padding:2px 4px;\n}\n.bp5-compound-tag .bp5-compound-tag-right > .bp5-icon, .bp5-compound-tag .bp5-compound-tag-right .bp5-icon-standard, .bp5-compound-tag .bp5-compound-tag-right .bp5-icon-large{\n  margin-left:4px;\n}\n.bp5-compound-tag .bp5-compound-tag-right .bp5-compound-tag-right-content{\n  flex-grow:1;\n}\n.bp5-compound-tag .bp5-compound-tag-right .bp5-tag-remove{\n  margin-left:2px;\n  margin-right:-4px !important;\n}\n.bp5-compound-tag.bp5-round{\n  padding:0;\n}\n.bp5-compound-tag.bp5-round .bp5-compound-tag-left{\n  border-bottom-left-radius:20px;\n  border-top-left-radius:20px;\n  padding-left:8px;\n}\n.bp5-compound-tag.bp5-round .bp5-compound-tag-right{\n  border-bottom-right-radius:20px;\n  border-top-right-radius:20px;\n  padding-right:8px;\n}\n.bp5-compound-tag.bp5-large{\n  padding:0;\n}\n.bp5-compound-tag.bp5-large .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-large .bp5-compound-tag-right{\n  padding:5px 8px;\n}\n.bp5-compound-tag.bp5-large .bp5-compound-tag-left > .bp5-icon, .bp5-compound-tag.bp5-large .bp5-compound-tag-left .bp5-icon-standard, .bp5-compound-tag.bp5-large .bp5-compound-tag-left .bp5-icon-large{\n  margin-right:7px;\n}\n.bp5-compound-tag.bp5-large .bp5-compound-tag-right > .bp5-icon, .bp5-compound-tag.bp5-large .bp5-compound-tag-right .bp5-icon-standard, .bp5-compound-tag.bp5-large .bp5-compound-tag-right .bp5-icon-large{\n  margin-left:7px;\n}\n.bp5-compound-tag.bp5-large .bp5-tag-remove{\n  margin-left:7px;\n  margin-right:-10px !important;\n}\n.bp5-compound-tag.bp5-large.bp5-round{\n  padding:0;\n}\n.bp5-compound-tag.bp5-large.bp5-round .bp5-compound-tag-left{\n  border-bottom-left-radius:30px;\n  border-top-left-radius:30px;\n  padding-left:12px;\n}\n.bp5-compound-tag.bp5-large.bp5-round .bp5-compound-tag-right{\n  border-bottom-right-radius:30px;\n  border-top-right-radius:30px;\n  padding-right:12px;\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]){\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.2);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.1);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.3);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.2);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.4);\n}\n.bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary{\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success{\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning{\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger{\n  background:none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]){\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]) .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(95, 107, 124, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal:not([class*=bp5-intent-]).bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(95, 107, 124, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary{\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(45, 114, 210, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(45, 114, 210, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success{\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(35, 133, 81, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(35, 133, 81, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning{\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(200, 118, 25, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(200, 118, 25, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger{\n  background:none;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.4);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.2);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.5);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.3);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-left, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:rgba(205, 66, 70, 0.55);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-right, .bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:rgba(205, 66, 70, 0.35);\n}\n.bp5-compound-tag.bp5-intent-primary{\n  background:none;\n}\n.bp5-compound-tag.bp5-intent-primary .bp5-compound-tag-left{\n  background-color:#215db0;\n}\n.bp5-compound-tag.bp5-intent-primary .bp5-compound-tag-right{\n  background-color:#2d72d2;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#184a90;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#215db0;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#11376b;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-intent-primary.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#184a90;\n}\n.bp5-compound-tag.bp5-intent-success{\n  background:none;\n}\n.bp5-compound-tag.bp5-intent-success .bp5-compound-tag-left{\n  background-color:#1c6e42;\n}\n.bp5-compound-tag.bp5-intent-success .bp5-compound-tag-right{\n  background-color:#238551;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#165a36;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#1c6e42;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#0f3e25;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-intent-success.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#165a36;\n}\n.bp5-compound-tag.bp5-intent-warning{\n  background:none;\n}\n.bp5-compound-tag.bp5-intent-warning .bp5-compound-tag-left{\n  background-color:#ec9a3c;\n}\n.bp5-compound-tag.bp5-intent-warning .bp5-compound-tag-right{\n  background-color:#fbb360;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#c87619;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#ec9a3c;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#935610;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-intent-warning.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#c87619;\n}\n.bp5-compound-tag.bp5-intent-danger{\n  background:none;\n}\n.bp5-compound-tag.bp5-intent-danger .bp5-compound-tag-left{\n  background-color:#ac2f33;\n}\n.bp5-compound-tag.bp5-intent-danger .bp5-compound-tag-right{\n  background-color:#cd4246;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-left{\n  background-color:#8e292c;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:hover .bp5-compound-tag-right{\n  background-color:#ac2f33;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-left, .bp5-compound-tag.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-left{\n  background-color:#782326;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:active .bp5-compound-tag-right, .bp5-compound-tag.bp5-intent-danger.bp5-interactive.bp5-active .bp5-compound-tag-right{\n  background-color:#8e292c;\n}\n\n.bp5-tag-input{\n  display:flex;\n  flex-direction:row;\n  align-items:flex-start;\n  cursor:text;\n  height:auto;\n  line-height:inherit;\n  min-height:30px;\n  padding-left:5px;\n  padding-right:0;\n}\n.bp5-tag-input > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-tag-input > .bp5-tag-input-values{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-tag-input .bp5-tag-input-icon{\n  color:#5f6b7c;\n  margin-left:2px;\n  margin-right:7px;\n  margin-top:7px;\n}\n.bp5-tag-input .bp5-tag-input-values{\n  display:flex;\n  flex-direction:row;\n  align-items:center;\n  align-self:stretch;\n  flex-wrap:wrap;\n  margin-right:7px;\n  margin-top:5px;\n  min-width:0;\n  position:relative;\n}\n.bp5-tag-input .bp5-tag-input-values > *{\n  flex-grow:0;\n  flex-shrink:0;\n}\n.bp5-tag-input .bp5-tag-input-values > .bp5-fill{\n  flex-grow:1;\n  flex-shrink:1;\n}\n.bp5-tag-input .bp5-tag-input-values::before,\n.bp5-tag-input .bp5-tag-input-values > *{\n  margin-right:5px;\n}\n.bp5-tag-input .bp5-tag-input-values:empty::before,\n.bp5-tag-input .bp5-tag-input-values > :last-child{\n  margin-right:0;\n}\n.bp5-tag-input .bp5-tag-input-values:first-child .bp5-tag ~ .bp5-input-ghost{\n  padding-left:0;\n}\n.bp5-tag-input .bp5-tag-input-values:first-child .bp5-input-ghost{\n  padding-left:5px;\n}\n.bp5-tag-input .bp5-tag-input-values > *{\n  margin-bottom:5px;\n}\n.bp5-tag-input .bp5-tag{\n  overflow-wrap:break-word;\n}\n.bp5-tag-input .bp5-tag.bp5-active{\n  outline:rgba(33, 93, 176, 0.752) solid 2px;\n  outline-offset:0;\n  -moz-outline-radius:6px;\n}\n.bp5-dark .bp5-tag-input .bp5-tag.bp5-active{\n  outline-color:rgba(138, 187, 255, 0.752);\n}\n.bp5-tag-input .bp5-input-ghost{\n  flex:1 1 auto;\n  line-height:20px;\n  width:80px;\n}\n.bp5-tag-input .bp5-input-ghost:disabled, .bp5-tag-input .bp5-input-ghost.bp5-disabled{\n  cursor:not-allowed;\n}\n.bp5-tag-input .bp5-button,\n.bp5-tag-input .bp5-spinner{\n  margin:3px;\n  margin-left:0;\n}\n.bp5-tag-input .bp5-button{\n  min-height:24px;\n  min-width:24px;\n  padding:0 7px;\n}\n.bp5-tag-input.bp5-large{\n  height:auto;\n  min-height:40px;\n}\n.bp5-tag-input.bp5-large::before,\n.bp5-tag-input.bp5-large > *{\n  margin-right:10px;\n}\n.bp5-tag-input.bp5-large:empty::before,\n.bp5-tag-input.bp5-large > :last-child{\n  margin-right:0;\n}\n.bp5-tag-input.bp5-large .bp5-tag-input-icon{\n  margin-left:5px;\n  margin-top:10px;\n}\n.bp5-tag-input.bp5-large .bp5-input-ghost{\n  line-height:30px;\n}\n.bp5-tag-input.bp5-large .bp5-button{\n  min-height:30px;\n  min-width:30px;\n  padding:5px 10px;\n  margin:5px;\n  margin-left:0;\n}\n.bp5-tag-input.bp5-large .bp5-spinner{\n  margin:8px;\n  margin-left:0;\n}\n.bp5-tag-input.bp5-active{\n  background-color:#ffffff;\n  box-shadow:inset 0 0 0 1px rgba(33, 93, 176, 0.752), 0 0 0 1px rgba(33, 93, 176, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-primary{\n  box-shadow:inset 0 0 0 1px rgba(45, 114, 210, 0.752), 0 0 0 1px rgba(45, 114, 210, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-success{\n  box-shadow:inset 0 0 0 1px rgba(35, 133, 81, 0.752), 0 0 0 1px rgba(35, 133, 81, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-warning{\n  box-shadow:inset 0 0 0 1px rgba(200, 118, 25, 0.752), 0 0 0 1px rgba(200, 118, 25, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-danger{\n  box-shadow:inset 0 0 0 1px rgba(205, 66, 70, 0.752), 0 0 0 1px rgba(205, 66, 70, 0.752), inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-tag-input .bp5-tag-input-icon, .bp5-tag-input.bp5-dark .bp5-tag-input-icon{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost, .bp5-tag-input.bp5-dark .bp5-input-ghost{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost::-moz-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-moz-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost:-ms-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost:-ms-input-placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost::placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::placeholder{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tag-input.bp5-active, .bp5-tag-input.bp5-dark.bp5-active{\n  background-color:rgba(17, 20, 24, 0.3);\n  box-shadow:inset 0 0 0 1px rgba(138, 187, 255, 0.752), 0 0 0 1px rgba(138, 187, 255, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-primary, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-primary{\n  box-shadow:inset 0 0 0 1px rgba(76, 144, 240, 0.752), 0 0 0 1px rgba(76, 144, 240, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-success, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-success{\n  box-shadow:inset 0 0 0 1px rgba(50, 164, 103, 0.752), 0 0 0 1px rgba(50, 164, 103, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-warning, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-warning{\n  box-shadow:inset 0 0 0 1px rgba(236, 154, 60, 0.752), 0 0 0 1px rgba(236, 154, 60, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-danger, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-danger{\n  box-shadow:inset 0 0 0 1px rgba(231, 106, 110, 0.752), 0 0 0 1px rgba(231, 106, 110, 0.752), inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n\n.bp5-input-ghost{\n  background:none;\n  border:none;\n  box-shadow:none;\n  padding:0;\n}\n.bp5-input-ghost::-moz-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input-ghost:-ms-input-placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input-ghost::placeholder{\n  color:#5f6b7c;\n  opacity:1;\n}\n.bp5-input-ghost:focus{\n  outline:none !important;\n}\n\n.bp5-resizable-input-span{\n  max-height:0;\n  max-width:100%;\n  min-width:80px;\n  opacity:0;\n  overflow:hidden;\n  position:absolute;\n  white-space:nowrap;\n  z-index:-1;\n}\n\n.bp5-toast{\n  align-items:flex-start;\n  background-color:#ffffff;\n  border-radius:2px;\n  box-shadow:inset 0 0 0 1px rgba(17, 20, 24, 0.2), 0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  display:flex;\n  margin:20px 0 0;\n  max-width:min(500px, 100%);\n  min-width:min(300px, 100%);\n  pointer-events:all;\n  position:relative !important;\n}\n.bp5-toast.bp5-toast-enter, .bp5-toast.bp5-toast-appear{\n  transform:translateY(-40px);\n}\n.bp5-toast.bp5-toast-enter-active, .bp5-toast.bp5-toast-appear-active{\n  transform:translateY(0);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-toast.bp5-toast-enter ~ .bp5-toast, .bp5-toast.bp5-toast-appear ~ .bp5-toast{\n  transform:translateY(-40px);\n}\n.bp5-toast.bp5-toast-enter-active ~ .bp5-toast, .bp5-toast.bp5-toast-appear-active ~ .bp5-toast{\n  transform:translateY(0);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-toast.bp5-toast-exit{\n  opacity:1;\n  filter:blur(0);\n}\n.bp5-toast.bp5-toast-exit-active{\n  opacity:0;\n  filter:blur(10px);\n  transition-delay:0;\n  transition-duration:300ms;\n  transition-property:opacity, filter;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-toast.bp5-toast-exit ~ .bp5-toast{\n  transform:translateY(0);\n}\n.bp5-toast.bp5-toast-exit-active ~ .bp5-toast{\n  transform:translateY(-40px);\n  transition-delay:50ms;\n  transition-duration:100ms;\n  transition-property:transform;\n  transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-toast .bp5-button-group{\n  flex:0 0 auto;\n  padding:5px;\n  padding-left:0;\n}\n.bp5-toast > .bp5-icon{\n  color:#5f6b7c;\n  margin:12px;\n  margin-right:0;\n}\n.bp5-toast.bp5-dark, .bp5-dark .bp5-toast{\n  background-color:#383e47;\n  box-shadow:inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-toast.bp5-dark > .bp5-icon, .bp5-dark .bp5-toast > .bp5-icon{\n  color:#abb3bf;\n}\n.bp5-toast.bp5-dark .bp5-button .bp5-icon, .bp5-dark .bp5-toast .bp5-button .bp5-icon{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast[class*=bp5-intent-] a{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast[class*=bp5-intent-] a:hover{\n  color:#ffffff;\n}\n.bp5-toast[class*=bp5-intent-] > .bp5-icon{\n  color:#ffffff;\n}\n.bp5-toast.bp5-intent-primary{\n  background-color:#2d72d2;\n  color:#ffffff;\n}\n.bp5-toast.bp5-intent-primary .bp5-icon:first-child{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast.bp5-intent-primary .bp5-button{\n  background-color:#2d72d2 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-primary .bp5-button:hover{\n  background-color:#215db0 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-primary .bp5-button:active{\n  background-color:#184a90 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-primary .bp5-button:focus{\n  outline-color:rgba(255, 255, 255, 0.75);\n}\n.bp5-toast.bp5-intent-primary .bp5-button:last-child > .bp5-icon-cross{\n  color:rgba(255, 255, 255, 0.7) !important;\n}\n.bp5-toast.bp5-intent-success{\n  background-color:#238551;\n  color:#ffffff;\n}\n.bp5-toast.bp5-intent-success .bp5-icon:first-child{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast.bp5-intent-success .bp5-button{\n  background-color:#238551 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-success .bp5-button:hover{\n  background-color:#1c6e42 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-success .bp5-button:active{\n  background-color:#165a36 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-success .bp5-button:focus{\n  outline-color:rgba(255, 255, 255, 0.75);\n}\n.bp5-toast.bp5-intent-success .bp5-button:last-child > .bp5-icon-cross{\n  color:rgba(255, 255, 255, 0.7) !important;\n}\n.bp5-toast.bp5-intent-warning{\n  background-color:#fbb360;\n  color:#1c2127;\n}\n.bp5-toast.bp5-intent-warning .bp5-icon:first-child{\n  color:rgba(28, 33, 39, 0.7);\n}\n.bp5-toast.bp5-intent-warning .bp5-button{\n  background-color:#fbb360 !important;\n  color:#1c2127 !important;\n}\n.bp5-toast.bp5-intent-warning .bp5-button:hover{\n  background-color:#ec9a3c !important;\n  color:#1c2127 !important;\n}\n.bp5-toast.bp5-intent-warning .bp5-button:active{\n  background-color:#c87619 !important;\n  color:#1c2127 !important;\n}\n.bp5-toast.bp5-intent-warning .bp5-button:focus{\n  outline-color:rgba(28, 33, 39, 0.75);\n}\n.bp5-toast.bp5-intent-warning .bp5-button:last-child > .bp5-icon-cross{\n  color:rgba(28, 33, 39, 0.7) !important;\n}\n.bp5-toast.bp5-intent-danger{\n  background-color:#cd4246;\n  color:#ffffff;\n}\n.bp5-toast.bp5-intent-danger .bp5-icon:first-child{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-toast.bp5-intent-danger .bp5-button{\n  background-color:#cd4246 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-danger .bp5-button:hover{\n  background-color:#ac2f33 !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-danger .bp5-button:active{\n  background-color:#8e292c !important;\n  color:#ffffff !important;\n}\n.bp5-toast.bp5-intent-danger .bp5-button:focus{\n  outline-color:rgba(255, 255, 255, 0.75);\n}\n.bp5-toast.bp5-intent-danger .bp5-button:last-child > .bp5-icon-cross{\n  color:rgba(255, 255, 255, 0.7) !important;\n}\n\n.bp5-toast-message{\n  flex:1 1 auto;\n  padding:11px;\n  word-break:break-word;\n}\n\n.bp5-toast-container{\n  align-items:center;\n  display:flex !important;\n  flex-direction:column;\n  left:0;\n  overflow:hidden;\n  padding:0 20px 20px;\n  pointer-events:none;\n  right:0;\n  z-index:40;\n}\n.bp5-toast-container.bp5-toast-container-in-portal{\n  position:fixed;\n}\n.bp5-toast-container.bp5-toast-container-inline{\n  position:absolute;\n}\n.bp5-toast-container.bp5-toast-container-top{\n  top:0;\n}\n.bp5-toast-container.bp5-toast-container-bottom{\n  bottom:0;\n  flex-direction:column-reverse;\n  top:auto;\n}\n.bp5-toast-container.bp5-toast-container-left{\n  align-items:flex-start;\n}\n.bp5-toast-container.bp5-toast-container-right{\n  align-items:flex-end;\n}\n\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active), .bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active) ~ .bp5-toast, .bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active), .bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active) ~ .bp5-toast, .bp5-toast-container-bottom .bp5-toast.bp5-toast-exit-active ~ .bp5-toast, .bp5-toast-container-bottom .bp5-toast.bp5-toast-leave-active ~ .bp5-toast{\n  transform:translateY(60px);\n}\n.bp5-tree .bp5-icon, .bp5-tree .bp5-icon-standard, .bp5-tree .bp5-icon-large{\n  color:#5f6b7c;\n}\n.bp5-tree .bp5-icon.bp5-intent-primary, .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-tree .bp5-icon-large.bp5-intent-primary{\n  color:#2d72d2;\n}\n.bp5-tree .bp5-icon.bp5-intent-success, .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-tree .bp5-icon-large.bp5-intent-success{\n  color:#238551;\n}\n.bp5-tree .bp5-icon.bp5-intent-warning, .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-tree .bp5-icon-large.bp5-intent-warning{\n  color:#c87619;\n}\n.bp5-tree .bp5-icon.bp5-intent-danger, .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-tree .bp5-icon-large.bp5-intent-danger{\n  color:#cd4246;\n}\n\n.bp5-tree-node-list{\n  list-style:none;\n  margin:0;\n  padding-left:0;\n}\n\n.bp5-tree-root{\n  background-color:transparent;\n  cursor:default;\n  padding-left:0;\n  position:relative;\n}\n\n.bp5-tree-node-content-0{\n  padding-left:0px;\n}\n\n.bp5-tree-node-content-1{\n  padding-left:23px;\n}\n\n.bp5-tree-node-content-2{\n  padding-left:46px;\n}\n\n.bp5-tree-node-content-3{\n  padding-left:69px;\n}\n\n.bp5-tree-node-content-4{\n  padding-left:92px;\n}\n\n.bp5-tree-node-content-5{\n  padding-left:115px;\n}\n\n.bp5-tree-node-content-6{\n  padding-left:138px;\n}\n\n.bp5-tree-node-content-7{\n  padding-left:161px;\n}\n\n.bp5-tree-node-content-8{\n  padding-left:184px;\n}\n\n.bp5-tree-node-content-9{\n  padding-left:207px;\n}\n\n.bp5-tree-node-content-10{\n  padding-left:230px;\n}\n\n.bp5-tree-node-content-11{\n  padding-left:253px;\n}\n\n.bp5-tree-node-content-12{\n  padding-left:276px;\n}\n\n.bp5-tree-node-content-13{\n  padding-left:299px;\n}\n\n.bp5-tree-node-content-14{\n  padding-left:322px;\n}\n\n.bp5-tree-node-content-15{\n  padding-left:345px;\n}\n\n.bp5-tree-node-content-16{\n  padding-left:368px;\n}\n\n.bp5-tree-node-content-17{\n  padding-left:391px;\n}\n\n.bp5-tree-node-content-18{\n  padding-left:414px;\n}\n\n.bp5-tree-node-content-19{\n  padding-left:437px;\n}\n\n.bp5-tree-node-content-20{\n  padding-left:460px;\n}\n\n.bp5-tree-node-content{\n  align-items:center;\n  background:none;\n  display:flex;\n  height:30px;\n  padding-right:5px;\n  width:100%;\n}\n.bp5-tree-node-content:hover{\n  background-color:rgba(143, 153, 168, 0.15);\n}\n.bp5-tree-node-content:active{\n  background-color:rgba(143, 153, 168, 0.3);\n}\n\n.bp5-tree-node-caret,\n.bp5-tree-node-caret-none{\n  min-width:30px;\n}\n\n.bp5-tree-node-caret{\n  color:#5f6b7c;\n  cursor:pointer;\n  padding:7px;\n  transform:rotate(0deg);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tree-node-caret:hover{\n  color:#1c2127;\n}\n.bp5-dark .bp5-tree-node-caret{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tree-node-caret:hover{\n  color:#f6f7f9;\n}\n.bp5-tree-node-caret:hover{\n  color:#1c2127;\n}\n.bp5-tree-node-caret.bp5-tree-node-caret-open{\n  transform:rotate(90deg);\n}\n.bp5-tree-node-caret.bp5-icon-standard::before{\n  content:\"\\f14d\";\n}\n\n.bp5-tree-node-icon{\n  margin-right:7px;\n  position:relative;\n}\n\n.bp5-tree-node-label{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  flex:1 1 auto;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-tree-node-label span{\n  display:inline;\n}\n\n.bp5-tree-node-secondary-label{\n  padding:0 5px;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n}\n.bp5-tree-node-secondary-label .bp5-popover-wrapper,\n.bp5-tree-node-secondary-label .bp5-popover-target{\n  align-items:center;\n  display:flex;\n}\n\n.bp5-tree-node.bp5-disabled .bp5-tree-node-content{\n  background-color:inherit;\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n.bp5-tree-node.bp5-disabled .bp5-tree-node-caret,\n.bp5-tree-node.bp5-disabled .bp5-tree-node-icon{\n  color:rgba(95, 107, 124, 0.6);\n  cursor:not-allowed;\n}\n\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#2d72d2;\n}\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content,\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-standard, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-large{\n  color:#ffffff;\n}\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret::before{\n  color:rgba(255, 255, 255, 0.7);\n}\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret:hover::before{\n  color:#ffffff;\n}\n\n.bp5-tree.bp5-compact .bp5-tree-node-content{\n  height:24px;\n}\n.bp5-tree.bp5-compact .bp5-tree-node-caret{\n  margin-right:3px;\n  min-width:24px;\n  padding:4px;\n}\n\n.bp5-dark .bp5-tree-node-content:hover{\n  background-color:rgba(95, 107, 124, 0.3);\n}\n.bp5-dark .bp5-tree .bp5-icon, .bp5-dark .bp5-tree .bp5-icon-standard, .bp5-dark .bp5-tree .bp5-icon-large{\n  color:#abb3bf;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-primary{\n  color:#8abbff;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-success{\n  color:#72ca9b;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-warning{\n  color:#fbb360;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-danger{\n  color:#fa999c;\n}\n.bp5-dark .bp5-tree-node:not(.bp5-disabled) .bp5-tree-node-caret:hover{\n  color:#f6f7f9;\n}\n.bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#2d72d2;\n}\n.bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon, .bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-standard, .bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-large{\n  color:#ffffff;\n}"
  },
  {
    "path": "www/dist-dev/static/global.css",
    "content": "@font-face {\n  font-family: \"Roboto Mono\";\n  src: url(\"RobotoMono-Regular.ttf\") format(\"truetype\");\n  font-weight: 400;\n}\n\n@font-face {\n  font-family: \"Roboto Mono\";\n  src: url(\"RobotoMono-Medium.ttf\") format(\"truetype\");\n  font-weight: 500;\n}\n\n*, *:before, *:after {\n  -webkit-box-sizing: border-box;\n  -webkit-appearance: none;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n}\n\nhtml {\n  scrollbar-color: #5f6b7c #ffffff;\n}\nhtml.dark3-scroll {\n  scrollbar-color: #555b63 #30404d;\n}\nhtml.dark5-scroll {\n  scrollbar-color: #555b63 #2f343c;\n}\n\nbody {\n  font-family: Roboto, Noto, sans-serif;\n  min-width: 1100px;\n}\n\nbody {\n  background-color: #f6f7f9;\n}\nbody.bp5-dark {\n  background-color: #252a31;\n}\n\ncode {\n  font-family: Consolas, Menlo, 'Roboto Mono', 'DejaVu Sans Mono';\n  font-weight: 400;\n  white-space: pre-wrap;\n  word-wrap: break-word;\n  overflow-wrap: break-word;\n}\n\nh2 {\n  font-size: 28px;\n}\n\nh5 {\n  font-size: 16px;\n}\n\n.no-select {\n  user-select: none;\n}\n\n.logo-dark {\n  display: none;\n}\n\n.hljs {\n  border-radius: 3px;\n}\n\ncode.intent-primary {\n  background-color: #ecf8ff;\n}\n.bp5-dark code.intent-primary {\n  background-color: #183345;\n}\n\ncode.intent-secondary {\n  background-color: #fff1e7;\n}\n.bp5-dark code.intent-secondary {\n  background-color: #3a322c\n}\n\n.bp5-dark .logo-light {\n  display: none;\n}\n\n.bp5-dark .logo-dark {\n  display: block;\n}\n\n.bp5-button:disabled, .bp5-button.bp5-disabled {\n  cursor: default !important;\n}\n\n.bp5-input:disabled, .bp5-input.bp5-disabled {\n  cursor: default !important;\n}\n\n.bp5-select select:disabled, .bp5-select select.bp5-disabled {\n  cursor: default !important;\n}\n\n.bp5-text-intent-primary {\n  color: #106ba3;\n}\n.bp5-dark .bp5-text-intent-primary {\n  color: #2b95d6;\n}\n\n.bp5-text-intent-success {\n  color: #0d8050;\n}\n.bp5-dark .bp5-text-intent-success {\n  color: #15b371;\n}\n\n.bp5-text-intent-warning {\n  color: #bf7326;\n}\n.bp5-dark .bp5-text-intent-warning {\n  color: #f29d49;\n}\n\n.bp5-text-intent-danger {\n  color: #c23030;\n}\n.bp5-dark .bp5-text-intent-danger {\n  color: #f55656;\n}\n\n.bp5-border {\n  border-width: 1px;\n  border-color: rgba(16, 22, 26, 0.15);\n}\n\n.bp5-dark .bp5-border {\n  border-color: rgba(255, 255, 255, 0.15);\n}\n\n.bp5-button.bp5-button-empty[class*=\"bp5-icon-\"] {\n  padding: 0;\n}\n\n.bp5-button.bp5-button-empty[class*=\"bp5-icon-\"]::before {\n  margin-right: 0;\n}\n\n.bp5-row .bp5-cell span {\n  vertical-align: middle;\n}\n\n.bp5-row .bp5-cell {\n  border-top: 1px solid rgba(16, 22, 26, 0.15);\n  border-bottom: 1px solid rgba(16, 22, 26, 0.15);\n}\n\n.bp5-row .bp5-cell:first-child {\n  border-top-left-radius: 3px;\n  border-bottom-left-radius: 3px;\n  border-left: 1px solid rgba(16, 22, 26, 0.15);\n}\n\n.bp5-row .bp5-cell:last-child {\n  border-top-right-radius: 3px;\n  border-bottom-right-radius: 3px;\n  border-right: 1px solid rgba(16, 22, 26, 0.15);\n}\n\n.bp5-dark .bp5-row .bp5-cell {\n  border-top: 1px solid rgba(16, 22, 26, 0.5);\n  border-bottom: 1px solid rgba(16, 22, 26, 0.5);\n}\n\n.bp5-dark .bp5-row .bp5-cell:first-child {\n  border-left: 1px solid rgba(16, 22, 26, 0.5);\n}\n\n.bp5-dark .bp5-row .bp5-cell:last-child {\n  border-right: 1px solid rgba(16, 22, 26, 0.5);\n}\n\n.bp5-progress-bar .bp5-loading-bar {\n  animation: bp5-loading-bar-frames 2s linear infinite;\n}\n\nlabel.bp5-label .bp5-button-group {\n  margin-top: 5px !important;\n}\n\n.bp5-select-popover .bp5-menu {\n  max-height: 400px;\n  overflow-y: auto;\n}\n\n@keyframes bp5-loading-bar-frames {\n  from {\n    left: -200px;\n    width: 30%;\n  }\n  50% {\n    width: 30%;\n  }\n  70% {\n    width: 70%;\n  }\n  80% {\n    left: 50%;\n  }\n  95% {\n    left: 120%;\n  }\n  to {\n    left: 100%;\n  }\n}\n\n.bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.06)\n}\n\n.bp5-dark .bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.13)\n}\n\n.bp5-toast-container.bp5-toast-container-bottom {\n  position: fixed !important;\n}\n.bp5-toast-container.bp5-toast-container-inline {\n  position: fixed !important;\n}\n\n.bp3-theme.bp5-dark .bp5-datepicker {\n  background: #30404d !important;\n}\n\n#chartjs-tooltip {\n   background-color: #fcfcfc;\n }\n\n.bp5-dark #chartjs-tooltip {\n  background-color: #232f38;\n}\n\n#chartjs-tooltip {\n  padding: 0 3px 3px 3px;\n}\n\n#chartjs-tooltip th {\n  font-size: 12px;\n}\n\n#chartjs-tooltip td {\n  font-size: 10px;\n  padding: 4px 4px 2px 6px;\n  position: relative;\n}\n\n#chartjs-tooltip td.line-box {\n  padding: 4px 4px 2px 17px;\n}\n\n#chartjs-tooltip td span {\n  width: 10px;\n  height: 10px;\n  border-width: 2px;\n  border-style: solid;\n  top: 4px;\n  left: 3px;\n  position: absolute;\n}\n\n/*******************************\n          Flex Layout\n*******************************/\n.layout.horizontal,\n.layout.horizontal-reverse,\n.layout.vertical,\n.layout.vertical-reverse {\n  display: -ms-flexbox;\n  display: -webkit-flex;\n  display: flex;\n}\n.layout.inline {\n  display: -ms-inline-flexbox;\n  display: -webkit-inline-flex;\n  display: inline-flex;\n}\n.layout.horizontal {\n  -ms-flex-direction: row;\n  -webkit-flex-direction: row;\n  flex-direction: row;\n}\n.layout.horizontal-reverse {\n  -ms-flex-direction: row-reverse;\n  -webkit-flex-direction: row-reverse;\n  flex-direction: row-reverse;\n}\n.layout.vertical {\n  -ms-flex-direction: column;\n  -webkit-flex-direction: column;\n  flex-direction: column;\n}\n.layout.vertical-reverse {\n  -ms-flex-direction: column-reverse;\n  -webkit-flex-direction: column-reverse;\n  flex-direction: column-reverse;\n}\n.layout.wrap {\n  -ms-flex-wrap: wrap;\n  -webkit-flex-wrap: wrap;\n  flex-wrap: wrap;\n}\n.layout.no-wrap {\n  -ms-flex-wrap: nowrap;\n  -webkit-flex-wrap: nowrap;\n  flex-wrap: nowrap;\n}\n.layout.wrap-reverse {\n  -ms-flex-wrap: wrap-reverse;\n  -webkit-flex-wrap: wrap-reverse;\n  flex-wrap: wrap-reverse;\n}\n.flex-auto {\n  -ms-flex: 1 1 auto;\n  -webkit-flex: 1 1 auto;\n  flex: 1 1 auto;\n}\n.flex-none {\n  -ms-flex: none;\n  -webkit-flex: none;\n  flex: none;\n}\n.flex,\n.flex-1 {\n  -ms-flex: 1;\n  -webkit-flex: 1;\n  flex: 1;\n}\n.flex-2 {\n  -ms-flex: 2;\n  -webkit-flex: 2;\n  flex: 2;\n}\n.flex-3 {\n  -ms-flex: 3;\n  -webkit-flex: 3;\n  flex: 3;\n}\n.flex-4 {\n  -ms-flex: 4;\n  -webkit-flex: 4;\n  flex: 4;\n}\n.flex-5 {\n  -ms-flex: 5;\n  -webkit-flex: 5;\n  flex: 5;\n}\n.flex-6 {\n  -ms-flex: 6;\n  -webkit-flex: 6;\n  flex: 6;\n}\n.flex-7 {\n  -ms-flex: 7;\n  -webkit-flex: 7;\n  flex: 7;\n}\n.flex-8 {\n  -ms-flex: 8;\n  -webkit-flex: 8;\n  flex: 8;\n}\n.flex-9 {\n  -ms-flex: 9;\n  -webkit-flex: 9;\n  flex: 9;\n}\n.flex-10 {\n  -ms-flex: 10;\n  -webkit-flex: 10;\n  flex: 10;\n}\n.flex-11 {\n  -ms-flex: 11;\n  -webkit-flex: 11;\n  flex: 11;\n}\n.flex-12 {\n  -ms-flex: 12;\n  -webkit-flex: 12;\n  flex: 12;\n}\n.layout.start {\n  -ms-flex-align: start;\n  -webkit-align-items: flex-start;\n  align-items: flex-start;\n}\n.layout.center,\n.layout.center-center {\n  -ms-flex-align: center;\n  -webkit-align-items: center;\n  align-items: center;\n}\n.layout.end {\n  -ms-flex-align: end;\n  -webkit-align-items: flex-end;\n  align-items: flex-end;\n}\n.layout.start-justified {\n  -ms-flex-pack: start;\n  -webkit-justify-content: flex-start;\n  justify-content: flex-start;\n}\n.layout.center-justified,\n.layout.center-center {\n  -ms-flex-pack: center;\n  -webkit-justify-content: center;\n  justify-content: center;\n}\n.layout.end-justified {\n  -ms-flex-pack: end;\n  -webkit-justify-content: flex-end;\n  justify-content: flex-end;\n}\n.layout.around-justified {\n  -ms-flex-pack: around;\n  -webkit-justify-content: space-around;\n  justify-content: space-around;\n}\n.layout.justified {\n  -ms-flex-pack: justify;\n  -webkit-justify-content: space-between;\n  justify-content: space-between;\n}\n.self-start {\n  -ms-align-self: flex-start;\n  -webkit-align-self: flex-start;\n  align-self: flex-start;\n}\n.self-center {\n  -ms-align-self: center;\n  -webkit-align-self: center;\n  align-self: center;\n}\n.self-end {\n  -ms-align-self: flex-end;\n  -webkit-align-self: flex-end;\n  align-self: flex-end;\n}\n.self-stretch {\n  -ms-align-self: stretch;\n  -webkit-align-self: stretch;\n  align-self: stretch;\n}\n.block {\n  display: block;\n}\n[hidden] {\n  display: none !important;\n}\n.invisible {\n  visibility: hidden !important;\n}\n.relative {\n  position: relative;\n}\n.fit {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n}\nbody.fullbleed {\n  margin: 0;\n  height: 100vh;\n}\n.scroll {\n  -webkit-overflow-scrolling: touch;\n  overflow: auto;\n}\n.fixed-bottom,\n.fixed-left,\n.fixed-right,\n.fixed-top {\n  position: fixed;\n}\n.fixed-top {\n  top: 0;\n  left: 0;\n  right: 0;\n}\n.fixed-right {\n  top: 0;\n  right: 0;\n  bottom: 0;\n}\n.fixed-bottom {\n  right: 0;\n  bottom: 0;\n  left: 0;\n}\n.fixed-left {\n  top: 0;\n  bottom: 0;\n  left: 0;\n}\n\n/* FIXES */\n\nlabel.bp5-label .bp5-button-group {\n  margin-top: 5px;\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-card {\n  background-color:#2f343c;\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-navbar {\n  background-color: rgb(47, 52, 60);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *) {\n  background-color: rgb(56, 62, 71);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *):hover {\n  background-color: rgb(47, 52, 60);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button.bp5-minimal:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *) {\n  background: none;\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button.bp5-minimal:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *):hover {\n  background: rgba(143, 153, 168, 0.15);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-select select:not(.bp5-datepicker *) {\n  background-color: rgb(56, 62, 71);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-select select:not(.bp5-datepicker *):hover {\n  background-color: rgb(47, 52, 60);\n}\n\n.bp5-card .bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.04);\n}\n\n.bp5-dark .bp5-card .bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.13);\n}\n\n.monaco-editor .codicon.codicon-diff-insert {\n  width: 5px !important;\n  background-color: #2ea043;\n  left: unset !important;\n  right: 0 !important;\n}\n.monaco-editor .codicon.codicon-diff-insert:before {\n  content: \"\" !important;\n}\n.monaco-editor .codicon.codicon-diff-remove {\n  width: 5px !important;\n  background-color: #f85149;\n  left: unset !important;\n  right: 0 !important;\n}\n.monaco-editor .codicon.codicon-diff-remove:before {\n  content: \"\" !important;\n}\n\n/***********************************************************************\nhighlight.js\n***********************************************************************/\npre code.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 1em\n}\ncode.hljs {\n  padding: 3px 5px\n}\n/*\n\nAtom One Light by Daniel Gamage\nOriginal One Light Syntax theme from https://github.com/atom/one-light-syntax\n\nbase:    #fafafa\nmono-1:  #383a42\nmono-2:  #686b77\nmono-3:  #a0a1a7\nhue-1:   #0184bb\nhue-2:   #4078f2\nhue-3:   #a626a4\nhue-4:   #50a14f\nhue-5:   #e45649\nhue-5-2: #c91243\nhue-6:   #986801\nhue-6-2: #c18401\n\n*/\n.hljs {\n  color: #383a42;\n  background: #fafafa\n}\n.hljs-comment,\n.hljs-quote {\n  color: #a0a1a7;\n  font-style: italic\n}\n.hljs-doctag,\n.hljs-keyword,\n.hljs-formula {\n  color: #a626a4\n}\n.hljs-section,\n.hljs-name,\n.hljs-selector-tag,\n.hljs-deletion,\n.hljs-subst {\n  color: #e45649\n}\n.hljs-literal {\n  color: #0184bb\n}\n.hljs-string,\n.hljs-regexp,\n.hljs-addition,\n.hljs-attribute,\n.hljs-meta .hljs-string {\n  color: #50a14f\n}\n.hljs-attr,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-type,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-number {\n  color: #986801\n}\n.hljs-symbol,\n.hljs-bullet,\n.hljs-link,\n.hljs-meta,\n.hljs-selector-id,\n.hljs-title {\n  color: #4078f2\n}\n.hljs-built_in,\n.hljs-title.class_,\n.hljs-class .hljs-title {\n  color: #c18401\n}\n.hljs-emphasis {\n  font-style: italic\n}\n.hljs-strong {\n  font-weight: bold\n}\n.hljs-link {\n  text-decoration: underline\n}\n/*\n\nAtom One Dark by Daniel Gamage\nOriginal One Dark Syntax theme from https://github.com/atom/one-dark-syntax\n\nbase:    #282c34\nmono-1:  #abb2bf\nmono-2:  #818896\nmono-3:  #5c6370\nhue-1:   #56b6c2\nhue-2:   #61aeee\nhue-3:   #c678dd\nhue-4:   #98c379\nhue-5:   #e06c75\nhue-5-2: #be5046\nhue-6:   #d19a66\nhue-6-2: #e6c07b\n\n*/\n.bp5-dark .hljs {\n  color: #abb2bf;\n  background: #282c34\n}\n.bp5-dark .hljs-comment,\n.bp5-dark .hljs-quote {\n  color: #5c6370;\n  font-style: italic\n}\n.bp5-dark .hljs-doctag,\n.bp5-dark .hljs-keyword,\n.bp5-dark .hljs-formula {\n  color: #c678dd\n}\n.bp5-dark .hljs-section,\n.bp5-dark .hljs-name,\n.bp5-dark .hljs-selector-tag,\n.bp5-dark .hljs-deletion,\n.bp5-dark .hljs-subst {\n  color: #e06c75\n}\n.bp5-dark .hljs-literal {\n  color: #56b6c2\n}\n.bp5-dark .hljs-string,\n.bp5-dark .hljs-regexp,\n.bp5-dark .hljs-addition,\n.bp5-dark .hljs-attribute,\n.bp5-dark .hljs-meta .hljs-string {\n  color: #98c379\n}\n.bp5-dark .hljs-attr,\n.bp5-dark .hljs-variable,\n.bp5-dark .hljs-template-variable,\n.bp5-dark .hljs-type,\n.bp5-dark .hljs-selector-class,\n.bp5-dark .hljs-selector-attr,\n.bp5-dark .hljs-selector-pseudo,\n.bp5-dark .hljs-number {\n  color: #d19a66\n}\n.bp5-dark .hljs-symbol,\n.bp5-dark .hljs-bullet,\n.bp5-dark .hljs-link,\n.bp5-dark .hljs-meta,\n.bp5-dark .hljs-selector-id,\n.bp5-dark .hljs-title {\n  color: #61aeee\n}\n.bp5-dark .hljs-built_in,\n.bp5-dark .hljs-title.class_,\n.bp5-dark .hljs-class .hljs-title {\n  color: #e6c07b\n}\n.bp5-dark .hljs-emphasis {\n  font-style: italic\n}\n.bp5-dark .hljs-strong {\n  font-weight: bold\n}\n.bp5-dark .hljs-link {\n  text-decoration: underline\n}\n\n/***********************************************************************\nbp5 datepicker\n***********************************************************************/\n.bp5-datepicker input[type=number]::-webkit-inner-spin-button,\n.bp5-datepicker input[type=number]::-webkit-outer-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\n.bp5-datepicker{\n  background:#ffffff;\n  border-radius:3px;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  padding:5px;\n  position:relative;\n  -webkit-user-select:none;\n  -moz-user-select:none;\n  -ms-user-select:none;\n  user-select:none; }\n.bp5-datepicker .DayPicker{\n  display:inline-block;\n  min-width:210px;\n  position:relative;\n  vertical-align:top; }\n.bp5-datepicker .DayPicker:focus{\n  outline:none; }\n.bp5-datepicker .bp5-datepicker-day-wrapper{\n  border-radius:3px;\n  padding:7px; }\n.bp5-datepicker .DayPicker-Month{\n  border-collapse:collapse;\n  border-spacing:0;\n  display:inline-table;\n  margin:0 5px 5px;\n  -webkit-user-select:none;\n  -moz-user-select:none;\n  -ms-user-select:none;\n  user-select:none; }\n.bp5-datepicker .DayPicker-Month + .bp5-datepicker .DayPicker-Month{\n  margin-left:10px; }\n.bp5-datepicker .DayPicker-Caption{\n  display:table-caption; }\n.bp5-datepicker .DayPicker-Weekdays{\n  display:table-header-group; }\n.bp5-datepicker .DayPicker-WeekdaysRow{\n  display:table-row; }\n.bp5-datepicker .DayPicker-Weekday{\n  display:table-cell;\n  height:30px;\n  line-height:1;\n  text-align:center;\n  vertical-align:middle;\n  width:30px;\n  font-weight:600;\n  padding-top:5px; }\n.bp5-datepicker .DayPicker-Weekday abbr[title]{\n  text-decoration:none; }\n.bp5-datepicker .DayPicker-Body{\n  display:table-row-group; }\n.bp5-datepicker .DayPicker-Week{\n  display:table-row; }\n.bp5-datepicker .DayPicker-WeekNumber{\n  display:table-cell;\n  height:30px;\n  line-height:1;\n  text-align:center;\n  vertical-align:middle;\n  width:30px;\n  color:rgba(92, 112, 128, 0.6);\n  font-size:14px; }\n.bp5-datepicker .DayPicker-Day{\n  display:table-cell;\n  height:30px;\n  line-height:1;\n  text-align:center;\n  vertical-align:middle;\n  width:30px;\n  border-radius:3px;\n  cursor:pointer; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--outside{\n  color:rgba(92, 112, 128, 0.6); }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--isToday .bp5-datepicker-day-wrapper{\n  border:1px solid rgba(16, 22, 26, 0.15); }\n.bp5-datepicker .DayPicker-Day:hover, .bp5-datepicker .DayPicker-Day:focus{\n  background:#d8e1e8;\n  color:#182026; }\n.bp5-datepicker .DayPicker-Day:active{\n  background:#ced9e0; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--selected{\n  background-color:#137cbd;\n  border-radius:3px;\n  color:#ffffff; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--selected:hover{\n  background-color:#106ba3; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--disabled{\n  background:none;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-datepicker-navbar{\n  -webkit-box-align:center;\n  -ms-flex-align:center;\n  align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:30px;\n  left:0;\n  position:absolute;\n  right:0;\n  top:0; }\n.bp5-datepicker-navbar > .DayPicker-NavButton--prev{\n  margin-right:auto; }\n.bp5-datepicker-navbar > .DayPicker-NavButton--next{\n  margin-left:auto; }\n\n.bp5-datepicker-caption{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n  -ms-flex-direction:row;\n  flex-direction:row;\n  -webkit-box-pack:justify;\n  -ms-flex-pack:justify;\n  justify-content:space-between;\n  margin:0 25px 5px; }\n.bp5-datepicker-caption > *{\n  -webkit-box-flex:0;\n  -ms-flex-positive:0;\n  flex-grow:0;\n  -ms-flex-negative:0;\n  flex-shrink:0; }\n.bp5-datepicker-caption > :first-child{\n  -webkit-box-flex:1;\n  -ms-flex-positive:1;\n  flex-grow:1;\n  -ms-flex-negative:1;\n  flex-shrink:1; }\n.bp5-datepicker-caption select{\n  font-weight:600;\n  padding-left:5px;\n  padding-right:16px; }\n.bp5-datepicker-caption select + .bp5-icon{\n  right:2px; }\n.bp5-datepicker-caption + .bp5-divider{\n  margin:0; }\n\n.bp5-datepicker-month-select{\n  -ms-flex-negative:1;\n  flex-shrink:1; }\n\n.bp5-datepicker-year-select{\n  -ms-flex-negative:1;\n  flex-shrink:1;\n  min-width:60px; }\n\n.bp5-datepicker-caption-measure{\n  font-weight:600;\n  padding-left:5px; }\n\n.bp5-datepicker-footer{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:justify;\n  -ms-flex-pack:justify;\n  justify-content:space-between; }\n\n.bp5-dark .bp5-datepicker{\n  background:#30404d; }\n.bp5-dark .bp5-datepicker .DayPicker-WeekNumber{\n  color:rgba(167, 182, 194, 0.6); }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--outside{\n  color:rgba(167, 182, 194, 0.6); }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--isToday .bp5-datepicker-day-wrapper{\n  border:1px solid rgba(255, 255, 255, 0.15); }\n.bp5-dark .bp5-datepicker .DayPicker-Day:hover, .bp5-dark .bp5-datepicker .DayPicker-Day:focus{\n  background:#5c7080;\n  color:#ffffff; }\n.bp5-dark .bp5-datepicker .DayPicker-Day:active{\n  background:#738694; }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--selected{\n  background-color:#137cbd; }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--selected:hover{\n  background-color:#2b95d6; }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--disabled{\n  background:none;\n  color:rgba(167, 182, 194, 0.6); }\n.bp5-dark .bp5-datepicker .bp5-datepicker-footer{\n  border-top-color:rgba(16, 22, 26, 0.4); }\n\n.bp5-datepicker-timepicker-wrapper{\n  -webkit-box-align:center;\n  -ms-flex-align:center;\n  align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n  -ms-flex-direction:column;\n  flex-direction:column; }\n.bp5-daterangepicker{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  white-space:nowrap; }\n.bp5-daterangepicker .DayPicker-NavButton--interactionDisabled{\n  display:none; }\n.bp5-daterangepicker .bp5-daterangepicker-timepickers{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-pack:distribute;\n  justify-content:space-around; }\n.bp5-daterangepicker.bp5-daterangepicker-contiguous .DayPicker{\n  min-width:220px; }\n.bp5-daterangepicker.bp5-daterangepicker-single-month .DayPicker{\n  min-width:210px; }\n.bp5-daterangepicker .DayPicker-Day{ }\n.bp5-daterangepicker .DayPicker-Day--outside{\n  visibility:hidden; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range{\n  border-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range:not(.DayPicker-Day--selected):not(.DayPicker-Day--selected-range):not(.DayPicker-Day--selected-range-start):not(.DayPicker-Day--selected-range-end){\n  background-color:#ebf1f5; }\n.bp5-daterangepicker .DayPicker-Day--selected-range{\n  background-color:#ebf1f5;\n  border-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--selected-range:hover{\n  background-color:#ced9e0; }\n.bp5-daterangepicker .DayPicker-Day--selected-range-start:not(.DayPicker-Day--selected-range-end):not(.DayPicker-Day--hovered-range-end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--selected-range-end:not(.DayPicker-Day--selected-range-start):not(.DayPicker-Day--hovered-range-start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range-start:not(.DayPicker-Day--hovered-range-end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range-end:not(.DayPicker-Day--hovered-range-start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0; }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--hovered-range{ }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--hovered-range:not(.DayPicker-Day--selected):not(.DayPicker-Day--selected-range):not(.DayPicker-Day--selected-range-start):not(.DayPicker-Day--selected-range-end){\n  background-color:#394b59; }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--selected-range{\n  background-color:#394b59; }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--selected-range:hover{\n  background-color:#5c7080; }\n\n.bp5-menu.bp5-daterangepicker-shortcuts{\n  min-width:120px;\n  padding:0; }\n.bp5-timepicker{\n  white-space:nowrap; }\n.bp5-timepicker .bp5-timepicker-arrow-row{\n  padding:0 1px; }\n.bp5-timepicker .bp5-timepicker-arrow-button{\n  color:#5c7080;\n  display:inline-block;\n  padding:4px 0;\n  text-align:center;\n  width:33px; }\n.bp5-timepicker .bp5-timepicker-arrow-button:hover{\n  color:#182026; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-arrow-button{\n  color:#a7b6c2; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-arrow-button:hover{\n  color:#f5f8fa; }\n.bp5-timepicker .bp5-timepicker-arrow-button + .bp5-timepicker-arrow-button{\n  margin-left:11px; }\n.bp5-timepicker .bp5-timepicker-arrow-button:hover{\n  cursor:pointer; }\n.bp5-timepicker .bp5-timepicker-input-row{\n  background:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  display:inline-block;\n  height:30px;\n  line-height:28px;\n  padding:0 1px;\n  vertical-align:middle; }\n.bp5-timepicker .bp5-timepicker-divider-text{\n  color:#5c7080;\n  display:inline-block;\n  font-size:16px;\n  text-align:center;\n  width:11px; }\n.bp5-timepicker .bp5-timepicker-input{\n  background:transparent;\n  border:0;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0);\n  box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0);\n  color:#182026;\n  height:28px;\n  outline:0;\n  padding:0;\n  text-align:center;\n  -webkit-transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width:33px; }\n.bp5-timepicker .bp5-timepicker-input:focus{\n  -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3);\n  box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary{\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:focus{\n  -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n  box-shadow:inset 0 0 0 1px #137cbd; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary{\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:focus{\n  -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n  box-shadow:inset 0 0 0 1px #137cbd; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success{\n  -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success:focus{\n  -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n  box-shadow:inset 0 0 0 1px #0f9960; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-success.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success{\n  -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success:focus{\n  -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n  box-shadow:inset 0 0 0 1px #0f9960; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning{\n  -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:focus{\n  -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n  box-shadow:inset 0 0 0 1px #d9822b; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning{\n  -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:focus{\n  -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n  box-shadow:inset 0 0 0 1px #d9822b; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger{\n  -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:focus{\n  -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #db3737;\n  box-shadow:inset 0 0 0 1px #db3737; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger{\n  -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:focus{\n  -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #db3737;\n  box-shadow:inset 0 0 0 1px #db3737; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-ampm-select{\n  margin-left:5px; }\n.bp5-timepicker.bp5-disabled .bp5-timepicker-input-row{\n  background:rgba(206, 217, 224, 0.5);\n  -webkit-box-shadow:none;\n  box-shadow:none;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed;\n  resize:none; }\n.bp5-timepicker.bp5-disabled .bp5-timepicker-input,\n.bp5-timepicker.bp5-disabled .bp5-timepicker-divider-text{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n.bp5-timepicker.bp5-disabled .bp5-timepicker-arrow-button,\n.bp5-timepicker.bp5-disabled .bp5-timepicker-arrow-button:hover{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-dark .bp5-timepicker .bp5-timepicker-input-row{\n  background:rgba(16, 22, 26, 0.3);\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-dark .bp5-timepicker .bp5-timepicker-divider-text{\n  color:#a7b6c2; }\n\n.bp5-dark .bp5-timepicker .bp5-timepicker-input{\n  color:#f5f8fa; }\n\n.bp5-datepicker .bp5-timepicker{\n  margin-bottom:10px;\n  margin-top:5px; }\n.bp5-datepicker .bp5-timepicker:last-child{\n  margin-bottom:5px; }\n.bp5-datetimepicker{\n  background-color:#ffffff;\n  border-radius:3px;\n  padding:10px;\n  text-align:center; }\n.bp5-dark .bp5-datetimepicker{\n  background:#30404d; }\n.bp5-dark .bp5-datetimepicker .bp5-datepicker{\n  border-bottom:1px solid rgba(16, 22, 26, 0.4); }\n.bp5-datetimepicker .bp5-datepicker{\n  border-bottom:1px solid rgba(16, 22, 26, 0.15);\n  padding:0 0 10px; }\n.bp5-datetimepicker .bp5-timepicker{\n  margin-top:10px; }\n\n.bp5-dateinput-popover{\n  padding:0; }\n/*# sourceMappingURL=blueprint-datetime.css.map */\n\n.bp5-dark .almalinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20id%3D%27Layer_1%27%20x%3D%270%27%20y%3D%270%27%20version%3D%271.1%27%20viewBox%3D%270%200%2061.028%2059.731%27%3E%3Cstyle%20id%3D%27style2%27%20type%3D%27text%2Fcss%27%3E.st1%7Bfill%3A%2386da2f%7D.st2%7Bfill%3A%2324c2ff%7D.st3%7Bfill%3A%23ffcb12%7D.st4%7Bfill%3A%230069da%7D.st5%7Bfill%3A%23ff4649%7D%3C%2Fstyle%3E%3Cpath%20id%3D%27path22%27%20d%3D%27M56.114%2033.731c2.6-.2%204.7%201.5%204.9%204.1.2%202.7-1.7%204.9-4.3%205.1-2.5.2-4.7-1.7-4.9-4.2-.2-2.7%201.6-4.7%204.3-5z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path24%27%20d%3D%27M24.514%2055.031c0-2.6%202-4.6%204.4-4.6%202.4%200%204.7%202.2%204.7%204.7%200%202.4-2%204.5-4.3%204.6-2.9%200-4.8-1.8-4.8-4.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path26%27%20d%3D%27M31.614%2025.831c-.4.2-.6-.1-.7-.4-3.7-6.9-2.6-15.6%203.9-20.8%201.7-1.4%204.9-1.7%206.3-.3.6.5.7%201.1.8%201.8.2%201.5.5%203%201.5%204.2%201.1%201.3%202.5%201.8%204.1%201.7%201.4%200%202.8-.2%203.7%201.4.5.9.3%204.4-.5%205.1-.4.3-.7.1-1%200-2.3-.9-4.7-.9-7.1-.5-.8.1-1.2-.1-1.2-1-.1-1.5-.4-2.9-1.2-4.2-1.5-2.7-4.3-2.8-6.1-.3-1.5%202-1.9%204.4-2.3%206.8-.4%202.1-.3%204.3-.2%206.5%200%200-.1%200%200%200z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path28%27%20d%3D%27M34.114%2027.331c-.2-.3-.1-.6.2-.8%205.7-5.2%2014.2-6.2%2020.8-1.1%201.7%201.4%202.8%204.3%201.9%206-.4.7-.9%201-1.5%201.2-1.4.6-2.7%201.2-3.6%202.5-.9%201.3-1.1%202.8-.7%204.4.3%201.3.8%202.7-.5%203.9-.7.7-4.1%201.3-5%20.7-.4-.3-.3-.6-.2-1%20.3-2.5-.3-4.8-1.2-7-.3-.8-.2-1.2.6-1.4%201.4-.4%202.7-1.1%203.7-2.1%202.2-2.1%201.7-4.8-1.2-6-2.3-1-4.7-.8-7-.6-2.2.1-4.3.7-6.3%201.3z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path30%27%20d%3D%27M32.814%2029.931c.3-.3.5-.2.8%200%206.6%204%2010%2011.9%207%2019.6-.8%202-3.4%204-5.3%203.5-.8-.2-1.2-.6-1.6-1.1-.9-1.2-1.9-2.3-3.4-2.8-1.6-.5-3-.2-4.4.6-1.2.7-2.4%201.6-3.9.7-.9-.5-2.4-3.6-2.1-4.6.2-.4.6-.4%201-.4%202.5-.4%204.5-1.6%206.4-3.2.6-.5%201.1-.5%201.6.2.8%201.2%201.8%202.2%203.1%202.9%202.6%201.5%205.1.2%205.4-2.8.3-2.5-.6-4.7-1.4-6.9-.9-2-2-3.9-3.2-5.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path32%27%20d%3D%27M29.614%2030.531c-.4%202-1.3%203.9-2.5%205.6-3.6%205.4-8.8%207.6-15.2%207-2.3-.2-4.2-2.1-4.4-4-.1-.8.1-1.4.6-2%20.7-.9%201.3-1.7%201.6-2.8.6-2.2-.2-4-1.8-5.6-2.2-2.2-1.9-4.2.7-5.8.3-.2.7-.4%201.1-.6.6-.3%201.1-.3%201.3.4.9%202.3%202.7%204%204.7%205.4.7.6.7%201%20.1%201.7-1.2%201.3-1.9%202.9-2%204.7-.2%202.2%201.1%203.6%203.3%203.6%201.4%200%202.7-.5%203.9-1.1%203.1-1.6%205.5-3.9%207.8-6.3.3-.1.4-.3.8-.2z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path34%27%20d%3D%27M13.214%209.531c.2%200%20.7.1%201.2.2%203.7.7%206-.6%207.2-4.1.8-2.3%202.5-3%204.7-1.8.1%200%20.1.1.2.1%202.3%201.3%202.3%201.5.9%203.5-1.2%201.6-1.8%203.4-2.1%205.3-.2%201.1-.6%201.3-1.6.9-1.6-.6-3.3-.6-5%200-1.9.6-2.7%202.3-2.1%204.2.8%202.5%203%203.6%204.9%204.9%201.9%201.3%204.1%202%206.2%202.9.3.1.8.1.7.6-.1.3-.5.3-.9.3-4.5.2-8.8-.5-12.3-3.5-3.3-2.7-5.7-6-5.3-10.6.3-1.5%201.4-2.6%203.3-2.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path36%27%20d%3D%27M5.014%2037.631c-2.4.3-4.8-1.7-5-4.2-.2-2.4%201.8-4.8%204.1-5%202.6-.3%205%201.5%205.2%203.9.1%202.3-1.4%205.1-4.3%205.3z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path38%27%20d%3D%27M47.014%202.031c2.5-.2%204.9%201.8%205.1%204.3.2%202.4-1.8%204.7-4.2%204.9-2.6.2-4.9-1.7-5.1-4.2-.2-2.5%201.6-4.8%204.2-5z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path40%27%20d%3D%27M20.914%203.931c.3%202.6-1.5%204.8-4.2%205.2-2.3.3-4.7-1.6-5-3.8-.3-2.9%201.3-5%204-5.3%202.5-.3%204.9%201.6%205.2%203.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.almalinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20id%3D%27Layer_1%27%20x%3D%270%27%20y%3D%270%27%20version%3D%271.1%27%20viewBox%3D%270%200%2061.028%2059.731%27%3E%3Cstyle%20id%3D%27style2%27%20type%3D%27text%2Fcss%27%3E.st1%7Bfill%3A%2386da2f%7D.st2%7Bfill%3A%2324c2ff%7D.st3%7Bfill%3A%23ffcb12%7D.st4%7Bfill%3A%230069da%7D.st5%7Bfill%3A%23ff4649%7D%3C%2Fstyle%3E%3Cpath%20id%3D%27path22%27%20d%3D%27M56.114%2033.731c2.6-.2%204.7%201.5%204.9%204.1.2%202.7-1.7%204.9-4.3%205.1-2.5.2-4.7-1.7-4.9-4.2-.2-2.7%201.6-4.7%204.3-5z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path24%27%20d%3D%27M24.514%2055.031c0-2.6%202-4.6%204.4-4.6%202.4%200%204.7%202.2%204.7%204.7%200%202.4-2%204.5-4.3%204.6-2.9%200-4.8-1.8-4.8-4.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path26%27%20d%3D%27M31.614%2025.831c-.4.2-.6-.1-.7-.4-3.7-6.9-2.6-15.6%203.9-20.8%201.7-1.4%204.9-1.7%206.3-.3.6.5.7%201.1.8%201.8.2%201.5.5%203%201.5%204.2%201.1%201.3%202.5%201.8%204.1%201.7%201.4%200%202.8-.2%203.7%201.4.5.9.3%204.4-.5%205.1-.4.3-.7.1-1%200-2.3-.9-4.7-.9-7.1-.5-.8.1-1.2-.1-1.2-1-.1-1.5-.4-2.9-1.2-4.2-1.5-2.7-4.3-2.8-6.1-.3-1.5%202-1.9%204.4-2.3%206.8-.4%202.1-.3%204.3-.2%206.5%200%200-.1%200%200%200z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path28%27%20d%3D%27M34.114%2027.331c-.2-.3-.1-.6.2-.8%205.7-5.2%2014.2-6.2%2020.8-1.1%201.7%201.4%202.8%204.3%201.9%206-.4.7-.9%201-1.5%201.2-1.4.6-2.7%201.2-3.6%202.5-.9%201.3-1.1%202.8-.7%204.4.3%201.3.8%202.7-.5%203.9-.7.7-4.1%201.3-5%20.7-.4-.3-.3-.6-.2-1%20.3-2.5-.3-4.8-1.2-7-.3-.8-.2-1.2.6-1.4%201.4-.4%202.7-1.1%203.7-2.1%202.2-2.1%201.7-4.8-1.2-6-2.3-1-4.7-.8-7-.6-2.2.1-4.3.7-6.3%201.3z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path30%27%20d%3D%27M32.814%2029.931c.3-.3.5-.2.8%200%206.6%204%2010%2011.9%207%2019.6-.8%202-3.4%204-5.3%203.5-.8-.2-1.2-.6-1.6-1.1-.9-1.2-1.9-2.3-3.4-2.8-1.6-.5-3-.2-4.4.6-1.2.7-2.4%201.6-3.9.7-.9-.5-2.4-3.6-2.1-4.6.2-.4.6-.4%201-.4%202.5-.4%204.5-1.6%206.4-3.2.6-.5%201.1-.5%201.6.2.8%201.2%201.8%202.2%203.1%202.9%202.6%201.5%205.1.2%205.4-2.8.3-2.5-.6-4.7-1.4-6.9-.9-2-2-3.9-3.2-5.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path32%27%20d%3D%27M29.614%2030.531c-.4%202-1.3%203.9-2.5%205.6-3.6%205.4-8.8%207.6-15.2%207-2.3-.2-4.2-2.1-4.4-4-.1-.8.1-1.4.6-2%20.7-.9%201.3-1.7%201.6-2.8.6-2.2-.2-4-1.8-5.6-2.2-2.2-1.9-4.2.7-5.8.3-.2.7-.4%201.1-.6.6-.3%201.1-.3%201.3.4.9%202.3%202.7%204%204.7%205.4.7.6.7%201%20.1%201.7-1.2%201.3-1.9%202.9-2%204.7-.2%202.2%201.1%203.6%203.3%203.6%201.4%200%202.7-.5%203.9-1.1%203.1-1.6%205.5-3.9%207.8-6.3.3-.1.4-.3.8-.2z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path34%27%20d%3D%27M13.214%209.531c.2%200%20.7.1%201.2.2%203.7.7%206-.6%207.2-4.1.8-2.3%202.5-3%204.7-1.8.1%200%20.1.1.2.1%202.3%201.3%202.3%201.5.9%203.5-1.2%201.6-1.8%203.4-2.1%205.3-.2%201.1-.6%201.3-1.6.9-1.6-.6-3.3-.6-5%200-1.9.6-2.7%202.3-2.1%204.2.8%202.5%203%203.6%204.9%204.9%201.9%201.3%204.1%202%206.2%202.9.3.1.8.1.7.6-.1.3-.5.3-.9.3-4.5.2-8.8-.5-12.3-3.5-3.3-2.7-5.7-6-5.3-10.6.3-1.5%201.4-2.6%203.3-2.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path36%27%20d%3D%27M5.014%2037.631c-2.4.3-4.8-1.7-5-4.2-.2-2.4%201.8-4.8%204.1-5%202.6-.3%205%201.5%205.2%203.9.1%202.3-1.4%205.1-4.3%205.3z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path38%27%20d%3D%27M47.014%202.031c2.5-.2%204.9%201.8%205.1%204.3.2%202.4-1.8%204.7-4.2%204.9-2.6.2-4.9-1.7-5.1-4.2-.2-2.5%201.6-4.8%204.2-5z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path40%27%20d%3D%27M20.914%203.931c.3%202.6-1.5%204.8-4.2%205.2-2.3.3-4.7-1.6-5-3.8-.3-2.9%201.3-5%204-5.3%202.5-.3%204.9%201.6%205.2%203.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .alpinelinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20width%3D%27186.125%27%20height%3D%27161.25%27%3E%3Cpath%20d%3D%27M0%200v15.7L-11.3%204.4a33.27%2033.27%200%200%201%203.44-2.11c1.08-.567%202.09-1%203.03-1.34.941-.334%201.81-.562%202.62-.71C-1.406.093-.67.027%200%20.018m57.8%201.3c.02-.017.13-.11.333-.239a5.89%205.89%200%200%201%20.898-.462%208.098%208.098%200%200%201%201.48-.448c.596-.122%201.29-.202%202.1-.202.671%200%201.41.059%202.22.2.812.142%201.69.367%202.64.699a23.4%2023.4%200%200%201%203.07%201.34c1.09.572%202.26%201.28%203.5%202.14l-8.57%208.4-28.4%2028.5-12.5-12.5-16.8%2017.3-42.2-41.7c1.24-.86%202.4-1.56%203.49-2.14%201.09-.571%202.12-1.01%203.07-1.34a16.71%2016.71%200%200%201%202.64-.698%2012.85%2012.85%200%200%201%202.22-.201c.804%200%201.5.08%202.1.202a7.78%207.78%200%200%201%201.48.449c.396.164.693.331.897.461.204.13.314.223.334.24l19.1%2019.1%206.81%206.53%2018.9-18.9%206.59-6.78c.02-.017.13-.11.334-.239a5.87%205.87%200%200%201%20.897-.462c.396-.164.89-.327%201.48-.448.596-.122%201.3-.202%202.1-.202.67%200%201.41.059%202.22.2.811.142%201.69.367%202.64.699.952.333%201.98.773%203.07%201.34%201.09.572%202.26%201.28%203.5%202.14l-15.1%2014.8%202.82%202.82%2013.1-13.1%207.64-7.57m-.163%2079%2037.2-64.5-37.2-64.5h-74.5l-37.2%2064.5%2037.2%2064.5z%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%2067.45%20100.309%29%27%2F%3E%3Cpath%20d%3D%27m0%200-9.91%209.9.705.709L.775.739z%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%20111.2%2076.559%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.alpinelinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20width%3D%27186.125%27%20height%3D%27161.25%27%3E%3Cpath%20d%3D%27M0%200v15.7L-11.3%204.4a33.27%2033.27%200%200%201%203.44-2.11c1.08-.567%202.09-1%203.03-1.34.941-.334%201.81-.562%202.62-.71C-1.406.093-.67.027%200%20.018m57.8%201.3c.02-.017.13-.11.333-.239a5.89%205.89%200%200%201%20.898-.462%208.098%208.098%200%200%201%201.48-.448c.596-.122%201.29-.202%202.1-.202.671%200%201.41.059%202.22.2.812.142%201.69.367%202.64.699a23.4%2023.4%200%200%201%203.07%201.34c1.09.572%202.26%201.28%203.5%202.14l-8.57%208.4-28.4%2028.5-12.5-12.5-16.8%2017.3-42.2-41.7c1.24-.86%202.4-1.56%203.49-2.14%201.09-.571%202.12-1.01%203.07-1.34a16.71%2016.71%200%200%201%202.64-.698%2012.85%2012.85%200%200%201%202.22-.201c.804%200%201.5.08%202.1.202a7.78%207.78%200%200%201%201.48.449c.396.164.693.331.897.461.204.13.314.223.334.24l19.1%2019.1%206.81%206.53%2018.9-18.9%206.59-6.78c.02-.017.13-.11.334-.239a5.87%205.87%200%200%201%20.897-.462c.396-.164.89-.327%201.48-.448.596-.122%201.3-.202%202.1-.202.67%200%201.41.059%202.22.2.811.142%201.69.367%202.64.699.952.333%201.98.773%203.07%201.34%201.09.572%202.26%201.28%203.5%202.14l-15.1%2014.8%202.82%202.82%2013.1-13.1%207.64-7.57m-.163%2079%2037.2-64.5-37.2-64.5h-74.5l-37.2%2064.5%2037.2%2064.5z%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%2067.45%20100.309%29%27%2F%3E%3Cpath%20d%3D%27m0%200-9.91%209.9.705.709L.775.739z%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%20111.2%2076.559%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .archlinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%22256%22%20height%3D%22256%22%3E%3Cpath%20d%3D%22M128%200c-11.395%2027.937-18.313%2046.237-31%2073.344%207.779%208.245%2017.338%2017.812%2032.844%2028.656-16.67-6.86-28.034-13.729-36.531-20.875C77.076%20115.005%2051.622%20163.246%200%20256c40.563-23.418%2072.008-37.862%20101.313-43.375-1.254-5.404-1.924-11.278-1.876-17.375l.032-1.281c.644-25.994%2014.17-45.982%2030.187-44.625s28.488%2023.537%2027.844%2049.531c-.121%204.896-.69%209.6-1.656%2013.969C184.834%20218.517%20215.982%20232.897%20256%20256a14843%2014843%200%200%201-21.687-40.094c-10.597-8.212-21.644-18.897-44.188-30.469%2015.496%204.027%2026.602%208.683%2035.25%2013.875C156.98%2071.973%20151.455%2055.04%20128%200%22%20style%3D%22fill%3A%23fff%3Bfill-opacity%3A1%22/%3E%3Cpath%20d%3D%22M818.226%20548.553c-41.181-55.895-50.727-100.945-53.145-111.7%2021.968%2050.668%2021.818%2051.29%2053.145%20111.7%22%20style%3D%22fill%3A%23fff%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3Cpath%20d%3D%22M765.098%20436.435c-1.056%202.597-2.086%205.117-3.061%207.515-1.082%202.655-2.11%205.19-3.132%207.677a1118%201118%200%200%201-3.038%207.306%20752%20752%200%200%201-3.038%207.143c-1.028%202.382-2.062%204.789-3.155%207.236a619%20619%200%200%201-3.432%207.538%20686%20686%200%200%201-3.804%208.095c-.186.39-.413.834-.603%201.229%205.755%206.096%2012.841%2013.15%2024.284%2021.152-12.34-5.078-20.766-10.157-27.067-15.446-.327.667-.614%201.264-.951%201.948-.444.9-.976%201.923-1.438%202.853-.81%201.66-1.656%203.365-2.528%205.125-.335.667-.63%201.243-.974%201.925-5.508%2011.053-12.34%2024.283-21.13%2040.728%2024.096-13.576%2050.086-33.163%2097.297-16.305a1624%201624%200%200%201-6.587-12.64%201193%201193%200%200%201-5.706-11.156%20865%20865%200%200%201-4.917-9.927%20671%20671%200%200%201-4.267-8.906%20563%20563%200%200%201-3.758-8.165%20521%20521%200%200%201-3.363-7.584%20548%20548%200%200%201-3.084-7.306%20692%20692%200%200%201-2.946-7.236c-.348-.866-.695-1.769-1.044-2.644-2.663-6.253-5.398-12.732-8.558-20.155%22%20style%3D%22fill%3A%23fff%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3C/svg%3E\");\n}\n\n.archlinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%22256%22%20height%3D%22256%22%3E%3Cpath%20d%3D%22M128%200c-11.395%2027.937-18.313%2046.237-31%2073.344%207.779%208.245%2017.338%2017.812%2032.844%2028.656-16.67-6.86-28.034-13.729-36.531-20.875C77.076%20115.005%2051.622%20163.246%200%20256c40.563-23.418%2072.008-37.862%20101.313-43.375-1.254-5.404-1.924-11.278-1.876-17.375l.032-1.281c.644-25.994%2014.17-45.982%2030.187-44.625s28.488%2023.537%2027.844%2049.531c-.121%204.896-.69%209.6-1.656%2013.969C184.834%20218.517%20215.982%20232.897%20256%20256a14843%2014843%200%200%201-21.687-40.094c-10.597-8.212-21.644-18.897-44.188-30.469%2015.496%204.027%2026.602%208.683%2035.25%2013.875C156.98%2071.973%20151.455%2055.04%20128%200%22%20style%3D%22fill%3A%23000%3Bfill-opacity%3A1%22/%3E%3Cpath%20d%3D%22M818.226%20548.553c-41.181-55.895-50.727-100.945-53.145-111.7%2021.968%2050.668%2021.818%2051.29%2053.145%20111.7%22%20style%3D%22fill%3A%23000%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3Cpath%20d%3D%22M765.098%20436.435c-1.056%202.597-2.086%205.117-3.061%207.515-1.082%202.655-2.11%205.19-3.132%207.677a1118%201118%200%200%201-3.038%207.306%20752%20752%200%200%201-3.038%207.143c-1.028%202.382-2.062%204.789-3.155%207.236a619%20619%200%200%201-3.432%207.538%20686%20686%200%200%201-3.804%208.095c-.186.39-.413.834-.603%201.229%205.755%206.096%2012.841%2013.15%2024.284%2021.152-12.34-5.078-20.766-10.157-27.067-15.446-.327.667-.614%201.264-.951%201.948-.444.9-.976%201.923-1.438%202.853-.81%201.66-1.656%203.365-2.528%205.125-.335.667-.63%201.243-.974%201.925-5.508%2011.053-12.34%2024.283-21.13%2040.728%2024.096-13.576%2050.086-33.163%2097.297-16.305a1624%201624%200%200%201-6.587-12.64%201193%201193%200%200%201-5.706-11.156%20865%20865%200%200%201-4.917-9.927%20671%20671%200%200%201-4.267-8.906%20563%20563%200%200%201-3.758-8.165%20521%20521%200%200%201-3.363-7.584%20548%20548%200%200%201-3.084-7.306%20692%20692%200%200%201-2.946-7.236c-.348-.866-.695-1.769-1.044-2.644-2.663-6.253-5.398-12.732-8.558-20.155%22%20style%3D%22fill%3A%23000%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3C/svg%3E\");\n}\n\n.bp5-dark .fedora-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27512.041%27%20height%3D%27502.439%27%20viewBox%3D%270%200%20135.478%20132.937%27%3E%3Cpath%20d%3D%27M67.775%200C30.366%200%20.079%2029.732.012%2066.439H0v51.43h.012c.018%208.325%206.903%2015.068%2015.393%2015.068H67.78c37.415-.024%2067.697-29.752%2067.697-66.439C135.478%2029.792%20105.157.06%2067.715.06zm13.771%2027.338-.007.02c11.376%200%2022.116%208.543%2022.116%2020.328%200%201.094.007%202.187-.176%203.427-.315%203.135-3.236%205.388-6.417%204.946-3.181-.447-5.34-3.41-4.752-6.509.054-.354.074-.91.074-1.865%200-6.683-5.576-9.264-10.846-9.264-5.27%200-10.019%204.346-10.026%209.264.091%205.687%200%2011.33%200%2017.007l9.78-.071c7.637-.155%207.723%2011.123.089%2011.07l-9.867.072c-.024%204.574.036%203.747.012%206.05%200%200%20.083%205.595-.087%209.834-1.18%2012.454-11.99%2022.407-24.98%2022.407-13.772%200-25.114-11.038-25.114-24.577.413-13.923%2011.733-24.875%2025.993-24.75l7.954-.058v11.05l-7.954.073h-.042c-7.835.227-14.552%205.447-14.677%2013.678%200%207.498%206.176%2013.506%2013.843%2013.506%207.657%200%2013.784-5.464%2013.784-13.493l-.012-41.855c.005-.777.03-1.394.117-2.251%201.292-10.238%2010.627-18.04%2021.195-18.04z%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.fedora-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27512.041%27%20height%3D%27502.439%27%20viewBox%3D%270%200%20135.478%20132.937%27%3E%3Cpath%20d%3D%27M67.775%200C30.366%200%20.079%2029.732.012%2066.439H0v51.43h.012c.018%208.325%206.903%2015.068%2015.393%2015.068H67.78c37.415-.024%2067.697-29.752%2067.697-66.439C135.478%2029.792%20105.157.06%2067.715.06zm13.771%2027.338-.007.02c11.376%200%2022.116%208.543%2022.116%2020.328%200%201.094.007%202.187-.176%203.427-.315%203.135-3.236%205.388-6.417%204.946-3.181-.447-5.34-3.41-4.752-6.509.054-.354.074-.91.074-1.865%200-6.683-5.576-9.264-10.846-9.264-5.27%200-10.019%204.346-10.026%209.264.091%205.687%200%2011.33%200%2017.007l9.78-.071c7.637-.155%207.723%2011.123.089%2011.07l-9.867.072c-.024%204.574.036%203.747.012%206.05%200%200%20.083%205.595-.087%209.834-1.18%2012.454-11.99%2022.407-24.98%2022.407-13.772%200-25.114-11.038-25.114-24.577.413-13.923%2011.733-24.875%2025.993-24.75l7.954-.058v11.05l-7.954.073h-.042c-7.835.227-14.552%205.447-14.677%2013.678%200%207.498%206.176%2013.506%2013.843%2013.506%207.657%200%2013.784-5.464%2013.784-13.493l-.012-41.855c.005-.777.03-1.394.117-2.251%201.292-10.238%2010.627-18.04%2021.195-18.04z%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .freebsd-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272500%27%20height%3D%272461%27%20preserveAspectRatio%3D%27xMidYMid%27%20viewBox%3D%270%200%20256%20252%27%3E%3Cg%20fill%3D%27%23B5010F%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%3E%3Cpath%20d%3D%27M252.723%205.11c13.508%2013.5-23.939%2072.848-30.27%2079.182-6.33%206.321-22.409.505-35.91-13-13.508-13.5-19.327-29.583-12.996-35.914%206.327-6.333%2065.671-43.777%2079.176-30.269M63.305%2019.394c-20.622-11.7-49.966-24.716-59.3-15.38-9.458%209.454%204.034%2039.458%2015.858%2060.117a126.812%20126.812%200%200%201%2043.442-44.737%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20d%3D%27M232.123%2079.636c1.899%206.44%201.558%2011.76-1.522%2014.834-7.193%207.196-26.624-.464-44.14-17.134a89.383%2089.383%200%200%201-3.627-3.428c-6.334-6.336-11.262-13.08-14.414-19.291-6.135-11.006-7.67-20.726-3.033-25.364%202.527-2.524%206.57-3.212%2011.502-2.325%203.216-2.034%207.013-4.3%2011.176-6.621-16.929-8.83-36.176-13.817-56.593-13.817C63.753%206.49%208.854%2061.38%208.854%20129.105c0%2067.713%2054.9%20122.61%20122.618%20122.61%2067.72%200%20122.616-54.897%20122.616-122.61%200-21.87-5.74-42.377-15.767-60.156-2.167%203.955-4.274%207.578-6.198%2010.687%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\");\n}\n\n.freebsd-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272500%27%20height%3D%272461%27%20preserveAspectRatio%3D%27xMidYMid%27%20viewBox%3D%270%200%20256%20252%27%3E%3Cg%20fill%3D%27%23B5010F%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%3E%3Cpath%20d%3D%27M252.723%205.11c13.508%2013.5-23.939%2072.848-30.27%2079.182-6.33%206.321-22.409.505-35.91-13-13.508-13.5-19.327-29.583-12.996-35.914%206.327-6.333%2065.671-43.777%2079.176-30.269M63.305%2019.394c-20.622-11.7-49.966-24.716-59.3-15.38-9.458%209.454%204.034%2039.458%2015.858%2060.117a126.812%20126.812%200%200%201%2043.442-44.737%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20d%3D%27M232.123%2079.636c1.899%206.44%201.558%2011.76-1.522%2014.834-7.193%207.196-26.624-.464-44.14-17.134a89.383%2089.383%200%200%201-3.627-3.428c-6.334-6.336-11.262-13.08-14.414-19.291-6.135-11.006-7.67-20.726-3.033-25.364%202.527-2.524%206.57-3.212%2011.502-2.325%203.216-2.034%207.013-4.3%2011.176-6.621-16.929-8.83-36.176-13.817-56.593-13.817C63.753%206.49%208.854%2061.38%208.854%20129.105c0%2067.713%2054.9%20122.61%20122.618%20122.61%2067.72%200%20122.616-54.897%20122.616-122.61%200-21.87-5.74-42.377-15.767-60.156-2.167%203.955-4.274%207.578-6.198%2010.687%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .oraclelinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2732%27%20height%3D%2720%27%3E%3Cpath%20fill%3D%27none%27%20stroke%3D%27%23C74634%27%20stroke-width%3D%274%27%20d%3D%27M10%202a8%208%200%201%200%200%2016h12a8%208%200%201%200%200-16z%27%20style%3D%27stroke%3A%23fff%3Bstroke-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.oraclelinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2732%27%20height%3D%2720%27%3E%3Cpath%20fill%3D%27none%27%20stroke%3D%27%23C74634%27%20stroke-width%3D%274%27%20d%3D%27M10%202a8%208%200%201%200%200%2016h12a8%208%200%201%200%200-16z%27%20style%3D%27stroke%3A%23000%3Bstroke-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .rockylinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%274096%27%20height%3D%274096%27%20fill%3D%27none%27%20viewBox%3D%270%200%20192%20192%27%3E%3Cpath%20fill%3D%27%2310B981%27%20fill-rule%3D%27evenodd%27%20d%3D%27M186.658%20127.658C190.119%20117.746%20192%20107.093%20192%2096c0-53.02-42.981-96-96-96C42.98%200%200%2042.98%200%2096c0%2026.234%2010.523%2050.011%2027.578%2067.338l97.38-97.38L149%2090l37.658%2037.658Zm-17.536%2030.547-44.164-44.163-69.16%2069.16C68.027%20188.849%2081.645%20192%2096%20192c29.288%200%2055.514-13.116%2073.122-33.795Z%27%20clip-rule%3D%27evenodd%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.rockylinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%274096%27%20height%3D%274096%27%20fill%3D%27none%27%20viewBox%3D%270%200%20192%20192%27%3E%3Cpath%20fill%3D%27%2310B981%27%20fill-rule%3D%27evenodd%27%20d%3D%27M186.658%20127.658C190.119%20117.746%20192%20107.093%20192%2096c0-53.02-42.981-96-96-96C42.98%200%200%2042.98%200%2096c0%2026.234%2010.523%2050.011%2027.578%2067.338l97.38-97.38L149%2090l37.658%2037.658Zm-17.536%2030.547-44.164-44.163-69.16%2069.16C68.027%20188.849%2081.645%20192%2096%20192c29.288%200%2055.514-13.116%2073.122-33.795Z%27%20clip-rule%3D%27evenodd%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .ubuntu-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20id%3D%27Layer_1%27%20data-name%3D%27Layer%201%27%20version%3D%271.1%27%20viewBox%3D%270%200%20767.407%20738.314%27%3E%3Cdefs%20id%3D%27defs1%27%3E%3Cstyle%20id%3D%27style1%27%3E.cls-1%7Bfill%3A%23e9500e%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ccircle%20id%3D%27circle1%27%20cx%3D%27109.027%27%20cy%3D%27348.094%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Ccircle%20id%3D%27circle2%27%20cx%3D%27563.084%27%20cy%3D%27109.027%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path2%27%20d%3D%27M492.482%201336.666a265.525%20265.525%200%200%201-181.071-138.098%20156.948%20156.948%200%200%201-93.22%2011.458A354.935%20354.935%200%200%200%20473.73%201424.19a359.208%20359.208%200%200%200%2077.42%207.967%20156.003%20156.003%200%200%201-31.923-91.154%20271.194%20271.194%200%200%201-26.744-4.337Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Ccircle%20id%3D%27circle3%27%20cx%3D%27538.52%27%20cy%3D%27629.287%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path3%27%20d%3D%27M827.798%201297.22a356.7%20356.7%200%200%200%2017.875-418.518%20157.144%20157.144%200%200%201-61.31%2071.803%20267.293%20267.293%200%200%201-8.737%20265.488%20156.347%20156.347%200%200%201%2052.172%2081.228Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Cpath%20id%3D%27path4%27%20d%3D%27M238.176%20899.72q4.15-.229%208.306-.226a157.346%20157.346%200%200%201%2082.682%2023.545%20265.907%20265.907%200%200%201%20214.108-114.51%20158.088%20158.088%200%200%201%2033.076-88.42c-137.103-10.857-270.324%2059.892-338.172%20179.61Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.ubuntu-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20id%3D%27Layer_1%27%20data-name%3D%27Layer%201%27%20version%3D%271.1%27%20viewBox%3D%270%200%20767.407%20738.314%27%3E%3Cdefs%20id%3D%27defs1%27%3E%3Cstyle%20id%3D%27style1%27%3E.cls-1%7Bfill%3A%23e9500e%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ccircle%20id%3D%27circle1%27%20cx%3D%27109.027%27%20cy%3D%27348.094%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Ccircle%20id%3D%27circle2%27%20cx%3D%27563.084%27%20cy%3D%27109.027%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path2%27%20d%3D%27M492.482%201336.666a265.525%20265.525%200%200%201-181.071-138.098%20156.948%20156.948%200%200%201-93.22%2011.458A354.935%20354.935%200%200%200%20473.73%201424.19a359.208%20359.208%200%200%200%2077.42%207.967%20156.003%20156.003%200%200%201-31.923-91.154%20271.194%20271.194%200%200%201-26.744-4.337Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Ccircle%20id%3D%27circle3%27%20cx%3D%27538.52%27%20cy%3D%27629.287%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path3%27%20d%3D%27M827.798%201297.22a356.7%20356.7%200%200%200%2017.875-418.518%20157.144%20157.144%200%200%201-61.31%2071.803%20267.293%20267.293%200%200%201-8.737%20265.488%20156.347%20156.347%200%200%201%2052.172%2081.228Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Cpath%20id%3D%27path4%27%20d%3D%27M238.176%20899.72q4.15-.229%208.306-.226a157.346%20157.346%200%200%201%2082.682%2023.545%20265.907%20265.907%200%200%201%20214.108-114.51%20158.088%20158.088%200%200%201%2033.076-88.42c-137.103-10.857-270.324%2059.892-338.172%20179.61Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n"
  },
  {
    "path": "www/dist-dev/static/normalize.css",
    "content": "/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n   ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n  line-height: 1.15; /* 1 */\n  -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n   ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n  margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n  display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n/* Grouping content\n   ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n  box-sizing: content-box; /* 1 */\n  height: 0; /* 1 */\n  overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n  font-family: monospace, monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n  background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n  border-bottom: none; /* 1 */\n  text-decoration: underline; /* 2 */\n  text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n  font-family: monospace, monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n  font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\n/* Embedded content\n   ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n  border-style: none;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: inherit; /* 1 */\n  font-size: 100%; /* 1 */\n  line-height: 1.15; /* 1 */\n  margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n  overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n  text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n  -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n  border-style: none;\n  padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n  outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n  padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n *    `fieldset` elements in all browsers.\n */\n\nlegend {\n  box-sizing: border-box; /* 1 */\n  color: inherit; /* 2 */\n  display: table; /* 1 */\n  max-width: 100%; /* 1 */\n  padding: 0; /* 3 */\n  white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n  vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n  overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n  box-sizing: border-box; /* 1 */\n  padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n  -webkit-appearance: textfield; /* 1 */\n  outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n  -webkit-appearance: button; /* 1 */\n  font: inherit; /* 2 */\n}\n\n/* Interactive\n   ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n  display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n  display: list-item;\n}\n\n/* Misc\n   ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n  display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n  display: none;\n}\n"
  },
  {
    "path": "www/dist-dev/uindex.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud Dev</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/normalize.css\"/>\n    <link id=\"blueprint3-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint3.css\" disabled/>\n    <link id=\"blueprint5-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint5.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/global.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-datetime2.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-icons.css\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"toaster\"></div>\n    <script>\n      window.user = true;\n    </script>\n    <script src=\"static/app.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/index.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud Dev</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/normalize.css\"/>\n    <link id=\"blueprint3-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint3.css\" disabled/>\n    <link id=\"blueprint5-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint5.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/global.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-datetime2.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-icons.css\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"toaster\"></div>\n    <script src=\"static/app.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/index_dist.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/normalize.css\"/>\n    <link id=\"blueprint3-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint3.css\" disabled/>\n    <link id=\"blueprint5-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint5.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/global.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-datetime2.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-icons.css\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"toaster\"></div>\n    <script src=\"static/app.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/login.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\"\n      content=\"user-scalable=no, width=device-width, initial-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"apple-touch-icon\" href=\"/logo.png\">\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <style>\n      progress,sub,sup{vertical-align:baseline}button,hr,input{overflow:visible}[type=checkbox],[type=radio],legend{box-sizing:border-box;padding:0}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}button,input,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{color:inherit;display:table;max-width:100%;white-space:normal}textarea{overflow:auto}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}\n\n      @charset \"UTF-8\";h1,h2,h3{line-height:.8em}h4,h5,h6{line-height:.9em}.pt-ui-text,body{letter-spacing:0;text-transform:none}a,a:hover{color:#106ba3}.pt-breadcrumbs>li::after,span.pt-icon-large,span.pt-icon-standard,span.pt-icon::before{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-button.pt-loading>:not(.pt-button-spinner),.pt-button.pt-loading[class*=pt-icon-]::before{visibility:hidden}.pt-monospace-text,.pt-ui-text,body,code,pre{text-transform:none}.pt-select::after,.pt-skeleton,.pt-tab-indicator-wrapper,.pt-transition-container.pt-popover-leave .pt-popover-content{pointer-events:none}html{-moz-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-moz-box-sizing:inherit;box-sizing:inherit}@font-face{font-family:Icons16;font-style:normal;font-weight:400;src:url(icons-16.eot?#iefix) format(\"embedded-opentype\"),url(icons-16.woff) format(\"woff\"),url(icons-16.ttf) format(\"truetype\")}@font-face{font-family:Icons20;font-style:normal;font-weight:400;src:url(icons-20.eot?#iefix) format(\"embedded-opentype\"),url(icons-20.woff) format(\"woff\"),url(icons-20.ttf) format(\"truetype\")}hr{margin:20px 0;border:none;border-bottom:1px solid rgba(16,22,26,.15)}code,pre{border-radius:3px}.pt-dark hr{border-color:rgba(255,255,255,.15)}::-moz-selection{background:rgba(125,188,255,.6)}::selection{background:rgba(125,188,255,.6)}body{line-height:1.28581;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,\"Open Sans\",\"Helvetica Neue\",Icons16,sans-serif;font-size:14px;font-weight:400;color:#182026}h1,h2,h3,h4,h5,h6{color:#182026;font-weight:600;margin:0 0 10px;padding:0}small{font-size:12px}.pt-monospace-text{font-family:monospace;font-size:smaller}h1{font-size:40px}h2{font-size:27px}h3{font-size:24px}h4{font-size:20px}h5{font-size:17px}h6{font-size:15px}.pt-ui-text{line-height:1.28581;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,\"Open Sans\",\"Helvetica Neue\",Icons16,sans-serif;font-size:14px;font-weight:400}.pt-ui-text-large{line-height:1.25;font-size:16px}.pt-running-text{line-height:1.5;font-size:16px}.pt-running-text h1,.pt-running-text h2,.pt-running-text h3,.pt-running-text h4,.pt-running-text h5,.pt-running-text h6{margin-top:40px;margin-bottom:20px}blockquote,p{margin:0 0 10px}ol,pre,ul{margin:10px 0}.pt-running-text-small{line-height:1.5;font-size:14px}p{padding:0}a{text-decoration:none}a:hover{cursor:pointer;text-decoration:underline}.pt-dark a code,a .pt-icon,a .pt-icon-large,a .pt-icon-standard,a code{color:inherit}.pt-dark a,.pt-dark a:hover{color:#48aff0}.pt-dark a .pt-icon,.pt-dark a .pt-icon-large,.pt-dark a .pt-icon-standard,.pt-dark a:hover .pt-icon,.pt-dark a:hover .pt-icon-large,.pt-dark a:hover .pt-icon-standard{color:inherit}code,pre{font-family:monospace;font-size:smaller}code{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2);background:rgba(255,255,255,.7);padding:2px 5px;color:#5c7080}.pt-dark code{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#bfccd6}pre{display:block;box-shadow:inset 0 0 0 1px rgba(16,22,26,.15);background:rgba(255,255,255,.7);padding:13px 15px 12px;line-height:1.4;color:#182026;font-size:13px;word-break:break-all;word-wrap:break-word}.pt-dialog-header h5,.pt-menu-header>h6,.pt-menu-item,.pt-tab,.pt-text-overflow-ellipsis,.pt-tree-node-label{text-overflow:ellipsis;word-wrap:normal}pre>code{border-radius:0;box-shadow:none;background:0 0;padding:0;white-space:pre-wrap;color:inherit;font-size:inherit}.pt-dialog-header h5,.pt-editable-text,.pt-menu-header>h6,.pt-menu-item,.pt-popover-target .pt-tooltip,.pt-tab,.pt-tree-node-label{white-space:nowrap}.pt-dark pre{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#f5f8fa}.pt-dark pre>code{box-shadow:none;background:0 0}blockquote{line-height:1.5;font-size:16px;border-left:solid 4px rgba(167,182,194,.5);padding:0 20px}span.pt-icon,span.pt-icon-large,span.pt-icon-standard{line-height:1;font-weight:400;font-style:normal}blockquote p:last-child{margin-bottom:0}.pt-dark blockquote{border-color:rgba(115,134,148,.5)}ol,ul{padding-left:40px}.pt-list li:not(:last-child),.pt-running-text ol li:not(:last-child),.pt-running-text ul li:not(:last-child){padding-bottom:5px}.pt-list li :last-child,.pt-running-text ol li :last-child,.pt-running-text ul li :last-child{margin-bottom:0}.pt-list ol,.pt-list ul,.pt-running-text ol ol,.pt-running-text ol ul,.pt-running-text ul ol,.pt-running-text ul ul{margin-top:5px}.pt-list-unstyled{margin:0;padding:0;list-style:none}.pt-list-unstyled li{padding:0}.pt-text-muted{color:#5c7080}.pt-dark .pt-text-muted{color:#bfccd6}.pt-text-overflow-ellipsis{overflow:hidden;white-space:nowrap}.pt-rtl{text-align:right}.pt-dark,.pt-dark h1,.pt-dark h2,.pt-dark h3,.pt-dark h4,.pt-dark h5,.pt-dark h6{color:#f5f8fa}.pt-icon,.pt-icon-large,.pt-icon-standard{display:inline-block}.pt-icon-large.pt-intent-primary,.pt-icon-standard.pt-intent-primary,.pt-icon.pt-intent-primary{color:#137cbd}.pt-icon-large.pt-intent-success,.pt-icon-standard.pt-intent-success,.pt-icon.pt-intent-success{color:#0f9960}.pt-icon-large.pt-intent-warning,.pt-icon-standard.pt-intent-warning,.pt-icon.pt-intent-warning{color:#d9822b}.pt-icon-large.pt-intent-danger,.pt-icon-standard.pt-intent-danger,.pt-icon.pt-intent-danger{color:#db3737}span.pt-icon-standard{font-family:Icons16,sans-serif;font-size:16px}span.pt-icon-large{font-family:Icons20,sans-serif;font-size:20px}span.pt-icon{font-family:Icons20;font-size:inherit}.pt-icon-blank::before{content:\"\"}.pt-icon-style::before{content:\"\"}.pt-icon-align-left::before{content:\"\"}.pt-icon-align-center::before{content:\"\"}.pt-icon-align-right::before{content:\"\"}.pt-icon-align-justify::before{content:\"\"}.pt-icon-bold::before{content:\"\"}.pt-icon-italic::before{content:\"\"}.pt-icon-underline::before{content:\"⎁\"}.pt-icon-search-around::before{content:\"\"}.pt-icon-graph-remove::before{content:\"\"}.pt-icon-group-objects::before{content:\"\"}.pt-icon-merge-links::before{content:\"\"}.pt-icon-layout::before{content:\"\"}.pt-icon-layout-auto::before{content:\"\"}.pt-icon-layout-circle::before{content:\"\"}.pt-icon-layout-hierarchy::before{content:\"\"}.pt-icon-layout-grid::before{content:\"\"}.pt-icon-layout-group-by::before{content:\"\"}.pt-icon-layout-skew-grid::before{content:\"\"}.pt-icon-geosearch::before{content:\"\"}.pt-icon-heatmap::before{content:\"\"}.pt-icon-drive-time::before{content:\"\"}.pt-icon-select::before{content:\"\"}.pt-icon-predictive-analysis::before{content:\"\"}.pt-icon-layers::before{content:\"\"}.pt-icon-locate::before{content:\"\"}.pt-icon-bookmark::before{content:\"\"}.pt-icon-citation::before{content:\"\"}.pt-icon-tag::before{content:\"\"}.pt-icon-clipboard::before{content:\"\"}.pt-icon-selection::before{content:\"⦿\"}.pt-icon-timeline-events::before{content:\"\"}.pt-icon-timeline-line-chart::before{content:\"\"}.pt-icon-timeline-bar-chart::before{content:\"\"}.pt-icon-applications::before{content:\"\"}.pt-icon-projects::before{content:\"\"}.pt-icon-changes::before{content:\"\"}.pt-icon-notifications::before{content:\"\"}.pt-icon-lock::before{content:\"\"}.pt-icon-unlock::before{content:\"\"}.pt-icon-user::before{content:\"\"}.pt-icon-search-template::before{content:\"\"}.pt-icon-inbox::before{content:\"\"}.pt-icon-more::before{content:\"\"}.pt-icon-help::before{content:\"?\"}.pt-icon-calendar::before{content:\"\"}.pt-icon-media::before{content:\"\"}.pt-icon-link::before{content:\"\"}.pt-icon-share::before{content:\"\"}.pt-icon-download::before{content:\"\"}.pt-icon-document::before{content:\"\"}.pt-icon-properties::before{content:\"\"}.pt-icon-import::before{content:\"\"}.pt-icon-export::before{content:\"\"}.pt-icon-minimize::before{content:\"\"}.pt-icon-maximize::before{content:\"\"}.pt-icon-tick::before{content:\"✓\"}.pt-icon-cross::before{content:\"✗\"}.pt-icon-plus::before{content:\"+\"}.pt-icon-minus::before{content:\"−\"}.pt-icon-arrow-left::before{content:\"←\"}.pt-icon-arrow-right::before{content:\"→\"}.pt-icon-exchange::before{content:\"\"}.pt-icon-comparison::before{content:\"\"}.pt-icon-list::before{content:\"☰\"}.pt-icon-filter::before{content:\"\"}.pt-icon-confirm::before{content:\"\"}.pt-icon-fork::before{content:\"\"}.pt-icon-trash::before{content:\"\"}.pt-icon-person::before{content:\"\"}.pt-icon-people::before{content:\"\"}.pt-icon-add::before{content:\"\"}.pt-icon-remove::before{content:\"\"}.pt-icon-geolocation::before{content:\"\"}.pt-icon-zoom-in::before{content:\"\"}.pt-icon-zoom-out::before{content:\"\"}.pt-icon-refresh::before{content:\"\"}.pt-icon-delete::before{content:\"\"}.pt-icon-cog::before{content:\"\"}.pt-icon-flag::before{content:\"⚑\"}.pt-icon-pin::before{content:\"\"}.pt-icon-warning-sign::before{content:\"\"}.pt-icon-error::before{content:\"\"}.pt-icon-info-sign::before{content:\"ℹ\"}.pt-icon-credit-card::before{content:\"\"}.pt-icon-edit::before{content:\"✎\"}.pt-icon-history::before{content:\"\"}.pt-icon-search::before{content:\"\"}.pt-icon-log-out::before{content:\"\"}.pt-icon-star::before{content:\"★\"}.pt-icon-star-empty::before{content:\"☆\"}.pt-icon-sort-alphabetical::before{content:\"\"}.pt-icon-sort-numerical::before{content:\"\"}.pt-icon-sort::before{content:\"\"}.pt-icon-folder-open::before{content:\"\"}.pt-icon-folder-close::before{content:\"\"}.pt-icon-folder-shared::before{content:\"\"}.pt-icon-caret-up::before{content:\"⌃\"}.pt-icon-caret-right::before{content:\"〉\"}.pt-icon-caret-down::before{content:\"⌄\"}.pt-icon-caret-left::before{content:\"〈\"}.pt-icon-menu-open::before{content:\"\"}.pt-icon-menu-closed::before{content:\"\"}.pt-icon-feed::before{content:\"\"}.pt-icon-two-columns::before{content:\"\"}.pt-icon-one-column::before{content:\"\"}.pt-icon-dot::before{content:\"•\"}.pt-icon-property::before{content:\"\"}.pt-icon-time::before{content:\"⏲\"}.pt-icon-disable::before{content:\"\"}.pt-icon-unpin::before{content:\"\"}.pt-icon-flows::before{content:\"\"}.pt-icon-new-text-box::before{content:\"\"}.pt-icon-new-link::before{content:\"\"}.pt-icon-new-object::before{content:\"\"}.pt-icon-path-search::before{content:\"\"}.pt-icon-automatic-updates::before{content:\"\"}.pt-icon-page-layout::before{content:\"\"}.pt-icon-code::before{content:\"\"}.pt-icon-map::before{content:\"\"}.pt-icon-search-text::before{content:\"\"}.pt-icon-envelope::before{content:\"✉\"}.pt-icon-paperclip::before{content:\"\"}.pt-icon-label::before{content:\"\"}.pt-icon-globe::before{content:\"\"}.pt-icon-home::before{content:\"⌂\"}.pt-icon-th::before{content:\"\"}.pt-icon-th-list::before{content:\"\"}.pt-icon-th-derived::before{content:\"\"}.pt-icon-circle::before{content:\"\"}.pt-icon-draw::before{content:\"\"}.pt-icon-insert::before{content:\"\"}.pt-icon-helper-management::before{content:\"\"}.pt-icon-send-to::before{content:\"\"}.pt-icon-eye-open::before{content:\"\"}.pt-icon-folder-shared-open::before{content:\"\"}.pt-icon-social-media::before{content:\"\"}.pt-icon-arrow-up::before{content:\"↑\"}.pt-icon-arrow-down::before{content:\"↓\"}.pt-icon-arrows-horizontal::before{content:\"↔\"}.pt-icon-arrows-vertical::before{content:\"↕\"}.pt-icon-resolve::before{content:\"\"}.pt-icon-graph::before{content:\"\"}.pt-icon-briefcase::before{content:\"\"}.pt-icon-dollar::before{content:\"$\"}.pt-icon-ninja::before{content:\"\"}.pt-icon-delta::before{content:\"Δ\"}.pt-icon-barcode::before{content:\"\"}.pt-icon-torch::before{content:\"\"}.pt-icon-widget::before{content:\"\"}.pt-icon-unresolve::before{content:\"\"}.pt-icon-offline::before{content:\"\"}.pt-icon-zoom-to-fit::before{content:\"\"}.pt-icon-add-to-artifact::before{content:\"\"}.pt-icon-map-marker::before{content:\"\"}.pt-icon-chart::before{content:\"\"}.pt-icon-control::before{content:\"\"}.pt-icon-multi-select::before{content:\"\"}.pt-icon-direction-left::before{content:\"\"}.pt-icon-direction-right::before{content:\"\"}.pt-icon-database::before{content:\"\"}.pt-icon-pie-chart::before{content:\"\"}.pt-icon-full-circle::before{content:\"\"}.pt-icon-square::before{content:\"\"}.pt-icon-print::before{content:\"⎙\"}.pt-icon-presentation::before{content:\"\"}.pt-icon-ungroup-objects::before{content:\"\"}.pt-icon-chat::before{content:\"\"}.pt-icon-comment::before{content:\"\"}.pt-icon-circle-arrow-right::before{content:\"\"}.pt-icon-circle-arrow-left::before{content:\"\"}.pt-icon-circle-arrow-up::before{content:\"\"}.pt-icon-circle-arrow-down::before{content:\"\"}.pt-icon-upload::before{content:\"\"}.pt-icon-asterisk::before{content:\"*\"}.pt-icon-cloud::before{content:\"☁\"}.pt-icon-cloud-download::before{content:\"\"}.pt-icon-cloud-upload::before{content:\"\"}.pt-icon-repeat::before{content:\"\"}.pt-icon-move::before{content:\"\"}.pt-icon-chevron-left::before{content:\"\"}.pt-icon-chevron-right::before{content:\"\"}.pt-icon-chevron-up::before{content:\"\"}.pt-icon-chevron-down::before{content:\"\"}.pt-icon-random::before{content:\"\"}.pt-icon-fullscreen::before{content:\"\"}.pt-icon-log-in::before{content:\"\"}.pt-icon-heart::before{content:\"♥\"}.pt-icon-office::before{content:\"\"}.pt-icon-duplicate::before{content:\"\"}.pt-icon-ban-circle::before{content:\"\"}.pt-icon-camera::before{content:\"\"}.pt-icon-mobile-video::before{content:\"\"}.pt-icon-video::before{content:\"\"}.pt-icon-film::before{content:\"\"}.pt-icon-settings::before{content:\"\"}.pt-icon-volume-off::before{content:\"\"}.pt-icon-volume-down::before{content:\"\"}.pt-icon-volume-up::before{content:\"\"}.pt-icon-music::before{content:\"\"}.pt-icon-step-backward::before{content:\"\"}.pt-icon-fast-backward::before{content:\"\"}.pt-icon-pause::before{content:\"\"}.pt-icon-stop::before{content:\"\"}.pt-icon-play::before{content:\"\"}.pt-icon-fast-forward::before{content:\"\"}.pt-icon-step-forward::before{content:\"\"}.pt-icon-eject::before{content:\"⏏\"}.pt-icon-record::before{content:\"\"}.pt-icon-desktop::before{content:\"\"}.pt-icon-phone::before{content:\"☎\"}.pt-icon-lightbulb::before{content:\"\"}.pt-icon-glass::before{content:\"\"}.pt-icon-tint::before{content:\"\"}.pt-icon-flash::before{content:\"\"}.pt-icon-font::before{content:\"\"}.pt-icon-header::before{content:\"\"}.pt-icon-saved::before{content:\"\"}.pt-icon-floppy-disk::before{content:\"\"}.pt-icon-book::before{content:\"\"}.pt-icon-hand-right::before{content:\"\"}.pt-icon-hand-up::before{content:\"\"}.pt-icon-hand-down::before{content:\"\"}.pt-icon-hand-left::before{content:\"\"}.pt-icon-thumbs-up::before{content:\"\"}.pt-icon-thumbs-down::before{content:\"\"}.pt-icon-box::before{content:\"\"}.pt-icon-compressed::before{content:\"\"}.pt-icon-shopping-cart::before{content:\"\"}.pt-icon-shop::before{content:\"\"}.pt-icon-layout-linear::before{content:\"\"}.pt-icon-undo::before{content:\"⎌\"}.pt-icon-redo::before{content:\"\"}.pt-icon-code-block::before{content:\"\"}.pt-icon-double-caret-vertical::before{content:\"\"}.pt-icon-double-caret-horizontal::before{content:\"\"}.pt-icon-sort-alphabetical-desc::before{content:\"\"}.pt-icon-sort-numerical-desc::before{content:\"\"}.pt-icon-take-action::before{content:\"\"}.pt-icon-contrast::before{content:\"\"}.pt-icon-eye-off::before{content:\"\"}.pt-icon-timeline-area-chart::before{content:\"\"}.pt-icon-doughnut-chart::before{content:\"\"}.pt-icon-layer::before{content:\"\"}.pt-icon-grid::before{content:\"\"}.pt-icon-polygon-filter::before{content:\"\"}.pt-icon-add-to-folder::before{content:\"\"}.pt-icon-layout-balloon::before{content:\"\"}.pt-icon-layout-sorted-clusters::before{content:\"\"}.pt-icon-sort-asc::before{content:\"\"}.pt-icon-sort-desc::before{content:\"\"}.pt-icon-small-cross::before{content:\"\"}.pt-icon-small-tick::before{content:\"\"}.pt-icon-power::before{content:\"\"}.pt-icon-column-layout::before{content:\"\"}.pt-icon-arrow-top-left::before{content:\"↖\"}.pt-icon-arrow-top-right::before{content:\"↗\"}.pt-icon-arrow-bottom-right::before{content:\"↘\"}.pt-icon-arrow-bottom-left::before{content:\"↙\"}.pt-icon-mugshot::before{content:\"\"}.pt-icon-headset::before{content:\"\"}.pt-icon-text-highlight::before{content:\"\"}.pt-icon-hand::before{content:\"\"}.pt-icon-chevron-backward::before{content:\"\"}.pt-icon-chevron-forward::before{content:\"\"}.pt-icon-rotate-document::before{content:\"\"}.pt-icon-rotate-page::before{content:\"\"}.pt-icon-badge::before{content:\"\"}.pt-icon-grid-view::before{content:\"\"}.pt-icon-function::before{content:\"\"}.pt-icon-waterfall-chart::before{content:\"\"}.pt-icon-stacked-chart::before{content:\"\"}.pt-icon-pulse::before{content:\"\"}.pt-icon-new-person::before{content:\"\"}.pt-icon-exclude-row::before{content:\"\"}.pt-icon-pivot-table::before{content:\"\"}.pt-icon-segmented-control::before{content:\"\"}.pt-icon-highlight::before{content:\"\"}.pt-icon-filter-list::before{content:\"\"}.pt-icon-cut::before{content:\"\"}.pt-icon-annotation::before{content:\"\"}.pt-icon-pivot::before{content:\"\"}.pt-icon-ring::before{content:\"\"}.pt-icon-heat-grid::before{content:\"\"}.pt-icon-gantt-chart::before{content:\"\"}.pt-icon-variable::before{content:\"\"}.pt-icon-manual::before{content:\"\"}.pt-icon-add-row-top::before{content:\"\"}.pt-icon-add-row-bottom::before{content:\"\"}.pt-icon-add-column-left::before{content:\"\"}.pt-icon-add-column-right::before{content:\"\"}.pt-icon-remove-row-top::before{content:\"\"}.pt-icon-remove-row-bottom::before{content:\"\"}.pt-icon-remove-column-left::before{content:\"\"}.pt-icon-remove-column-right::before{content:\"\"}.pt-icon-double-chevron-left::before{content:\"\"}.pt-icon-double-chevron-right::before{content:\"\"}.pt-icon-double-chevron-up::before{content:\"\"}.pt-icon-double-chevron-down::before{content:\"\"}.pt-icon-key-control::before{content:\"\"}.pt-icon-key-command::before{content:\"\"}.pt-icon-key-shift::before{content:\"\"}.pt-icon-key-backspace::before{content:\"\"}.pt-icon-key-delete::before{content:\"\"}.pt-icon-key-escape::before{content:\"\"}.pt-icon-key-enter::before{content:\"\"}.pt-icon-calculator::before{content:\"\"}.pt-icon-horizontal-bar-chart::before{content:\"\"}.pt-icon-small-plus::before{content:\"\"}.pt-icon-small-minus::before{content:\"\"}.pt-icon-step-chart::before{content:\"\"}.pt-icon-euro::before{content:\"€\"}.pt-icon-drag-handle-vertical::before{content:\"\"}.pt-icon-drag-handle-horizontal::before{content:\"\"}.pt-icon-mobile-phone::before{content:\"\"}.pt-icon-sim-card::before{content:\"\"}.pt-icon-trending-up::before{content:\"\"}.pt-icon-trending-down::before{content:\"\"}.pt-icon-curved-range-chart::before{content:\"\"}.pt-icon-vertical-bar-chart-desc::before{content:\"\"}.pt-icon-horizontal-bar-chart-desc::before{content:\"\"}.pt-icon-document-open::before{content:\"\"}.pt-icon-document-share::before{content:\"\"}.pt-icon-horizontal-distribution::before{content:\"\"}.pt-icon-vertical-distribution::before{content:\"\"}.pt-icon-alignment-left::before{content:\"\"}.pt-icon-alignment-vertical-center::before{content:\"\"}.pt-icon-alignment-right::before{content:\"\"}.pt-icon-alignment-top::before{content:\"\"}.pt-icon-alignment-horizontal-center::before{content:\"\"}.pt-icon-alignment-bottom::before{content:\"\"}.pt-icon-git-pull::before{content:\"\"}.pt-icon-git-merge::before{content:\"\"}.pt-icon-git-branch::before{content:\"\"}.pt-icon-git-commit::before{content:\"\"}.pt-icon-git-push::before{content:\"\"}.pt-icon-build::before{content:\"\"}.pt-icon-symbol-circle::before{content:\"\"}.pt-icon-symbol-square::before{content:\"\"}.pt-icon-symbol-diamond::before{content:\"\"}.pt-icon-symbol-cross::before{content:\"\"}.pt-icon-symbol-triangle-up::before{content:\"\"}.pt-icon-symbol-triangle-down::before{content:\"\"}.pt-icon-wrench::before{content:\"\"}.pt-icon-application::before{content:\"\"}.pt-icon-send-to-graph::before{content:\"\"}.pt-icon-send-to-map::before{content:\"\"}.pt-icon-join-table::before{content:\"\"}.pt-icon-derive-column::before{content:\"\"}.pt-icon-image-rotate-left::before{content:\"\"}.pt-icon-image-rotate-right::before{content:\"\"}.pt-icon-known-vehicle::before{content:\"\"}.pt-icon-unknown-vehicle::before{content:\"\"}.pt-icon-scatter-plot::before{content:\"\"}.pt-icon-oil-field::before{content:\"\"}.pt-icon-rig::before{content:\"\"}.pt-icon-map-create::before{content:\"\"}.pt-icon-key-option::before{content:\"\"}.pt-icon-list-detail-view::before{content:\"\"}.pt-icon-swap-vertical::before{content:\"\"}.pt-icon-swap-horizontal::before{content:\"\"}.pt-icon-numbered-list::before{content:\"\"}.pt-icon-new-grid-item::before{content:\"\"}.pt-icon-git-repo::before{content:\"\"}.pt-icon-git-new-branch::before{content:\"\"}.pt-icon-manually-entered-data::before{content:\"\"}.pt-icon-airplane::before{content:\"\"}.pt-icon-merge-columns::before{content:\"\"}.pt-icon-split-columns::before{content:\"\"}.pt-icon-dashboard::before{content:\"\"}.pt-icon-publish-function::before{content:\"\"}.pt-icon-path::before{content:\"\"}.pt-icon-moon::before{content:\"\"}.pt-icon-remove-column::before{content:\"\"}.pt-icon-numerical::before{content:\"\"}.pt-icon-key-tab::before{content:\"\"}.pt-icon-regression-chart::before{content:\"\"}.pt-icon-translate::before{content:\"\"}.pt-icon-eye-on::before{content:\"\"}.pt-icon-vertical-bar-chart-asc::before{content:\"\"}.pt-icon-horizontal-bar-chart-asc::before{content:\"\"}.pt-icon-grouped-bar-chart::before{content:\"\"}.pt-icon-full-stacked-chart::before{content:\"\"}.pt-icon-endorsed::before{content:\"\"}.pt-icon-follower::before{content:\"\"}.pt-icon-following::before{content:\"\"}.pt-icon-menu::before{content:\"\"}.pt-icon-collapse-all::before{content:\"\"}.pt-icon-expand-all::before{content:\"\"}.pt-icon-intersection::before{content:\"\"}.pt-icon-blocked-person::before{content:\"\"}.pt-icon-slash::before{content:\"\"}.pt-icon-percentage::before{content:\"\"}.pt-icon-satellite::before{content:\"\"}.pt-icon-paragraph::before{content:\"\"}.pt-icon-banl-account::before{content:\"\"}.pt-icon-cell-tower::before{content:\"\"}.pt-icon-id-number::before{content:\"\"}.pt-icon-ip-address::before{content:\"\"}:focus{outline:rgba(19,124,189,.5) auto 2px;outline-offset:2px;-moz-outline-radius:6px}.pt-focus-disabled :focus,.pt-focus-disabled :focus~.pt-control-indicator{outline:0!important}.pt-alert{max-width:400px;padding:20px}.pt-alert-body{display:-webkit-flex;display:flex}.pt-alert-body .pt-icon{margin-right:20px;font-size:40px}.pt-alert-footer{display:-webkit-flex;display:flex;-webkit-flex-direction:row-reverse;flex-direction:row-reverse;margin-top:10px}.pt-alert-footer .pt-button{margin-left:10px}.pt-breadcrumbs{display:inline-block;margin:0;cursor:default;padding:0;list-style:none;vertical-align:top;line-height:30px}.pt-breadcrumbs>li{float:left}.pt-breadcrumbs>li::after{line-height:1;font-family:Icons16,sans-serif;font-size:16px;font-weight:400;font-style:normal;padding:0 5px;color:#5c7080;content:\"\"}.pt-breadcrumbs>li:last-child::after{display:none}.pt-breadcrumb,.pt-breadcrumb-current,.pt-breadcrumbs-collapsed{display:inline-block;line-height:19px;font-size:16px}.pt-breadcrumb,.pt-breadcrumbs-collapsed{color:#5c7080}.pt-breadcrumb:hover{text-decoration:none}.pt-breadcrumb.pt-disabled{cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-breadcrumb-current{color:inherit;font-weight:600}.pt-breadcrumb-current .pt-input{vertical-align:baseline;font-size:inherit;font-weight:inherit}.pt-breadcrumbs-collapsed::before,.pt-button[class*=pt-icon-]::before{font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-breadcrumbs-collapsed{margin-right:2px;border:none;border-radius:3px;background:#ced9e0;cursor:pointer;padding:0 5px}.pt-breadcrumbs-collapsed::before{font-family:Icons20,sans-serif;font-size:20px;line-height:19px;content:\"\"}.pt-breadcrumbs-collapsed:hover{background:#bfccd6;text-decoration:none;color:#182026}.pt-dark .pt-breadcrumb,.pt-dark .pt-breadcrumbs-collapsed,.pt-dark .pt-breadcrumbs>li::after{color:#bfccd6}.pt-dark .pt-breadcrumb.pt-disabled{color:rgba(191,204,214,.5)}.pt-dark .pt-breadcrumb-current{color:#f5f8fa}.pt-dark .pt-breadcrumbs-collapsed{background:rgba(16,22,26,.4)}.pt-dark .pt-breadcrumbs-collapsed:hover{background:rgba(16,22,26,.6);color:#f5f8fa}.pt-button{display:inline-block;border:none;border-radius:3px;cursor:pointer;padding:0 10px;vertical-align:middle;font-size:14px;min-width:30px;min-height:30px;line-height:30px}.pt-button.pt-disabled,.pt-button:disabled{cursor:not-allowed}.pt-button.pt-fill{width:100%}.pt-button:not([class*=pt-intent-]){background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);color:#182026}.pt-button:not([class*=pt-intent-]):hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);background-clip:padding-box}.pt-button:not([class*=pt-intent-]).pt-active,.pt-button:not([class*=pt-intent-]):active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-button:not([class*=pt-intent-]).pt-disabled,.pt-button:not([class*=pt-intent-]):disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-button.pt-intent-primary,.pt-button.pt-intent-primary:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button:not([class*=pt-intent-]).pt-disabled.pt-active,.pt-button:not([class*=pt-intent-]):disabled.pt-active{background:rgba(206,217,224,.7)}.pt-button.pt-intent-primary{background:#137cbd;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #137cbd;color:#fff}.pt-button.pt-intent-primary.pt-active,.pt-button.pt-intent-primary:active,.pt-button.pt-intent-primary:hover{color:#fff}.pt-button.pt-intent-primary:hover{background:#106ba3;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #106ba3}.pt-button.pt-intent-primary.pt-active,.pt-button.pt-intent-primary:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#0e5a8a;background-image:none}.pt-button.pt-intent-primary.pt-disabled,.pt-button.pt-intent-primary:disabled{border-color:transparent;box-shadow:none;background-color:rgba(19,124,189,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button.pt-intent-success,.pt-button.pt-intent-success:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button.pt-intent-success{background:#0f9960;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #0f9960;color:#fff}.pt-button.pt-intent-success.pt-active,.pt-button.pt-intent-success:active,.pt-button.pt-intent-success:hover{color:#fff}.pt-button.pt-intent-success:hover{background:#0d8050;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #0d8050}.pt-button.pt-intent-success.pt-active,.pt-button.pt-intent-success:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#0a6640;background-image:none}.pt-button.pt-intent-success.pt-disabled,.pt-button.pt-intent-success:disabled{border-color:transparent;box-shadow:none;background-color:rgba(15,153,96,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button.pt-intent-warning,.pt-button.pt-intent-warning:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button.pt-intent-warning{background:#d9822b;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #d9822b;color:#fff}.pt-button.pt-intent-warning.pt-active,.pt-button.pt-intent-warning:active,.pt-button.pt-intent-warning:hover{color:#fff}.pt-button.pt-intent-warning:hover{background:#bf7326;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #bf7326}.pt-button.pt-intent-warning.pt-active,.pt-button.pt-intent-warning:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#a66321;background-image:none}.pt-button.pt-intent-warning.pt-disabled,.pt-button.pt-intent-warning:disabled{border-color:transparent;box-shadow:none;background-color:rgba(217,130,43,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button.pt-intent-danger,.pt-button.pt-intent-danger:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-button.pt-intent-danger{background:#db3737;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #db3737;color:#fff}.pt-button.pt-intent-danger.pt-active,.pt-button.pt-intent-danger:active,.pt-button.pt-intent-danger:hover{color:#fff}.pt-button.pt-intent-danger:hover{background:#c23030;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #c23030}.pt-button.pt-intent-danger.pt-active,.pt-button.pt-intent-danger:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#a82a2a;background-image:none}.pt-button.pt-intent-danger.pt-disabled,.pt-button.pt-intent-danger:disabled{border-color:transparent;box-shadow:none;background-color:rgba(219,55,55,.5);background-image:none;color:rgba(255,255,255,.6)}.pt-button[class*=pt-intent-] .pt-button-spinner .pt-spinner-head{stroke:#fff}.pt-button.pt-loading{position:relative}.pt-button.pt-loading .pt-button-spinner{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.pt-button[class*=pt-icon-]::before{line-height:1;font-family:Icons16,sans-serif;font-size:16px;margin-right:7px;color:#5c7080}.pt-button .pt-icon,.pt-button .pt-icon-large,.pt-button .pt-icon-standard{margin-right:7px;color:#5c7080}.pt-button .pt-icon-large.pt-align-right,.pt-button .pt-icon-standard.pt-align-right,.pt-button .pt-icon.pt-align-right{margin-right:0;margin-left:7px}.pt-dark .pt-button:not([class*=pt-intent-]){background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-button:not([class*=pt-intent-]).pt-active,.pt-dark .pt-button:not([class*=pt-intent-]):active,.pt-dark .pt-button:not([class*=pt-intent-]):hover{color:#f5f8fa}.pt-dark .pt-button:not([class*=pt-intent-]):hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-button:not([class*=pt-intent-]).pt-active,.pt-dark .pt-button:not([class*=pt-intent-]):active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-button:not([class*=pt-intent-]).pt-disabled,.pt-dark .pt-button:not([class*=pt-intent-]):disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-button:not([class*=pt-intent-]).pt-disabled.pt-active,.pt-dark .pt-button:not([class*=pt-intent-]):disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-button:not([class*=pt-intent-]) .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-dark .pt-button:not([class*=pt-intent-]) .pt-icon,.pt-dark .pt-button:not([class*=pt-intent-]) .pt-icon-large,.pt-dark .pt-button:not([class*=pt-intent-]) .pt-icon-standard,.pt-dark .pt-button:not([class*=pt-intent-])[class*=pt-icon-]::before{color:#bfccd6}.pt-dark .pt-button[class*=pt-intent-],.pt-dark .pt-button[class*=pt-intent-]:hover{box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-button[class*=pt-intent-].pt-active,.pt-dark .pt-button[class*=pt-intent-]:active{box-shadow:0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2)}.pt-dark .pt-button[class*=pt-intent-].pt-disabled,.pt-dark .pt-button[class*=pt-intent-]:disabled{box-shadow:none;background-image:none;color:rgba(255,255,255,.3)}.pt-dark .pt-button[class*=pt-intent-] .pt-button-spinner .pt-spinner-head{stroke:#8a9ba8}.pt-button.pt-disabled .pt-icon,.pt-button.pt-disabled .pt-icon-large,.pt-button.pt-disabled .pt-icon-standard,.pt-button.pt-disabled::before,.pt-button:disabled .pt-icon,.pt-button:disabled .pt-icon-large,.pt-button:disabled .pt-icon-standard,.pt-button:disabled::before,.pt-button[class*=pt-intent-] .pt-icon,.pt-button[class*=pt-intent-] .pt-icon-large,.pt-button[class*=pt-intent-] .pt-icon-standard,.pt-button[class*=pt-intent-]::before{color:inherit!important}.pt-button.pt-minimal{box-shadow:none;background:0 0}.pt-button.pt-minimal:focus,.pt-button.pt-minimal:hover{box-shadow:none;background:rgba(167,182,194,.3);text-decoration:none;color:#182026}.pt-button.pt-minimal.pt-active,.pt-button.pt-minimal:active{box-shadow:none;background:rgba(115,134,148,.3);color:#182026}.pt-button.pt-minimal.pt-disabled,.pt-button.pt-minimal.pt-disabled:hover,.pt-button.pt-minimal:disabled,.pt-button.pt-minimal:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-dark .pt-button.pt-minimal{box-shadow:none;background:0 0;color:inherit}.pt-dark .pt-button.pt-minimal.pt-active,.pt-dark .pt-button.pt-minimal:active,.pt-dark .pt-button.pt-minimal:hover{box-shadow:none}.pt-dark .pt-button.pt-minimal:hover{background:rgba(138,155,168,.15)}.pt-dark .pt-button.pt-minimal.pt-active,.pt-dark .pt-button.pt-minimal:active{background:rgba(138,155,168,.3);color:#f5f8fa}.pt-dark .pt-button.pt-minimal.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-disabled:hover,.pt-dark .pt-button.pt-minimal:disabled,.pt-dark .pt-button.pt-minimal:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(191,204,214,.5)}.pt-button.pt-minimal.pt-intent-primary{color:#106ba3}.pt-button.pt-minimal.pt-intent-primary.pt-active,.pt-button.pt-minimal.pt-intent-primary:active,.pt-button.pt-minimal.pt-intent-primary:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-primary:focus,.pt-button.pt-minimal.pt-intent-primary:hover{background:rgba(19,124,189,.15);color:#106ba3}.pt-button.pt-minimal.pt-intent-primary.pt-active,.pt-button.pt-minimal.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#106ba3}.pt-button.pt-minimal.pt-intent-primary.pt-disabled,.pt-button.pt-minimal.pt-intent-primary:disabled{background:0 0;color:rgba(16,107,163,.5)}.pt-button.pt-minimal.pt-intent-primary .pt-button-spinner .pt-spinner-head{stroke:#106ba3}.pt-dark .pt-button.pt-minimal.pt-intent-primary{color:#48aff0}.pt-dark .pt-button.pt-minimal.pt-intent-primary:focus,.pt-dark .pt-button.pt-minimal.pt-intent-primary:hover{background:rgba(19,124,189,.2);color:#48aff0}.pt-dark .pt-button.pt-minimal.pt-intent-primary.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#48aff0}.pt-dark .pt-button.pt-minimal.pt-intent-primary.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-primary:disabled{background:0 0;color:rgba(72,175,240,.5)}.pt-button.pt-minimal.pt-intent-success{color:#0d8050}.pt-button.pt-minimal.pt-intent-success.pt-active,.pt-button.pt-minimal.pt-intent-success:active,.pt-button.pt-minimal.pt-intent-success:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-success:focus,.pt-button.pt-minimal.pt-intent-success:hover{background:rgba(15,153,96,.15);color:#0d8050}.pt-button.pt-minimal.pt-intent-success.pt-active,.pt-button.pt-minimal.pt-intent-success:active{background:rgba(15,153,96,.3);color:#0d8050}.pt-button.pt-minimal.pt-intent-success.pt-disabled,.pt-button.pt-minimal.pt-intent-success:disabled{background:0 0;color:rgba(13,128,80,.5)}.pt-button.pt-minimal.pt-intent-success .pt-button-spinner .pt-spinner-head{stroke:#0d8050}.pt-dark .pt-button.pt-minimal.pt-intent-success{color:#3dcc91}.pt-dark .pt-button.pt-minimal.pt-intent-success:focus,.pt-dark .pt-button.pt-minimal.pt-intent-success:hover{background:rgba(15,153,96,.2);color:#3dcc91}.pt-dark .pt-button.pt-minimal.pt-intent-success.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-success:active{background:rgba(15,153,96,.3);color:#3dcc91}.pt-dark .pt-button.pt-minimal.pt-intent-success.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-success:disabled{background:0 0;color:rgba(61,204,145,.5)}.pt-button.pt-minimal.pt-intent-warning{color:#bf7326}.pt-button.pt-minimal.pt-intent-warning.pt-active,.pt-button.pt-minimal.pt-intent-warning:active,.pt-button.pt-minimal.pt-intent-warning:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-warning:focus,.pt-button.pt-minimal.pt-intent-warning:hover{background:rgba(217,130,43,.15);color:#bf7326}.pt-button.pt-minimal.pt-intent-warning.pt-active,.pt-button.pt-minimal.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#bf7326}.pt-button.pt-minimal.pt-intent-warning.pt-disabled,.pt-button.pt-minimal.pt-intent-warning:disabled{background:0 0;color:rgba(191,115,38,.5)}.pt-button.pt-minimal.pt-intent-warning .pt-button-spinner .pt-spinner-head{stroke:#bf7326}.pt-dark .pt-button.pt-minimal.pt-intent-warning{color:#ffb366}.pt-dark .pt-button.pt-minimal.pt-intent-warning:focus,.pt-dark .pt-button.pt-minimal.pt-intent-warning:hover{background:rgba(217,130,43,.2);color:#ffb366}.pt-dark .pt-button.pt-minimal.pt-intent-warning.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#ffb366}.pt-dark .pt-button.pt-minimal.pt-intent-warning.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-warning:disabled{background:0 0;color:rgba(255,179,102,.5)}.pt-button.pt-minimal.pt-intent-danger{color:#c23030}.pt-button.pt-minimal.pt-intent-danger.pt-active,.pt-button.pt-minimal.pt-intent-danger:active,.pt-button.pt-minimal.pt-intent-danger:hover{box-shadow:none}.pt-button.pt-minimal.pt-intent-danger:focus,.pt-button.pt-minimal.pt-intent-danger:hover{background:rgba(219,55,55,.15);color:#c23030}.pt-button.pt-minimal.pt-intent-danger.pt-active,.pt-button.pt-minimal.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#c23030}.pt-button.pt-minimal.pt-intent-danger.pt-disabled,.pt-button.pt-minimal.pt-intent-danger:disabled{background:0 0;color:rgba(194,48,48,.5)}.pt-button.pt-minimal.pt-intent-danger .pt-button-spinner .pt-spinner-head{stroke:#c23030}.pt-dark .pt-button.pt-minimal.pt-intent-danger{color:#ff7373}.pt-dark .pt-button.pt-minimal.pt-intent-danger:focus,.pt-dark .pt-button.pt-minimal.pt-intent-danger:hover{background:rgba(219,55,55,.2);color:#ff7373}.pt-dark .pt-button.pt-minimal.pt-intent-danger.pt-active,.pt-dark .pt-button.pt-minimal.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#ff7373}.pt-dark .pt-button.pt-minimal.pt-intent-danger.pt-disabled,.pt-dark .pt-button.pt-minimal.pt-intent-danger:disabled{background:0 0;color:rgba(255,115,115,.5)}.pt-button.pt-large,.pt-large .pt-button{min-width:40px;min-height:40px;line-height:40px;padding:0 15px;font-size:16px}.pt-button.pt-large .pt-icon,.pt-button.pt-large .pt-icon-large,.pt-button.pt-large .pt-icon-standard,.pt-button.pt-large[class*=pt-icon-]::before,.pt-large .pt-button .pt-icon,.pt-large .pt-button .pt-icon-large,.pt-large .pt-button .pt-icon-standard,.pt-large .pt-button[class*=pt-icon-]::before{margin-right:10px}.pt-button.pt-large .pt-icon-large.pt-align-right,.pt-button.pt-large .pt-icon-standard.pt-align-right,.pt-button.pt-large .pt-icon.pt-align-right,.pt-large .pt-button .pt-icon-large.pt-align-right,.pt-large .pt-button .pt-icon-standard.pt-align-right,.pt-large .pt-button .pt-icon.pt-align-right{margin-right:0;margin-left:10px}.pt-button.pt-small,.pt-small .pt-button{min-width:24px;min-height:24px;line-height:24px;padding:0 7px;font-size:12px}.pt-button.pt-small .pt-icon,.pt-button.pt-small .pt-icon-large,.pt-button.pt-small .pt-icon-standard,.pt-button.pt-small[class*=pt-icon-]::before,.pt-small .pt-button .pt-icon,.pt-small .pt-button .pt-icon-large,.pt-small .pt-button .pt-icon-standard,.pt-small .pt-button[class*=pt-icon-]::before{display:none}.pt-button[class*=pt-icon-]:empty{padding:0}.pt-button[class*=pt-icon-]:empty::before{margin-right:0}a.pt-button{text-align:center;text-decoration:none;transition:none}a.pt-button,a.pt-button:active,a.pt-button:hover{color:#182026}a.pt-button.pt-disabled{color:rgba(92,112,128,.5)}.pt-button-group{display:-webkit-inline-flex;display:inline-flex}.pt-button-group .pt-button{-webkit-flex:0 0 auto;flex:0 0 auto;position:relative;z-index:4}.pt-button-group .pt-button:focus{z-index:5}.pt-button-group .pt-button:hover{z-index:6}.pt-button-group .pt-button.pt-active,.pt-button-group .pt-button:active{z-index:7}.pt-button-group .pt-button.pt-disabled,.pt-button-group .pt-button:disabled{z-index:3}.pt-button-group .pt-button[class*=pt-intent-]{z-index:9}.pt-button-group .pt-button[class*=pt-intent-]:focus{z-index:10}.pt-button-group .pt-button[class*=pt-intent-]:hover{z-index:11}.pt-button-group .pt-button[class*=pt-intent-].pt-active,.pt-button-group .pt-button[class*=pt-intent-]:active{z-index:12}.pt-button-group .pt-button[class*=pt-intent-].pt-disabled,.pt-button-group .pt-button[class*=pt-intent-]:disabled{z-index:8}.pt-button-group:not(.pt-minimal)>.pt-button:not(:first-child),.pt-button-group:not(.pt-minimal)>.pt-popover-target:not(:first-child) .pt-button{border-top-left-radius:0;border-bottom-left-radius:0}.pt-button-group:not(.pt-minimal)>.pt-button:not(:last-child),.pt-button-group:not(.pt-minimal)>.pt-popover-target:not(:last-child) .pt-button{margin-right:-1px;border-top-right-radius:0;border-bottom-right-radius:0}.pt-button-group.pt-minimal .pt-button{box-shadow:none;background:0 0;margin-right:11px;border-radius:3px!important;overflow:visible}.pt-button-group.pt-minimal .pt-button:focus,.pt-button-group.pt-minimal .pt-button:hover{box-shadow:none;background:rgba(167,182,194,.3);text-decoration:none;color:#182026}.pt-button-group.pt-minimal .pt-button.pt-active,.pt-button-group.pt-minimal .pt-button:active{box-shadow:none;background:rgba(115,134,148,.3);color:#182026}.pt-button-group.pt-minimal .pt-button.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-disabled:hover,.pt-button-group.pt-minimal .pt-button:disabled,.pt-button-group.pt-minimal .pt-button:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-dark .pt-button-group.pt-minimal .pt-button{box-shadow:none;background:0 0;color:inherit}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button:active,.pt-dark .pt-button-group.pt-minimal .pt-button:hover{box-shadow:none}.pt-dark .pt-button-group.pt-minimal .pt-button:hover{background:rgba(138,155,168,.15)}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button:active{background:rgba(138,155,168,.3);color:#f5f8fa}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-disabled:hover,.pt-dark .pt-button-group.pt-minimal .pt-button:disabled,.pt-dark .pt-button-group.pt-minimal .pt-button:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(191,204,214,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-primary{color:#106ba3}.pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:active,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-primary:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:hover{background:rgba(19,124,189,.15);color:#106ba3}.pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#106ba3}.pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-primary:disabled{background:0 0;color:rgba(16,107,163,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-primary .pt-button-spinner .pt-spinner-head{stroke:#106ba3}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary{color:#48aff0}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:hover{background:rgba(19,124,189,.2);color:#48aff0}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#48aff0}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-primary:disabled{background:0 0;color:rgba(72,175,240,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-success{color:#0d8050}.pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-success:active,.pt-button-group.pt-minimal .pt-button.pt-intent-success:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-success:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-success:hover{background:rgba(15,153,96,.15);color:#0d8050}.pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-success:active{background:rgba(15,153,96,.3);color:#0d8050}.pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-success:disabled{background:0 0;color:rgba(13,128,80,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-success .pt-button-spinner .pt-spinner-head{stroke:#0d8050}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success{color:#3dcc91}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:hover{background:rgba(15,153,96,.2);color:#3dcc91}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:active{background:rgba(15,153,96,.3);color:#3dcc91}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-success:disabled{background:0 0;color:rgba(61,204,145,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-warning{color:#bf7326}.pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:active,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-warning:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:hover{background:rgba(217,130,43,.15);color:#bf7326}.pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#bf7326}.pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-warning:disabled{background:0 0;color:rgba(191,115,38,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-warning .pt-button-spinner .pt-spinner-head{stroke:#bf7326}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning{color:#ffb366}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:hover{background:rgba(217,130,43,.2);color:#ffb366}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#ffb366}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-warning:disabled{background:0 0;color:rgba(255,179,102,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-danger{color:#c23030}.pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:active,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:hover{box-shadow:none}.pt-button-group.pt-minimal .pt-button.pt-intent-danger:focus,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:hover{background:rgba(219,55,55,.15);color:#c23030}.pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-active,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#c23030}.pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-disabled,.pt-button-group.pt-minimal .pt-button.pt-intent-danger:disabled{background:0 0;color:rgba(194,48,48,.5)}.pt-button-group.pt-minimal .pt-button.pt-intent-danger .pt-button-spinner .pt-spinner-head{stroke:#c23030}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger{color:#ff7373}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:focus,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:hover{background:rgba(219,55,55,.2);color:#ff7373}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-active,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#ff7373}.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger.pt-disabled,.pt-dark .pt-button-group.pt-minimal .pt-button.pt-intent-danger:disabled{background:0 0;color:rgba(255,115,115,.5)}.pt-button-group.pt-minimal .pt-button:focus{outline-style:solid}.pt-dialog:focus,.pt-editable-input:focus{outline:0}.pt-button-group.pt-minimal .pt-button::after{margin:5px;background:rgba(16,22,26,.15);width:1px;display:inline-block;position:absolute;top:10%;bottom:10%;left:100%;content:\"\"}.pt-dark .pt-button-group.pt-minimal .pt-button::after{background:rgba(255,255,255,.15)}.pt-button-group.pt-minimal>.pt-button:last-child,.pt-button-group.pt-minimal>.pt-popover-target:last-child .pt-button{margin-right:0}.pt-button-group.pt-minimal>.pt-button:last-child::after,.pt-button-group.pt-minimal>.pt-popover-target:last-child .pt-button::after{display:none}.pt-button-group.pt-fill{display:-webkit-flex;display:flex}.pt-button-group .pt-button.pt-fill,.pt-button-group.pt-fill .pt-button:not(.pt-fixed){-webkit-flex:1 1 auto;flex:1 1 auto}.pt-button-group.pt-vertical{-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;align-items:stretch;vertical-align:top}.pt-button-group.pt-vertical .pt-button{margin-right:0!important}.pt-button-group.pt-vertical>.pt-button:first-child,.pt-button-group.pt-vertical>.pt-popover-target:first-child .pt-button{border-radius:3px 3px 0 0}.pt-button-group.pt-vertical>.pt-button:last-child,.pt-button-group.pt-vertical>.pt-popover-target:last-child .pt-button{border-radius:0 0 3px 3px}.pt-button-group.pt-vertical>.pt-button:not(:last-child),.pt-button-group.pt-vertical>.pt-popover-target:not(:last-child) .pt-button{margin-bottom:-1px}.pt-button-group.pt-vertical.pt-minimal .pt-button:not(:last-child){margin-bottom:11px}.pt-button-group.pt-vertical.pt-minimal .pt-button::after{top:100%;right:0;bottom:auto;left:0;width:auto;height:1px}.pt-button-group.pt-align-left .pt-button{text-align:left}.pt-dark .pt-button-group:not(.pt-minimal)>.pt-button:not(:last-child),.pt-dark .pt-button-group:not(.pt-minimal)>.pt-popover-target:not(:last-child) .pt-button{margin-right:1px}.pt-dark .pt-button-group.pt-vertical>.pt-button:not(:last-child),.pt-dark .pt-button-group.pt-vertical>.pt-popover-target:not(:last-child) .pt-button{margin-bottom:1px}.pt-callout{line-height:1.5;font-size:14px;position:relative;border-radius:3px;background-color:rgba(138,155,168,.15);padding:10px 12px 9px}.pt-callout[class*=pt-icon-]::before,.pt-dialog-close-button{font-family:Icons20,sans-serif;font-size:20px;font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-callout[class*=pt-icon-]{padding-left:40px}.pt-callout[class*=pt-icon-]::before{line-height:1;position:absolute;top:10px;left:10px;color:#5c7080}.pt-callout h5{margin-top:0;margin-bottom:5px;line-height:20px}.pt-dark .pt-callout{background-color:rgba(138,155,168,.2)}.pt-dark .pt-callout[class*=pt-icon-]::before{color:#bfccd6}.pt-callout.pt-intent-primary{background-color:rgba(19,124,189,.15)}.pt-callout.pt-intent-primary h5,.pt-callout.pt-intent-primary[class*=pt-icon-]::before{color:#106ba3}.pt-dark .pt-callout.pt-intent-primary{background-color:rgba(19,124,189,.25)}.pt-dark .pt-callout.pt-intent-primary h5,.pt-dark .pt-callout.pt-intent-primary[class*=pt-icon-]::before{color:#48aff0}.pt-callout.pt-intent-success{background-color:rgba(15,153,96,.15)}.pt-callout.pt-intent-success h5,.pt-callout.pt-intent-success[class*=pt-icon-]::before{color:#0d8050}.pt-dark .pt-callout.pt-intent-success{background-color:rgba(15,153,96,.25)}.pt-dark .pt-callout.pt-intent-success h5,.pt-dark .pt-callout.pt-intent-success[class*=pt-icon-]::before{color:#3dcc91}.pt-callout.pt-intent-warning{background-color:rgba(217,130,43,.15)}.pt-callout.pt-intent-warning h5,.pt-callout.pt-intent-warning[class*=pt-icon-]::before{color:#bf7326}.pt-dark .pt-callout.pt-intent-warning{background-color:rgba(217,130,43,.25)}.pt-dark .pt-callout.pt-intent-warning h5,.pt-dark .pt-callout.pt-intent-warning[class*=pt-icon-]::before{color:#ffb366}.pt-callout.pt-intent-danger{background-color:rgba(219,55,55,.15)}.pt-callout.pt-intent-danger h5,.pt-callout.pt-intent-danger[class*=pt-icon-]::before{color:#c23030}.pt-dark .pt-callout.pt-intent-danger{background-color:rgba(219,55,55,.25)}.pt-dark .pt-callout.pt-intent-danger h5,.pt-dark .pt-callout.pt-intent-danger[class*=pt-icon-]::before{color:#ff7373}.pt-running-text .pt-callout{margin:20px 0}.pt-card{border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.15),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0);background-color:#fff;padding:20px;transition:box-shadow .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),box-shadow .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),box-shadow .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9)}.pt-card.pt-dark,.pt-dark .pt-card{box-shadow:0 0 0 1px rgba(16,22,26,.4),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0);background-color:#30404d}.pt-elevation-0{box-shadow:0 0 0 1px rgba(16,22,26,.15),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0)}.pt-dark .pt-elevation-0,.pt-elevation-0.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.4),0 0 0 rgba(16,22,26,0),0 0 0 rgba(16,22,26,0)}.pt-elevation-1{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.2)}.pt-dark .pt-elevation-1,.pt-elevation-1.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-elevation-2{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 1px 1px rgba(16,22,26,.2),0 2px 6px rgba(16,22,26,.2)}.pt-dark .pt-elevation-2,.pt-elevation-2.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.4),0 2px 6px rgba(16,22,26,.4)}.pt-elevation-3{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2)}.pt-dark .pt-elevation-3,.pt-elevation-3.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-elevation-4{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 4px 8px rgba(16,22,26,.2),0 18px 46px 6px rgba(16,22,26,.2)}.pt-dark .pt-elevation-4,.pt-elevation-4.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 4px 8px rgba(16,22,26,.4),0 18px 46px 6px rgba(16,22,26,.4)}.pt-card.pt-interactive:hover{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);cursor:pointer}.pt-card.pt-interactive:hover.pt-dark,.pt-dark .pt-card.pt-interactive:hover{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-card.pt-interactive:active{opacity:.9;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.2);transition-duration:0}.pt-card.pt-interactive:active.pt-dark,.pt-dark .pt-card.pt-interactive:active{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-collapse{height:0;overflow-y:hidden;transition:height .2s cubic-bezier(.4,1,.75,.9)}.pt-collapse .pt-collapse-body{transition:-webkit-transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9)}.pt-context-menu .pt-popover-target{display:block}.pt-context-menu-popover-target{position:fixed}.pt-dialog{opacity:1;-webkit-transform:translateX(50%) scale(1);transform:translateX(50%) scale(1);display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;position:absolute;top:25%;right:50%;z-index:20;margin-bottom:20px;border-radius:6px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 4px 8px rgba(16,22,26,.2),0 18px 46px 6px rgba(16,22,26,.2);background:#ebf1f5;width:500px;padding-bottom:20px}.pt-dialog.pt-overlay-appear,.pt-dialog.pt-overlay-enter{opacity:0;-webkit-transform:translateX(50%) scale(.5);transform:translateX(50%) scale(.5)}.pt-dialog.pt-overlay-appear-active,.pt-dialog.pt-overlay-enter-active{opacity:1;-webkit-transform:translateX(50%) scale(1);transform:translateX(50%) scale(1);transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-dialog.pt-overlay-leave{opacity:1;-webkit-transform:translateX(50%) scale(1);transform:translateX(50%) scale(1)}.pt-dialog.pt-overlay-leave-active{opacity:0;-webkit-transform:translateX(50%) scale(.5);transform:translateX(50%) scale(.5);transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-dark .pt-dialog,.pt-dialog.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 4px 8px rgba(16,22,26,.4),0 18px 46px 6px rgba(16,22,26,.4);background:#293742;color:#f5f8fa}.pt-dialog-header{display:-webkit-flex;display:flex;-webkit-flex:0 0 auto;flex:0 0 auto;-webkit-align-items:center;align-items:center;border-radius:6px 6px 0 0;box-shadow:0 1px 0 rgba(16,22,26,.15);background:#fff;min-height:40px;padding-left:20px}.pt-dialog-header .pt-icon-large{-webkit-flex:0 0 auto;flex:0 0 auto;margin-right:10px;color:#5c7080}.pt-dialog-header h5{overflow:hidden;-webkit-flex:1 1 auto;flex:1 1 auto;margin:0;line-height:inherit}.pt-dialog-header h5:last-child{margin-right:20px}.pt-dark .pt-dialog-header{box-shadow:0 1px 0 rgba(16,22,26,.4);background:#30404d}.pt-dark .pt-dialog-header .pt-icon-large{color:#bfccd6}.pt-dark .pt-dialog-header h5{color:#f5f8fa}.pt-dialog-close-button{line-height:1;color:#5c7080;-webkit-flex:0 0 auto;flex:0 0 auto;border:none;background:0 0;cursor:pointer;padding:10px}.pt-dialog-close-button:hover{color:#182026}.pt-dark .pt-dialog-close-button{color:#bfccd6}.pt-dark .pt-dialog-close-button:hover{color:#f5f8fa}.pt-dialog-body{-webkit-flex:1 1 auto;flex:1 1 auto;margin:20px;line-height:18px}.pt-dialog-footer{-webkit-flex:0 0 auto;flex:0 0 auto;margin:0 20px}.pt-dialog-footer-actions{display:-webkit-flex;display:flex;-webkit-justify-content:flex-end;justify-content:flex-end}.pt-dialog-footer-actions .pt-button{margin-left:10px}.pt-editable-text{display:inline-block;position:relative;cursor:text;max-width:100%;vertical-align:top}.pt-editable-text::before{position:absolute;top:-3px;right:-3px;bottom:-3px;left:-3px;border-radius:3px;content:\"\";transition:background-color .1s cubic-bezier(.4,1,.75,.9),box-shadow .1s cubic-bezier(.4,1,.75,.9)}.pt-editable-text:hover::before{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.15)}.pt-editable-text.pt-editable-editing::before{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2);background-color:#fff}.pt-editable-text.pt-disabled::before{box-shadow:none}.pt-editable-text.pt-intent-primary .pt-editable-content,.pt-editable-text.pt-intent-primary .pt-editable-input{color:#137cbd}.pt-editable-text.pt-intent-primary:hover::before{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(19,124,189,.4)}.pt-editable-text.pt-intent-primary.pt-editable-editing::before{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-editable-text.pt-intent-success .pt-editable-content,.pt-editable-text.pt-intent-success .pt-editable-input{color:#0f9960}.pt-editable-text.pt-intent-success:hover::before{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px rgba(15,153,96,.4)}.pt-editable-text.pt-intent-success.pt-editable-editing::before{box-shadow:0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-editable-text.pt-intent-warning .pt-editable-content,.pt-editable-text.pt-intent-warning .pt-editable-input{color:#d9822b}.pt-editable-text.pt-intent-warning:hover::before{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px rgba(217,130,43,.4)}.pt-editable-text.pt-intent-warning.pt-editable-editing::before{box-shadow:0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-editable-text.pt-intent-danger .pt-editable-content,.pt-editable-text.pt-intent-danger .pt-editable-input{color:#db3737}.pt-editable-text.pt-intent-danger:hover::before{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px rgba(219,55,55,.4)}.pt-editable-text.pt-intent-danger.pt-editable-editing::before{box-shadow:0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-dark .pt-editable-text:hover::before{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(255,255,255,.15)}.pt-dark .pt-editable-text.pt-editable-editing::before{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background-color:rgba(16,22,26,.3)}.pt-dark .pt-editable-text.pt-disabled::before{box-shadow:none}.pt-dark .pt-editable-text.pt-intent-primary .pt-editable-content{color:#48aff0}.pt-dark .pt-editable-text.pt-intent-primary:hover::before{box-shadow:0 0 0 0 rgba(72,175,240,0),0 0 0 0 rgba(72,175,240,0),inset 0 0 0 1px rgba(72,175,240,.4)}.pt-dark .pt-editable-text.pt-intent-primary.pt-editable-editing::before{box-shadow:0 0 0 1px #48aff0,0 0 0 3px rgba(72,175,240,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-editable-text.pt-intent-success .pt-editable-content{color:#3dcc91}.pt-dark .pt-editable-text.pt-intent-success:hover::before{box-shadow:0 0 0 0 rgba(61,204,145,0),0 0 0 0 rgba(61,204,145,0),inset 0 0 0 1px rgba(61,204,145,.4)}.pt-dark .pt-editable-text.pt-intent-success.pt-editable-editing::before{box-shadow:0 0 0 1px #3dcc91,0 0 0 3px rgba(61,204,145,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-editable-text.pt-intent-warning .pt-editable-content{color:#ffb366}.pt-dark .pt-editable-text.pt-intent-warning:hover::before{box-shadow:0 0 0 0 rgba(255,179,102,0),0 0 0 0 rgba(255,179,102,0),inset 0 0 0 1px rgba(255,179,102,.4)}.pt-dark .pt-editable-text.pt-intent-warning.pt-editable-editing::before{box-shadow:0 0 0 1px #ffb366,0 0 0 3px rgba(255,179,102,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-editable-text.pt-intent-danger .pt-editable-content{color:#ff7373}.pt-dark .pt-editable-text.pt-intent-danger:hover::before{box-shadow:0 0 0 0 rgba(255,115,115,0),0 0 0 0 rgba(255,115,115,0),inset 0 0 0 1px rgba(255,115,115,.4)}.pt-dark .pt-editable-text.pt-intent-danger.pt-editable-editing::before{box-shadow:0 0 0 1px #ff7373,0 0 0 3px rgba(255,115,115,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-editable-content,.pt-editable-input{display:inherit;position:relative;min-width:inherit;max-width:inherit;vertical-align:top;text-transform:inherit;letter-spacing:inherit;color:inherit;font:inherit;resize:none}.pt-control .pt-control-indicator,.pt-input-group .pt-icon,.pt-select::after{font-style:normal;-moz-osx-font-smoothing:grayscale}.pt-editable-input{border:none;box-shadow:none;background:0 0;width:100%;padding:0;white-space:pre-wrap}.pt-editable-input::-ms-clear{display:none}.pt-editable-content{overflow:hidden;padding-right:2px;text-overflow:ellipsis;white-space:pre}.pt-editable-editing>.pt-editable-content{position:absolute;left:0;visibility:hidden}.pt-control,.pt-control-group .pt-button,.pt-control-group .pt-input,.pt-control-group .pt-select{position:relative}.pt-editable-placeholder>.pt-editable-content{color:rgba(92,112,128,.5)}.pt-dark .pt-editable-placeholder>.pt-editable-content{color:rgba(191,204,214,.5)}.pt-editable-text.pt-multiline{display:block}.pt-editable-text.pt-multiline .pt-editable-content{overflow:auto;white-space:pre-wrap}.pt-control-group{-webkit-transform:translateZ(0);transform:translateZ(0);display:-webkit-flex;display:flex;-webkit-align-items:flex-start;align-items:flex-start}.pt-control-group>*{-webkit-flex:0 0 auto;flex:0 0 auto}.pt-control-group .pt-input{z-index:2;border-radius:inherit}.pt-control-group .pt-input:focus{z-index:14;border-radius:3px}.pt-control-group .pt-input[class*=pt-intent]{z-index:13}.pt-control-group .pt-input[class*=pt-intent]:focus{z-index:15}.pt-control-group .pt-input.pt-disabled,.pt-control-group .pt-input:disabled,.pt-control-group .pt-input[readonly]{z-index:1}.pt-control-group .pt-input-group[class*=pt-intent] .pt-input{z-index:13}.pt-control-group .pt-input-group[class*=pt-intent] .pt-input:focus{z-index:15}.pt-control-group .pt-button,.pt-control-group .pt-select select{z-index:4;border-radius:inherit}.pt-control-group .pt-button:focus,.pt-control-group .pt-select select:focus{position:relative;z-index:5}.pt-control-group .pt-button:hover,.pt-control-group .pt-select select:hover{z-index:6}.pt-control-group .pt-button:active,.pt-control-group .pt-select select:active{z-index:7}.pt-control-group .pt-button.pt-disabled,.pt-control-group .pt-button:disabled,.pt-control-group .pt-button[readonly],.pt-control-group .pt-select select.pt-disabled,.pt-control-group .pt-select select:disabled,.pt-control-group .pt-select select[readonly]{z-index:3}.pt-control-group .pt-button[class*=pt-intent],.pt-control-group .pt-select select[class*=pt-intent]{z-index:9}.pt-control-group .pt-button[class*=pt-intent]:focus,.pt-control-group .pt-select select[class*=pt-intent]:focus,.pt-navbar{z-index:10}.pt-control-group .pt-button[class*=pt-intent]:hover,.pt-control-group .pt-select select[class*=pt-intent]:hover{z-index:11}.pt-control-group .pt-button[class*=pt-intent]:active,.pt-control-group .pt-select select[class*=pt-intent]:active{z-index:12}.pt-control-group .pt-button[class*=pt-intent].pt-disabled,.pt-control-group .pt-button[class*=pt-intent]:disabled,.pt-control-group .pt-button[class*=pt-intent][readonly],.pt-control-group .pt-select select[class*=pt-intent].pt-disabled,.pt-control-group .pt-select select[class*=pt-intent]:disabled,.pt-control-group .pt-select select[class*=pt-intent][readonly]{z-index:8}.pt-control-group .pt-input-group>.pt-button,.pt-control-group .pt-input-group>.pt-icon,.pt-control-group .pt-input-group>.pt-input-action{z-index:16}.pt-control-group .pt-select::after{z-index:17}.pt-control-group:not(.pt-vertical)>*{margin-right:-1px}.pt-dark .pt-control-group:not(.pt-vertical)>*{margin-right:0}.pt-dark .pt-control-group:not(.pt-vertical)>.pt-button+.pt-button{margin-left:1px}.pt-control-group>:first-child{border-radius:3px 0 0 3px}.pt-control-group>:last-child{margin-right:0;border-radius:0 3px 3px 0}.pt-control-group .pt-input-group .pt-button{border-radius:3px}.pt-control-group.pt-fill>:not(.pt-fixed),.pt-control-group>.pt-fill{-webkit-flex:1 1 auto;flex:1 1 auto}.pt-control-group.pt-vertical{-webkit-flex-direction:column;flex-direction:column}.pt-control-group.pt-vertical>*{margin-top:-1px;width:100%}.pt-control-group.pt-vertical>:first-child{margin-top:0;border-radius:3px 3px 0 0}.pt-control-group.pt-vertical>:last-child{border-radius:0 0 3px 3px}.pt-control{display:block;margin-bottom:10px;cursor:pointer;min-height:20px;padding-left:26px;text-transform:none;line-height:16px}.pt-control.pt-disabled{cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-control.pt-inline{display:inline-block;margin-right:20px}.pt-control input{position:absolute;top:0;left:0;opacity:0;z-index:-1}.pt-control .pt-control-indicator{font-family:Icons16,sans-serif;font-size:16px;font-weight:400;-webkit-font-smoothing:antialiased;background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;position:absolute;top:0;left:0;margin:0;border:none;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);background-clip:padding-box;cursor:pointer;width:16px;height:16px;line-height:16px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pt-control .pt-control-indicator::before{position:relative;content:\"\"}.pt-control input:checked~.pt-control-indicator,.pt-control.pt-checkbox input:indeterminate~.pt-control-indicator{background:#137cbd;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #137cbd;box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2);color:#fff}.pt-control:hover .pt-control-indicator{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5}.pt-control.pt-checkbox .pt-control:hover input:indeterminate~.pt-control-indicator,.pt-control.pt-checkbox:hover input:indeterminate~.pt-control-indicator,.pt-control:hover .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control:hover input:checked~.pt-control-indicator{background:#106ba3;background:linear-gradient(to bottom,rgba(255,255,255,.1),rgba(255,255,255,0)) left no-repeat,center no-repeat #106ba3;box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 -1px 0 rgba(16,22,26,.2)}.pt-control input:not(:disabled):active~.pt-control-indicator{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background:#d8e1e8}.pt-control input:not(:disabled):active:checked~.pt-control-indicator,.pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background:#0e5a8a}.pt-control input:focus~.pt-control-indicator{outline:rgba(19,124,189,.5) auto 2px;outline-offset:2px;-moz-outline-radius:6px}.pt-control input:disabled~.pt-control-indicator{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed}.pt-control input:disabled:checked~.pt-control-indicator,.pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator{box-shadow:none;background:rgba(19,124,189,.5)}.pt-control.pt-checkbox .pt-control-indicator{border-radius:3px;font-size:16px}.pt-control.pt-checkbox input:checked~.pt-control-indicator::before,.pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before{content:\"\"}.pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before{content:\"\"}.pt-control.pt-radio .pt-control-indicator{border-radius:50%;font-size:6px}.pt-control.pt-checkbox .pt-control.pt-radio input:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio input:checked~.pt-control-indicator::before{display:inline-block;position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border-radius:50%;background:#fff;width:1em;height:1em;content:\"\"}.pt-control.pt-checkbox .pt-control.pt-radio input:disabled:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator::before,.pt-control.pt-radio input:checked:disabled~.pt-control-indicator::before{box-shadow:none}.pt-control.pt-radio input:focus~.pt-control-indicator{-moz-outline-radius:16px}.pt-control.pt-switch{padding-left:38px}.pt-control.pt-switch .pt-control-indicator{border:none;border-radius:28px;box-shadow:none;background:rgba(167,182,194,.5);width:28px;height:16px;transition:background .1s cubic-bezier(.4,1,.75,.9)}.pt-control.pt-switch .pt-control-indicator::before{display:block;top:2px;left:2px;border-radius:16px;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2);background:#fff;background-clip:padding-box;width:12px;height:12px;content:\"\";transition:left .1s cubic-bezier(.4,1,.75,.9)}.pt-file-upload-input,.pt-input{vertical-align:middle;transition:box-shadow .1s cubic-bezier(.4,1,.75,.9);-moz-appearance:none;outline:0}.pt-control.pt-checkbox .pt-control.pt-switch input:indeterminate~.pt-control-indicator,.pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control.pt-switch input:checked~.pt-control-indicator{box-shadow:none;background-color:#137cbd;width:28px;height:16px}.pt-control.pt-checkbox .pt-control.pt-switch input:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch input:checked~.pt-control-indicator::before{left:14px;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2)}.pt-control.pt-switch:hover .pt-control-indicator{background-color:rgba(115,134,148,.5)}.pt-control.pt-checkbox .pt-control.pt-switch:hover input:indeterminate~.pt-control-indicator,.pt-control.pt-switch:hover .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control.pt-switch:hover input:checked~.pt-control-indicator{background-color:#106ba3}.pt-control.pt-switch input:not(:disabled):active~.pt-control-indicator{box-shadow:none;background-color:rgba(92,112,128,.5)}.pt-control.pt-checkbox .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator{background-color:#0e5a8a}.pt-control.pt-checkbox .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator::before{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2)}.pt-control.pt-switch input:disabled~.pt-control-indicator{background-color:rgba(206,217,224,.5)}.pt-control.pt-switch input:disabled~.pt-control-indicator::before{box-shadow:none;background-color:rgba(255,255,255,.8)}.pt-control.pt-checkbox .pt-control.pt-switch input:disabled:indeterminate~.pt-control-indicator,.pt-control.pt-switch .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator,.pt-control.pt-switch input:disabled:checked~.pt-control-indicator{background-color:rgba(19,124,189,.5)}.pt-control.pt-align-right{padding-right:26px;padding-left:0}.pt-control.pt-align-right .pt-control-indicator{right:0;left:auto}.pt-control.pt-large{padding-left:30px;line-height:20px;font-size:16px}.pt-control.pt-large .pt-control-indicator{width:20px;height:20px;line-height:20px;font-family:Icons20;font-size:20px}.pt-input-group .pt-icon,.pt-select::after{font-family:Icons16,sans-serif;font-weight:400;-webkit-font-smoothing:antialiased}.pt-control.pt-large.pt-checkbox .pt-control.pt-checkbox:hover input:indeterminate~.pt-control-indicator::before,.pt-control.pt-large.pt-checkbox input:checked~.pt-control-indicator::before,.pt-control.pt-large.pt-checkbox input:indeterminate~.pt-control-indicator::before{top:1px}.pt-control.pt-large.pt-radio .pt-control-indicator{font-size:8px}.pt-control.pt-large.pt-switch{padding-left:42px}.pt-control.pt-large.pt-switch .pt-control-indicator{width:32px;height:20px}.pt-control.pt-large.pt-switch .pt-control-indicator::before{width:16px;height:16px}.pt-control.pt-checkbox .pt-control.pt-large.pt-switch input:indeterminate~.pt-control-indicator,.pt-control.pt-large.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-control.pt-large.pt-switch input:checked~.pt-control-indicator{width:32px;height:20px}.pt-control.pt-checkbox .pt-control.pt-large.pt-switch input:indeterminate~.pt-control-indicator::before,.pt-control.pt-large.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-control.pt-large.pt-switch input:checked~.pt-control-indicator::before{left:14px}.pt-dark .pt-control{color:#f5f8fa}.pt-dark .pt-control.pt-disabled{color:rgba(92,112,128,.5)}.pt-dark .pt-control .pt-control-indicator{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control input:checked~.pt-control-indicator,.pt-dark .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator{box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control:hover .pt-control-indicator{background:#30404d;background:linear-gradient(to bottom,rgba(16,22,26,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d}.pt-dark .pt-control input:not(:disabled):active~.pt-control-indicator{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background:#202b33}.pt-dark .pt-control input:not(:disabled):active:checked~.pt-control-indicator,.pt-dark .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator{box-shadow:0 0 0 1px rgba(16,22,26,.4),inset 0 1px 2px rgba(16,22,26,.2);background-color:#0e5a8a}.pt-dark .pt-control input:disabled~.pt-control-indicator{box-shadow:none;background:rgba(57,75,89,.5);cursor:not-allowed}.pt-dark .pt-control input:disabled:checked~.pt-control-indicator,.pt-dark .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator{box-shadow:none;background:rgba(14,90,138,.5)}.pt-dark .pt-control.pt-checkbox input:checked:disabled~.pt-control-indicator::before,.pt-dark .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator::before{color:rgba(191,204,214,.5)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-radio input:disabled:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-radio .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-radio input:checked:disabled~.pt-control-indicator::before{background:rgba(191,204,214,.5)}.pt-dark .pt-control.pt-switch .pt-control-indicator{box-shadow:none;background:rgba(16,22,26,.5)}.pt-dark .pt-control.pt-switch .pt-control-indicator::before{box-shadow:0 0 0 1px rgba(16,22,26,.4);background:#30404d}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch input:checked~.pt-control-indicator{box-shadow:none;background-color:#137cbd}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch input:checked~.pt-control-indicator::before{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control.pt-switch:hover .pt-control-indicator{background:rgba(16,22,26,.7)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch:hover input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch:hover .pt-control.pt-checkbox input:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch:hover input:checked~.pt-control-indicator{background:#2b95d6}.pt-dark .pt-control.pt-switch input:not(:disabled):active~.pt-control-indicator{box-shadow:none;background:rgba(16,22,26,.9)}.pt-dark .pt-control.pt-switch input:not(:disabled):active~.pt-control-indicator::before{box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator{background:#48aff0}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:not(:disabled):active:indeterminate~.pt-control-indicator::before,.pt-dark .pt-control.pt-switch input:not(:disabled):active:checked~.pt-control-indicator::before{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-control.pt-switch input:disabled~.pt-control-indicator{background:rgba(57,75,89,.5)}.pt-dark .pt-control.pt-switch input:disabled~.pt-control-indicator::before{box-shadow:none;background:rgba(16,22,26,.4)}.pt-control.pt-checkbox .pt-dark .pt-control.pt-switch input:disabled:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch .pt-control.pt-checkbox input:disabled:indeterminate~.pt-control-indicator,.pt-dark .pt-control.pt-switch input:disabled:checked~.pt-control-indicator{background:rgba(14,90,138,.5)}.pt-file-upload{display:inline-block;position:relative;cursor:pointer;height:30px}.pt-file-upload-input,.pt-file-upload-input::after{position:absolute;height:30px;line-height:30px;right:0;top:0}.pt-file-upload input{opacity:0;margin:0;min-width:200px}.pt-file-upload input.pt-disabled+.pt-file-upload-input,.pt-file-upload input:disabled+.pt-file-upload-input{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5);resize:none}.pt-file-upload input.pt-disabled+.pt-file-upload-input::after,.pt-file-upload input:disabled+.pt-file-upload-input::after{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-file-upload input.pt-disabled+.pt-file-upload-input::after.pt-active,.pt-file-upload input:disabled+.pt-file-upload-input::after.pt-active{background:rgba(206,217,224,.7)}.pt-dark .pt-file-upload input.pt-disabled+.pt-file-upload-input,.pt-dark .pt-file-upload input:disabled+.pt-file-upload-input{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload input.pt-disabled+.pt-file-upload-input::after,.pt-dark .pt-file-upload input:disabled+.pt-file-upload-input::after{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload input.pt-disabled+.pt-file-upload-input::after.pt-active,.pt-dark .pt-file-upload input:disabled+.pt-file-upload-input::after.pt-active{background:rgba(57,75,89,.7)}.pt-file-upload.pt-fill{width:100%}.pt-file-upload-input{border:none;border-radius:3px 0 0 3px;box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2);background:#fff;padding:0 10px;font-size:14px;font-weight:400;-webkit-appearance:none;appearance:none;left:0;margin-right:70px;color:#5c7080;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pt-menu-item,.pt-skeleton{-moz-user-select:none;-ms-user-select:none}.pt-file-upload-input::-webkit-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input::-moz-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input:-ms-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input::placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-file-upload-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-file-upload-input.pt-round,.pt-file-upload-input[type=search]{border-radius:30px;-moz-box-sizing:border-box;box-sizing:border-box;padding-left:10px}.pt-file-upload-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.15)}.pt-file-upload-input.pt-disabled,.pt-file-upload-input:disabled{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5);resize:none}.pt-file-upload-input::after,.pt-file-upload-input::after:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1)}.pt-file-upload-input::after{background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;color:#182026;left:100%;margin-left:-1px;border-radius:0 3px 3px 0;width:70px;padding:0 10px;text-align:center;content:\"Browse\"}.pt-file-upload-input::after:hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;background-clip:padding-box}.pt-file-upload-input::after.pt-active,.pt-file-upload-input::after:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-file-upload-input::after.pt-disabled,.pt-file-upload-input::after:disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-file-upload-input::after.pt-disabled.pt-active,.pt-file-upload-input::after:disabled.pt-active{background:rgba(206,217,224,.7)}.pt-file-upload-input:hover::after{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1);background-clip:padding-box}.pt-file-upload-input:active::after{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-large .pt-file-upload-input{margin-right:85px;height:40px;line-height:40px;font-size:16px}.pt-large .pt-file-upload-input::after{width:85px;height:40px;padding:0 15px;line-height:40px;font-size:16px}.pt-dark .pt-file-upload-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#bfccd6}.pt-dark .pt-file-upload-input::-webkit-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::-moz-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input:-ms-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input.pt-disabled,.pt-dark .pt-file-upload-input:disabled{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::after{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-file-upload-input::after.pt-active,.pt-dark .pt-file-upload-input::after:active,.pt-dark .pt-file-upload-input::after:hover{color:#f5f8fa}.pt-dark .pt-file-upload-input::after:hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input::after.pt-active,.pt-dark .pt-file-upload-input::after:active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-file-upload-input::after.pt-disabled,.pt-dark .pt-file-upload-input::after:disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-file-upload-input::after.pt-disabled.pt-active,.pt-dark .pt-file-upload-input::after:disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-file-upload-input::after .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-dark .pt-file-upload-input:hover::after{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-file-upload-input:active::after{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-form-group{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;margin:0 0 15px}.pt-form-group label.pt-label{margin-bottom:5px}.pt-form-group .pt-form-helper-text{margin-top:5px;color:#5c7080;font-size:12px}.pt-form-group.pt-intent-primary .pt-form-helper-text{color:#106ba3}.pt-form-group.pt-intent-success .pt-form-helper-text{color:#0d8050}.pt-form-group.pt-intent-warning .pt-form-helper-text{color:#bf7326}.pt-form-group.pt-intent-danger .pt-form-helper-text{color:#c23030}.pt-form-group.pt-inline{-webkit-flex-direction:row;flex-direction:row}.pt-form-group.pt-inline.pt-large label.pt-label{margin:0 10px 0 0;line-height:40px}.pt-form-group.pt-inline label.pt-label{margin:0 10px 0 0;line-height:30px}.pt-form-group.pt-disabled .pt-form-helper-text,.pt-form-group.pt-disabled .pt-label,.pt-form-group.pt-disabled .pt-text-muted{color:rgba(92,112,128,.5)!important}.pt-dark .pt-form-group.pt-intent-primary .pt-form-helper-text{color:#48aff0}.pt-dark .pt-form-group.pt-intent-success .pt-form-helper-text{color:#3dcc91}.pt-dark .pt-form-group.pt-intent-warning .pt-form-helper-text{color:#ffb366}.pt-dark .pt-form-group.pt-intent-danger .pt-form-helper-text{color:#ff7373}.pt-dark .pt-form-group .pt-form-helper-text{color:#bfccd6}.pt-dark .pt-form-group.pt-disabled .pt-form-helper-text,.pt-dark .pt-form-group.pt-disabled .pt-label,.pt-dark .pt-form-group.pt-disabled .pt-text-muted{color:rgba(191,204,214,.5)!important}.pt-input-group{display:block;position:relative}.pt-input-group .pt-input{position:relative;width:100%}.pt-input-group .pt-input:not(:first-child){padding-left:30px}.pt-input-group .pt-input:not(:last-child){padding-right:30px}.pt-input-group .pt-input-action,.pt-input-group>.pt-button,.pt-input-group>.pt-icon{position:absolute;top:0}.pt-input-group .pt-input-action:first-child,.pt-input-group>.pt-button:first-child,.pt-input-group>.pt-icon:first-child{left:0}.pt-input-group .pt-input-action:last-child,.pt-input-group>.pt-button:last-child,.pt-input-group>.pt-icon:last-child{right:0}.pt-input-group .pt-button{min-width:24px;min-height:24px;line-height:24px;margin:3px;padding-top:0;padding-bottom:0}.pt-input-group .pt-icon{font-size:16px;z-index:1;margin:0 7px;line-height:30px;color:#5c7080}.pt-input-group .pt-spinner{margin:3px}.pt-input-group .pt-tag{margin:5px}.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus),.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus){color:#5c7080}.pt-dark .pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus),.pt-dark .pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus){color:#bfccd6}.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-standard,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:not(:hover):not(:focus) .pt-icon-standard{color:#5c7080}.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-button.pt-minimal:disabled .pt-icon-standard,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled .pt-icon,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled .pt-icon-large,.pt-input-group .pt-input:not(:focus)+.pt-input-action .pt-button.pt-minimal:disabled .pt-icon-standard{color:rgba(92,112,128,.5)!important}.pt-input-group.pt-disabled{cursor:not-allowed}.pt-input-group.pt-disabled .pt-icon{color:rgba(92,112,128,.5)}.pt-input-group.pt-large .pt-button{min-width:30px;min-height:30px;margin:5px;line-height:0}.pt-input-group.pt-large .pt-icon{margin:0 12px;line-height:40px}.pt-input-group.pt-large .pt-input{height:40px;line-height:40px;font-size:16px}.pt-input-group.pt-large .pt-input.pt-round,.pt-input-group.pt-large .pt-input[type=search]{padding:0 15px}.pt-input-group.pt-large .pt-input:not(:first-child){padding-left:40px}.pt-input-group.pt-large .pt-input:not(:last-child){padding-right:40px}.pt-input-group.pt-large .pt-spinner{margin:8px}.pt-input-group.pt-round .pt-button,.pt-input-group.pt-round .pt-input,.pt-input-group.pt-round .pt-tag{border-radius:30px}.pt-dark .pt-input-group .pt-icon{color:#bfccd6}.pt-dark .pt-input-group.pt-disabled .pt-icon{color:rgba(191,204,214,.5)}.pt-input-group.pt-intent-primary .pt-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px #137cbd,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-primary .pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-primary .pt-input[readonly]{box-shadow:inset 0 0 0 1px #137cbd}.pt-input-group.pt-intent-primary .pt-input.pt-disabled,.pt-input-group.pt-intent-primary .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-primary .pt-icon{color:#106ba3}.pt-dark .pt-input-group.pt-intent-primary .pt-icon{color:#48aff0}.pt-input-group.pt-intent-success .pt-input{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px #0f9960,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-success .pt-input:focus{box-shadow:0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-success .pt-input[readonly]{box-shadow:inset 0 0 0 1px #0f9960}.pt-input-group.pt-intent-success .pt-input.pt-disabled,.pt-input-group.pt-intent-success .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-success .pt-icon{color:#0d8050}.pt-dark .pt-input-group.pt-intent-success .pt-icon{color:#3dcc91}.pt-input-group.pt-intent-warning .pt-input{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px #d9822b,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-warning .pt-input:focus{box-shadow:0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-warning .pt-input[readonly]{box-shadow:inset 0 0 0 1px #d9822b}.pt-input-group.pt-intent-warning .pt-input.pt-disabled,.pt-input-group.pt-intent-warning .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-warning .pt-icon{color:#bf7326}.pt-dark .pt-input-group.pt-intent-warning .pt-icon{color:#ffb366}.pt-input-group.pt-intent-danger .pt-input{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px #db3737,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-danger .pt-input:focus{box-shadow:0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input-group.pt-intent-danger .pt-input[readonly]{box-shadow:inset 0 0 0 1px #db3737}.pt-input-group.pt-intent-danger .pt-input.pt-disabled,.pt-input-group.pt-intent-danger .pt-input:disabled{box-shadow:none}.pt-input-group.pt-intent-danger .pt-icon{color:#c23030}.pt-dark .pt-input-group.pt-intent-danger .pt-icon{color:#ff7373}.pt-input{border:none;border-radius:3px;box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2);background:#fff;height:30px;padding:0 10px;line-height:30px;color:#182026;font-size:14px;font-weight:400;-webkit-appearance:none;appearance:none}.pt-input::-webkit-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input::-moz-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input:-ms-input-placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input::placeholder{opacity:1;color:rgba(92,112,128,.5)}.pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-round,.pt-input[type=search]{border-radius:30px;-moz-box-sizing:border-box;box-sizing:border-box;padding-left:10px}.pt-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.15)}.pt-input.pt-disabled,.pt-input:disabled{box-shadow:none;background:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5);resize:none}.pt-input.pt-large{height:40px;line-height:40px;font-size:16px}.pt-input.pt-large.pt-round,.pt-input.pt-large[type=search]{padding:0 15px}.pt-input.pt-fill{width:100%}.pt-dark .pt-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#f5f8fa}.pt-dark .pt-input::-webkit-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input::-moz-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input:-ms-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input::placeholder{color:rgba(191,204,214,.5)}.pt-dark .pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-disabled,.pt-dark .pt-input:disabled{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-input.pt-intent-primary{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px #137cbd,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-primary:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-primary[readonly]{box-shadow:inset 0 0 0 1px #137cbd}.pt-input.pt-intent-primary.pt-disabled,.pt-input.pt-intent-primary:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-primary{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px #137cbd,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-primary:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-primary[readonly]{box-shadow:inset 0 0 0 1px #137cbd}.pt-dark .pt-input.pt-intent-primary.pt-disabled,.pt-dark .pt-input.pt-intent-primary:disabled{box-shadow:none}.pt-input.pt-intent-success{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px #0f9960,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-success:focus{box-shadow:0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-success[readonly]{box-shadow:inset 0 0 0 1px #0f9960}.pt-input.pt-intent-success.pt-disabled,.pt-input.pt-intent-success:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-success{box-shadow:0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),0 0 0 0 rgba(15,153,96,0),inset 0 0 0 1px #0f9960,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-success:focus{box-shadow:0 0 0 1px #0f9960,0 0 0 1px #0f9960,0 0 0 3px rgba(15,153,96,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-success[readonly]{box-shadow:inset 0 0 0 1px #0f9960}.pt-dark .pt-input.pt-intent-success.pt-disabled,.pt-dark .pt-input.pt-intent-success:disabled{box-shadow:none}.pt-input.pt-intent-warning{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px #d9822b,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-warning:focus{box-shadow:0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-warning[readonly]{box-shadow:inset 0 0 0 1px #d9822b}.pt-input.pt-intent-warning.pt-disabled,.pt-input.pt-intent-warning:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-warning{box-shadow:0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),0 0 0 0 rgba(217,130,43,0),inset 0 0 0 1px #d9822b,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-warning:focus{box-shadow:0 0 0 1px #d9822b,0 0 0 1px #d9822b,0 0 0 3px rgba(217,130,43,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-warning[readonly]{box-shadow:inset 0 0 0 1px #d9822b}.pt-dark .pt-input.pt-intent-warning.pt-disabled,.pt-dark .pt-input.pt-intent-warning:disabled{box-shadow:none}.pt-input.pt-intent-danger{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px #db3737,inset 0 0 0 1px rgba(16,22,26,.15),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-danger:focus{box-shadow:0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 1px 1px rgba(16,22,26,.2)}.pt-input.pt-intent-danger[readonly]{box-shadow:inset 0 0 0 1px #db3737}.pt-input.pt-intent-danger.pt-disabled,.pt-input.pt-intent-danger:disabled{box-shadow:none}.pt-dark .pt-input.pt-intent-danger{box-shadow:0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),0 0 0 0 rgba(219,55,55,0),inset 0 0 0 1px #db3737,inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-danger:focus{box-shadow:0 0 0 1px #db3737,0 0 0 1px #db3737,0 0 0 3px rgba(219,55,55,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-input.pt-intent-danger[readonly]{box-shadow:inset 0 0 0 1px #db3737}.pt-dark .pt-input.pt-intent-danger.pt-disabled,.pt-dark .pt-input.pt-intent-danger:disabled{box-shadow:none}.pt-input::-ms-clear{display:none}textarea.pt-input{max-width:100%;height:auto;padding:10px;line-height:1.28581}textarea.pt-input.pt-large{line-height:1.28581;font-size:16px}.pt-dark textarea.pt-input{box-shadow:0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),0 0 0 0 rgba(19,124,189,0),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4);background:rgba(16,22,26,.3);color:#f5f8fa}.pt-dark textarea.pt-input::-webkit-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input::-moz-placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input:-ms-input-placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input::placeholder{color:rgba(191,204,214,.5)}.pt-dark textarea.pt-input:focus{box-shadow:0 0 0 1px #137cbd,0 0 0 1px #137cbd,0 0 0 3px rgba(19,124,189,.3),inset 0 0 0 1px rgba(16,22,26,.3),inset 0 1px 1px rgba(16,22,26,.4)}.pt-dark textarea.pt-input[readonly]{box-shadow:inset 0 0 0 1px rgba(16,22,26,.4)}.pt-dark textarea.pt-input.pt-disabled,.pt-dark textarea.pt-input:disabled{box-shadow:none;background:rgba(57,75,89,.5);color:rgba(191,204,214,.5)}.pt-select select,.pt-select select:hover{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 -1px 0 rgba(16,22,26,.1)}label.pt-label{display:block;margin:0 0 15px}label.pt-label .pt-input,label.pt-label .pt-select{display:block;margin-top:5px;text-transform:none}label.pt-label .pt-select select{width:100%;vertical-align:top;font-weight:400}label.pt-label.pt-inline{line-height:30px}label.pt-label.pt-inline .pt-input,label.pt-label.pt-inline .pt-input-group,label.pt-label.pt-inline .pt-select{display:inline-block;margin:0 0 0 5px;vertical-align:top}label.pt-label.pt-inline .pt-input-group .pt-input{margin-left:0}label.pt-label.pt-inline.pt-large{line-height:40px}label.pt-label.pt-disabled,label.pt-label.pt-disabled .pt-text-muted{color:rgba(92,112,128,.5)}.pt-dark label.pt-label{color:#f5f8fa}.pt-dark label.pt-label.pt-disabled,.pt-dark label.pt-label.pt-disabled .pt-text-muted{color:rgba(191,204,214,.5)}.pt-select{display:inline-block;position:relative;vertical-align:middle;letter-spacing:normal}.pt-select select::-ms-expand{display:none}.pt-key,.pt-select select{display:inline-block;vertical-align:middle}.pt-select.pt-large::after{top:0;right:12px;line-height:40px}.pt-select.pt-fill,.pt-select.pt-fill select{width:100%}.pt-dark .pt-select option{background-color:#30404d;color:#f5f8fa}.pt-dark .pt-select::after{color:#bfccd6}.pt-select select{border:none;cursor:pointer;font-size:14px;background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;color:#182026;-webkit-appearance:none;-moz-appearance:none;border-radius:3px;height:30px;padding:0 25px 0 10px}.pt-select select:hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;background-clip:padding-box}.pt-select select.pt-active,.pt-select select:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-select select.pt-disabled,.pt-select select:disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-select select.pt-disabled.pt-active,.pt-select select:disabled.pt-active{background:rgba(206,217,224,.7)}.pt-select.pt-minimal select{box-shadow:none;background:0 0}.pt-select.pt-minimal select:focus,.pt-select.pt-minimal select:hover{box-shadow:none;background:rgba(167,182,194,.3);text-decoration:none;color:#182026}.pt-select.pt-minimal select.pt-active,.pt-select.pt-minimal select:active{box-shadow:none;background:rgba(115,134,148,.3);color:#182026}.pt-select.pt-minimal select.pt-disabled,.pt-select.pt-minimal select.pt-disabled:hover,.pt-select.pt-minimal select:disabled,.pt-select.pt-minimal select:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-dark .pt-select.pt-minimal select,.pt-select.pt-minimal .pt-dark select{box-shadow:none;background:0 0;color:inherit}.pt-dark .pt-select.pt-minimal select.pt-active,.pt-dark .pt-select.pt-minimal select:active,.pt-dark .pt-select.pt-minimal select:hover,.pt-select.pt-minimal .pt-dark select.pt-active,.pt-select.pt-minimal .pt-dark select:active,.pt-select.pt-minimal .pt-dark select:hover{box-shadow:none}.pt-dark .pt-select.pt-minimal select:hover,.pt-select.pt-minimal .pt-dark select:hover{background:rgba(138,155,168,.15)}.pt-dark .pt-select.pt-minimal select.pt-active,.pt-dark .pt-select.pt-minimal select:active,.pt-select.pt-minimal .pt-dark select.pt-active,.pt-select.pt-minimal .pt-dark select:active{background:rgba(138,155,168,.3);color:#f5f8fa}.pt-dark .pt-select.pt-minimal select.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-disabled:hover,.pt-dark .pt-select.pt-minimal select:disabled,.pt-dark .pt-select.pt-minimal select:disabled:hover,.pt-select.pt-minimal .pt-dark select.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-disabled:hover,.pt-select.pt-minimal .pt-dark select:disabled,.pt-select.pt-minimal .pt-dark select:disabled:hover{background:0 0;cursor:not-allowed;color:rgba(191,204,214,.5)}.pt-select.pt-minimal select.pt-intent-primary{color:#106ba3}.pt-select.pt-minimal select.pt-intent-primary.pt-active,.pt-select.pt-minimal select.pt-intent-primary:active,.pt-select.pt-minimal select.pt-intent-primary:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-primary:focus,.pt-select.pt-minimal select.pt-intent-primary:hover{background:rgba(19,124,189,.15);color:#106ba3}.pt-select.pt-minimal select.pt-intent-primary.pt-active,.pt-select.pt-minimal select.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#106ba3}.pt-select.pt-minimal select.pt-intent-primary.pt-disabled,.pt-select.pt-minimal select.pt-intent-primary:disabled{background:0 0;color:rgba(16,107,163,.5)}.pt-select.pt-minimal select.pt-intent-primary .pt-button-spinner .pt-spinner-head{stroke:#106ba3}.pt-dark .pt-select.pt-minimal select.pt-intent-primary,.pt-select.pt-minimal .pt-dark select.pt-intent-primary{color:#48aff0}.pt-dark .pt-select.pt-minimal select.pt-intent-primary:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-primary:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:hover{background:rgba(19,124,189,.2);color:#48aff0}.pt-dark .pt-select.pt-minimal select.pt-intent-primary.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-primary:active,.pt-select.pt-minimal .pt-dark select.pt-intent-primary.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:active{background:rgba(19,124,189,.3);color:#48aff0}.pt-dark .pt-select.pt-minimal select.pt-intent-primary.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-primary:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-primary.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-primary:disabled{background:0 0;color:rgba(72,175,240,.5)}.pt-select.pt-minimal select.pt-intent-success{color:#0d8050}.pt-select.pt-minimal select.pt-intent-success.pt-active,.pt-select.pt-minimal select.pt-intent-success:active,.pt-select.pt-minimal select.pt-intent-success:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-success:focus,.pt-select.pt-minimal select.pt-intent-success:hover{background:rgba(15,153,96,.15);color:#0d8050}.pt-select.pt-minimal select.pt-intent-success.pt-active,.pt-select.pt-minimal select.pt-intent-success:active{background:rgba(15,153,96,.3);color:#0d8050}.pt-select.pt-minimal select.pt-intent-success.pt-disabled,.pt-select.pt-minimal select.pt-intent-success:disabled{background:0 0;color:rgba(13,128,80,.5)}.pt-select.pt-minimal select.pt-intent-success .pt-button-spinner .pt-spinner-head{stroke:#0d8050}.pt-dark .pt-select.pt-minimal select.pt-intent-success,.pt-select.pt-minimal .pt-dark select.pt-intent-success{color:#3dcc91}.pt-dark .pt-select.pt-minimal select.pt-intent-success:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-success:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-success:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-success:hover{background:rgba(15,153,96,.2);color:#3dcc91}.pt-dark .pt-select.pt-minimal select.pt-intent-success.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-success:active,.pt-select.pt-minimal .pt-dark select.pt-intent-success.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-success:active{background:rgba(15,153,96,.3);color:#3dcc91}.pt-dark .pt-select.pt-minimal select.pt-intent-success.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-success:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-success.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-success:disabled{background:0 0;color:rgba(61,204,145,.5)}.pt-select.pt-minimal select.pt-intent-warning{color:#bf7326}.pt-select.pt-minimal select.pt-intent-warning.pt-active,.pt-select.pt-minimal select.pt-intent-warning:active,.pt-select.pt-minimal select.pt-intent-warning:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-warning:focus,.pt-select.pt-minimal select.pt-intent-warning:hover{background:rgba(217,130,43,.15);color:#bf7326}.pt-select.pt-minimal select.pt-intent-warning.pt-active,.pt-select.pt-minimal select.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#bf7326}.pt-select.pt-minimal select.pt-intent-warning.pt-disabled,.pt-select.pt-minimal select.pt-intent-warning:disabled{background:0 0;color:rgba(191,115,38,.5)}.pt-select.pt-minimal select.pt-intent-warning .pt-button-spinner .pt-spinner-head{stroke:#bf7326}.pt-dark .pt-select.pt-minimal select.pt-intent-warning,.pt-select.pt-minimal .pt-dark select.pt-intent-warning{color:#ffb366}.pt-dark .pt-select.pt-minimal select.pt-intent-warning:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-warning:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:hover{background:rgba(217,130,43,.2);color:#ffb366}.pt-dark .pt-select.pt-minimal select.pt-intent-warning.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-warning:active,.pt-select.pt-minimal .pt-dark select.pt-intent-warning.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:active{background:rgba(217,130,43,.3);color:#ffb366}.pt-dark .pt-select.pt-minimal select.pt-intent-warning.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-warning:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-warning.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-warning:disabled{background:0 0;color:rgba(255,179,102,.5)}.pt-select.pt-minimal select.pt-intent-danger{color:#c23030}.pt-select.pt-minimal select.pt-intent-danger.pt-active,.pt-select.pt-minimal select.pt-intent-danger:active,.pt-select.pt-minimal select.pt-intent-danger:hover{box-shadow:none}.pt-select.pt-minimal select.pt-intent-danger:focus,.pt-select.pt-minimal select.pt-intent-danger:hover{background:rgba(219,55,55,.15);color:#c23030}.pt-select.pt-minimal select.pt-intent-danger.pt-active,.pt-select.pt-minimal select.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#c23030}.pt-select.pt-minimal select.pt-intent-danger.pt-disabled,.pt-select.pt-minimal select.pt-intent-danger:disabled{background:0 0;color:rgba(194,48,48,.5)}.pt-select.pt-minimal select.pt-intent-danger .pt-button-spinner .pt-spinner-head{stroke:#c23030}.pt-dark .pt-select.pt-minimal select.pt-intent-danger,.pt-select.pt-minimal .pt-dark select.pt-intent-danger{color:#ff7373}.pt-dark .pt-select.pt-minimal select.pt-intent-danger:focus,.pt-dark .pt-select.pt-minimal select.pt-intent-danger:hover,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:focus,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:hover{background:rgba(219,55,55,.2);color:#ff7373}.pt-dark .pt-select.pt-minimal select.pt-intent-danger.pt-active,.pt-dark .pt-select.pt-minimal select.pt-intent-danger:active,.pt-select.pt-minimal .pt-dark select.pt-intent-danger.pt-active,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:active{background:rgba(219,55,55,.3);color:#ff7373}.pt-dark .pt-select.pt-minimal select.pt-intent-danger.pt-disabled,.pt-dark .pt-select.pt-minimal select.pt-intent-danger:disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-danger.pt-disabled,.pt-select.pt-minimal .pt-dark select.pt-intent-danger:disabled{background:0 0;color:rgba(255,115,115,.5)}.pt-select.pt-large select{height:40px;padding-right:35px;font-size:16px}.pt-dark .pt-select select{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-select select.pt-active,.pt-dark .pt-select select:active,.pt-dark .pt-select select:hover{color:#f5f8fa}.pt-dark .pt-select select:hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-select select.pt-active,.pt-dark .pt-select select:active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-select select.pt-disabled,.pt-dark .pt-select select:disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-select select.pt-disabled.pt-active,.pt-dark .pt-select select:disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-select select .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-select select:disabled{box-shadow:none;background-color:rgba(206,217,224,.5);cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-select::after{font-size:16px;position:absolute;top:0;right:7px;line-height:30px;color:#5c7080;content:\"⌄\"}.pt-disabled.pt-select::after{color:rgba(92,112,128,.5)}.pt-numeric-input .pt-button-group.pt-vertical>.pt-button:first-child{min-height:16px;border-radius:0 3px 0 0}.pt-numeric-input .pt-button-group.pt-vertical>.pt-button:last-child{min-height:15px;border-radius:0 0 3px}.pt-numeric-input .pt-button-group.pt-vertical>.pt-button[class*=pt-icon-]::before{display:block;height:14px;overflow:hidden;line-height:14px}.pt-numeric-input .pt-button-group.pt-vertical:first-child>.pt-button:first-child{border-radius:3px 0 0}.pt-numeric-input .pt-button-group.pt-vertical:first-child>.pt-button:last-child{border-radius:0 0 0 3px}.pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:first-child{min-height:21px}.pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:last-child{min-height:20px}.pt-dark .pt-numeric-input .pt-button-group.pt-vertical>.pt-button{line-height:14px}.pt-dark .pt-numeric-input .pt-button-group.pt-vertical>.pt-button:first-child{min-height:15px}.pt-dark .pt-numeric-input .pt-button-group.pt-vertical>.pt-button:last-child{min-height:14px}.pt-dark .pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:first-child{min-height:20px}.pt-dark .pt-numeric-input.pt-large .pt-button-group.pt-vertical>.pt-button:last-child{min-height:19px}form{display:block}.pt-key{border-radius:2px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 1px 1px rgba(16,22,26,.2),0 2px 6px rgba(16,22,26,.2);background:#fff;min-width:25px;height:25px;padding:3px 5px;text-align:center;line-height:20px;color:#5c7080;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,\"Open Sans\",\"Helvetica Neue\",Icons16,sans-serif;font-size:12px}.pt-menu-item::before,.pt-submenu>.pt-popover-target>.pt-menu-item::after{font-family:Icons16,sans-serif;font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.pt-key.pt-modifier-key{padding:3px 8px 3px 6px}.pt-key.pt-modifier-key .pt-icon-standard{margin-right:5px}.pt-dark .pt-key{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.4),0 2px 6px rgba(16,22,26,.4),inset 0 1px 0 rgba(255,255,255,.05);background:#394b59;color:#bfccd6}.pt-key-combo .pt-key:not(:last-child){margin-right:5px}.pt-hotkey-dialog{top:40px;padding-bottom:0}.pt-hotkey-dialog .pt-dialog-body{margin:0;padding:0}.pt-hotkey-dialog .pt-key-combo{-webkit-flex:1 0 auto;flex:1 0 auto;width:160px;text-align:right}.pt-hotkey-column{margin:auto;max-height:80vh;overflow-y:auto;padding:30px}.pt-menu-item,body.pt-overlay-open{overflow:hidden}.pt-hotkey-column .pt-hotkey-group{margin-bottom:30px}.pt-hotkey-column .pt-hotkey-group:not(:first-child){margin-top:50px}.pt-hotkey{display:-webkit-flex;display:flex;-webkit-align-items:baseline;align-items:baseline;-webkit-justify-content:space-between;justify-content:space-between;margin-right:0;margin-left:0}.pt-hotkey:not(:last-child){margin-bottom:10px}.pt-submenu>.pt-popover-target{display:inherit}.pt-submenu>.pt-popover-target>.pt-menu-item{padding-right:26px}.pt-submenu>.pt-popover-target>.pt-menu-item::after{line-height:1;font-size:16px;position:absolute;right:5px;content:\"〉\"}.pt-large .pt-submenu>.pt-popover-target>.pt-menu-item::after{line-height:20px}.pt-submenu .pt-popover{position:relative;top:-5px;left:5px}.pt-navbar.pt-fixed-top,.pt-overlay-backdrop{top:0;right:0;left:0;position:fixed}.pt-submenu .pt-popover.pt-align-left{right:5px;left:auto}.pt-menu{margin:0;border-radius:3px;background:#fff;min-width:180px;padding:5px;list-style:none;text-align:left;color:#182026}.pt-dark .pt-menu-item,.pt-menu-item{color:inherit}.pt-menu-divider{display:block;margin:5px;border-top:1px solid rgba(16,22,26,.15)}.pt-menu-item{display:block;border-radius:2px;padding:7px;line-height:16px;-webkit-user-select:none;user-select:none}.pt-menu-item:hover,.pt-submenu>.pt-popover-open>.pt-menu-item{background-color:rgba(167,182,194,.3);cursor:pointer}.pt-dark .pt-menu-item:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-menu-item{background-color:rgba(138,155,168,.15);color:inherit}.pt-dark .pt-menu-item.pt-disabled{background-color:inherit}.pt-menu-item.pt-intent-primary,.pt-menu-item.pt-intent-primary .pt-menu-item-label,.pt-menu-item.pt-intent-primary::after,.pt-menu-item.pt-intent-primary::before{color:#106ba3}.pt-menu-item.pt-intent-primary.pt-active,.pt-menu-item.pt-intent-primary:hover,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item{background-color:#137cbd}.pt-menu-item.pt-intent-primary:active{background-color:#106ba3}.pt-menu-item.pt-intent-primary.pt-active,.pt-menu-item.pt-intent-primary.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-primary.pt-active::after,.pt-menu-item.pt-intent-primary.pt-active::before,.pt-menu-item.pt-intent-primary:active,.pt-menu-item.pt-intent-primary:active .pt-menu-item-label,.pt-menu-item.pt-intent-primary:active::after,.pt-menu-item.pt-intent-primary:active::before,.pt-menu-item.pt-intent-primary:hover,.pt-menu-item.pt-intent-primary:hover .pt-menu-item-label,.pt-menu-item.pt-intent-primary:hover::after,.pt-menu-item.pt-intent-primary:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::before{color:#fff}.pt-menu-item.pt-intent-success,.pt-menu-item.pt-intent-success .pt-menu-item-label,.pt-menu-item.pt-intent-success::after,.pt-menu-item.pt-intent-success::before{color:#0d8050}.pt-menu-item.pt-intent-success.pt-active,.pt-menu-item.pt-intent-success:hover,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item{background-color:#0f9960}.pt-menu-item.pt-intent-success:active{background-color:#0d8050}.pt-menu-item.pt-intent-success.pt-active,.pt-menu-item.pt-intent-success.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-success.pt-active::after,.pt-menu-item.pt-intent-success.pt-active::before,.pt-menu-item.pt-intent-success:active,.pt-menu-item.pt-intent-success:active .pt-menu-item-label,.pt-menu-item.pt-intent-success:active::after,.pt-menu-item.pt-intent-success:active::before,.pt-menu-item.pt-intent-success:hover,.pt-menu-item.pt-intent-success:hover .pt-menu-item-label,.pt-menu-item.pt-intent-success:hover::after,.pt-menu-item.pt-intent-success:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::before{color:#fff}.pt-menu-item.pt-intent-warning,.pt-menu-item.pt-intent-warning .pt-menu-item-label,.pt-menu-item.pt-intent-warning::after,.pt-menu-item.pt-intent-warning::before{color:#bf7326}.pt-menu-item.pt-intent-warning.pt-active,.pt-menu-item.pt-intent-warning:hover,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item{background-color:#d9822b}.pt-menu-item.pt-intent-warning:active{background-color:#bf7326}.pt-menu-item.pt-intent-warning.pt-active,.pt-menu-item.pt-intent-warning.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-warning.pt-active::after,.pt-menu-item.pt-intent-warning.pt-active::before,.pt-menu-item.pt-intent-warning:active,.pt-menu-item.pt-intent-warning:active .pt-menu-item-label,.pt-menu-item.pt-intent-warning:active::after,.pt-menu-item.pt-intent-warning:active::before,.pt-menu-item.pt-intent-warning:hover,.pt-menu-item.pt-intent-warning:hover .pt-menu-item-label,.pt-menu-item.pt-intent-warning:hover::after,.pt-menu-item.pt-intent-warning:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::before{color:#fff}.pt-menu-item.pt-intent-danger,.pt-menu-item.pt-intent-danger .pt-menu-item-label,.pt-menu-item.pt-intent-danger::after,.pt-menu-item.pt-intent-danger::before{color:#c23030}.pt-menu-item.pt-intent-danger.pt-active,.pt-menu-item.pt-intent-danger:hover,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item{background-color:#db3737}.pt-menu-item.pt-intent-danger:active{background-color:#c23030}.pt-menu-item.pt-intent-danger.pt-active,.pt-menu-item.pt-intent-danger.pt-active .pt-menu-item-label,.pt-menu-item.pt-intent-danger.pt-active::after,.pt-menu-item.pt-intent-danger.pt-active::before,.pt-menu-item.pt-intent-danger:active,.pt-menu-item.pt-intent-danger:active .pt-menu-item-label,.pt-menu-item.pt-intent-danger:active::after,.pt-menu-item.pt-intent-danger:active::before,.pt-menu-item.pt-intent-danger:hover,.pt-menu-item.pt-intent-danger:hover .pt-menu-item-label,.pt-menu-item.pt-intent-danger:hover::after,.pt-menu-item.pt-intent-danger:hover::before,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item .pt-menu-item-label,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::after,.pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::before{color:#fff}.pt-menu-item::before{line-height:1;font-size:16px;float:left;margin-right:7px}.pt-menu-item .pt-menu-item-label,.pt-menu-item::after,.pt-menu-item::before{color:#5c7080}.pt-menu-item:hover,.pt-submenu>.pt-popover-open>.pt-menu-item{color:inherit}.pt-menu-item.pt-disabled,.pt-menu-item.pt-disabled .pt-menu-item-label,.pt-menu-item.pt-disabled::after,.pt-menu-item.pt-disabled::before{color:rgba(92,112,128,.5)!important}.pt-menu-item.pt-active,.pt-menu-item:active{background-color:rgba(115,134,148,.3)}.pt-menu-item.pt-disabled{outline:0!important;background-color:inherit!important;cursor:not-allowed!important}.pt-overlay-backdrop:focus,.pt-slider,.pt-transition-container:focus{outline:0}.pt-large .pt-menu-item{padding:10px 7px;line-height:20px;font-size:16px}.pt-large .pt-menu-item::before{line-height:1;font-family:Icons20,sans-serif;font-size:20px;font-weight:400;font-style:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;margin-right:10px}.pt-submenu>.pt-popover-open>a.pt-menu-item,a.pt-menu-item,a.pt-menu-item:hover{text-decoration:none}button.pt-menu-item{border:none;background:0 0;width:100%;text-align:left}.pt-menu-item-label{float:right;margin-left:7px}.pt-menu-header{display:block;margin:5px;border-top:1px solid rgba(16,22,26,.15);cursor:default;padding-left:2px}.pt-menu-header:first-of-type{border-top:none}.pt-menu-header>h6{color:#182026;font-weight:600;overflow:hidden;margin:0;padding:10px 7px 0 1px;line-height:17px}.pt-menu-header:first-of-type>h6{padding-top:0}.pt-large .pt-menu-header>h6{padding-top:15px;padding-bottom:5px;font-size:18px}.pt-large .pt-menu-header:first-of-type>h6{padding-top:0}.pt-dark .pt-menu{background:#30404d;color:#f5f8fa}.pt-dark .pt-menu-item.pt-intent-primary,.pt-dark .pt-menu-item.pt-intent-primary .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary::after,.pt-dark .pt-menu-item.pt-intent-primary::before{color:#48aff0}.pt-dark .pt-menu-item.pt-intent-primary.pt-active,.pt-dark .pt-menu-item.pt-intent-primary:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item{background-color:#137cbd}.pt-dark .pt-menu-item.pt-intent-primary:active{background-color:#106ba3}.pt-dark .pt-menu-item.pt-intent-primary.pt-active,.pt-dark .pt-menu-item.pt-intent-primary.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary.pt-active::after,.pt-dark .pt-menu-item.pt-intent-primary.pt-active::before,.pt-dark .pt-menu-item.pt-intent-primary:active,.pt-dark .pt-menu-item.pt-intent-primary:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary:active::after,.pt-dark .pt-menu-item.pt-intent-primary:active::before,.pt-dark .pt-menu-item.pt-intent-primary:hover,.pt-dark .pt-menu-item.pt-intent-primary:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-primary:hover::after,.pt-dark .pt-menu-item.pt-intent-primary:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-primary.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-intent-success,.pt-dark .pt-menu-item.pt-intent-success .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success::after,.pt-dark .pt-menu-item.pt-intent-success::before{color:#3dcc91}.pt-dark .pt-menu-item.pt-intent-success.pt-active,.pt-dark .pt-menu-item.pt-intent-success:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item{background-color:#0f9960}.pt-dark .pt-menu-item.pt-intent-success:active{background-color:#0d8050}.pt-dark .pt-menu-item.pt-intent-success.pt-active,.pt-dark .pt-menu-item.pt-intent-success.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success.pt-active::after,.pt-dark .pt-menu-item.pt-intent-success.pt-active::before,.pt-dark .pt-menu-item.pt-intent-success:active,.pt-dark .pt-menu-item.pt-intent-success:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success:active::after,.pt-dark .pt-menu-item.pt-intent-success:active::before,.pt-dark .pt-menu-item.pt-intent-success:hover,.pt-dark .pt-menu-item.pt-intent-success:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-success:hover::after,.pt-dark .pt-menu-item.pt-intent-success:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-success.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-intent-warning,.pt-dark .pt-menu-item.pt-intent-warning .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning::after,.pt-dark .pt-menu-item.pt-intent-warning::before{color:#ffb366}.pt-dark .pt-menu-item.pt-intent-warning.pt-active,.pt-dark .pt-menu-item.pt-intent-warning:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item{background-color:#d9822b}.pt-dark .pt-menu-item.pt-intent-warning:active{background-color:#bf7326}.pt-dark .pt-menu-item.pt-intent-warning.pt-active,.pt-dark .pt-menu-item.pt-intent-warning.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning.pt-active::after,.pt-dark .pt-menu-item.pt-intent-warning.pt-active::before,.pt-dark .pt-menu-item.pt-intent-warning:active,.pt-dark .pt-menu-item.pt-intent-warning:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning:active::after,.pt-dark .pt-menu-item.pt-intent-warning:active::before,.pt-dark .pt-menu-item.pt-intent-warning:hover,.pt-dark .pt-menu-item.pt-intent-warning:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-warning:hover::after,.pt-dark .pt-menu-item.pt-intent-warning:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-warning.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-intent-danger,.pt-dark .pt-menu-item.pt-intent-danger .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger::after,.pt-dark .pt-menu-item.pt-intent-danger::before{color:#ff7373}.pt-dark .pt-menu-item.pt-intent-danger.pt-active,.pt-dark .pt-menu-item.pt-intent-danger:hover,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item{background-color:#db3737}.pt-dark .pt-menu-item.pt-intent-danger:active{background-color:#c23030}.pt-dark .pt-menu-item.pt-intent-danger.pt-active,.pt-dark .pt-menu-item.pt-intent-danger.pt-active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger.pt-active::after,.pt-dark .pt-menu-item.pt-intent-danger.pt-active::before,.pt-dark .pt-menu-item.pt-intent-danger:active,.pt-dark .pt-menu-item.pt-intent-danger:active .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger:active::after,.pt-dark .pt-menu-item.pt-intent-danger:active::before,.pt-dark .pt-menu-item.pt-intent-danger:hover,.pt-dark .pt-menu-item.pt-intent-danger:hover .pt-menu-item-label,.pt-dark .pt-menu-item.pt-intent-danger:hover::after,.pt-dark .pt-menu-item.pt-intent-danger:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item .pt-menu-item-label,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-intent-danger.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item .pt-menu-item-label,.pt-dark .pt-menu-item::after,.pt-dark .pt-menu-item::before{color:#bfccd6}.pt-dark .pt-menu-item:hover::after,.pt-dark .pt-menu-item:hover::before,.pt-dark .pt-submenu>.pt-popover-open>.pt-menu-item::after,.pt-dark .pt-submenu>.pt-popover-open>.pt-menu-item::before{color:#fff}.pt-dark .pt-menu-item.pt-active,.pt-dark .pt-menu-item:active{background-color:rgba(138,155,168,.3)}.pt-dark .pt-menu-item.pt-disabled,.pt-dark .pt-menu-item.pt-disabled .pt-menu-item-label,.pt-dark .pt-menu-item.pt-disabled::after,.pt-dark .pt-menu-item.pt-disabled::before{color:rgba(191,204,214,.5)!important}.pt-dark .pt-menu-divider,.pt-dark .pt-menu-header{border-color:rgba(255,255,255,.15)}.pt-dark .pt-menu-header>h6{color:#f5f8fa}.pt-label .pt-menu{margin-top:5px}.pt-navbar{position:relative;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.2);background-color:#fff;height:50px;padding:0 15px}.pt-dark .pt-navbar,.pt-navbar.pt-dark{background-color:#394b59}.pt-navbar.pt-dark{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-dark .pt-navbar{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 0 0 rgba(16,22,26,0),0 1px 1px rgba(16,22,26,.4)}.pt-navbar .pt-logo{margin-right:15px;width:20px}.pt-navbar-heading{margin-right:15px;font-size:16px}.pt-navbar-group{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;height:50px}.pt-navbar-group.pt-align-left{float:left}.pt-navbar-group.pt-align-right{float:right}.pt-navbar-divider{margin:0 10px;border-left:1px solid rgba(16,22,26,.15);height:20px}.pt-dark .pt-navbar-divider{border-left-color:rgba(255,255,255,.15)}.pt-non-ideal-state{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;margin:0 auto;width:100%;max-width:400px;height:100%}.pt-non-ideal-state>:not(:last-child){margin-bottom:20px}.pt-non-ideal-state-icon .pt-icon{color:rgba(92,112,128,.5);font-size:60px}.pt-dark .pt-non-ideal-state-icon .pt-icon{color:rgba(191,204,214,.5)}.pt-non-ideal-state-description{text-align:center}.pt-overlay-backdrop{bottom:0;opacity:1;z-index:20;background-color:rgba(16,22,26,.7);overflow:auto}.pt-overlay-backdrop.pt-overlay-appear,.pt-overlay-backdrop.pt-overlay-enter{opacity:0}.pt-overlay-backdrop.pt-overlay-appear-active,.pt-overlay-backdrop.pt-overlay-enter-active{opacity:1;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-overlay-backdrop.pt-overlay-leave{opacity:1}.pt-overlay-backdrop.pt-overlay-leave-active{opacity:0;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-overlay-content{position:fixed}.pt-overlay-inline .pt-overlay-backdrop,.pt-overlay-inline .pt-overlay-content{position:absolute}.pt-overlay-scroll-container.pt-overlay-open{position:fixed;top:0;right:0;bottom:0;left:0;z-index:20;overflow:auto}.pt-overlay-scroll-container.pt-overlay-open .pt-overlay-content{position:absolute}.pt-popover{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);-webkit-transform:scale(1);transform:scale(1);display:inline-block;z-index:20;border-radius:3px}.pt-popover .pt-popover-arrow{position:absolute;width:30px;height:30px}.pt-popover .pt-popover-arrow::before{margin:5px;width:20px;height:20px;box-shadow:1px 1px 6px rgba(16,22,26,.2)}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-popover{margin-top:-17px;margin-bottom:17px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-popover>.pt-popover-arrow{bottom:-11px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover{margin-left:17px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow{left:-11px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(0);transform:rotate(0)}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-popover{margin-top:17px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-popover>.pt-popover-arrow{top:-11px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-popover{margin-right:17px;margin-left:-17px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-popover>.pt-popover-arrow{right:-11px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-popover>.pt-popover-arrow svg{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.pt-tether-element-attached-middle>.pt-popover>.pt-popover-arrow{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.pt-tether-element-attached-center>.pt-popover>.pt-popover-arrow{right:50%;-webkit-transform:translateX(50%);transform:translateX(50%)}.pt-tether-element-attached-top.pt-tether-target-attached-top>.pt-popover>.pt-popover-arrow{top:-.39px}.pt-tether-element-attached-right.pt-tether-target-attached-right>.pt-popover>.pt-popover-arrow{right:-.39px}.pt-tether-element-attached-left.pt-tether-target-attached-left>.pt-popover>.pt-popover-arrow{left:-.39px}.pt-tether-element-attached-bottom.pt-tether-target-attached-bottom>.pt-popover>.pt-popover-arrow{bottom:-.39px}.pt-tether-element-attached-top.pt-tether-element-attached-left>.pt-popover{-webkit-transform-origin:top left;transform-origin:top left}.pt-tether-element-attached-top.pt-tether-element-attached-center>.pt-popover{-webkit-transform-origin:top center;transform-origin:top center}.pt-tether-element-attached-top.pt-tether-element-attached-right>.pt-popover{-webkit-transform-origin:top right;transform-origin:top right}.pt-tether-element-attached-middle.pt-tether-element-attached-left>.pt-popover{-webkit-transform-origin:center left;transform-origin:center left}.pt-tether-element-attached-middle.pt-tether-element-attached-center>.pt-popover{-webkit-transform-origin:center center;transform-origin:center center}.pt-tether-element-attached-middle.pt-tether-element-attached-right>.pt-popover{-webkit-transform-origin:center right;transform-origin:center right}.pt-tether-element-attached-bottom.pt-tether-element-attached-left>.pt-popover{-webkit-transform-origin:bottom left;transform-origin:bottom left}.pt-tether-element-attached-bottom.pt-tether-element-attached-center>.pt-popover{-webkit-transform-origin:bottom center;transform-origin:bottom center}.pt-tether-element-attached-bottom.pt-tether-element-attached-right>.pt-popover{-webkit-transform-origin:bottom right;transform-origin:bottom right}.pt-popover .pt-popover-content{background:#fff;color:inherit;position:relative;border-radius:3px}.pt-popover .pt-popover-arrow-border{fill:#10161a;fill-opacity:.1}.pt-popover .pt-popover-arrow-fill{fill:#fff}.pt-popover-appear>.pt-popover,.pt-popover-enter>.pt-popover{-webkit-transform:scale(.3);transform:scale(.3)}.pt-popover-appear-active>.pt-popover,.pt-popover-enter-active>.pt-popover{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-popover-leave>.pt-popover{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-leave-active>.pt-popover{-webkit-transform:scale(.3);transform:scale(.3);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-popover.pt-popover-content-sizing .pt-popover-content{max-width:350px;padding:20px}.pt-popover-target .pt-popover.pt-popover-content-sizing{width:350px}.pt-popover.pt-minimal{margin:0!important}.pt-popover.pt-minimal .pt-popover-arrow{display:none}.pt-popover-appear>.pt-popover.pt-minimal.pt-popover,.pt-popover-enter>.pt-popover.pt-minimal.pt-popover,.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-appear-active>.pt-popover.pt-minimal.pt-popover,.pt-popover-enter-active>.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-popover-leave>.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-leave-active>.pt-popover.pt-minimal.pt-popover{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-dark .pt-popover,.pt-popover.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-dark .pt-popover .pt-popover-content,.pt-popover.pt-dark .pt-popover-content{background:#30404d;color:inherit}.pt-dark .pt-popover .pt-popover-arrow::before,.pt-popover.pt-dark .pt-popover-arrow::before{box-shadow:1px 1px 6px rgba(16,22,26,.4)}.pt-dark .pt-popover .pt-popover-arrow-border,.pt-popover.pt-dark .pt-popover-arrow-border{fill:#10161a;fill-opacity:.2}.pt-dark .pt-popover .pt-popover-arrow-fill,.pt-popover.pt-dark .pt-popover-arrow-fill{fill:#30404d}.pt-popover-arrow::before{display:block;position:absolute;-webkit-transform:rotate(45deg);transform:rotate(45deg);border-radius:2px;content:\"\"}.pt-tether-pinned .pt-popover-arrow{display:none}.pt-popover-target,.pt-popover-target>.pt-popover-target{display:inline-block}.pt-popover-backdrop{background:rgba(255,255,255,0)}.pt-transition-container{opacity:1;z-index:20}.pt-transition-container.pt-popover-appear,.pt-transition-container.pt-popover-enter{opacity:0}.pt-transition-container.pt-popover-appear-active,.pt-transition-container.pt-popover-enter-active{opacity:1;transition-property:opacity;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-transition-container.pt-popover-leave{opacity:1}.pt-transition-container.pt-popover-leave-active{opacity:0;transition-property:opacity;transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-popover-target{position:relative;vertical-align:top}.pt-popover-target .pt-transition-container,.pt-portal{position:absolute}.pt-popover-target .pt-transition-container.pt-tether-element-attached-bottom.pt-tether-target-attached-top{bottom:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-left.pt-tether-target-attached-right{left:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-top.pt-tether-target-attached-bottom{top:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-right.pt-tether-target-attached-left{right:100%}.pt-popover-target .pt-transition-container.pt-tether-element-attached-top.pt-tether-target-attached-top{top:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-right.pt-tether-target-attached-right{right:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-bottom.pt-tether-target-attached-bottom{bottom:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-left.pt-tether-target-attached-left{left:0}.pt-popover-target .pt-transition-container.pt-tether-element-attached-middle{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.pt-popover-target .pt-transition-container.pt-tether-element-attached-center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pt-button-group.pt-vertical .pt-popover-target{display:block}.pt-button-group:not(.pt-vertical) .pt-popover-target,.pt-button-group:not(.pt-vertical) .pt-tether-target{float:left}.pt-portal{top:0;right:0;left:0}@-webkit-keyframes linear-progress-bar-stripes{from{background-position:0 0}to{background-position:30px 0}}@keyframes linear-progress-bar-stripes{from{background-position:0 0}to{background-position:30px 0}}.pt-progress-bar{display:block;position:relative;border-radius:40px;background:rgba(92,112,128,.2);width:100%;height:8px;overflow:hidden}.pt-progress-bar .pt-progress-meter{display:inline-block;position:absolute;border-radius:40px;background:linear-gradient(-45deg,rgba(255,255,255,.2) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.2) 50%,rgba(255,255,255,.2) 75%,transparent 75%);background-color:rgba(92,112,128,.8);background-size:30px 30px;width:100%;height:100%;transition:width .2s cubic-bezier(.4,1,.75,.9)}.pt-progress-bar:not(.pt-no-animation):not(.pt-no-stripes) .pt-progress-meter{-webkit-animation:linear-progress-bar-stripes .3s linear infinite reverse;animation:linear-progress-bar-stripes .3s linear infinite reverse}.pt-progress-bar.pt-no-stripes .pt-progress-meter{background-image:none}.pt-dark .pt-progress-bar,.pt-progress-bar.pt-dark{background:rgba(16,22,26,.5)}.pt-dark .pt-progress-bar .pt-progress-meter,.pt-progress-bar.pt-dark .pt-progress-meter{background-color:#8a9ba8}.pt-progress-bar.pt-intent-primary .pt-progress-meter{background-color:#137cbd}.pt-progress-bar.pt-intent-success .pt-progress-meter{background-color:#0f9960}.pt-progress-bar.pt-intent-warning .pt-progress-meter{background-color:#d9822b}.pt-progress-bar.pt-intent-danger .pt-progress-meter{background-color:#db3737}@-webkit-keyframes glow{0%,100%{border-color:rgba(167,182,194,.2);background-color:rgba(167,182,194,.2)}50%{border-color:rgba(92,112,128,.2);background-color:rgba(92,112,128,.2)}}@keyframes glow{0%,100%{border-color:rgba(167,182,194,.2);background-color:rgba(167,182,194,.2)}50%{border-color:rgba(92,112,128,.2);background-color:rgba(92,112,128,.2)}}.pt-skeleton{border-color:rgba(167,182,194,.2)!important;border-radius:2px;box-shadow:none!important;background:rgba(167,182,194,.2)!important;background-clip:padding-box!important;cursor:default;color:transparent!important;-webkit-animation:2s linear infinite glow;animation:2s linear infinite glow;-webkit-user-select:none;user-select:none}.pt-slider,.pt-tree-node-label{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.pt-slider{position:relative;cursor:default;width:100%;min-width:150px;height:40px;user-select:none}.pt-slider:hover{cursor:pointer}.pt-slider:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pt-slider.pt-disabled{opacity:.5;cursor:not-allowed}.pt-slider.pt-slider-unlabeled{height:16px}.pt-slider-progress,.pt-slider-track{position:absolute;top:5px;right:0;left:0;border-radius:3px;background:rgba(92,112,128,.2);height:6px}.pt-dark .pt-slider-progress,.pt-dark .pt-slider-track{background:rgba(16,22,26,.5)}.pt-dark .pt-slider-progress,.pt-slider-progress{background:#137cbd}.pt-slider-handle{background:#f5f8fa;background:linear-gradient(to bottom,#fff,rgba(255,255,255,0)) left no-repeat,center no-repeat #f5f8fa;color:#182026;position:absolute;top:0;left:0;border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2);cursor:pointer;width:16px;height:16px}.pt-slider-handle.pt-active,.pt-slider-handle:active{box-shadow:inset 0 0 0 1px rgba(16,22,26,.2),inset 0 1px 2px rgba(16,22,26,.2);background-color:#d8e1e8;background-image:none}.pt-slider-handle.pt-disabled,.pt-slider-handle:disabled{outline:0;box-shadow:none;background-color:rgba(206,217,224,.5);background-image:none;cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-slider-handle.pt-disabled.pt-active,.pt-slider-handle:disabled.pt-active{background:rgba(206,217,224,.7)}.pt-slider-handle:focus{z-index:1}.pt-slider-handle:hover{background:#ebf1f5;background:linear-gradient(to bottom,rgba(255,255,255,.5),rgba(255,255,255,0)) left no-repeat,center no-repeat #ebf1f5;background-clip:padding-box;z-index:2;box-shadow:0 0 0 1px rgba(16,22,26,.2),0 1px 1px rgba(16,22,26,.2);cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.pt-slider-handle.pt-active{background-color:#d8e1e8;background-image:none;box-shadow:0 0 0 1px rgba(16,22,26,.2),inset 0 1px 1px rgba(16,22,26,.1);cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pt-disabled .pt-slider-handle{box-shadow:none;background:#bfccd6;pointer-events:none}.pt-dark .pt-slider-handle{background:#394b59;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #394b59;box-shadow:0 0 0 1px rgba(16,22,26,.4);color:#f5f8fa}.pt-dark .pt-slider-handle.pt-active,.pt-dark .pt-slider-handle:active,.pt-dark .pt-slider-handle:hover{color:#f5f8fa}.pt-dark .pt-slider-handle:hover{background:#30404d;background:linear-gradient(to bottom,rgba(255,255,255,.05),rgba(255,255,255,0)) left no-repeat,center no-repeat #30404d;box-shadow:0 0 0 1px rgba(16,22,26,.4)}.pt-dark .pt-slider-handle.pt-active,.pt-dark .pt-slider-handle:active{box-shadow:0 0 0 1px rgba(16,22,26,.6),inset 0 1px 2px rgba(16,22,26,.2);background-color:#202b33;background-image:none}.pt-dark .pt-slider-handle.pt-disabled,.pt-dark .pt-slider-handle:disabled{box-shadow:none;background-color:rgba(57,75,89,.5);background-image:none;color:rgba(191,204,214,.5)}.pt-dark .pt-slider-handle.pt-disabled.pt-active,.pt-dark .pt-slider-handle:disabled.pt-active{background:rgba(57,75,89,.7)}.pt-dark .pt-slider-handle .pt-button-spinner .pt-spinner-head{background:rgba(16,22,26,.5);stroke:#8a9ba8}.pt-dark .pt-slider-handle,.pt-dark .pt-slider-handle:hover{background-color:#394b59}.pt-dark .pt-slider-handle.pt-active{background-color:#293742}.pt-dark .pt-disabled .pt-slider-handle{border-color:#5c7080;box-shadow:none;background:#5c7080}.pt-slider-handle .pt-slider-label{-webkit-transform:translate(-50%,20px);transform:translate(-50%,20px);margin-left:8px;border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);background:#394b59;color:#f5f8fa}.pt-dark .pt-slider-handle .pt-slider-label{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4);background:#e1e8ed;color:#394b59}.pt-disabled .pt-slider-handle .pt-slider-label{box-shadow:none}.pt-slider-label{display:inline-block;padding:2px 5px;vertical-align:top;line-height:1;font-size:12px;position:absolute;-webkit-transform:translate(-50%,20px);transform:translate(-50%,20px)}.pt-range-slider .pt-slider-handle{width:8px}.pt-range-slider .pt-slider-handle:first-of-type{border-top-right-radius:0;border-bottom-right-radius:0}.pt-range-slider .pt-slider-handle:last-of-type{margin-left:8px;border-top-left-radius:0;border-bottom-left-radius:0}.pt-range-slider .pt-slider-handle:last-of-type .pt-slider-label{margin-left:0}.pt-range-slider .pt-slider-progress{border-radius:0}@-webkit-keyframes pt-spinner-animation{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes pt-spinner-animation{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.pt-spinner:not(.pt-svg-spinner){width:50px;display:inline-block}.pt-spinner.pt-svg-spinner,.pt-spinner:not(.pt-svg-spinner) .pt-spinner-svg-container{-webkit-animation-duration:.4s;animation-duration:.4s}.pt-spinner .pt-svg-spinner-transform-group{-webkit-transform:scale(.5);transform:scale(.5)}.pt-spinner path{stroke-width:5;fill-opacity:0}.pt-spinner .pt-spinner-head{transition:stroke-dashoffset .2s cubic-bezier(.4,1,.75,.9);stroke:rgba(92,112,128,.8);stroke-linecap:round}.pt-spinner .pt-spinner-track{stroke:rgba(92,112,128,.2)}.pt-spinner.pt-small:not(.pt-svg-spinner){width:24px}.pt-spinner.pt-small.pt-svg-spinner,.pt-spinner.pt-small:not(.pt-svg-spinner) .pt-spinner-svg-container{-webkit-animation-duration:.4s;animation-duration:.4s}.pt-spinner.pt-small .pt-svg-spinner-transform-group{-webkit-transform:scale(.24);transform:scale(.24)}.pt-spinner.pt-small path{stroke-width:12}.pt-spinner.pt-large:not(.pt-svg-spinner){width:100px}.pt-spinner.pt-large.pt-svg-spinner,.pt-spinner.pt-large:not(.pt-svg-spinner) .pt-spinner-svg-container{-webkit-animation-duration:450ms;animation-duration:450ms}.pt-spinner.pt-large .pt-svg-spinner-transform-group{-webkit-transform:scale(1);transform:scale(1)}.pt-spinner.pt-large path{stroke-width:3}.pt-spinner:not(.pt-svg-spinner) .pt-spinner-svg-container{position:relative;width:100%;height:0;padding:0 0 100%;-webkit-animation:pt-spinner-animation .4s linear infinite;animation:pt-spinner-animation .4s linear infinite}.pt-spinner:not(.pt-svg-spinner).pt-no-spin .pt-spinner-svg-container{-webkit-animation:none;animation:none}.pt-spinner:not(.pt-svg-spinner) svg{position:absolute;top:0;left:0;width:100%;height:100%}.pt-svg-spinner{-webkit-transform-origin:center;transform-origin:center;-webkit-animation:pt-spinner-animation .4s linear infinite;animation:pt-spinner-animation .4s linear infinite}.pt-svg-spinner.pt-no-spin{-webkit-animation:none;animation:none}.pt-svg-spinner-transform-group{-webkit-transform-origin:center;transform-origin:center}.pt-dark .pt-spinner .pt-spinner-head{stroke:#8a9ba8}.pt-dark .pt-spinner .pt-spinner-track{stroke:rgba(16,22,26,.5)}.pt-spinner.pt-intent-primary .pt-spinner-head{stroke:#137cbd}.pt-spinner.pt-intent-success .pt-spinner-head{stroke:#0f9960}.pt-spinner.pt-intent-warning .pt-spinner-head{stroke:#d9822b}.pt-spinner.pt-intent-danger .pt-spinner-head{stroke:#db3737}table.pt-table{border-spacing:0;font-size:14px}table.pt-table td,table.pt-table th{padding:11px;vertical-align:top;text-align:left}table.pt-table th{color:#182026;font-weight:600}table.pt-table td{color:#182026}table.pt-table tbody tr:first-child td{box-shadow:inset 0 1px 0 0 rgba(16,22,26,.15)}table.pt-table.pt-condensed td,table.pt-table.pt-condensed th{padding-top:6px;padding-bottom:6px}table.pt-table.pt-striped tbody tr:nth-child(odd) td{background:rgba(191,204,214,.2)}table.pt-table.pt-bordered th:not(:first-child){box-shadow:inset 1px 0 0 0 rgba(16,22,26,.15)}table.pt-table.pt-bordered tbody tr td{box-shadow:inset 0 1px 0 0 rgba(16,22,26,.15)}table.pt-table.pt-bordered tbody tr td:not(:first-child){box-shadow:inset 1px 1px 0 0 rgba(16,22,26,.15)}table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td{box-shadow:none}table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td:not(:first-child){box-shadow:inset 1px 0 0 0 rgba(16,22,26,.15)}table.pt-table.pt-interactive tbody tr:hover td{background-color:rgba(191,204,214,.4);cursor:pointer}.pt-dark table.pt-table td,.pt-dark table.pt-table th{color:#f5f8fa}.pt-dark table.pt-table tbody tr:first-child td{box-shadow:inset 0 1px 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-striped tbody tr:nth-child(odd) td{background:rgba(92,112,128,.15)}.pt-dark table.pt-table.pt-bordered th:not(:first-child){box-shadow:inset 1px 0 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered tbody tr td{box-shadow:inset 0 1px 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered tbody tr td:not(:first-child){box-shadow:inset 1px 1px 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td{box-shadow:inset 1px 0 0 0 rgba(255,255,255,.15)}.pt-dark table.pt-table.pt-bordered.pt-striped tbody tr:not(:first-child) td:first-child{box-shadow:none}.pt-dark table.pt-table.pt-interactive tbody tr:hover td{background-color:rgba(92,112,128,.3);cursor:pointer}.pt-tabs.pt-vertical{display:-webkit-flex;display:flex}.pt-tabs.pt-vertical>.pt-tab-list{-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:flex-start;align-items:flex-start}.pt-tabs.pt-vertical>.pt-tab-list .pt-tab{width:100%;padding:0 10px}.pt-tabs.pt-vertical>.pt-tab-list .pt-tab-indicator-wrapper .pt-tab-indicator{top:0;right:0;bottom:0;left:0;border-radius:3px;background-color:rgba(19,124,189,.2);height:auto}.pt-tabs.pt-vertical>.pt-tab-panel{margin-top:0;padding-left:20px}.pt-tab-list{display:-webkit-flex;display:flex;-webkit-flex:0 0 auto;flex:0 0 auto;-webkit-align-items:flex-end;align-items:flex-end;position:relative;margin:0;border:none;padding:0;list-style:none}.pt-tab-list>:not(:last-child){margin-right:20px}.pt-tab{overflow:hidden;-webkit-flex:0 0 auto;flex:0 0 auto;position:relative;cursor:pointer;max-width:100%;vertical-align:top;line-height:30px;color:#182026;font-size:14px}.pt-tab-indicator-wrapper~.pt-tab{box-shadow:none!important}.pt-tab[aria-disabled=true]{cursor:not-allowed;color:rgba(92,112,128,.5)}.pt-tab[aria-selected=true]{border-radius:0;box-shadow:inset 0 -3px 0 #106ba3}.pt-tab:not([aria-disabled=true]):hover,.pt-tab[aria-selected=true]{color:#106ba3}.pt-tab:focus{-moz-outline-radius:0}.pt-large .pt-tab{line-height:40px;font-size:16px}.pt-tab-panel{margin-top:20px}.pt-tab-panel[aria-hidden=true]{display:none}.pt-tab-indicator-wrapper{position:absolute;top:0;left:0;-webkit-transform:translateX(0),translateY(0);transform:translateX(0),translateY(0);transition:height,width,-webkit-transform;transition:height,transform,width;transition:height,transform,width,-webkit-transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,1,.75,.9)}.pt-tab-indicator-wrapper .pt-tab-indicator{position:absolute;right:0;bottom:0;left:0;background-color:#106ba3;height:3px}.pt-tab-indicator-wrapper.pt-no-animation{transition:none}.pt-dark .pt-tab{color:#f5f8fa}.pt-dark .pt-tab[aria-disabled=true]{color:rgba(191,204,214,.5)}.pt-dark .pt-tab[aria-selected=true]{box-shadow:inset 0 -3px 0 #48aff0}.pt-dark .pt-tab:not([aria-disabled=true]):hover,.pt-dark .pt-tab[aria-selected=true]{color:#48aff0}.pt-dark .pt-tab-indicator{background-color:#48aff0}.pt-flex-expander{-webkit-flex:1 1;flex:1 1}.pt-tag{display:inline-block;position:relative;border:none;border-radius:3px;box-shadow:none;background-color:#5c7080;min-width:20px;padding:2px 6px;line-height:16px;color:#f5f8fa;font-size:12px}.pt-tag.pt-round{border-radius:10px}.pt-dark .pt-tag{background-color:#bfccd6;color:#182026}.pt-tag.pt-tag-removable{padding-right:20px}.pt-large .pt-tag,.pt-tag.pt-large{min-width:30px;padding:5px 10px;line-height:20px;font-size:14px}.pt-large .pt-tag-remove,.pt-tag-remove{line-height:1;font-weight:400;font-style:normal}.pt-large .pt-tag.pt-round,.pt-tag.pt-large.pt-round{border-radius:15px}.pt-large .pt-tag.pt-tag-removable,.pt-tag.pt-large.pt-tag-removable{padding-right:30px}.pt-tag.pt-intent-primary{background:#137cbd;color:#fff}.pt-tag.pt-intent-success{background:#0f9960;color:#fff}.pt-tag.pt-intent-warning{background:#d9822b;color:#fff}.pt-tag.pt-intent-danger{background:#db3737;color:#fff}.pt-tag.pt-minimal:not([class*=pt-intent-]){background-color:rgba(138,155,168,.2);color:#182026}.pt-dark .pt-tag.pt-minimal:not([class*=pt-intent-]){color:#f5f8fa}.pt-tag.pt-minimal.pt-intent-primary{background-color:rgba(19,124,189,.15);color:#106ba3}.pt-dark .pt-tag.pt-minimal.pt-intent-primary{background-color:rgba(19,124,189,.25);color:#48aff0}.pt-tag.pt-minimal.pt-intent-success{background-color:rgba(15,153,96,.15);color:#0d8050}.pt-dark .pt-tag.pt-minimal.pt-intent-success{background-color:rgba(15,153,96,.25);color:#3dcc91}.pt-tag.pt-minimal.pt-intent-warning{background-color:rgba(217,130,43,.15);color:#bf7326}.pt-dark .pt-tag.pt-minimal.pt-intent-warning{background-color:rgba(217,130,43,.25);color:#ffb366}.pt-tag.pt-minimal.pt-intent-danger{background-color:rgba(219,55,55,.15);color:#c23030}.pt-dark .pt-tag.pt-minimal.pt-intent-danger{background-color:rgba(219,55,55,.25);color:#ff7373}.pt-tag-remove{font-family:Icons16,sans-serif;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;position:absolute;top:0;right:0;opacity:.5;border:none;background:0 0;cursor:pointer;padding:2px;color:inherit}.pt-tag-remove:hover{opacity:.8;background:0 0;text-decoration:none}.pt-tag-remove:active{opacity:1}.pt-tag-remove::before{content:\"\"}.pt-large .pt-tag-remove{font-family:Icons20,sans-serif;font-size:20px;padding:5px}.pt-toast{display:-webkit-flex;display:flex;-webkit-align-items:flex-start;align-items:flex-start;position:relative!important;margin:15px 0 0;border-radius:3px;box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);background-color:#fff;min-width:300px;max-width:500px;pointer-events:all}.pt-toast.pt-toast-appear,.pt-toast.pt-toast-enter{-webkit-transform:translateY(-40px);transform:translateY(-40px)}.pt-toast.pt-toast-appear-active,.pt-toast.pt-toast-enter-active{-webkit-transform:translateY(0);transform:translateY(0);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-toast.pt-toast-appear~.pt-toast,.pt-toast.pt-toast-enter~.pt-toast{-webkit-transform:translateY(-40px);transform:translateY(-40px)}.pt-toast.pt-toast-appear-active~.pt-toast,.pt-toast.pt-toast-enter-active~.pt-toast{-webkit-transform:translateY(0);transform:translateY(0);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.54,1.12,.38,1.11);transition-delay:0}.pt-toast.pt-toast-leave{opacity:1;-webkit-filter:blur(0);filter:blur(0)}.pt-toast.pt-toast-leave-active{opacity:0;-webkit-filter:blur(10px);filter:blur(10px);transition-property:opacity,-webkit-filter;transition-property:opacity,filter;transition-property:opacity,filter,-webkit-filter;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,1,.75,.9);transition-delay:0}.pt-popover-appear-active>.pt-tooltip,.pt-popover-enter-active>.pt-tooltip,.pt-popover-leave-active>.pt-tooltip,.pt-toast.pt-toast-leave-active~.pt-toast{transition-duration:.1s;transition-timing-function:cubic-bezier(.4,1,.75,.9)}.pt-toast.pt-toast-leave~.pt-toast{-webkit-transform:translateY(0);transform:translateY(0)}.pt-toast.pt-toast-leave-active~.pt-toast{-webkit-transform:translateY(-40px);transform:translateY(-40px);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-delay:50ms}.pt-toast .pt-button-group{-webkit-flex:0 0 auto;flex:0 0 auto;padding:5px 5px 5px 0}.pt-toast>.pt-icon-standard{padding:12px 0 12px 12px;color:#5c7080}.pt-dark .pt-toast,.pt-toast.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4);background-color:#394b59}.pt-dark .pt-toast>.pt-icon-standard,.pt-toast.pt-dark>.pt-icon-standard{color:#bfccd6}.pt-toast[class*=pt-intent-] a{color:rgba(255,255,255,.7)}.pt-toast[class*=pt-intent-] a:hover,.pt-toast[class*=pt-intent-]>.pt-icon-standard{color:#fff}.pt-toast[class*=pt-intent-] .pt-button,.pt-toast[class*=pt-intent-] .pt-button::before,.pt-toast[class*=pt-intent-] .pt-button:active{color:rgba(255,255,255,.7)!important}.pt-toast[class*=pt-intent-] .pt-button:focus{outline-color:rgba(255,255,255,.5)}.pt-toast[class*=pt-intent-] .pt-button:hover{background-color:rgba(255,255,255,.15)!important;color:#fff!important}.pt-toast[class*=pt-intent-] .pt-button:active{background-color:rgba(255,255,255,.3)!important;color:#fff!important}.pt-toast[class*=pt-intent-] .pt-button::after{background:rgba(255,255,255,.3)!important}.pt-toast.pt-intent-primary{background-color:#137cbd;color:#fff}.pt-toast.pt-intent-success{background-color:#0f9960;color:#fff}.pt-toast.pt-intent-warning{background-color:#d9822b;color:#fff}.pt-toast.pt-intent-danger{background-color:#db3737;color:#fff}.pt-toast-message{-webkit-flex:1 1 auto;flex:1 1 auto;padding:11px}.pt-toast-container{position:fixed;right:0;left:0;z-index:40;padding:0 15px 15px;pointer-events:none}.pt-toast-container>span{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:center;align-items:center}.pt-toast-container.pt-toast-container-top{top:0}.pt-toast-container.pt-toast-container-bottom{bottom:0}.pt-toast-container.pt-toast-container-bottom>span{-webkit-flex-direction:column-reverse;flex-direction:column-reverse}.pt-toast-container.pt-toast-container-left>span{-webkit-align-items:flex-start;align-items:flex-start}.pt-toast-container.pt-toast-container-right>span{-webkit-align-items:flex-end;align-items:flex-end}.pt-toast-container-bottom .pt-toast.pt-toast-enter:not(.pt-toast-enter-active),.pt-toast-container-bottom .pt-toast.pt-toast-enter:not(.pt-toast-enter-active)~.pt-toast,.pt-toast-container-bottom .pt-toast.pt-toast-leave-active~.pt-toast{-webkit-transform:translateY(55px);transform:translateY(55px)}.pt-tooltip{box-shadow:0 0 0 1px rgba(16,22,26,.1),0 2px 4px rgba(16,22,26,.2),0 8px 24px rgba(16,22,26,.2);-webkit-transform:scale(1);transform:scale(1)}.pt-tooltip .pt-popover-arrow{position:absolute;width:22px;height:22px}.pt-tooltip .pt-popover-arrow::before{margin:4px;width:14px;height:14px;box-shadow:1px 1px 6px rgba(16,22,26,.2)}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-tooltip{margin-top:-11px;margin-bottom:11px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-tooltip>.pt-popover-arrow{bottom:-8px}.pt-tether-element-attached-bottom.pt-tether-target-attached-top>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-tooltip{margin-left:11px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-tooltip>.pt-popover-arrow{left:-8px}.pt-tether-element-attached-left.pt-tether-target-attached-right>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(0);transform:rotate(0)}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-tooltip{margin-top:11px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-tooltip>.pt-popover-arrow{top:-8px}.pt-tether-element-attached-top.pt-tether-target-attached-bottom>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-tooltip{margin-right:11px;margin-left:-11px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-tooltip>.pt-popover-arrow{right:-8px}.pt-tether-element-attached-right.pt-tether-target-attached-left>.pt-tooltip>.pt-popover-arrow svg{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.pt-tether-element-attached-middle>.pt-tooltip>.pt-popover-arrow{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.pt-tether-element-attached-center>.pt-tooltip>.pt-popover-arrow{right:50%;-webkit-transform:translateX(50%);transform:translateX(50%)}.pt-tether-element-attached-top.pt-tether-target-attached-top>.pt-tooltip>.pt-popover-arrow{top:-.22px}.pt-tether-element-attached-right.pt-tether-target-attached-right>.pt-tooltip>.pt-popover-arrow{right:-.22px}.pt-tether-element-attached-left.pt-tether-target-attached-left>.pt-tooltip>.pt-popover-arrow{left:-.22px}.pt-tether-element-attached-bottom.pt-tether-target-attached-bottom>.pt-tooltip>.pt-popover-arrow{bottom:-.22px}.pt-tether-element-attached-top.pt-tether-element-attached-left>.pt-tooltip{-webkit-transform-origin:top left;transform-origin:top left}.pt-tether-element-attached-top.pt-tether-element-attached-center>.pt-tooltip{-webkit-transform-origin:top center;transform-origin:top center}.pt-tether-element-attached-top.pt-tether-element-attached-right>.pt-tooltip{-webkit-transform-origin:top right;transform-origin:top right}.pt-tether-element-attached-middle.pt-tether-element-attached-left>.pt-tooltip{-webkit-transform-origin:center left;transform-origin:center left}.pt-tether-element-attached-middle.pt-tether-element-attached-center>.pt-tooltip{-webkit-transform-origin:center center;transform-origin:center center}.pt-tether-element-attached-middle.pt-tether-element-attached-right>.pt-tooltip{-webkit-transform-origin:center right;transform-origin:center right}.pt-tether-element-attached-bottom.pt-tether-element-attached-left>.pt-tooltip{-webkit-transform-origin:bottom left;transform-origin:bottom left}.pt-tether-element-attached-bottom.pt-tether-element-attached-center>.pt-tooltip{-webkit-transform-origin:bottom center;transform-origin:bottom center}.pt-tether-element-attached-bottom.pt-tether-element-attached-right>.pt-tooltip{-webkit-transform-origin:bottom right;transform-origin:bottom right}.pt-tooltip .pt-popover-content{background:#394b59;color:#f5f8fa;padding:10px 12px}.pt-tree-node-content-0,.pt-tree-root{padding-left:0}.pt-tooltip .pt-popover-arrow-border{fill:#10161a;fill-opacity:.1}.pt-tooltip .pt-popover-arrow-fill{fill:#394b59}.pt-popover-appear>.pt-tooltip,.pt-popover-enter>.pt-tooltip{-webkit-transform:scale(.8);transform:scale(.8)}.pt-popover-appear-active>.pt-tooltip,.pt-popover-enter-active>.pt-tooltip{-webkit-transform:scale(1);transform:scale(1);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-delay:0}.pt-popover-leave>.pt-tooltip{-webkit-transform:scale(1);transform:scale(1)}.pt-popover-leave-active>.pt-tooltip{-webkit-transform:scale(.8);transform:scale(.8);transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-delay:0}.pt-dark .pt-tooltip,.pt-tooltip.pt-dark{box-shadow:0 0 0 1px rgba(16,22,26,.2),0 2px 4px rgba(16,22,26,.4),0 8px 24px rgba(16,22,26,.4)}.pt-dark .pt-tooltip .pt-popover-content,.pt-tooltip.pt-dark .pt-popover-content{background:#e1e8ed;color:#394b59}.pt-dark .pt-tooltip .pt-popover-arrow::before,.pt-tooltip.pt-dark .pt-popover-arrow::before{box-shadow:1px 1px 6px rgba(16,22,26,.4)}.pt-dark .pt-tooltip .pt-popover-arrow-border,.pt-tooltip.pt-dark .pt-popover-arrow-border{fill:#10161a;fill-opacity:.2}.pt-dark .pt-tooltip .pt-popover-arrow-fill,.pt-tooltip.pt-dark .pt-popover-arrow-fill{fill:#e1e8ed}.pt-tooltip.pt-intent-primary .pt-popover-content{background:#137cbd;color:#fff}.pt-tooltip.pt-intent-primary .pt-popover-arrow-fill{fill:#137cbd}.pt-tooltip.pt-intent-success .pt-popover-content{background:#0f9960;color:#fff}.pt-tooltip.pt-intent-success .pt-popover-arrow-fill{fill:#0f9960}.pt-tooltip.pt-intent-warning .pt-popover-content{background:#d9822b;color:#fff}.pt-tooltip.pt-intent-warning .pt-popover-arrow-fill{fill:#d9822b}.pt-tooltip.pt-intent-danger .pt-popover-content{background:#db3737;color:#fff}.pt-tooltip.pt-intent-danger .pt-popover-arrow-fill{fill:#db3737}.pt-tooltip-indicator{border-bottom:dotted 1px;cursor:help}.pt-tree-node-list{margin:0;padding-left:0;list-style:none}.pt-tree-root{position:relative;background-color:transparent;cursor:default}.pt-tree-node-content-1{padding-left:23px}.pt-tree-node-content-2{padding-left:46px}.pt-tree-node-content-3{padding-left:69px}.pt-tree-node-content-4{padding-left:92px}.pt-tree-node-content-5{padding-left:115px}.pt-tree-node-content-6{padding-left:138px}.pt-tree-node-content-7{padding-left:161px}.pt-tree-node-content-8{padding-left:184px}.pt-tree-node-content-9{padding-left:207px}.pt-tree-node-content-10{padding-left:230px}.pt-tree-node-content-11{padding-left:253px}.pt-tree-node-content-12{padding-left:276px}.pt-tree-node-content-13{padding-left:299px}.pt-tree-node-content-14{padding-left:322px}.pt-tree-node-content-15{padding-left:345px}.pt-tree-node-content-16{padding-left:368px}.pt-tree-node-content-17{padding-left:391px}.pt-tree-node-content-18{padding-left:414px}.pt-tree-node-content-19{padding-left:437px}.pt-tree-node-content-20{padding-left:460px}.pt-tree-node-content{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;width:100%;height:30px;padding-right:5px}.pt-tree-node-content:hover{background-color:rgba(191,204,214,.4)}.pt-tree-node-caret,.pt-tree-node-caret-none{position:relative;min-width:30px;line-height:30px!important}.pt-tree-node-caret{color:#5c7080;cursor:pointer;text-align:center}.pt-tree-node-caret:hover{color:#182026}.pt-dark .pt-tree-node-caret{color:#bfccd6}.pt-dark .pt-tree-node-caret:hover{color:#f5f8fa}.pt-tree-node-caret::before{display:inline-block;content:\"\";transition:-webkit-transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9);transition:transform .2s cubic-bezier(.4,1,.75,.9),-webkit-transform .2s cubic-bezier(.4,1,.75,.9)}.pt-tree-node-caret.pt-tree-node-caret-open::before{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.pt-tree-node-icon{position:relative;margin-right:7px;color:#5c7080}.pt-tree-node-label{overflow:hidden;-webkit-flex:1 1 auto;flex:1 1 auto;position:relative;user-select:none}.pt-tree-node-label span{display:inline}.pt-tree-node-secondary-label{padding:0 5px;line-height:30px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content{background-color:#137cbd}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content,.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-icon,.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-icon-large,.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-icon-standard{color:#fff}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-tree-node-caret::before{color:rgba(255,255,255,.7)}.pt-tree-node.pt-tree-node-selected>.pt-tree-node-content .pt-tree-node-caret:hover::before{color:#fff}.pt-dark .pt-tree-node-content:hover{background-color:rgba(92,112,128,.3)}.pt-dark .pt-tree-node-icon{color:#bfccd6}.pt-dark .pt-tree-node.pt-tree-node-selected>.pt-tree-node-content{background-color:#137cbd}\n\n      body {\n        background-color: #f5f8fa;\n        width: 100%;\n        height: 100%;\n      }\n\n      body.pt-dark {\n        background-color: #293742;\n      }\n\n      form {\n        margin: 0;\n      }\n\n      #logo-light,\n      #logo-dark {\n        height: 34px;\n      }\n\n      #logo-dark {\n        display: none;\n      }\n\n      .pt-card.box {\n        padding: 0;\n      }\n\n      .pt-card.box .box-header {\n        background: rgba(0, 0, 0, 0.07);\n        padding: 10px;\n      }\n\n      .pt-dark .pt-card.box .box-header {\n        background: rgba(0, 0, 0, 0.16);\n        padding: 10px;\n      }\n\n      .pt-card.box .box-body {\n        padding: 40px 50px 50px 50px;\n      }\n\n      .pt-dark #logo-light {\n        display: none;\n      }\n      .pt-dark #logo-dark {\n        display: block;\n      }\n\n      .pt-card {\n        padding: 50px;\n        min-width: 260px;\n        max-width: 300px;\n        margin: 0 auto;\n        position: relative;\n        top: 50%;\n        transform: translateY(-50%);\n      }\n\n      .pt-card .pt-button {\n        width: 100%;\n        margin-bottom: 7px;\n      }\n\n      .pt-input {\n        width: 100%;\n      }\n\n      .device-info {\n        margin-bottom: 16px;\n      }\n\n      .pt-card .pt-button.auth-cancel,\n      #auth-buttons .pt-button:last-child,\n      #auth-secondary .pt-button:last-child {\n        margin-bottom: 0;\n      }\n\n      #alert,\n      #secondary-alert,\n      #device-alert {\n        display: none;\n        margin-bottom: 15px;\n        transition: background-color ease-in-out 0.1s,\n          background-color ease-in-out 0.1s;\n        word-break: break-word;\n      }\n\n      #alert.flash,\n      #secondary-alert.flash,\n      #device-alert.flash {\n        background-color: rgba(255, 255, 255, 0.50);\n      }\n\n      .auth-button {\n        padding: 15px 10px;\n      }\n\n      #auth-local {\n        display: none;\n      }\n\n      #auth-secondary {\n        display: none;\n      }\n\n      #auth-device {\n        display: none;\n      }\n\n      #secondary-passcode {\n        margin-bottom: 7px;\n      }\n    </style>\n  </head>\n  <body class=\"pt-dark pt-focus-disabled\">\n    <div class=\"box pt-card pt-elevation-2\">\n      <div class=\"box-header\">\n        <svg id=\"logo-light\" role=\"img\" viewBox=\"0 0 123.159 21.35\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#2e4153\">\n          <path d=\"M80.048 16.606q-1.126 0-2.067-.348-.92-.347-1.596-.982-.675-.655-1.043-1.534-.348-.9-.348-2.006 0-1.104.348-1.984.368-.9 1.023-1.555.675-.655 1.596-1.003.92-.348 2.046-.348.675 0 1.37.246.717.225 1.29.614.573.368.818.839.143.266.143.388 0 .123-.102.246-.123.143-.286.225-.144.082-.266.082-.246 0-.471-.287-.205-.286-.491-.511t-.634-.389q-.328-.164-.676-.246-.347-.102-.695-.102-1.146 0-2.005.47-.86.471-1.33 1.33-.471.84-.471 1.985 0 1.126.47 1.985.471.84 1.31 1.31.86.47 1.985.47.47 0 .941-.122.47-.123.9-.348.43-.246.737-.553.164-.143.266-.225.123-.102.286-.102.123 0 .225.102.123.082.246.246.082.082.102.204.02.103-.02.225-.02.123-.123.225-.43.43-1.043.758-.594.327-1.249.511-.634.184-1.186.184m8.675 0q-.573 0-1.043-.123-.45-.122-.798-.368-.348-.245-.594-.614-.225-.368-.347-.86-.103-.49-.103-1.084V2.917q0-.245.041-.368.061-.143.184-.204.143-.062.348-.062.225 0 .348.062.143.04.205.164.06.122.06.347v10.742q0 .45.103.798.103.328.307.573.225.225.532.348.328.102.737.102.245 0 .368.062.143.04.205.184.061.123.061.348t-.061.368q-.062.123-.205.164-.123.061-.348.061m7.12.082q-1.022 0-1.923-.389-.9-.388-1.596-1.064-.675-.675-1.064-1.575-.388-.9-.388-1.924 0-1.043.368-1.923.389-.9 1.064-1.575.696-.696 1.575-1.064.9-.39 1.924-.39 1.37 0 2.476.656 1.104.634 1.76 1.76.654 1.104.654 2.516 0 1.064-.389 1.964-.368.9-1.043 1.576-.655.675-1.535 1.064-.88.368-1.882.368m-.04-1.105q1.043 0 1.882-.511.86-.532 1.35-1.412.512-.9.512-1.985 0-1.105-.49-1.964-.492-.86-1.351-1.33-.84-.491-1.924-.491-1.064 0-1.923.511-.839.491-1.35 1.35-.492.86-.492 1.945 0 1.104.492 1.984.511.88 1.37 1.392t1.924.511m10.906 1.044q-.88 0-1.617-.287-.737-.307-1.289-.86-.552-.572-.86-1.37-.306-.818-.306-1.862V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.348-.062.225 0 .347.062.144.061.185.204.061.123.061.348v4.808q0 .983.327 1.74.328.736.983 1.145.654.41 1.636.41 1.003 0 1.72-.41.715-.409 1.104-1.145.389-.757.389-1.74V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.368-.062.205 0 .328.062.122.061.163.204.062.123.062.348v8.594q0 .184-.062.307-.04.122-.163.163-.123.062-.348.062-.144 0-.246-.041-.102-.02-.164-.082-.06-.061-.102-.164-.02-.102-.02-.245v-1.207q-.266.347-.594.675-.327.307-.736.573-.39.245-.88.388-.491.144-1.085.144m11.478-.02q-.941 0-1.78-.349-.818-.347-1.432-1.002t-.962-1.555q-.348-.921-.348-2.026 0-1.084.348-1.964.348-.9.962-1.535.614-.655 1.432-1.002.839-.348 1.78-.348 1.003 0 1.842.348.839.347 1.452 1.002.614.655.942 1.555.347.9.347 2.005t-.347 2.006q-.328.88-.942 1.534-.613.635-1.452.982-.84.348-1.842.348m.184-1.085q1.023 0 1.821-.47.798-.492 1.269-1.33.47-.86.47-1.986 0-1.145-.47-2.005t-1.29-1.33q-.797-.49-1.8-.49-1.002 0-1.82.49-.82.47-1.29 1.33t-.47 2.005q0 1.126.47 1.985.47.84 1.29 1.33.818.47 1.82.47m4.215 1.084q-.327 0-.47-.143-.123-.143-.123-.47V2.958q0-.246.04-.369.062-.143.185-.204.143-.062.348-.062.225 0 .348.062.122.061.184.204.061.123.061.348v13.034q0 .225-.061.368-.041.143-.164.205-.123.061-.348.061\" />\n          <path d=\"M68.888 21.35q-.24 0-.408-.072-.144-.072-.216-.24-.048-.144-.048-.408V.696q0-.264.048-.408.072-.168.216-.216.168-.072.432-.072t.408.072.192.24q.072.144.072.432v19.91q0 .264-.072.408-.048.168-.216.216-.144.072-.408.072\" />\n          <path d=\"M5.606 16.685q-1.002 0-1.739-.41-.736-.409-1.207-1.104-.47-.717-.696-1.617t-.204-1.903q.02-.982.245-1.903.246-.92.716-1.636.47-.717 1.187-1.126.716-.43 1.698-.43.9 0 1.74.43.859.43 1.534 1.166.675.717 1.064 1.637.389.921.389 1.903 0 1.023-.389 1.944-.389.9-1.064 1.596-.655.675-1.514 1.064-.839.389-1.76.389m-3.785 4.583q-.9 0-1.289-.266t-.47-.675Q0 19.917 0 19.467V8.357q0-.45.061-.86.082-.409.45-.675.39-.286 1.29-.286.716 0 1.186.205.47.184.676.593v12.113q.04.45-.062.86-.082.429-.47.695-.39.266-1.31.266m3.294-8.062q.41 0 .737-.204.348-.205.552-.532.205-.348.205-.737 0-.41-.205-.736-.184-.348-.532-.532-.327-.205-.736-.205-.41 0-.757.205-.328.204-.532.532-.184.327-.184.736 0 .389.204.737.205.327.532.532.328.204.716.204m7.878 3.724q-.92 0-1.31-.266-.388-.286-.47-.716-.062-.45-.062-.9v-6.67q0-.471.082-.88.082-.43.47-.696.39-.266 1.31-.266.9 0 1.269.246.389.245.47.552.082.286.082.45l-.327-.123.164-.184q.163-.184.47-.43.328-.245.757-.43.45-.183 1.003-.183.184 0 .45.04.286.021.593.103.328.082.594.245.286.164.45.43.184.266.184.655 0 1.023-.45 1.74-.43.715-1.146.715-.389 0-.552-.04-.164-.062-.246-.103-.061-.061-.164-.102-.081-.062-.327-.062-.307 0-.573.082-.245.062-.45.205-.184.143-.286.348-.103.184-.103.409v3.99q0 .45-.082.88-.081.43-.47.695-.389.266-1.33.266m8.941 0q-.92 0-1.31-.266-.388-.286-.47-.716-.061-.43-.061-.88v-6.67q0-.45.082-.86.081-.43.47-.695.389-.287 1.31-.287.92 0 1.289.287.389.266.47.696.082.43.082.9v6.65q0 .45-.082.88-.081.43-.47.695-.368.266-1.31.266m0-11.376q-.92 0-1.31-.266-.388-.287-.47-.716-.061-.43-.061-.88 0-.47.082-.88.081-.43.47-.696t1.31-.266 1.289.287q.389.266.47.696.082.43.082.88t-.082.88-.47.695q-.368.266-1.31.266m8.758 11.376q-1.085 0-1.883-.245-.798-.246-1.33-.757-.511-.512-.777-1.29-.246-.777-.246-1.82v-7.94q0-.47.082-.859.082-.409.47-.654.39-.266 1.31-.266.9 0 1.29.286.388.266.47.696t.082.88v7.672q0 .226.02.39.041.142.102.224.062.082.164.123t.266.041q.532 0 .941.082.43.082.655.43.246.347.246 1.166 0 .92-.287 1.31-.286.388-.716.45-.43.081-.86.081M26.026 6.66h2.394l2.824-.103q.45 0 .86.082.43.082.695.47.287.39.287 1.33 0 .86-.287 1.249-.266.389-.696.491-.409.102-.88.102l-2.598-.061h-2.68q-.696-.04-.962-.45t-.266-1.35q0-.88.327-1.31.328-.45.982-.45m12.523 10.23q-.982 0-1.842-.388-.839-.39-1.473-1.085-.634-.716-.982-1.657t-.348-2.046V8.378q0-.471.082-.88.082-.43.47-.696.39-.286 1.31-.286.941 0 1.31.286.388.266.47.696t.082.9v3.315q0 .47.164.798.184.327.511.511.348.164.798.164t.778-.184.511-.512.184-.777V8.357q0-.47.082-.88.082-.43.47-.695.39-.266 1.31-.266.942 0 1.31.286.389.266.47.696.082.43.082.88v6.731q0 .45-.082.86-.081.409-.47.675t-1.31.266q-.675 0-1.064-.164-.368-.143-.532-.368-.163-.246-.204-.47-.02-.246-.02-.41l.347.102-.164.205q-.163.184-.49.45-.308.246-.758.45-.43.184-1.002.184m8.818.02q-.92 0-1.31-.265-.388-.287-.47-.716-.061-.43-.061-.88V8.316q0-.45.082-.86.082-.408.47-.654.39-.266 1.31-.266.839 0 1.207.225.389.205.491.512.103.307.082.573 0 .245.041.306l-.164.123q-.02-.307.225-.614.266-.307.676-.552.43-.266.9-.41.49-.163.92-.163 1.024 0 1.801.389.778.389 1.29 1.105.531.695.797 1.637.266.94.266 2.046v3.355q0 .45-.082.88-.081.43-.47.696-.369.266-1.31.266t-1.33-.266q-.388-.287-.47-.716-.082-.43-.082-.88v-3.335q0-.47-.184-.798-.164-.328-.491-.491-.307-.185-.778-.185-.45 0-.798.185-.327.184-.511.511-.185.328-.185.778v3.355q0 .45-.081.88-.082.43-.471.696-.368.266-1.31.266m14.078.021q-.942 0-1.658-.082-.695-.081-1.186-.327-.491-.245-.798-.696-.307-.47-.45-1.207-.144-.757-.144-1.882V3.67q0-.47.082-.88.082-.429.47-.695.39-.286 1.31-.286.921 0 1.29.286.388.266.47.696t.082.9V13q.02.164.061.246.061.082.164.103h.327q.266 0 .553.081.307.062.511.43.225.348.225 1.228 0 .92-.225 1.31-.204.388-.511.45-.307.081-.573.081\" />\n        </svg>\n        <svg id=\"logo-dark\" role=\"img\" viewBox=\"0 0 123.159 21.35\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\">\n          <path d=\"M80.048 16.606q-1.126 0-2.067-.348-.92-.347-1.596-.982-.675-.655-1.043-1.534-.348-.9-.348-2.006 0-1.104.348-1.984.368-.9 1.023-1.555.675-.655 1.596-1.003.92-.348 2.046-.348.675 0 1.37.246.717.225 1.29.614.573.368.818.839.143.266.143.388 0 .123-.102.246-.123.143-.286.225-.144.082-.266.082-.246 0-.471-.287-.205-.286-.491-.511t-.634-.389q-.328-.164-.676-.246-.347-.102-.695-.102-1.146 0-2.005.47-.86.471-1.33 1.33-.471.84-.471 1.985 0 1.126.47 1.985.471.84 1.31 1.31.86.47 1.985.47.47 0 .941-.122.47-.123.9-.348.43-.246.737-.553.164-.143.266-.225.123-.102.286-.102.123 0 .225.102.123.082.246.246.082.082.102.204.02.103-.02.225-.02.123-.123.225-.43.43-1.043.758-.594.327-1.249.511-.634.184-1.186.184m8.675 0q-.573 0-1.043-.123-.45-.122-.798-.368-.348-.245-.594-.614-.225-.368-.347-.86-.103-.49-.103-1.084V2.917q0-.245.041-.368.061-.143.184-.204.143-.062.348-.062.225 0 .348.062.143.04.205.164.06.122.06.347v10.742q0 .45.103.798.103.328.307.573.225.225.532.348.328.102.737.102.245 0 .368.062.143.04.205.184.061.123.061.348t-.061.368q-.062.123-.205.164-.123.061-.348.061m7.12.082q-1.022 0-1.923-.389-.9-.388-1.596-1.064-.675-.675-1.064-1.575-.388-.9-.388-1.924 0-1.043.368-1.923.389-.9 1.064-1.575.696-.696 1.575-1.064.9-.39 1.924-.39 1.37 0 2.476.656 1.104.634 1.76 1.76.654 1.104.654 2.516 0 1.064-.389 1.964-.368.9-1.043 1.576-.655.675-1.535 1.064-.88.368-1.882.368m-.04-1.105q1.043 0 1.882-.511.86-.532 1.35-1.412.512-.9.512-1.985 0-1.105-.49-1.964-.492-.86-1.351-1.33-.84-.491-1.924-.491-1.064 0-1.923.511-.839.491-1.35 1.35-.492.86-.492 1.945 0 1.104.492 1.984.511.88 1.37 1.392t1.924.511m10.906 1.044q-.88 0-1.617-.287-.737-.307-1.289-.86-.552-.572-.86-1.37-.306-.818-.306-1.862V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.348-.062.225 0 .347.062.144.061.185.204.061.123.061.348v4.808q0 .983.327 1.74.328.736.983 1.145.654.41 1.636.41 1.003 0 1.72-.41.715-.409 1.104-1.145.389-.757.389-1.74V7.44q0-.225.04-.348.062-.143.185-.184.143-.062.368-.062.205 0 .328.062.122.061.163.204.062.123.062.348v8.594q0 .184-.062.307-.04.122-.163.163-.123.062-.348.062-.144 0-.246-.041-.102-.02-.164-.082-.06-.061-.102-.164-.02-.102-.02-.245v-1.207q-.266.347-.594.675-.327.307-.736.573-.39.245-.88.388-.491.144-1.085.144m11.478-.02q-.941 0-1.78-.349-.818-.347-1.432-1.002t-.962-1.555q-.348-.921-.348-2.026 0-1.084.348-1.964.348-.9.962-1.535.614-.655 1.432-1.002.839-.348 1.78-.348 1.003 0 1.842.348.839.347 1.452 1.002.614.655.942 1.555.347.9.347 2.005t-.347 2.006q-.328.88-.942 1.534-.613.635-1.452.982-.84.348-1.842.348m.184-1.085q1.023 0 1.821-.47.798-.492 1.269-1.33.47-.86.47-1.986 0-1.145-.47-2.005t-1.29-1.33q-.797-.49-1.8-.49-1.002 0-1.82.49-.82.47-1.29 1.33t-.47 2.005q0 1.126.47 1.985.47.84 1.29 1.33.818.47 1.82.47m4.215 1.084q-.327 0-.47-.143-.123-.143-.123-.47V2.958q0-.246.04-.369.062-.143.185-.204.143-.062.348-.062.225 0 .348.062.122.061.184.204.061.123.061.348v13.034q0 .225-.061.368-.041.143-.164.205-.123.061-.348.061\" />\n          <path d=\"M68.888 21.35q-.24 0-.408-.072-.144-.072-.216-.24-.048-.144-.048-.408V.696q0-.264.048-.408.072-.168.216-.216.168-.072.432-.072t.408.072.192.24q.072.144.072.432v19.91q0 .264-.072.408-.048.168-.216.216-.144.072-.408.072\" />\n          <path d=\"M5.606 16.685q-1.002 0-1.739-.41-.736-.409-1.207-1.104-.47-.717-.696-1.617t-.204-1.903q.02-.982.245-1.903.246-.92.716-1.636.47-.717 1.187-1.126.716-.43 1.698-.43.9 0 1.74.43.859.43 1.534 1.166.675.717 1.064 1.637.389.921.389 1.903 0 1.023-.389 1.944-.389.9-1.064 1.596-.655.675-1.514 1.064-.839.389-1.76.389m-3.785 4.583q-.9 0-1.289-.266t-.47-.675Q0 19.917 0 19.467V8.357q0-.45.061-.86.082-.409.45-.675.39-.286 1.29-.286.716 0 1.186.205.47.184.676.593v12.113q.04.45-.062.86-.082.429-.47.695-.39.266-1.31.266m3.294-8.062q.41 0 .737-.204.348-.205.552-.532.205-.348.205-.737 0-.41-.205-.736-.184-.348-.532-.532-.327-.205-.736-.205-.41 0-.757.205-.328.204-.532.532-.184.327-.184.736 0 .389.204.737.205.327.532.532.328.204.716.204m7.878 3.724q-.92 0-1.31-.266-.388-.286-.47-.716-.062-.45-.062-.9v-6.67q0-.471.082-.88.082-.43.47-.696.39-.266 1.31-.266.9 0 1.269.246.389.245.47.552.082.286.082.45l-.327-.123.164-.184q.163-.184.47-.43.328-.245.757-.43.45-.183 1.003-.183.184 0 .45.04.286.021.593.103.328.082.594.245.286.164.45.43.184.266.184.655 0 1.023-.45 1.74-.43.715-1.146.715-.389 0-.552-.04-.164-.062-.246-.103-.061-.061-.164-.102-.081-.062-.327-.062-.307 0-.573.082-.245.062-.45.205-.184.143-.286.348-.103.184-.103.409v3.99q0 .45-.082.88-.081.43-.47.695-.389.266-1.33.266m8.941 0q-.92 0-1.31-.266-.388-.286-.47-.716-.061-.43-.061-.88v-6.67q0-.45.082-.86.081-.43.47-.695.389-.287 1.31-.287.92 0 1.289.287.389.266.47.696.082.43.082.9v6.65q0 .45-.082.88-.081.43-.47.695-.368.266-1.31.266m0-11.376q-.92 0-1.31-.266-.388-.287-.47-.716-.061-.43-.061-.88 0-.47.082-.88.081-.43.47-.696t1.31-.266 1.289.287q.389.266.47.696.082.43.082.88t-.082.88-.47.695q-.368.266-1.31.266m8.758 11.376q-1.085 0-1.883-.245-.798-.246-1.33-.757-.511-.512-.777-1.29-.246-.777-.246-1.82v-7.94q0-.47.082-.859.082-.409.47-.654.39-.266 1.31-.266.9 0 1.29.286.388.266.47.696t.082.88v7.672q0 .226.02.39.041.142.102.224.062.082.164.123t.266.041q.532 0 .941.082.43.082.655.43.246.347.246 1.166 0 .92-.287 1.31-.286.388-.716.45-.43.081-.86.081M26.026 6.66h2.394l2.824-.103q.45 0 .86.082.43.082.695.47.287.39.287 1.33 0 .86-.287 1.249-.266.389-.696.491-.409.102-.88.102l-2.598-.061h-2.68q-.696-.04-.962-.45t-.266-1.35q0-.88.327-1.31.328-.45.982-.45m12.523 10.23q-.982 0-1.842-.388-.839-.39-1.473-1.085-.634-.716-.982-1.657t-.348-2.046V8.378q0-.471.082-.88.082-.43.47-.696.39-.286 1.31-.286.941 0 1.31.286.388.266.47.696t.082.9v3.315q0 .47.164.798.184.327.511.511.348.164.798.164t.778-.184.511-.512.184-.777V8.357q0-.47.082-.88.082-.43.47-.695.39-.266 1.31-.266.942 0 1.31.286.389.266.47.696.082.43.082.88v6.731q0 .45-.082.86-.081.409-.47.675t-1.31.266q-.675 0-1.064-.164-.368-.143-.532-.368-.163-.246-.204-.47-.02-.246-.02-.41l.347.102-.164.205q-.163.184-.49.45-.308.246-.758.45-.43.184-1.002.184m8.818.02q-.92 0-1.31-.265-.388-.287-.47-.716-.061-.43-.061-.88V8.316q0-.45.082-.86.082-.408.47-.654.39-.266 1.31-.266.839 0 1.207.225.389.205.491.512.103.307.082.573 0 .245.041.306l-.164.123q-.02-.307.225-.614.266-.307.676-.552.43-.266.9-.41.49-.163.92-.163 1.024 0 1.801.389.778.389 1.29 1.105.531.695.797 1.637.266.94.266 2.046v3.355q0 .45-.082.88-.081.43-.47.696-.369.266-1.31.266t-1.33-.266q-.388-.287-.47-.716-.082-.43-.082-.88v-3.335q0-.47-.184-.798-.164-.328-.491-.491-.307-.185-.778-.185-.45 0-.798.185-.327.184-.511.511-.185.328-.185.778v3.355q0 .45-.081.88-.082.43-.471.696-.368.266-1.31.266m14.078.021q-.942 0-1.658-.082-.695-.081-1.186-.327-.491-.245-.798-.696-.307-.47-.45-1.207-.144-.757-.144-1.882V3.67q0-.47.082-.88.082-.429.47-.695.39-.286 1.31-.286.921 0 1.29.286.388.266.47.696t.082.9V13q.02.164.061.246.061.082.164.103h.327q.266 0 .553.081.307.062.511.43.225.348.225 1.228 0 .92-.225 1.31-.204.388-.511.45-.307.081-.573.081\" />\n        </svg>\n      </div>\n      <div class=\"box-body\">\n        <div id=\"auth-buttons\">\n        </div>\n        <div id=\"auth-secondary\">\n          <h5 id=\"secondary-label\"></h5>\n          <div id=\"secondary-alert\" class=\"pt-callout\"></div>\n          <button id=\"secondary-push\" class=\"pt-button auth-button\">Push</button>\n          <button id=\"secondary-phone\" class=\"pt-button auth-button\">Call Me</button>\n          <button id=\"secondary-sms\" class=\"pt-button auth-button\">Text Me</button>\n          <input id=\"secondary-passcode\" class=\"pt-input\"\n            autocapitalize=\"off\" spellcheck=\"false\" autocomplete=\"off\"\n            type=\"text\" placeholder=\"Passcode\"/>\n          <button id=\"secondary-submit\" class=\"pt-button auth-button\">Submit</button>\n          <button id=\"secondary-cancel\" class=\"pt-button auth-button\">Cancel</button>\n        </div>\n        <div id=\"auth-device\">\n          <h5 id=\"device-label\"></h5>\n          <div id=\"device-alert\" class=\"pt-callout\"></div>\n          <form id=\"device-form\">\n            <label class=\"pt-label\">\n              <input id=\"device-name\" class=\"pt-input\" autocomplete=\"off\"\n                type=\"text\" placeholder=\"Device name\" autofocus/>\n            </label>\n            <input id=\"device-submit\" class=\"pt-button\" disabled\n              type=\"submit\" value=\"Register Device\"/>\n          </form>\n        </div>\n        <div id=\"auth-local\">\n          <div id=\"alert\" class=\"pt-callout\"></div>\n          <form id=\"login-form\">\n            <label class=\"pt-label\">\n              <input id=\"username\" class=\"pt-input\"\n                autocapitalize=\"off\" spellcheck=\"false\" autocomplete=\"off\"\n                type=\"text\" placeholder=\"Username\" autofocus/>\n            </label>\n            <label class=\"pt-label\">\n              <input id=\"password\" class=\"pt-input\" autocomplete=\"off\"\n                type=\"password\" placeholder=\"Password\"/>\n            </label>\n            <input id=\"submit\" class=\"pt-button\" type=\"submit\" value=\"Login\"/>\n          </form>\n          <button class=\"pt-button auth-cancel\">Cancel</button>\n        </div>\n      </div>\n    </div>\n    <script>\n      var __defProp = Object.defineProperty;\n      var __export = (target, all) => {\n        for (var name in all)\n          __defProp(target, name, { get: all[name], enumerable: true });\n      };\n      var __async = (__this, __arguments, generator) => {\n        return new Promise((resolve, reject) => {\n          var fulfilled = (value) => {\n            try {\n              step(generator.next(value));\n            } catch (e) {\n              reject(e);\n            }\n          };\n          var rejected = (value) => {\n            try {\n              step(generator.throw(value));\n            } catch (e) {\n              reject(e);\n            }\n          };\n          var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);\n          step((generator = generator.apply(__this, __arguments)).next());\n        });\n      };\n\n      // src/webauthn-json/index.ts\n      var webauthn_json_exports = {};\n      __export(webauthn_json_exports, {\n        create: () => create,\n        get: () => get,\n        schema: () => schema,\n        supported: () => supported\n      });\n\n      // src/webauthn-json/base64url.ts\n      function base64urlToBuffer(baseurl64String) {\n        const padding = \"==\".slice(0, (4 - baseurl64String.length % 4) % 4);\n        const base64String = baseurl64String.replace(/-/g, \"+\").replace(/_/g, \"/\") + padding;\n        const str = atob(base64String);\n        const buffer = new ArrayBuffer(str.length);\n        const byteView = new Uint8Array(buffer);\n        for (let i = 0; i < str.length; i++) {\n          byteView[i] = str.charCodeAt(i);\n        }\n        return buffer;\n      }\n      function bufferToBase64url(buffer) {\n        const byteView = new Uint8Array(buffer);\n        let str = \"\";\n        for (const charCode of byteView) {\n          str += String.fromCharCode(charCode);\n        }\n        const base64String = btoa(str);\n        const base64urlString = base64String.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=/g, \"\");\n        return base64urlString;\n      }\n\n      // src/webauthn-json/convert.ts\n      var copyValue = \"copy\";\n      var convertValue = \"convert\";\n      function convert(conversionFn, schema2, input) {\n        if (schema2 === copyValue) {\n          return input;\n        }\n        if (schema2 === convertValue) {\n          return conversionFn(input);\n        }\n        if (schema2 instanceof Array) {\n          return input.map((v) => convert(conversionFn, schema2[0], v));\n        }\n        if (schema2 instanceof Object) {\n          const output = {};\n          for (const [key, schemaField] of Object.entries(schema2)) {\n            if (schemaField.deriveFn) {\n              const v = schemaField.deriveFn(input);\n              if (v !== void 0) {\n                input[key] = v;\n              }\n            }\n            if (!(key in input)) {\n              if (schemaField.required) {\n                throw new Error(`Missing key: ${key}`);\n              }\n              continue;\n            }\n            if (input[key] == null) {\n              output[key] = null;\n              continue;\n            }\n            output[key] = convert(conversionFn, schemaField.schema, input[key]);\n          }\n          return output;\n        }\n      }\n      function derived(schema2, deriveFn) {\n        return {\n          required: true,\n          schema: schema2,\n          deriveFn\n        };\n      }\n      function required(schema2) {\n        return {\n          required: true,\n          schema: schema2\n        };\n      }\n      function optional(schema2) {\n        return {\n          required: false,\n          schema: schema2\n        };\n      }\n\n      // src/webauthn-json/basic/schema.ts\n      var publicKeyCredentialDescriptorSchema = {\n        type: required(copyValue),\n        id: required(convertValue),\n        transports: optional(copyValue)\n      };\n      var simplifiedExtensionsSchema = {\n        appid: optional(copyValue),\n        appidExclude: optional(copyValue),\n        credProps: optional(copyValue)\n      };\n      var simplifiedClientExtensionResultsSchema = {\n        appid: optional(copyValue),\n        appidExclude: optional(copyValue),\n        credProps: optional(copyValue)\n      };\n      var credentialCreationOptions = {\n        publicKey: required({\n          rp: required(copyValue),\n          user: required({\n            id: required(convertValue),\n            name: required(copyValue),\n            displayName: required(copyValue)\n          }),\n          challenge: required(convertValue),\n          pubKeyCredParams: required(copyValue),\n          timeout: optional(copyValue),\n          excludeCredentials: optional([publicKeyCredentialDescriptorSchema]),\n          authenticatorSelection: optional(copyValue),\n          attestation: optional(copyValue),\n          extensions: optional(simplifiedExtensionsSchema)\n        }),\n        signal: optional(copyValue)\n      };\n      var publicKeyCredentialWithAttestation = {\n        type: required(copyValue),\n        id: required(copyValue),\n        rawId: required(convertValue),\n        response: required({\n          clientDataJSON: required(convertValue),\n          attestationObject: required(convertValue),\n          transports: derived(copyValue, (response) => {\n            var _a;\n            return ((_a = response.getTransports) == null ? void 0 : _a.call(response)) || [];\n          })\n        }),\n        clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults())\n      };\n      var credentialRequestOptions = {\n        mediation: optional(copyValue),\n        publicKey: required({\n          challenge: required(convertValue),\n          timeout: optional(copyValue),\n          rpId: optional(copyValue),\n          allowCredentials: optional([publicKeyCredentialDescriptorSchema]),\n          userVerification: optional(copyValue),\n          extensions: optional(simplifiedExtensionsSchema)\n        }),\n        signal: optional(copyValue)\n      };\n      var publicKeyCredentialWithAssertion = {\n        type: required(copyValue),\n        id: required(copyValue),\n        rawId: required(convertValue),\n        response: required({\n          clientDataJSON: required(convertValue),\n          authenticatorData: required(convertValue),\n          signature: required(convertValue),\n          userHandle: required(convertValue)\n        }),\n        clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults())\n      };\n      var schema = {\n        credentialCreationOptions,\n        publicKeyCredentialWithAttestation,\n        credentialRequestOptions,\n        publicKeyCredentialWithAssertion\n      };\n\n      // src/webauthn-json/basic/api.ts\n      function createRequestFromJSON(requestJSON) {\n        return convert(base64urlToBuffer, credentialCreationOptions, requestJSON);\n      }\n      function createResponseToJSON(credential) {\n        return convert(bufferToBase64url, publicKeyCredentialWithAttestation, credential);\n      }\n      function create(requestJSON) {\n        return __async(this, null, function* () {\n          const credential = yield navigator.credentials.create(createRequestFromJSON(requestJSON));\n          return createResponseToJSON(credential);\n        });\n      }\n      function getRequestFromJSON(requestJSON) {\n        return convert(base64urlToBuffer, credentialRequestOptions, requestJSON);\n      }\n      function getResponseToJSON(credential) {\n        return convert(bufferToBase64url, publicKeyCredentialWithAssertion, credential);\n      }\n      function get(requestJSON) {\n        return __async(this, null, function* () {\n          const credential = yield navigator.credentials.get(getRequestFromJSON(requestJSON));\n          return getResponseToJSON(credential);\n        });\n      }\n\n      // src/webauthn-json/basic/supported.ts\n      function supported() {\n        return !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get && window.PublicKeyCredential);\n      }\n\n      // src/webauthn-json/browser-global.ts\n      globalThis.webauthnJSON = webauthn_json_exports;\n      //# sourceMappingURL=webauthn-json.browser-global.js.map\n    </script>\n    <script type=\"text/javascript\">\n      var i;\n      var state;\n      var authButtons = document.getElementById('auth-buttons');\n      var authLocal = document.getElementById('auth-local');\n      var alertElm = document.getElementById('alert');\n      var usernameElm = document.getElementById('username');\n      var passwordElm = document.getElementById('password');\n      var submitElm = document.getElementById('submit');\n\n      var secondaryToken = null;\n      var secondaryLabl = '';\n      var secondaryFactors = {\n        push: false,\n        phone: false,\n        sms: false,\n        passcode: false,\n        device: false,\n        device_register: false\n      };\n      var secondaryAuth = document.getElementById('auth-secondary');\n      var secondaryLabel = document.getElementById('secondary-label');\n      var secondaryAlert = document.getElementById('secondary-alert');\n      var secondaryPush = document.getElementById('secondary-push');\n      var secondaryPhone = document.getElementById('secondary-phone');\n      var secondarySms = document.getElementById('secondary-sms');\n      var secondaryPasscode = document.getElementById('secondary-passcode');\n      var secondarySubmit = document.getElementById('secondary-submit');\n      var secondaryCancel = document.getElementById('secondary-cancel');\n\n      var queryVals = window.location.search.substring(1).split('&');\n      for (i = 0; i < queryVals.length; i++) {\n        var queryKeyVal = queryVals[i].split('=');\n        if (queryKeyVal[0] === 'secondary') {\n          secondaryToken = queryKeyVal[1];\n        } else if (queryKeyVal[0] === 'label') {\n          secondaryLabl = decodeURIComponent(queryKeyVal[1]);\n        } else if (queryKeyVal[0] === 'factors') {\n          if (queryKeyVal[1].indexOf('push') !== -1) {\n            secondaryFactors.push = true;\n          }\n          if (queryKeyVal[1].indexOf('phone') !== -1) {\n            secondaryFactors.phone = true;\n          }\n          if (queryKeyVal[1].indexOf('sms') !== -1) {\n            secondaryFactors.sms = true;\n          }\n          if (queryKeyVal[1].indexOf('passcode') !== -1) {\n            secondaryFactors.passcode = true;\n          }\n          if (queryKeyVal[1].indexOf('device') !== -1) {\n            secondaryFactors.device = true;\n          }\n          if (queryKeyVal[1].indexOf('device_register') !== -1) {\n            secondaryFactors.device_register = true;\n          }\n        }\n      }\n\n      var deviceAuthElm = document.getElementById('auth-device');\n      var deviceLabelElm = document.getElementById('device-label');\n      var deviceFormElm = document.getElementById('device-form');\n      var deviceNameElm = document.getElementById('device-name');\n      var deviceSubmitElm = document.getElementById('device-submit');\n      var deviceAlertElm = document.getElementById('device-alert');\n\n      var deviceRegisterPost = function(cred) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              setDeviceAlert();\n              window.location = '/';\n            } else if (xmlhttp.status === 202) {\n              setDeviceAlert();\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setDeviceAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/webauthn/register' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify(cred));\n      }\n\n      var deviceRegister = function() {\n        setDeviceAlert('WebAuthn device registration required', 'warning');\n\n        deviceNameElm.onkeyup = function(evt) {\n          deviceSubmitElm.disabled = !evt.target.value;\n        };\n\n        deviceFormElm.onsubmit = function(evt) {\n          if (evt) {\n            evt.preventDefault ? evt.preventDefault() :\n              evt.returnValue = false;\n          }\n\n          deviceSubmitElm.disabled = true;\n          deviceNameElm.disabled = true;\n\n          var xmlhttp = new XMLHttpRequest();\n\n          xmlhttp.onreadystatechange = function() {\n            if (xmlhttp.readyState === 4) {\n              if (xmlhttp.status === 200) {\n                setDeviceAlert(\n                  'Insert your security key and tap the button', 'info');\n\n                var resp = JSON.parse(xmlhttp.responseText);\n                create(resp).then(function (cred) {\n                  cred.name = deviceNameElm.value;\n                  cred.token = secondaryToken;\n                  deviceRegisterPost(cred);\n                }).catch(function (err) {\n                  setDeviceAlert(err, 'danger');\n                });\n              } else {\n                var errorMsg;\n\n                try {\n                  errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n                } catch(error) {\n                  errorMsg = 'Server error occurred';\n                }\n\n                setDeviceAlert(errorMsg, 'danger');\n              }\n            }\n          };\n\n          xmlhttp.open(\n            'GET',\n            '/auth/webauthn/register?token=' + secondaryToken,\n            true\n          );\n          xmlhttp.send();\n        }\n      };\n\n      var deviceRespond = function(cred) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              window.location = '/';\n            } else if (xmlhttp.status === 201) {\n              setSecondaryAlert();\n\n              var data = JSON.parse(xmlhttp.response);\n              secondaryToken = data['token'];\n              secondaryLabl = data['label'];\n              secondaryFactors.push = data['push'];\n              secondaryFactors.phone = data['phone'];\n              secondaryFactors.sms = data['sms'];\n              secondaryFactors.passcode = data['passcode'];\n              secondaryFactors.device = data['device'];\n              secondaryFactors.device_register = data['device_register'];\n\n              secondaryLabel.innerText = secondaryLabl;\n              deviceLabelElm.innerText = secondaryLabl;\n              secondaryPush.style.display = secondaryFactors.push ?\n                'block' : 'none';\n              secondaryPhone.style.display = secondaryFactors.phone ?\n                'block' : 'none';\n              secondarySms.style.display = secondaryFactors.sms ?\n                'block' : 'none';\n              secondaryPasscode.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              secondarySubmit.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              authLocal.style.display = 'none';\n\n              if (secondaryFactors.device_register) {\n                deviceAuthElm.style.display = 'block';\n                deviceRegister();\n                return;\n              }\n\n              secondaryAuth.style.display = 'block';\n              if (secondaryFactors.device) {\n                deviceAuth();\n              }\n            } else if (xmlhttp.status === 202) {\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setSecondaryAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/webauthn/respond' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify(cred));\n      };\n\n      var deviceAuth = function() {\n        setSecondaryAlert(\n          'Insert your security key and tap the button.', 'info');\n\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              var options = JSON.parse(xmlhttp.responseText);\n\n              get(options).then(function(cred) {\n                cred.token = secondaryToken;\n                deviceRespond(cred);\n              }).catch(function(err) {\n                var errorMsg = 'Webauthn error ' + err;\n                setSecondaryAlert(errorMsg, 'danger');\n              });\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setSecondaryAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'GET',\n          '/auth/webauthn/request?token=' + secondaryToken,\n          true\n        );\n        xmlhttp.send();\n      };\n\n      var loadState = function(redirect) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              state = JSON.parse(xmlhttp.responseText);\n\n              var buttons = '';\n\n              var provider;\n              for (i = 0; i < state.providers.length; i++) {\n                provider = state.providers[i];\n\n                if (provider.id === 'demo') {\n                  usernameElm.value = 'demo';\n                  passwordElm.value = 'demo';\n                  continue;\n                }\n\n                if (provider.type === 'local') {\n                  buttons += '<button id=\"auth-local-btn\" ' +\n                    'class=\"pt-button auth-button\">Username/Password</button>';\n                  continue;\n                }\n\n                buttons += '<button id=\"' + provider.id + '\" ' +\n                  'class=\"pt-button auth-button auth-' + provider.type + '\"' +\n                  '>' + provider.label + '</button>';\n              }\n\n              authButtons.innerHTML = buttons;\n\n              bindState();\n\n              // if (redirect && state.providers.length === 1) {\n              //   provider = state.providers[0];\n              //\n              //   if (provider.type === 'local') {\n              //     onAuthLocal();\n              //   } else {\n              //     onAuthProvider(provider.id);\n              //   }\n              // }\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open('GET', '/auth/state', true);\n        xmlhttp.send();\n\n        return false;\n      };\n\n      var onAuthLocal = function () {\n        authButtons.style.display = 'none';\n        authLocal.style.display = 'block';\n      };\n\n      var onAuthProvider = function (providerId) {\n        var location = '/auth/request';\n\n        if (window.location.search !== '') {\n          location += window.location.search + '&';\n        } else {\n          location += '?';\n        }\n\n        location += 'id=' + providerId;\n\n        window.location = location;\n      };\n\n      var bindState = function() {\n        for (i = 0; i < state.providers.length; i++) {\n          if (state.providers[i].type === 'local') {\n            document.getElementById('auth-local-btn').onclick = onAuthLocal;\n            continue;\n          }\n\n          (function(provider) {\n            document.getElementById(provider.id).onclick = function() {\n              onAuthProvider(provider.id);\n            };\n          })(state.providers[i]);\n        }\n      };\n\n      var flashAlert = function() {\n        var alertClass = alertElm.className;\n        var alertClassFlash = alertClass + ' flash';\n\n        alertElm.className = alertClassFlash;\n        setTimeout(function() {\n          alertElm.className = alertClass;\n          setTimeout(function() {\n            alertElm.className = alertClassFlash;\n            setTimeout(function() {\n              alertElm.className = alertClass;\n            }.bind(this), 150);\n          }.bind(this), 150);\n        }.bind(this), 150);\n      };\n\n      var setAlert = function(alert, intent) {\n        intent = intent || 'default';\n\n        if (alert) {\n          alertElm.className = 'pt-callout pt-intent-' + intent;\n          alertElm.innerHTML = alert;\n          if (alertElm.style.display === 'block') {\n            flashAlert(alertElm);\n          } else {\n            alertElm.style.display = 'block';\n          }\n        } else {\n          alertElm.style.display = 'none';\n        }\n      };\n\n      document.getElementById('login-form').onsubmit = function(evt) {\n        if (evt) {\n          evt.preventDefault ? evt.preventDefault() : evt.returnValue = false;\n        }\n\n        submitElm.disabled = true;\n        for (i = 0; i < authCancels.length; i++) {\n          authCancels[i].disabled = true;\n        }\n\n        var username = usernameElm.value;\n        var password = passwordElm.value;\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            setTimeout(function() {\n              submitElm.disabled = false;\n              for (i = 0; i < authCancels.length; i++) {\n                authCancels[i].disabled = false;\n              }\n            }, 500);\n\n            if (xmlhttp.status === 200) {\n              setAlert();\n              window.location = '/';\n            } else if (xmlhttp.status === 201) {\n              setAlert();\n\n              var data = JSON.parse(xmlhttp.response);\n              secondaryToken = data['token'];\n              secondaryLabl = data['label'];\n              secondaryFactors.push = data['push'];\n              secondaryFactors.phone = data['phone'];\n              secondaryFactors.sms = data['sms'];\n              secondaryFactors.passcode = data['passcode'];\n              secondaryFactors.device = data['device'];\n              secondaryFactors.device_register = data['device_register'];\n\n              secondaryLabel.innerText = secondaryLabl;\n              deviceLabelElm.innerText = secondaryLabl;\n              secondaryPush.style.display = secondaryFactors.push ?\n                'block' : 'none';\n              secondaryPhone.style.display = secondaryFactors.phone ?\n                'block' : 'none';\n              secondarySms.style.display = secondaryFactors.sms ?\n                'block' : 'none';\n              secondaryPasscode.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              secondarySubmit.style.display = secondaryFactors.passcode ?\n                'block' : 'none';\n              authLocal.style.display = 'none';\n\n              if (secondaryFactors.device_register) {\n                deviceAuthElm.style.display = 'block';\n                deviceRegister();\n              } else {\n                secondaryAuth.style.display = 'block';\n                if (secondaryFactors.device) {\n                  deviceAuth();\n                }\n              }\n            } else if (xmlhttp.status === 202) {\n              setAlert();\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setAlert(errorMsg, 'danger');\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/session' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify({\n          'username': username,\n          'password': password\n        }));\n\n        return false;\n      };\n\n      var flashSecondaryAlert = function() {\n        var alertClass = secondaryAlert.className;\n        var alertClassFlash = alertClass + ' flash';\n\n        secondaryAlert.className = alertClassFlash;\n        setTimeout(function() {\n          secondaryAlert.className = alertClass;\n          setTimeout(function() {\n            secondaryAlert.className = alertClassFlash;\n            setTimeout(function() {\n              secondaryAlert.className = alertClass;\n            }.bind(this), 150);\n          }.bind(this), 150);\n        }.bind(this), 150);\n      };\n\n      var setSecondaryAlert = function(alert, intent) {\n        intent = intent || 'default';\n\n        if (alert) {\n          secondaryAlert.className = 'pt-callout pt-intent-' + intent;\n          secondaryAlert.innerHTML = alert;\n          if (secondaryAlert.style.display === 'block') {\n            flashSecondaryAlert(secondaryAlert);\n          } else {\n            secondaryAlert.style.display = 'block';\n          }\n        } else {\n          secondaryAlert.style.display = 'none';\n        }\n      };\n\n      var flashDeviceAlert = function() {\n        var alertClass = deviceAlertElm.className;\n        var alertClassFlash = alertClass + ' flash';\n\n        deviceAlertElm.className = alertClassFlash;\n        setTimeout(function() {\n          deviceAlertElm.className = alertClass;\n          setTimeout(function() {\n            deviceAlertElm.className = alertClassFlash;\n            setTimeout(function() {\n              deviceAlertElm.className = alertClass;\n            }.bind(this), 150);\n          }.bind(this), 150);\n        }.bind(this), 150);\n      };\n\n      var setDeviceAlert = function(alert, intent) {\n        intent = intent || 'default';\n\n        if (alert) {\n          deviceAlertElm.className = 'pt-callout pt-intent-' + intent;\n          deviceAlertElm.innerHTML = alert;\n          if (deviceAlertElm.style.display === 'block') {\n            flashDeviceAlert(deviceAlertElm);\n          } else {\n            deviceAlertElm.style.display = 'block';\n          }\n        } else {\n          deviceAlertElm.style.display = 'none';\n        }\n      };\n\n      var secondaryChallenge = function(factor, passcode, callback) {\n        var xmlhttp = new XMLHttpRequest();\n\n        xmlhttp.onreadystatechange = function() {\n          if (xmlhttp.readyState === 4) {\n            if (xmlhttp.status === 200) {\n              setSecondaryAlert();\n              callback(true);\n              window.location = '/';\n            } else if (xmlhttp.status === 201) {\n              setSecondaryAlert();\n              callback(true);\n\n              var data = JSON.parse(xmlhttp.response);\n              secondaryToken = data['token'];\n              secondaryLabl = data['label'];\n              secondaryFactors.push = data['push'];\n              secondaryFactors.phone = data['phone'];\n              secondaryFactors.sms = data['sms'];\n              secondaryFactors.passcode = data['passcode'];\n              secondaryFactors.device = data['device'];\n              secondaryFactors.device_register = data['device_register'];\n\n              deviceLabelElm.innerText = secondaryLabl;\n\n              secondaryAuth.style.display = 'none';\n              deviceAuthElm.style.display = 'block';\n\n              deviceRegister();\n            } else if (xmlhttp.status === 202) {\n              setSecondaryAlert();\n              callback(true);\n              window.location = JSON.parse(xmlhttp.response)['redirect'];\n            } else if (xmlhttp.status === 206 && factor === 'sms') {\n              setSecondaryAlert('Text message sent', 'info');\n              callback(true);\n            } else {\n              var errorMsg;\n\n              try {\n                errorMsg = JSON.parse(xmlhttp.responseText)['error_msg'];\n              } catch(error) {\n                errorMsg = 'Server error occurred';\n              }\n\n              setSecondaryAlert(errorMsg, 'danger');\n              callback(xmlhttp.status === 401);\n            }\n          }\n        };\n\n        xmlhttp.open(\n          'POST',\n          '/auth/secondary' + window.location.search,\n          true\n        );\n        xmlhttp.setRequestHeader('Content-Type', 'application/json');\n        xmlhttp.send(JSON.stringify({\n          'token': secondaryToken,\n          'factor': factor,\n          'passcode': passcode\n        }));\n      };\n\n      secondaryPush.onclick = function() {\n        secondaryPush.disabled = true;\n        secondaryChallenge('push', null, function(success) {\n          if (!success) {\n            secondaryPush.disabled = false;\n          }\n        });\n      };\n      secondaryPhone.onclick = function() {\n        secondaryPhone.disabled = true;\n        secondaryChallenge('phone', null, function(success) {\n          if (!success) {\n            secondaryPhone.disabled = false;\n          }\n        });\n      };\n      secondarySms.onclick = function() {\n        secondarySms.disabled = true;\n        secondaryChallenge('sms', null, function(success) {\n          if (!success) {\n            secondarySms.disabled = false;\n          }\n        });\n      };\n      var secondarySub = function() {\n        var val = secondaryPasscode.value;\n        if (!val) {\n          return;\n        }\n\n        secondaryPasscode.disabled = true;\n        secondarySubmit.disabled = true;\n        secondaryChallenge('passcode', val, function() {\n          secondaryPasscode.disabled = false;\n          secondarySubmit.disabled = false;\n        });\n      };\n      secondaryPasscode.onkeypress = function(evt) {\n        if (evt.keyCode === 13) {\n          secondarySub();\n        }\n      };\n      secondarySubmit.onclick = secondarySub;\n      secondaryCancel.onclick = function() {\n        var query = [];\n\n        var queryVals = window.location.search.substring(1).split('&');\n        for (i = 0; i < queryVals.length; i++) {\n          var queryKeyVal = queryVals[i].split('=');\n          if (queryKeyVal[0] === 'secondary' ||\n              queryKeyVal[0] === 'label' ||\n              queryKeyVal[0] === 'factors') {\n            continue;\n          }\n          query.push(queryVals[i]);\n        }\n\n        var location = '/login';\n        if (query.length) {\n          location += '?' + query.join('&');\n        }\n        window.location = location;\n      };\n\n      var authCancels = document.querySelectorAll('.auth-cancel');\n      for (i = 0; i < authCancels.length; i++) {\n        authCancels[i].onclick = function() {\n          authLocal.style.display = 'none';\n          authButtons.style.display = 'block';\n        };\n      }\n\n      if (secondaryToken) {\n        primary = false;\n        secondaryLabel.innerText = secondaryLabl;\n        deviceLabelElm.innerText = secondaryLabl;\n        secondaryPush.style.display = secondaryFactors.push ?\n          'block' : 'none';\n        secondaryPhone.style.display = secondaryFactors.phone ?\n          'block' : 'none';\n        secondarySms.style.display = secondaryFactors.sms ?\n          'block' : 'none';\n        secondaryPasscode.style.display = secondaryFactors.passcode ?\n          'block' : 'none';\n        secondarySubmit.style.display = secondaryFactors.passcode ?\n          'block' : 'none';\n        authButtons.style.display = 'none';\n\n        if (secondaryFactors.device_register) {\n          deviceAuthElm.style.display = 'block';\n          deviceRegister();\n        } else {\n          secondaryAuth.style.display = 'block';\n          if (secondaryFactors.device) {\n            deviceAuth();\n          }\n        }\n\n        loadState(false);\n      } else {\n        loadState(true);\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/package.json",
    "content": "{\n  \"name\": \"pritunl-cloud\",\n  \"dependencies\": {\n    \"@babel/runtime\": \"7.26.10\",\n    \"@blueprintjs/core\": \"5.16.5\",\n    \"@blueprintjs/datetime2\": \"2.3.22\",\n    \"@blueprintjs/icons\": \"5.18.0\",\n    \"@blueprintjs/select\": \"5.3.10\",\n    \"@github/webauthn-json\": \"2.1.1\",\n    \"@monaco-editor/react\": \"4.6.0\",\n    \"@novnc/novnc\": \"1.4.0\",\n    \"@popperjs/core\": \"2.11.8\",\n    \"@types/chart.js\": \"2.9.41\",\n    \"@types/jquery\": \"3.5.30\",\n    \"@types/node\": \"22.13.14\",\n    \"@types/react\": \"18.3.2\",\n    \"@types/react-dom\": \"18.3.0\",\n    \"@types/superagent\": \"8.1.7\",\n    \"chart.js\": \"4.4.3\",\n    \"chartjs-adapter-moment\": \"1.0.1\",\n    \"events\": \"3.3.0\",\n    \"highlight.js\": \"11.9.0\",\n    \"history\": \"5.3.0\",\n    \"jquery\": \"3.7.1\",\n    \"mobile-detect\": \"1.4.5\",\n    \"monaco-editor\": \"0.50.0\",\n    \"monaco-editor-webpack-plugin\": \"7.1.0\",\n    \"monaco-yaml\": \"5.3.1\",\n    \"react\": \"18.3.1\",\n    \"react-day-picker\": \"8.10.1\",\n    \"react-dom\": \"18.3.1\",\n    \"react-lifecycles-compat\": \"3.0.4\",\n    \"react-markdown\": \"9.0.1\",\n    \"react-transition-group\": \"4.4.5\",\n    \"superagent\": \"9.0.2\",\n    \"systemjs\": \"6.15.1\",\n    \"terser-webpack-plugin\": \"5.3.10\",\n    \"formidable\": \"3.5.4\",\n    \"form-data\": \"4.0.4\"\n  },\n  \"devDependencies\": {\n    \"@babel/runtime\": \"7.26.10\",\n    \"css-loader\": \"7.1.2\",\n    \"monaco-editor-webpack-plugin\": \"7.1.0\",\n    \"source-map-loader\": \"3.0.1\",\n    \"style-loader\": \"4.0.0\",\n    \"typescript\": \"5.9.2\",\n    \"webpack\": \"5.98.0\",\n    \"webpack-cli\": \"6.0.1\"\n  }\n}\n"
  },
  {
    "path": "www/styles/blueprint.css",
    "content": "@charset \"UTF-8\";\nhtml{\n  -webkit-box-sizing:border-box;\n          box-sizing:border-box; }\n\n*,\n*::before,\n*::after{\n  -webkit-box-sizing:inherit;\n          box-sizing:inherit; }\n\nbody {\n  background-color: #f5f8fa !important;\n}\nbody.bp5-dark {\n  background-color: #293742 !important;\n}\n\nbody{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none;\n  color:#182026;\n  background-color: #f5f8fa;\n  font-family:-apple-system, \"BlinkMacSystemFont\", \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Open Sans\", \"Helvetica Neue\", \"blueprint-icons-16\", sans-serif; }\n\np{\n  margin-bottom:10px;\n  margin-top:0; }\n\nsmall{\n  font-size:12px; }\n\nstrong{\n  font-weight:600; }\n\n::-moz-selection{\n  background:rgba(125, 188, 255, 0.6); }\n\n::selection{\n  background:rgba(125, 188, 255, 0.6); }\n.bp5-heading{\n  color:#182026;\n  font-weight:600;\n  margin:0 0 10px;\n  padding:0; }\n  .bp5-dark .bp5-heading{\n    color:#f5f8fa; }\n\nh1.bp5-heading, .bp5-running-text h1{\n  font-size:36px;\n  line-height:40px; }\n\nh2.bp5-heading, .bp5-running-text h2{\n  font-size:28px;\n  line-height:32px; }\n\nh3.bp5-heading, .bp5-running-text h3{\n  font-size:22px;\n  line-height:25px; }\n\nh4.bp5-heading, .bp5-running-text h4{\n  font-size:18px;\n  line-height:21px; }\n\nh5.bp5-heading, .bp5-running-text h5{\n  font-size:16px;\n  line-height:19px; }\n\nh6.bp5-heading, .bp5-running-text h6{\n  font-size:14px;\n  line-height:16px; }\n.bp5-ui-text{\n  font-size:14px;\n  font-weight:400;\n  letter-spacing:0;\n  line-height:1.28581;\n  text-transform:none; }\n\n.bp5-monospace-text{\n  font-family:monospace;\n  text-transform:none; }\n\n.bp5-text-muted{\n  color:#5c7080; }\n  .bp5-dark .bp5-text-muted{\n    color:#a7b6c2; }\n\n.bp5-text-disabled{\n  color:rgba(92, 112, 128, 0.6); }\n  .bp5-dark .bp5-text-disabled{\n    color:rgba(167, 182, 194, 0.6); }\n\n.bp5-text-overflow-ellipsis{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal; }\n.bp5-running-text{\n  font-size:14px;\n  line-height:1.5; }\n  .bp5-running-text h1{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h1{\n      color:#f5f8fa; }\n  .bp5-running-text h2{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h2{\n      color:#f5f8fa; }\n  .bp5-running-text h3{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h3{\n      color:#f5f8fa; }\n  .bp5-running-text h4{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h4{\n      color:#f5f8fa; }\n  .bp5-running-text h5{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h5{\n      color:#f5f8fa; }\n  .bp5-running-text h6{\n    color:#182026;\n    font-weight:600;\n    margin-bottom:20px;\n    margin-top:40px; }\n    .bp5-dark .bp5-running-text h6{\n      color:#f5f8fa; }\n  .bp5-running-text hr{\n    border:none;\n    border-bottom:1px solid rgba(16, 22, 26, 0.15);\n    margin:20px 0; }\n    .bp5-dark .bp5-running-text hr{\n      border-color:rgba(255, 255, 255, 0.15); }\n  .bp5-running-text p{\n    margin:0 0 10px;\n    padding:0; }\n\n.bp5-text-large{\n  font-size:16px; }\n\n.bp5-text-small{\n  font-size:12px; }\na{\n  color:#106ba3;\n  text-decoration:none; }\n  a:hover{\n    color:#106ba3;\n    cursor:pointer;\n    text-decoration:underline; }\n  a .bp5-icon, a .bp5-icon-standard, a .bp5-icon-large{\n    color:inherit; }\n  a code,\n  .bp5-dark a code{\n    color:inherit; }\n  .bp5-dark a,\n  .bp5-dark a:hover{\n    color:#48aff0; }\n    .bp5-dark a .bp5-icon, .bp5-dark a .bp5-icon-standard, .bp5-dark a .bp5-icon-large,\n    .bp5-dark a:hover .bp5-icon,\n    .bp5-dark a:hover .bp5-icon-standard,\n    .bp5-dark a:hover .bp5-icon-large{\n      color:inherit; }\n.bp5-running-text code, .bp5-code{\n  font-family:monospace;\n  text-transform:none;\n  background:rgba(255, 255, 255, 0.7);\n  border-radius:3px;\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2);\n  color:#5c7080;\n  font-size:smaller;\n  padding:2px 5px; }\n  .bp5-dark .bp5-running-text code, .bp5-running-text .bp5-dark code, .bp5-dark .bp5-code{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#a7b6c2; }\n  .bp5-running-text a > code, a > .bp5-code{\n    color:#137cbd; }\n    .bp5-dark .bp5-running-text a > code, .bp5-running-text .bp5-dark a > code, .bp5-dark a > .bp5-code{\n      color:inherit; }\n\n.bp5-running-text pre, .bp5-code-block{\n  font-family:monospace;\n  text-transform:none;\n  background:rgba(255, 255, 255, 0.7);\n  border-radius:3px;\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n  color:#182026;\n  display:block;\n  font-size:13px;\n  line-height:1.4;\n  margin:10px 0;\n  padding:13px 15px 12px;\n  word-break:break-all;\n  word-wrap:break-word; }\n  .bp5-dark .bp5-running-text pre, .bp5-running-text .bp5-dark pre, .bp5-dark .bp5-code-block{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n  .bp5-running-text pre > code, .bp5-code-block > code{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:inherit;\n    font-size:inherit;\n    padding:0; }\n\n.bp5-running-text kbd, .bp5-key{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n  color:#5c7080;\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  font-family:inherit;\n  font-size:12px;\n  height:24px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  line-height:24px;\n  min-width:24px;\n  padding:3px 6px;\n  vertical-align:middle; }\n  .bp5-running-text kbd .bp5-icon, .bp5-key .bp5-icon, .bp5-running-text kbd .bp5-icon-standard, .bp5-key .bp5-icon-standard, .bp5-running-text kbd .bp5-icon-large, .bp5-key .bp5-icon-large{\n    margin-right:5px; }\n  .bp5-dark .bp5-running-text kbd, .bp5-running-text .bp5-dark kbd, .bp5-dark .bp5-key{\n    background:#394b59;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#a7b6c2; }\n.bp5-running-text blockquote, .bp5-blockquote{\n  border-left:solid 4px rgba(167, 182, 194, 0.5);\n  margin:0 0 10px;\n  padding:0 20px; }\n  .bp5-dark .bp5-running-text blockquote, .bp5-running-text .bp5-dark blockquote, .bp5-dark .bp5-blockquote{\n    border-color:rgba(115, 134, 148, 0.5); }\n.bp5-running-text ul,\n.bp5-running-text ol, .bp5-list{\n  margin:10px 0;\n  padding-left:30px; }\n  .bp5-running-text ul li:not(:last-child), .bp5-running-text ol li:not(:last-child), .bp5-list li:not(:last-child){\n    margin-bottom:5px; }\n  .bp5-running-text ul ol, .bp5-running-text ol ol, .bp5-list ol,\n  .bp5-running-text ul ul,\n  .bp5-running-text ol ul,\n  .bp5-list ul{\n    margin-top:5px; }\n\n.bp5-list-unstyled{\n  list-style:none;\n  margin:0;\n  padding:0; }\n  .bp5-list-unstyled li{\n    padding:0; }\n.bp5-rtl{\n  text-align:right; }\n\n.bp5-dark{\n  color:#f5f8fa; }\n\n:focus{\n  outline:rgba(19, 124, 189, 0.6) auto 2px;\n  outline-offset:2px;\n  -moz-outline-radius:6px; }\n\n.bp5-focus-disabled :focus{\n  outline:none !important; }\n  .bp5-focus-disabled :focus ~ .bp5-control-indicator{\n    outline:none !important; }\n.bp5-alert{\n  max-width:400px;\n  padding:20px; }\n\n.bp5-alert-body{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex; }\n  .bp5-alert-body .bp5-icon{\n    font-size:40px;\n    margin-right:20px;\n    margin-top:0; }\n\n.bp5-alert-contents{\n  word-break:break-word; }\n\n.bp5-alert-footer{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:reverse;\n      -ms-flex-direction:row-reverse;\n          flex-direction:row-reverse;\n  margin-top:10px; }\n  .bp5-alert-footer .bp5-button{\n    margin-left:10px; }\n.bp5-breadcrumbs{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  cursor:default;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-wrap:wrap;\n      flex-wrap:wrap;\n  height:30px;\n  list-style:none;\n  margin:0;\n  padding:0; }\n  .bp5-breadcrumbs > li{\n    -webkit-box-align:center;\n        -ms-flex-align:center;\n            align-items:center;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex; }\n    .bp5-breadcrumbs > li::after{\n      background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 00-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='%235C7080'/%3e%3c/svg%3e\");\n      content:\"\";\n      display:block;\n      height:16px;\n      margin:0 5px;\n      width:16px; }\n    .bp5-breadcrumbs > li:last-of-type::after{\n      display:none; }\n\n.bp5-breadcrumb,\n.bp5-breadcrumb-current,\n.bp5-breadcrumbs-collapsed{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  font-size:16px; }\n\n.bp5-breadcrumb,\n.bp5-breadcrumbs-collapsed{\n  color:#5c7080; }\n\n.bp5-breadcrumb:hover{\n  text-decoration:none; }\n\n.bp5-breadcrumb.bp5-disabled{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-breadcrumb .bp5-icon{\n  margin-right:5px; }\n\n.bp5-breadcrumb-current{\n  color:inherit;\n  font-weight:600; }\n  .bp5-breadcrumb-current .bp5-input{\n    font-size:inherit;\n    font-weight:inherit;\n    vertical-align:baseline; }\n\n.bp5-breadcrumbs-collapsed{\n  background:#ced9e0;\n  border:none;\n  border-radius:3px;\n  cursor:pointer;\n  margin-right:2px;\n  padding:1px 5px;\n  vertical-align:text-bottom; }\n  .bp5-breadcrumbs-collapsed::before{\n    background:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cg fill='%235C7080'%3e%3ccircle cx='2' cy='8.03' r='2'/%3e%3ccircle cx='14' cy='8.03' r='2'/%3e%3ccircle cx='8' cy='8.03' r='2'/%3e%3c/g%3e%3c/svg%3e\") center no-repeat;\n    content:\"\";\n    display:block;\n    height:16px;\n    width:16px; }\n  .bp5-breadcrumbs-collapsed:hover{\n    background:#bfccd6;\n    color:#182026;\n    text-decoration:none; }\n\n.bp5-dark .bp5-breadcrumb,\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  color:#a7b6c2; }\n\n.bp5-dark .bp5-breadcrumbs > li::after{\n  color:#a7b6c2; }\n\n.bp5-dark .bp5-breadcrumb.bp5-disabled{\n  color:rgba(167, 182, 194, 0.6); }\n\n.bp5-dark .bp5-breadcrumb-current{\n  color:#f5f8fa; }\n\n.bp5-dark .bp5-breadcrumbs-collapsed{\n  background:rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-breadcrumbs-collapsed:hover{\n    background:rgba(16, 22, 26, 0.6);\n    color:#f5f8fa; }\n.bp5-button{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  border:none;\n  border-radius:3px;\n  cursor:pointer;\n  font-size:14px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  min-height:30px;\n  min-width:30px; }\n  .bp5-button > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-button > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-button::before,\n  .bp5-button > *{\n    margin-right:7px; }\n  .bp5-button:empty::before,\n  .bp5-button > :last-child{\n    margin-right:0; }\n  .bp5-button:empty{\n    padding:0 !important; }\n  .bp5-button:disabled, .bp5-button.bp5-disabled{\n    cursor:not-allowed; }\n  .bp5-button.bp5-fill{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    width:100%; }\n  .bp5-button.bp5-align-right,\n  .bp5-align-right .bp5-button{\n    text-align:right; }\n  .bp5-button.bp5-align-left,\n  .bp5-align-left .bp5-button{\n    text-align:left; }\n  .bp5-button:not([class*=\"bp5-intent-\"]){\n    background-color:#f5f8fa;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    color:#182026; }\n    .bp5-button:not([class*=\"bp5-intent-\"]):hover{\n      background-clip:padding-box;\n      background-color:#ebf1f5;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n    .bp5-button:not([class*=\"bp5-intent-\"]):active, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-active{\n      background-color:#d8e1e8;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button:not([class*=\"bp5-intent-\"]):disabled, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled{\n      background-color:rgba(206, 217, 224, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed;\n      outline:none; }\n      .bp5-button:not([class*=\"bp5-intent-\"]):disabled.bp5-active, .bp5-button:not([class*=\"bp5-intent-\"]):disabled.bp5-active:hover, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled.bp5-active, .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled.bp5-active:hover{\n        background:rgba(206, 217, 224, 0.7); }\n  .bp5-button.bp5-intent-primary{\n    background-color:#137cbd;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-primary:hover, .bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-primary:hover{\n      background-color:#106ba3;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-primary:active, .bp5-button.bp5-intent-primary.bp5-active{\n      background-color:#0e5a8a;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-primary:disabled, .bp5-button.bp5-intent-primary.bp5-disabled{\n      background-color:rgba(19, 124, 189, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button.bp5-intent-success{\n    background-color:#0f9960;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-success:hover, .bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-success:hover{\n      background-color:#0d8050;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-success:active, .bp5-button.bp5-intent-success.bp5-active{\n      background-color:#0a6640;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-success:disabled, .bp5-button.bp5-intent-success.bp5-disabled{\n      background-color:rgba(15, 153, 96, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button.bp5-intent-warning{\n    background-color:#d9822b;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-warning:hover, .bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-warning:hover{\n      background-color:#bf7326;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-warning:active, .bp5-button.bp5-intent-warning.bp5-active{\n      background-color:#a66321;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-warning:disabled, .bp5-button.bp5-intent-warning.bp5-disabled{\n      background-color:rgba(217, 130, 43, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button.bp5-intent-danger{\n    background-color:#db3737;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n    .bp5-button.bp5-intent-danger:hover, .bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n      color:#ffffff; }\n    .bp5-button.bp5-intent-danger:hover{\n      background-color:#c23030;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-danger:active, .bp5-button.bp5-intent-danger.bp5-active{\n      background-color:#a82a2a;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-button.bp5-intent-danger:disabled, .bp5-button.bp5-intent-danger.bp5-disabled{\n      background-color:rgba(219, 55, 55, 0.5);\n      background-image:none;\n      border-color:transparent;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.6); }\n  .bp5-button[class*=\"bp5-intent-\"] .bp5-button-spinner .bp5-spinner-head{\n    stroke:#ffffff; }\n  .bp5-button.bp5-large,\n  .bp5-large .bp5-button{\n    min-height:40px;\n    min-width:40px;\n    font-size:16px;\n    padding:5px 15px; }\n    .bp5-button.bp5-large::before,\n    .bp5-button.bp5-large > *,\n    .bp5-large .bp5-button::before,\n    .bp5-large .bp5-button > *{\n      margin-right:10px; }\n    .bp5-button.bp5-large:empty::before,\n    .bp5-button.bp5-large > :last-child,\n    .bp5-large .bp5-button:empty::before,\n    .bp5-large .bp5-button > :last-child{\n      margin-right:0; }\n  .bp5-button.bp5-small,\n  .bp5-small .bp5-button{\n    min-height:24px;\n    min-width:24px;\n    padding:0 7px; }\n  .bp5-button.bp5-loading{\n    position:relative; }\n    .bp5-button.bp5-loading[class*=\"bp5-icon-\"]::before{\n      visibility:hidden; }\n    .bp5-button.bp5-loading .bp5-button-spinner{\n      margin:0;\n      position:absolute; }\n    .bp5-button.bp5-loading > :not(.bp5-button-spinner){\n      visibility:hidden; }\n  .bp5-button[class*=\"bp5-icon-\"]::before{\n    font-family:\"blueprint-icons-16\", sans-serif;\n    font-size:16px;\n    font-style:normal;\n    font-weight:400;\n    line-height:1;\n    -moz-osx-font-smoothing:grayscale;\n    -webkit-font-smoothing:antialiased;\n    color:#5c7080; }\n  .bp5-button .bp5-icon, .bp5-button .bp5-icon-standard, .bp5-button .bp5-icon-large{\n    color:#5c7080; }\n    .bp5-button .bp5-icon.bp5-align-right, .bp5-button .bp5-icon-standard.bp5-align-right, .bp5-button .bp5-icon-large.bp5-align-right{\n      margin-left:7px; }\n  .bp5-button .bp5-icon:first-child:last-child,\n  .bp5-button .bp5-spinner + .bp5-icon:last-child{\n    margin:0 -7px; }\n  .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]){\n    background-color:#394b59;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):hover, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):active, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-active{\n      color:#f5f8fa; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):hover{\n      background-color:#30404d;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):active, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-active{\n      background-color:#202b33;\n      background-image:none;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):disabled, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled{\n      background-color:rgba(57, 75, 89, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n      .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]):disabled.bp5-active, .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]).bp5-disabled.bp5-active{\n        background:rgba(57, 75, 89, 0.7); }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-button-spinner .bp5-spinner-head{\n      background:rgba(16, 22, 26, 0.5);\n      stroke:#8a9ba8; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"])[class*=\"bp5-icon-\"]::before{\n      color:#a7b6c2; }\n    .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-icon:not([class*=\"bp5-intent-\"]), .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-icon-standard:not([class*=\"bp5-intent-\"]), .bp5-dark .bp5-button:not([class*=\"bp5-intent-\"]) .bp5-icon-large:not([class*=\"bp5-intent-\"]){\n      color:#a7b6c2; }\n  .bp5-dark .bp5-button[class*=\"bp5-intent-\"]{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"]:hover{\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"]:active, .bp5-dark .bp5-button[class*=\"bp5-intent-\"].bp5-active{\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"]:disabled, .bp5-dark .bp5-button[class*=\"bp5-intent-\"].bp5-disabled{\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(255, 255, 255, 0.3); }\n    .bp5-dark .bp5-button[class*=\"bp5-intent-\"] .bp5-button-spinner .bp5-spinner-head{\n      stroke:#8a9ba8; }\n  .bp5-button:disabled::before,\n  .bp5-button:disabled .bp5-icon, .bp5-button:disabled .bp5-icon-standard, .bp5-button:disabled .bp5-icon-large, .bp5-button.bp5-disabled::before,\n  .bp5-button.bp5-disabled .bp5-icon, .bp5-button.bp5-disabled .bp5-icon-standard, .bp5-button.bp5-disabled .bp5-icon-large, .bp5-button[class*=\"bp5-intent-\"]::before,\n  .bp5-button[class*=\"bp5-intent-\"] .bp5-icon, .bp5-button[class*=\"bp5-intent-\"] .bp5-icon-standard, .bp5-button[class*=\"bp5-intent-\"] .bp5-icon-large{\n    color:inherit !important; }\n  .bp5-button.bp5-minimal{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n    .bp5-button.bp5-minimal:hover{\n      background:rgba(167, 182, 194, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026;\n      text-decoration:none; }\n    .bp5-button.bp5-minimal:active, .bp5-button.bp5-minimal.bp5-active{\n      background:rgba(115, 134, 148, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026; }\n    .bp5-button.bp5-minimal:disabled, .bp5-button.bp5-minimal:disabled:hover, .bp5-button.bp5-minimal.bp5-disabled, .bp5-button.bp5-minimal.bp5-disabled:hover{\n      background:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed; }\n      .bp5-button.bp5-minimal:disabled.bp5-active, .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n        background:rgba(115, 134, 148, 0.3); }\n    .bp5-dark .bp5-button.bp5-minimal{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:inherit; }\n      .bp5-dark .bp5-button.bp5-minimal:hover, .bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n      .bp5-dark .bp5-button.bp5-minimal:hover{\n        background:rgba(138, 155, 168, 0.15); }\n      .bp5-dark .bp5-button.bp5-minimal:active, .bp5-dark .bp5-button.bp5-minimal.bp5-active{\n        background:rgba(138, 155, 168, 0.3);\n        color:#f5f8fa; }\n      .bp5-dark .bp5-button.bp5-minimal:disabled, .bp5-dark .bp5-button.bp5-minimal:disabled:hover, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover{\n        background:none;\n        color:rgba(167, 182, 194, 0.6);\n        cursor:not-allowed; }\n        .bp5-dark .bp5-button.bp5-minimal:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active{\n          background:rgba(138, 155, 168, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-primary{\n      color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:hover, .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.15);\n        color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#106ba3; }\n      .bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(16, 107, 163, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n        stroke:#106ba3; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary{\n        color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:hover{\n          background:rgba(19, 124, 189, 0.2);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active{\n          background:rgba(19, 124, 189, 0.3);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled{\n          background:none;\n          color:rgba(72, 175, 240, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active{\n            background:rgba(19, 124, 189, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-success{\n      color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:hover, .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.15);\n        color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#0d8050; }\n      .bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(13, 128, 80, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n        stroke:#0d8050; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success{\n        color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:hover{\n          background:rgba(15, 153, 96, 0.2);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-active{\n          background:rgba(15, 153, 96, 0.3);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled{\n          background:none;\n          color:rgba(61, 204, 145, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active{\n            background:rgba(15, 153, 96, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-warning{\n      color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:hover, .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.15);\n        color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#bf7326; }\n      .bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(191, 115, 38, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n        stroke:#bf7326; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning{\n        color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:hover{\n          background:rgba(217, 130, 43, 0.2);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active{\n          background:rgba(217, 130, 43, 0.3);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled{\n          background:none;\n          color:rgba(255, 179, 102, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active{\n            background:rgba(217, 130, 43, 0.3); }\n    .bp5-button.bp5-minimal.bp5-intent-danger{\n      color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:hover, .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.15);\n        color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#c23030; }\n      .bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(194, 48, 48, 0.5); }\n        .bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n      .bp5-button.bp5-minimal.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n        stroke:#c23030; }\n      .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger{\n        color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:hover{\n          background:rgba(219, 55, 55, 0.2);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active{\n          background:rgba(219, 55, 55, 0.3);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled{\n          background:none;\n          color:rgba(255, 115, 115, 0.5); }\n          .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active{\n            background:rgba(219, 55, 55, 0.3); }\n  .bp5-button.bp5-outlined{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    border:1px solid rgba(24, 32, 38, 0.2);\n    -webkit-box-sizing:border-box;\n            box-sizing:border-box; }\n    .bp5-button.bp5-outlined:hover{\n      background:rgba(167, 182, 194, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026;\n      text-decoration:none; }\n    .bp5-button.bp5-outlined:active, .bp5-button.bp5-outlined.bp5-active{\n      background:rgba(115, 134, 148, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026; }\n    .bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined.bp5-disabled:hover{\n      background:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed; }\n      .bp5-button.bp5-outlined:disabled.bp5-active, .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n        background:rgba(115, 134, 148, 0.3); }\n    .bp5-dark .bp5-button.bp5-outlined{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:inherit; }\n      .bp5-dark .bp5-button.bp5-outlined:hover, .bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n      .bp5-dark .bp5-button.bp5-outlined:hover{\n        background:rgba(138, 155, 168, 0.15); }\n      .bp5-dark .bp5-button.bp5-outlined:active, .bp5-dark .bp5-button.bp5-outlined.bp5-active{\n        background:rgba(138, 155, 168, 0.3);\n        color:#f5f8fa; }\n      .bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n        background:none;\n        color:rgba(167, 182, 194, 0.6);\n        cursor:not-allowed; }\n        .bp5-dark .bp5-button.bp5-outlined:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined:disabled:hover.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active{\n          background:rgba(138, 155, 168, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-primary{\n      color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:hover, .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.15);\n        color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#106ba3; }\n      .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(16, 107, 163, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n        stroke:#106ba3; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n        color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:hover{\n          background:rgba(19, 124, 189, 0.2);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active{\n          background:rgba(19, 124, 189, 0.3);\n          color:#48aff0; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n          background:none;\n          color:rgba(72, 175, 240, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active{\n            background:rgba(19, 124, 189, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-success{\n      color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:hover, .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.15);\n        color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#0d8050; }\n      .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(13, 128, 80, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n        stroke:#0d8050; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n        color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:hover{\n          background:rgba(15, 153, 96, 0.2);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-active{\n          background:rgba(15, 153, 96, 0.3);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n          background:none;\n          color:rgba(61, 204, 145, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active{\n            background:rgba(15, 153, 96, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-warning{\n      color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:hover, .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.15);\n        color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#bf7326; }\n      .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(191, 115, 38, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n        stroke:#bf7326; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n        color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:hover{\n          background:rgba(217, 130, 43, 0.2);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active{\n          background:rgba(217, 130, 43, 0.3);\n          color:#ffb366; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n          background:none;\n          color:rgba(255, 179, 102, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active{\n            background:rgba(217, 130, 43, 0.3); }\n    .bp5-button.bp5-outlined.bp5-intent-danger{\n      color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:hover, .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.15);\n        color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#c23030; }\n      .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(194, 48, 48, 0.5); }\n        .bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n      .bp5-button.bp5-outlined.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n        stroke:#c23030; }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n        color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:hover{\n          background:rgba(219, 55, 55, 0.2);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active{\n          background:rgba(219, 55, 55, 0.3);\n          color:#ff7373; }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n          background:none;\n          color:rgba(255, 115, 115, 0.5); }\n          .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active{\n            background:rgba(219, 55, 55, 0.3); }\n    .bp5-button.bp5-outlined:disabled, .bp5-button.bp5-outlined.bp5-disabled, .bp5-button.bp5-outlined:disabled:hover, .bp5-button.bp5-outlined.bp5-disabled:hover{\n      border-color:rgba(92, 112, 128, 0.1); }\n    .bp5-dark .bp5-button.bp5-outlined{\n      border-color:rgba(255, 255, 255, 0.4); }\n      .bp5-dark .bp5-button.bp5-outlined:disabled, .bp5-dark .bp5-button.bp5-outlined:disabled:hover, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover{\n        border-color:rgba(255, 255, 255, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-primary{\n      border-color:rgba(16, 107, 163, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n        border-color:rgba(16, 107, 163, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary{\n        border-color:rgba(72, 175, 240, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled{\n          border-color:rgba(72, 175, 240, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-success{\n      border-color:rgba(13, 128, 80, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n        border-color:rgba(13, 128, 80, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success{\n        border-color:rgba(61, 204, 145, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled{\n          border-color:rgba(61, 204, 145, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-warning{\n      border-color:rgba(191, 115, 38, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n        border-color:rgba(191, 115, 38, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning{\n        border-color:rgba(255, 179, 102, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled{\n          border-color:rgba(255, 179, 102, 0.2); }\n    .bp5-button.bp5-outlined.bp5-intent-danger{\n      border-color:rgba(194, 48, 48, 0.6); }\n      .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n        border-color:rgba(194, 48, 48, 0.2); }\n      .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger{\n        border-color:rgba(255, 115, 115, 0.6); }\n        .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled, .bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled{\n          border-color:rgba(255, 115, 115, 0.2); }\n\na.bp5-button{\n  text-align:center;\n  text-decoration:none;\n  -webkit-transition:none;\n  transition:none; }\n  a.bp5-button, a.bp5-button:hover, a.bp5-button:active{\n    color:#182026; }\n  a.bp5-button.bp5-disabled{\n    color:rgba(92, 112, 128, 0.6); }\n\n.bp5-button-text{\n  -webkit-box-flex:0;\n      -ms-flex:0 1 auto;\n          flex:0 1 auto; }\n\n.bp5-button.bp5-align-left .bp5-button-text, .bp5-button.bp5-align-right .bp5-button-text,\n.bp5-button-group.bp5-align-left .bp5-button-text,\n.bp5-button-group.bp5-align-right .bp5-button-text{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto; }\n.bp5-button-group{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex; }\n  .bp5-button-group .bp5-button{\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    position:relative;\n    z-index:4; }\n    .bp5-button-group .bp5-button:focus{\n      z-index:5; }\n    .bp5-button-group .bp5-button:hover{\n      z-index:6; }\n    .bp5-button-group .bp5-button:active, .bp5-button-group .bp5-button.bp5-active{\n      z-index:7; }\n    .bp5-button-group .bp5-button:disabled, .bp5-button-group .bp5-button.bp5-disabled{\n      z-index:3; }\n    .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]{\n      z-index:9; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:focus{\n        z-index:10; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:hover{\n        z-index:11; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:active, .bp5-button-group .bp5-button[class*=\"bp5-intent-\"].bp5-active{\n        z-index:12; }\n      .bp5-button-group .bp5-button[class*=\"bp5-intent-\"]:disabled, .bp5-button-group .bp5-button[class*=\"bp5-intent-\"].bp5-disabled{\n        z-index:8; }\n  .bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:first-child) .bp5-button,\n  .bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:first-child){\n    border-bottom-left-radius:0;\n    border-top-left-radius:0; }\n  .bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n  .bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:last-child){\n    border-bottom-right-radius:0;\n    border-top-right-radius:0;\n    margin-right:-1px; }\n  .bp5-button-group.bp5-minimal .bp5-button{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n    .bp5-button-group.bp5-minimal .bp5-button:hover{\n      background:rgba(167, 182, 194, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026;\n      text-decoration:none; }\n    .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-active{\n      background:rgba(115, 134, 148, 0.3);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#182026; }\n    .bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover{\n      background:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed; }\n      .bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active{\n        background:rgba(115, 134, 148, 0.3); }\n    .bp5-dark .bp5-button-group.bp5-minimal .bp5-button{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:inherit; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover{\n        background:rgba(138, 155, 168, 0.15); }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active{\n        background:rgba(138, 155, 168, 0.3);\n        color:#f5f8fa; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover{\n        background:none;\n        color:rgba(167, 182, 194, 0.6);\n        cursor:not-allowed; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active{\n          background:rgba(138, 155, 168, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary{\n      color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.15);\n        color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#106ba3; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(16, 107, 163, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n        stroke:#106ba3; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary{\n        color:#48aff0; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover{\n          background:rgba(19, 124, 189, 0.2);\n          color:#48aff0; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active{\n          background:rgba(19, 124, 189, 0.3);\n          color:#48aff0; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled{\n          background:none;\n          color:rgba(72, 175, 240, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active{\n            background:rgba(19, 124, 189, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success{\n      color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.15);\n        color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#0d8050; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(13, 128, 80, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n        stroke:#0d8050; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success{\n        color:#3dcc91; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover{\n          background:rgba(15, 153, 96, 0.2);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active{\n          background:rgba(15, 153, 96, 0.3);\n          color:#3dcc91; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled{\n          background:none;\n          color:rgba(61, 204, 145, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled.bp5-active{\n            background:rgba(15, 153, 96, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning{\n      color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.15);\n        color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#bf7326; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(191, 115, 38, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n        stroke:#bf7326; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning{\n        color:#ffb366; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover{\n          background:rgba(217, 130, 43, 0.2);\n          color:#ffb366; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active{\n          background:rgba(217, 130, 43, 0.3);\n          color:#ffb366; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled{\n          background:none;\n          color:rgba(255, 179, 102, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active{\n            background:rgba(217, 130, 43, 0.3); }\n    .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger{\n      color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active{\n        background:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.15);\n        color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#c23030; }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(194, 48, 48, 0.5); }\n        .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n      .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n        stroke:#c23030; }\n      .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger{\n        color:#ff7373; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover{\n          background:rgba(219, 55, 55, 0.2);\n          color:#ff7373; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active{\n          background:rgba(219, 55, 55, 0.3);\n          color:#ff7373; }\n        .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled{\n          background:none;\n          color:rgba(255, 115, 115, 0.5); }\n          .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active{\n            background:rgba(219, 55, 55, 0.3); }\n  .bp5-button-group .bp5-popover-wrapper,\n  .bp5-button-group .bp5-popover-target{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-button-group.bp5-fill{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    width:100%; }\n  .bp5-button-group .bp5-button.bp5-fill,\n  .bp5-button-group.bp5-fill .bp5-button:not(.bp5-fixed){\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-button-group.bp5-vertical{\n    -webkit-box-align:stretch;\n        -ms-flex-align:stretch;\n            align-items:stretch;\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:column;\n            flex-direction:column;\n    vertical-align:top; }\n    .bp5-button-group.bp5-vertical.bp5-fill{\n      height:100%;\n      width:unset; }\n    .bp5-button-group.bp5-vertical .bp5-button{\n      margin-right:0 !important;\n      width:100%; }\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:first-child .bp5-button,\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:first-child{\n      border-radius:3px 3px 0 0; }\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:last-child .bp5-button,\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:last-child{\n      border-radius:0 0 3px 3px; }\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n    .bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:not(:last-child){\n      margin-bottom:-1px; }\n  .bp5-button-group.bp5-align-left .bp5-button{\n    text-align:left; }\n  .bp5-dark .bp5-button-group:not(.bp5-minimal) > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n  .bp5-dark .bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:last-child){\n    margin-right:1px; }\n  .bp5-dark .bp5-button-group.bp5-vertical > .bp5-popover-wrapper:not(:last-child) .bp5-button,\n  .bp5-dark .bp5-button-group.bp5-vertical > .bp5-button:not(:last-child){\n    margin-bottom:1px; }\n.bp5-callout{\n  font-size:14px;\n  line-height:1.5;\n  background-color:rgba(138, 155, 168, 0.15);\n  border-radius:3px;\n  padding:10px 12px 9px;\n  position:relative;\n  width:100%; }\n  .bp5-callout[class*=\"bp5-icon-\"]{\n    padding-left:40px; }\n    .bp5-callout[class*=\"bp5-icon-\"]::before{\n      font-family:\"blueprint-icons-20\", sans-serif;\n      font-size:20px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1;\n      -moz-osx-font-smoothing:grayscale;\n      -webkit-font-smoothing:antialiased;\n      color:#5c7080;\n      left:10px;\n      position:absolute;\n      top:10px; }\n  .bp5-callout.bp5-callout-icon{\n    padding-left:40px; }\n    .bp5-callout.bp5-callout-icon > .bp5-icon:first-child{\n      color:#5c7080;\n      left:10px;\n      position:absolute;\n      top:10px; }\n  .bp5-callout .bp5-heading{\n    line-height:20px;\n    margin-bottom:5px;\n    margin-top:0; }\n    .bp5-callout .bp5-heading:last-child{\n      margin-bottom:0; }\n  .bp5-dark .bp5-callout{\n    background-color:rgba(138, 155, 168, 0.2); }\n    .bp5-dark .bp5-callout[class*=\"bp5-icon-\"]::before{\n      color:#a7b6c2; }\n  .bp5-callout.bp5-intent-primary{\n    background-color:rgba(19, 124, 189, 0.15); }\n    .bp5-callout.bp5-intent-primary[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-primary .bp5-heading{\n      color:#106ba3; }\n    .bp5-dark .bp5-callout.bp5-intent-primary{\n      background-color:rgba(19, 124, 189, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-primary[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-primary .bp5-heading{\n        color:#48aff0; }\n  .bp5-callout.bp5-intent-success{\n    background-color:rgba(15, 153, 96, 0.15); }\n    .bp5-callout.bp5-intent-success[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-success .bp5-heading{\n      color:#0d8050; }\n    .bp5-dark .bp5-callout.bp5-intent-success{\n      background-color:rgba(15, 153, 96, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-success[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-success .bp5-heading{\n        color:#3dcc91; }\n  .bp5-callout.bp5-intent-warning{\n    background-color:rgba(217, 130, 43, 0.15); }\n    .bp5-callout.bp5-intent-warning[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-warning .bp5-heading{\n      color:#bf7326; }\n    .bp5-dark .bp5-callout.bp5-intent-warning{\n      background-color:rgba(217, 130, 43, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-warning[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-warning .bp5-heading{\n        color:#ffb366; }\n  .bp5-callout.bp5-intent-danger{\n    background-color:rgba(219, 55, 55, 0.15); }\n    .bp5-callout.bp5-intent-danger[class*=\"bp5-icon-\"]::before,\n    .bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n    .bp5-callout.bp5-intent-danger .bp5-heading{\n      color:#c23030; }\n    .bp5-dark .bp5-callout.bp5-intent-danger{\n      background-color:rgba(219, 55, 55, 0.25); }\n      .bp5-dark .bp5-callout.bp5-intent-danger[class*=\"bp5-icon-\"]::before,\n      .bp5-dark .bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n      .bp5-dark .bp5-callout.bp5-intent-danger .bp5-heading{\n        color:#ff7373; }\n  .bp5-running-text .bp5-callout{\n    margin:20px 0; }\n.bp5-card{\n  background-color:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n  padding:20px;\n  -webkit-transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-card.bp5-dark,\n  .bp5-dark .bp5-card{\n    background-color:#30404d;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); }\n\n.bp5-elevation-0{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.15), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); }\n  .bp5-elevation-0.bp5-dark,\n  .bp5-dark .bp5-elevation-0{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); }\n\n.bp5-elevation-1{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-1.bp5-dark,\n  .bp5-dark .bp5-elevation-1{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-elevation-2{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 1px 1px rgba(16, 22, 26, 0.2), 0 2px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 1px 1px rgba(16, 22, 26, 0.2), 0 2px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-2.bp5-dark,\n  .bp5-dark .bp5-elevation-2{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.4), 0 2px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.4), 0 2px 6px rgba(16, 22, 26, 0.4); }\n\n.bp5-elevation-3{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-3.bp5-dark,\n  .bp5-dark .bp5-elevation-3{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n\n.bp5-elevation-4{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-elevation-4.bp5-dark,\n  .bp5-dark .bp5-elevation-4{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4); }\n\n.bp5-card.bp5-interactive:hover{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  cursor:pointer; }\n  .bp5-card.bp5-interactive:hover.bp5-dark,\n  .bp5-dark .bp5-card.bp5-interactive:hover{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n\n.bp5-card.bp5-interactive:active{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n  opacity:0.9;\n  -webkit-transition-duration:0;\n          transition-duration:0; }\n  .bp5-card.bp5-interactive:active.bp5-dark,\n  .bp5-dark .bp5-card.bp5-interactive:active{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-collapse{\n  height:0;\n  overflow-y:hidden;\n  -webkit-transition:height 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:height 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-collapse .bp5-collapse-body{\n    -webkit-transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-collapse .bp5-collapse-body[aria-hidden=\"true\"]{\n      display:none; }\n\n.bp5-context-menu .bp5-popover-target{\n  display:block; }\n\n.bp5-context-menu-popover-target{\n  position:fixed; }\n.bp5-divider{\n  border-bottom:1px solid rgba(16, 22, 26, 0.15);\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  margin:5px; }\n  .bp5-dark .bp5-divider{\n    border-color:rgba(16, 22, 26, 0.4); }\n.bp5-dialog-container{\n  opacity:1;\n  -webkit-transform:scale(1);\n          transform:scale(1);\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  min-height:100%;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  width:100%; }\n  .bp5-dialog-container.bp5-overlay-enter > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear > .bp5-dialog{\n    opacity:0;\n    -webkit-transform:scale(0.5);\n            transform:scale(0.5); }\n  .bp5-dialog-container.bp5-overlay-enter-active > .bp5-dialog, .bp5-dialog-container.bp5-overlay-appear-active > .bp5-dialog{\n    opacity:1;\n    -webkit-transform:scale(1);\n            transform:scale(1);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:opacity, -webkit-transform;\n    transition-property:opacity, -webkit-transform;\n    transition-property:opacity, transform;\n    transition-property:opacity, transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-dialog-container.bp5-overlay-exit > .bp5-dialog{\n    opacity:1;\n    -webkit-transform:scale(1);\n            transform:scale(1); }\n  .bp5-dialog-container.bp5-overlay-exit-active > .bp5-dialog{\n    opacity:0;\n    -webkit-transform:scale(0.5);\n            transform:scale(0.5);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:opacity, -webkit-transform;\n    transition-property:opacity, -webkit-transform;\n    transition-property:opacity, transform;\n    transition-property:opacity, transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n\n.bp5-dialog{\n  background:#ebf1f5;\n  border-radius:6px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin:30px 0;\n  padding-bottom:20px;\n  pointer-events:all;\n  -webkit-user-select:text;\n     -moz-user-select:text;\n      -ms-user-select:text;\n          user-select:text;\n  width:500px; }\n  .bp5-dialog:focus{\n    outline:0; }\n  .bp5-dialog.bp5-dark,\n  .bp5-dark .bp5-dialog{\n    background:#293742;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n\n.bp5-dialog-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background:#ffffff;\n  border-radius:6px 6px 0 0;\n  -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  min-height:40px;\n  padding-left:20px;\n  padding-right:5px;\n  z-index:0; }\n  .bp5-dialog-header .bp5-icon-large,\n  .bp5-dialog-header .bp5-icon{\n    color:#5c7080;\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    margin-right:10px; }\n  .bp5-dialog-header .bp5-heading{\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    line-height:inherit;\n    margin:0; }\n    .bp5-dialog-header .bp5-heading:last-child{\n      margin-right:20px; }\n  .bp5-dark .bp5-dialog-header{\n    background:#30404d;\n    -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.4);\n            box-shadow:0 1px 0 rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-dialog-header .bp5-icon-large,\n    .bp5-dark .bp5-dialog-header .bp5-icon{\n      color:#a7b6c2; }\n\n.bp5-dialog-body{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  line-height:18px;\n  margin:20px; }\n\n.bp5-dialog-footer{\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  margin:0 20px; }\n\n.bp5-dialog-footer-actions{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:end;\n      -ms-flex-pack:end;\n          justify-content:flex-end; }\n  .bp5-dialog-footer-actions .bp5-button{\n    margin-left:10px; }\n.bp5-multistep-dialog-panels{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex; }\n  .bp5-multistep-dialog-panels:first-child .bp5-dialog-step-container:first-child{\n    border-radius:6px 0 0 0; }\n  .bp5-multistep-dialog-panels:first-child .bp5-multistep-dialog-right-panel{\n    border-top-right-radius:6px; }\n\n.bp5-multistep-dialog-left-panel{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:1;\n      -ms-flex:1;\n          flex:1;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column; }\n  .bp5-dark .bp5-multistep-dialog-left-panel{\n    background:#202b33; }\n\n.bp5-multistep-dialog-right-panel{\n  background-color:#f5f8fa;\n  border-left:1px solid rgba(16, 22, 26, 0.15);\n  border-radius:0 0 6px 0;\n  -webkit-box-flex:3;\n      -ms-flex:3;\n          flex:3;\n  min-width:0; }\n  .bp5-dark .bp5-multistep-dialog-right-panel{\n    background-color:#293742;\n    border-left:1px solid rgba(16, 22, 26, 0.4); }\n\n.bp5-multistep-dialog-footer{\n  background-color:#ffffff;\n  border-radius:0 0 6px 0;\n  border-top:1px solid rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:justify;\n      -ms-flex-pack:justify;\n          justify-content:space-between;\n  padding:10px; }\n  .bp5-dark .bp5-multistep-dialog-footer{\n    background:#30404d;\n    border-top:1px solid rgba(16, 22, 26, 0.4); }\n  .bp5-multistep-dialog-footer .bp5-dialog-footer-actions{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1; }\n\n.bp5-dialog-step-container{\n  background-color:#f5f8fa;\n  border-bottom:1px solid rgba(16, 22, 26, 0.15); }\n  .bp5-dark .bp5-dialog-step-container{\n    background:#293742;\n    border-bottom:1px solid rgba(16, 22, 26, 0.4); }\n  .bp5-dialog-step-container.bp5-dialog-step-viewed{\n    background-color:#ffffff; }\n    .bp5-dark .bp5-dialog-step-container.bp5-dialog-step-viewed{\n      background:#30404d; }\n\n.bp5-dialog-step{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background-color:#f5f8fa;\n  border-radius:6px;\n  cursor:not-allowed;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  margin:4px;\n  padding:6px 14px; }\n  .bp5-dark .bp5-dialog-step{\n    background:#293742; }\n  .bp5-dialog-step-viewed .bp5-dialog-step{\n    background-color:#ffffff;\n    cursor:pointer; }\n    .bp5-dark .bp5-dialog-step-viewed .bp5-dialog-step{\n      background:#30404d; }\n  .bp5-dialog-step:hover{\n    background-color:#f5f8fa; }\n    .bp5-dark .bp5-dialog-step:hover{\n      background:#293742; }\n\n.bp5-dialog-step-icon{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background-color:rgba(92, 112, 128, 0.6);\n  border-radius:50%;\n  color:#ffffff;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:25px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  width:25px; }\n  .bp5-dark .bp5-dialog-step-icon{\n    background-color:rgba(167, 182, 194, 0.6); }\n  .bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-icon{\n    background-color:#2b95d6; }\n  .bp5-dialog-step-viewed .bp5-dialog-step-icon{\n    background-color:#8a9ba8; }\n\n.bp5-dialog-step-title{\n  color:rgba(92, 112, 128, 0.6);\n  -webkit-box-flex:1;\n      -ms-flex:1;\n          flex:1;\n  padding-left:10px; }\n  .bp5-dark .bp5-dialog-step-title{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-title{\n    color:#2b95d6; }\n  .bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n    color:#182026; }\n    .bp5-dark .bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title{\n      color:#f5f8fa; }\n.bp5-drawer{\n  background:#ffffff;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 4px 8px rgba(16, 22, 26, 0.2), 0 18px 46px 6px rgba(16, 22, 26, 0.2);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin:0;\n  padding:0; }\n  .bp5-drawer:focus{\n    outline:0; }\n  .bp5-drawer.bp5-position-top{\n    height:50%;\n    left:0;\n    right:0;\n    top:0; }\n    .bp5-drawer.bp5-position-top.bp5-overlay-enter, .bp5-drawer.bp5-position-top.bp5-overlay-appear{\n      -webkit-transform:translateY(-100%);\n              transform:translateY(-100%); }\n    .bp5-drawer.bp5-position-top.bp5-overlay-enter-active, .bp5-drawer.bp5-position-top.bp5-overlay-appear-active{\n      -webkit-transform:translateY(0);\n              transform:translateY(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-top.bp5-overlay-exit{\n      -webkit-transform:translateY(0);\n              transform:translateY(0); }\n    .bp5-drawer.bp5-position-top.bp5-overlay-exit-active{\n      -webkit-transform:translateY(-100%);\n              transform:translateY(-100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-position-bottom{\n    bottom:0;\n    height:50%;\n    left:0;\n    right:0; }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-enter, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%); }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-enter-active, .bp5-drawer.bp5-position-bottom.bp5-overlay-appear-active{\n      -webkit-transform:translateY(0);\n              transform:translateY(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-exit{\n      -webkit-transform:translateY(0);\n              transform:translateY(0); }\n    .bp5-drawer.bp5-position-bottom.bp5-overlay-exit-active{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-position-left{\n    bottom:0;\n    left:0;\n    top:0;\n    width:50%; }\n    .bp5-drawer.bp5-position-left.bp5-overlay-enter, .bp5-drawer.bp5-position-left.bp5-overlay-appear{\n      -webkit-transform:translateX(-100%);\n              transform:translateX(-100%); }\n    .bp5-drawer.bp5-position-left.bp5-overlay-enter-active, .bp5-drawer.bp5-position-left.bp5-overlay-appear-active{\n      -webkit-transform:translateX(0);\n              transform:translateX(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-left.bp5-overlay-exit{\n      -webkit-transform:translateX(0);\n              transform:translateX(0); }\n    .bp5-drawer.bp5-position-left.bp5-overlay-exit-active{\n      -webkit-transform:translateX(-100%);\n              transform:translateX(-100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-position-right{\n    bottom:0;\n    right:0;\n    top:0;\n    width:50%; }\n    .bp5-drawer.bp5-position-right.bp5-overlay-enter, .bp5-drawer.bp5-position-right.bp5-overlay-appear{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%); }\n    .bp5-drawer.bp5-position-right.bp5-overlay-enter-active, .bp5-drawer.bp5-position-right.bp5-overlay-appear-active{\n      -webkit-transform:translateX(0);\n              transform:translateX(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer.bp5-position-right.bp5-overlay-exit{\n      -webkit-transform:translateX(0);\n              transform:translateX(0); }\n    .bp5-drawer.bp5-position-right.bp5-overlay-exit-active{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n  .bp5-position-right):not(.bp5-vertical){\n    bottom:0;\n    right:0;\n    top:0;\n    width:50%; }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-enter, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-appear{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-enter-active, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-appear-active{\n      -webkit-transform:translateX(0);\n              transform:translateX(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-exit{\n      -webkit-transform:translateX(0);\n              transform:translateX(0); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right):not(.bp5-vertical).bp5-overlay-exit-active{\n      -webkit-transform:translateX(100%);\n              transform:translateX(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n  .bp5-position-right).bp5-vertical{\n    bottom:0;\n    height:50%;\n    left:0;\n    right:0; }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-enter, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-appear{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-enter-active, .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-appear-active{\n      -webkit-transform:translateY(0);\n              transform:translateY(0);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:200ms;\n              transition-duration:200ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-exit{\n      -webkit-transform:translateY(0);\n              transform:translateY(0); }\n    .bp5-drawer:not(.bp5-position-top):not(.bp5-position-bottom):not(.bp5-position-left):not(\n    .bp5-position-right).bp5-vertical.bp5-overlay-exit-active{\n      -webkit-transform:translateY(100%);\n              transform:translateY(100%);\n      -webkit-transition-delay:0;\n              transition-delay:0;\n      -webkit-transition-duration:100ms;\n              transition-duration:100ms;\n      -webkit-transition-property:-webkit-transform;\n      transition-property:-webkit-transform;\n      transition-property:transform;\n      transition-property:transform, -webkit-transform;\n      -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n              transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-drawer.bp5-dark,\n  .bp5-dark .bp5-drawer{\n    background:#30404d;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 4px 8px rgba(16, 22, 26, 0.4), 0 18px 46px 6px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n\n.bp5-drawer-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  border-radius:0;\n  -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px 0 rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  min-height:40px;\n  padding:5px;\n  padding-left:20px;\n  position:relative; }\n  .bp5-drawer-header .bp5-icon-large,\n  .bp5-drawer-header .bp5-icon{\n    color:#5c7080;\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    margin-right:10px; }\n  .bp5-drawer-header .bp5-heading{\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    line-height:inherit;\n    margin:0; }\n    .bp5-drawer-header .bp5-heading:last-child{\n      margin-right:20px; }\n  .bp5-dark .bp5-drawer-header{\n    -webkit-box-shadow:0 1px 0 rgba(16, 22, 26, 0.4);\n            box-shadow:0 1px 0 rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-drawer-header .bp5-icon-large,\n    .bp5-dark .bp5-drawer-header .bp5-icon{\n      color:#a7b6c2; }\n\n.bp5-drawer-body{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  line-height:18px;\n  overflow:auto; }\n\n.bp5-drawer-footer{\n  -webkit-box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.15);\n          box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.15);\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  padding:10px 20px;\n  position:relative; }\n  .bp5-dark .bp5-drawer-footer{\n    -webkit-box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 1px 0 rgba(16, 22, 26, 0.4); }\n.bp5-editable-text{\n  cursor:text;\n  display:inline-block;\n  max-width:100%;\n  position:relative;\n  vertical-align:top;\n  white-space:nowrap; }\n  .bp5-editable-text::before{\n    bottom:-3px;\n    left:-3px;\n    position:absolute;\n    right:-3px;\n    top:-3px;\n    border-radius:3px;\n    content:\"\";\n    -webkit-transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9), box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-editable-text:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15); }\n  .bp5-editable-text.bp5-editable-text-editing::before{\n    background-color:#ffffff;\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-disabled::before{\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-primary .bp5-editable-text-content{\n    color:#137cbd; }\n  .bp5-editable-text.bp5-intent-primary:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(19, 124, 189, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(19, 124, 189, 0.4); }\n  .bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-intent-success .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-success .bp5-editable-text-content{\n    color:#0f9960; }\n  .bp5-editable-text.bp5-intent-success:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px rgba(15, 153, 96, 0.4);\n            box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px rgba(15, 153, 96, 0.4); }\n  .bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-warning .bp5-editable-text-content{\n    color:#d9822b; }\n  .bp5-editable-text.bp5-intent-warning:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px rgba(217, 130, 43, 0.4);\n            box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px rgba(217, 130, 43, 0.4); }\n  .bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input,\n  .bp5-editable-text.bp5-intent-danger .bp5-editable-text-content{\n    color:#db3737; }\n  .bp5-editable-text.bp5-intent-danger:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px rgba(219, 55, 55, 0.4);\n            box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px rgba(219, 55, 55, 0.4); }\n  .bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-editable-text:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.15);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.15); }\n  .bp5-dark .bp5-editable-text.bp5-editable-text-editing::before{\n    background-color:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-disabled::before{\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-content{\n    color:#48aff0; }\n  .bp5-dark .bp5-editable-text.bp5-intent-primary:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(72, 175, 240, 0), 0 0 0 0 rgba(72, 175, 240, 0), inset 0 0 0 1px rgba(72, 175, 240, 0.4);\n            box-shadow:0 0 0 0 rgba(72, 175, 240, 0), 0 0 0 0 rgba(72, 175, 240, 0), inset 0 0 0 1px rgba(72, 175, 240, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #48aff0, 0 0 0 3px rgba(72, 175, 240, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #48aff0, 0 0 0 3px rgba(72, 175, 240, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-content{\n    color:#3dcc91; }\n  .bp5-dark .bp5-editable-text.bp5-intent-success:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(61, 204, 145, 0), 0 0 0 0 rgba(61, 204, 145, 0), inset 0 0 0 1px rgba(61, 204, 145, 0.4);\n            box-shadow:0 0 0 0 rgba(61, 204, 145, 0), 0 0 0 0 rgba(61, 204, 145, 0), inset 0 0 0 1px rgba(61, 204, 145, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #3dcc91, 0 0 0 3px rgba(61, 204, 145, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #3dcc91, 0 0 0 3px rgba(61, 204, 145, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-content{\n    color:#ffb366; }\n  .bp5-dark .bp5-editable-text.bp5-intent-warning:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(255, 179, 102, 0), 0 0 0 0 rgba(255, 179, 102, 0), inset 0 0 0 1px rgba(255, 179, 102, 0.4);\n            box-shadow:0 0 0 0 rgba(255, 179, 102, 0), 0 0 0 0 rgba(255, 179, 102, 0), inset 0 0 0 1px rgba(255, 179, 102, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #ffb366, 0 0 0 3px rgba(255, 179, 102, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #ffb366, 0 0 0 3px rgba(255, 179, 102, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-content{\n    color:#ff7373; }\n  .bp5-dark .bp5-editable-text.bp5-intent-danger:hover::before{\n    -webkit-box-shadow:0 0 0 0 rgba(255, 115, 115, 0), 0 0 0 0 rgba(255, 115, 115, 0), inset 0 0 0 1px rgba(255, 115, 115, 0.4);\n            box-shadow:0 0 0 0 rgba(255, 115, 115, 0), 0 0 0 0 rgba(255, 115, 115, 0), inset 0 0 0 1px rgba(255, 115, 115, 0.4); }\n  .bp5-dark .bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before{\n    -webkit-box-shadow:0 0 0 1px #ff7373, 0 0 0 3px rgba(255, 115, 115, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #ff7373, 0 0 0 3px rgba(255, 115, 115, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-editable-text-input,\n.bp5-editable-text-content{\n  color:inherit;\n  display:inherit;\n  font:inherit;\n  letter-spacing:inherit;\n  max-width:inherit;\n  min-width:inherit;\n  position:relative;\n  resize:none;\n  text-transform:inherit;\n  vertical-align:top; }\n\n.bp5-editable-text-input{\n  background:none;\n  border:none;\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  padding:0;\n  white-space:pre-wrap;\n  width:100%; }\n  .bp5-editable-text-input::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-editable-text-input:focus{\n    outline:none; }\n  .bp5-editable-text-input::-ms-clear{\n    display:none; }\n\n.bp5-editable-text-content{\n  overflow:hidden;\n  padding-right:2px;\n  text-overflow:ellipsis;\n  white-space:pre; }\n  .bp5-editable-text-editing > .bp5-editable-text-content{\n    left:0;\n    position:absolute;\n    visibility:hidden; }\n  .bp5-editable-text-placeholder > .bp5-editable-text-content{\n    color:rgba(92, 112, 128, 0.6); }\n    .bp5-dark .bp5-editable-text-placeholder > .bp5-editable-text-content{\n      color:rgba(167, 182, 194, 0.6); }\n\n.bp5-editable-text.bp5-multiline{\n  display:block; }\n  .bp5-editable-text.bp5-multiline .bp5-editable-text-content{\n    overflow:auto;\n    white-space:pre-wrap;\n    word-wrap:break-word; }\n.bp5-divider{\n  border-bottom:1px solid rgba(16, 22, 26, 0.15);\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  margin:5px; }\n  .bp5-dark .bp5-divider{\n    border-color:rgba(16, 22, 26, 0.4); }\n.bp5-control-group{\n  -webkit-transform:translateZ(0);\n          transform:translateZ(0);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:stretch;\n      -ms-flex-align:stretch;\n          align-items:stretch; }\n  .bp5-control-group > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-control-group > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-control-group .bp5-button,\n  .bp5-control-group .bp5-html-select,\n  .bp5-control-group .bp5-input,\n  .bp5-control-group .bp5-select{\n    position:relative; }\n  .bp5-control-group .bp5-input{\n    border-radius:inherit;\n    z-index:2; }\n    .bp5-control-group .bp5-input:focus{\n      border-radius:3px;\n      z-index:14; }\n    .bp5-control-group .bp5-input[class*=\"bp5-intent\"]{\n      z-index:13; }\n      .bp5-control-group .bp5-input[class*=\"bp5-intent\"]:focus{\n        z-index:15; }\n    .bp5-control-group .bp5-input[readonly], .bp5-control-group .bp5-input:disabled, .bp5-control-group .bp5-input.bp5-disabled{\n      z-index:1; }\n  .bp5-control-group .bp5-input-group[class*=\"bp5-intent\"] .bp5-input{\n    z-index:13; }\n    .bp5-control-group .bp5-input-group[class*=\"bp5-intent\"] .bp5-input:focus{\n      z-index:15; }\n  .bp5-control-group .bp5-button,\n  .bp5-control-group .bp5-html-select select,\n  .bp5-control-group .bp5-select select{\n    -webkit-transform:translateZ(0);\n            transform:translateZ(0);\n    border-radius:inherit;\n    z-index:4; }\n    .bp5-control-group .bp5-button:focus,\n    .bp5-control-group .bp5-html-select select:focus,\n    .bp5-control-group .bp5-select select:focus{\n      z-index:5; }\n    .bp5-control-group .bp5-button:hover,\n    .bp5-control-group .bp5-html-select select:hover,\n    .bp5-control-group .bp5-select select:hover{\n      z-index:6; }\n    .bp5-control-group .bp5-button:active,\n    .bp5-control-group .bp5-html-select select:active,\n    .bp5-control-group .bp5-select select:active{\n      z-index:7; }\n    .bp5-control-group .bp5-button[readonly], .bp5-control-group .bp5-button:disabled, .bp5-control-group .bp5-button.bp5-disabled,\n    .bp5-control-group .bp5-html-select select[readonly],\n    .bp5-control-group .bp5-html-select select:disabled,\n    .bp5-control-group .bp5-html-select select.bp5-disabled,\n    .bp5-control-group .bp5-select select[readonly],\n    .bp5-control-group .bp5-select select:disabled,\n    .bp5-control-group .bp5-select select.bp5-disabled{\n      z-index:3; }\n    .bp5-control-group .bp5-button[class*=\"bp5-intent\"],\n    .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"],\n    .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]{\n      z-index:9; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:focus,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:focus,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:focus{\n        z-index:10; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:hover,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:hover,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:hover{\n        z-index:11; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:active,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:active,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:active{\n        z-index:12; }\n      .bp5-control-group .bp5-button[class*=\"bp5-intent\"][readonly], .bp5-control-group .bp5-button[class*=\"bp5-intent\"]:disabled, .bp5-control-group .bp5-button[class*=\"bp5-intent\"].bp5-disabled,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"][readonly],\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"]:disabled,\n      .bp5-control-group .bp5-html-select select[class*=\"bp5-intent\"].bp5-disabled,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"][readonly],\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"]:disabled,\n      .bp5-control-group .bp5-select select[class*=\"bp5-intent\"].bp5-disabled{\n        z-index:8; }\n  .bp5-control-group .bp5-input-group > .bp5-icon,\n  .bp5-control-group .bp5-input-group > .bp5-button,\n  .bp5-control-group .bp5-input-group > .bp5-input-left-container,\n  .bp5-control-group .bp5-input-group > .bp5-input-action{\n    z-index:16; }\n  .bp5-control-group .bp5-select::after,\n  .bp5-control-group .bp5-html-select::after,\n  .bp5-control-group .bp5-select > .bp5-icon,\n  .bp5-control-group .bp5-html-select > .bp5-icon{\n    z-index:17; }\n  .bp5-control-group .bp5-select:focus-within{\n    z-index:5; }\n  .bp5-control-group:not(.bp5-vertical) > *:not(.bp5-divider){\n    margin-right:-1px; }\n  .bp5-control-group:not(.bp5-vertical) > .bp5-divider:not(:first-child){\n    margin-left:6px; }\n  .bp5-dark .bp5-control-group:not(.bp5-vertical) > *:not(.bp5-divider){\n    margin-right:0; }\n  .bp5-dark .bp5-control-group:not(.bp5-vertical) > .bp5-button + .bp5-button{\n    margin-left:1px; }\n  .bp5-control-group .bp5-popover-wrapper,\n  .bp5-control-group .bp5-popover-target{\n    border-radius:inherit; }\n  .bp5-control-group > :first-child{\n    border-radius:3px 0 0 3px; }\n  .bp5-control-group > :last-child{\n    border-radius:0 3px 3px 0;\n    margin-right:0; }\n  .bp5-control-group > :only-child{\n    border-radius:3px;\n    margin-right:0; }\n  .bp5-control-group .bp5-input-group .bp5-button{\n    border-radius:3px; }\n  .bp5-control-group .bp5-numeric-input:not(:first-child) .bp5-input-group{\n    border-bottom-left-radius:0;\n    border-top-left-radius:0; }\n  .bp5-control-group.bp5-fill{\n    width:100%; }\n  .bp5-control-group > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-control-group.bp5-fill > *:not(.bp5-fixed){\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto; }\n  .bp5-control-group.bp5-vertical{\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:column;\n            flex-direction:column; }\n    .bp5-control-group.bp5-vertical > *{\n      margin-top:-1px; }\n    .bp5-control-group.bp5-vertical > :first-child{\n      border-radius:3px 3px 0 0;\n      margin-top:0; }\n    .bp5-control-group.bp5-vertical > :last-child{\n      border-radius:0 0 3px 3px; }\n.bp5-control{\n  cursor:pointer;\n  display:block;\n  margin-bottom:10px;\n  position:relative;\n  text-transform:none; }\n  .bp5-control input:checked ~ .bp5-control-indicator{\n    background-color:#137cbd;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n  .bp5-control:hover input:checked ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n  .bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n    background:#0e5a8a;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n    background:rgba(19, 124, 189, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-dark .bp5-control input:checked ~ .bp5-control-indicator{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control:hover input:checked ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator{\n    background-color:#0e5a8a;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-control input:disabled:checked ~ .bp5-control-indicator{\n    background:rgba(14, 90, 138, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-control:not(.bp5-align-right){\n    padding-left:26px; }\n    .bp5-control:not(.bp5-align-right) .bp5-control-indicator{\n      margin-left:-26px; }\n  .bp5-control.bp5-align-right{\n    padding-right:26px; }\n    .bp5-control.bp5-align-right .bp5-control-indicator{\n      margin-right:-26px; }\n  .bp5-control.bp5-disabled{\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n  .bp5-control.bp5-inline{\n    display:inline-block;\n    margin-right:20px; }\n  .bp5-control input{\n    left:0;\n    opacity:0;\n    position:absolute;\n    top:0;\n    z-index:-1; }\n  .bp5-control .bp5-control-indicator{\n    background-clip:padding-box;\n    background-color:#f5f8fa;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n    border:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    cursor:pointer;\n    display:inline-block;\n    font-size:16px;\n    height:1em;\n    margin-right:10px;\n    margin-top:-3px;\n    position:relative;\n    -webkit-user-select:none;\n       -moz-user-select:none;\n        -ms-user-select:none;\n            user-select:none;\n    vertical-align:middle;\n    width:1em; }\n    .bp5-control .bp5-control-indicator::before{\n      content:\"\";\n      display:block;\n      height:1em;\n      width:1em; }\n  .bp5-control:hover .bp5-control-indicator{\n    background-color:#ebf1f5; }\n  .bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n    background:#d8e1e8;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-control input:disabled ~ .bp5-control-indicator{\n    background:rgba(206, 217, 224, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    cursor:not-allowed; }\n  .bp5-control input:focus ~ .bp5-control-indicator{\n    outline:rgba(19, 124, 189, 0.6) auto 2px;\n    outline-offset:2px;\n    -moz-outline-radius:6px; }\n  .bp5-control.bp5-align-right .bp5-control-indicator{\n    float:right;\n    margin-left:10px;\n    margin-top:1px; }\n  .bp5-control.bp5-large{\n    font-size:16px; }\n    .bp5-control.bp5-large:not(.bp5-align-right){\n      padding-left:30px; }\n      .bp5-control.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n        margin-left:-30px; }\n    .bp5-control.bp5-large.bp5-align-right{\n      padding-right:30px; }\n      .bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n        margin-right:-30px; }\n    .bp5-control.bp5-large .bp5-control-indicator{\n      font-size:20px; }\n    .bp5-control.bp5-large.bp5-align-right .bp5-control-indicator{\n      margin-top:0; }\n  .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    background-color:#137cbd;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.1)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n    color:#ffffff; }\n  .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 -1px 0 rgba(16, 22, 26, 0.2); }\n  .bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n    background:#0e5a8a;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n    background:rgba(19, 124, 189, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-dark .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator{\n    background-color:#106ba3;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-checkbox input:not(:disabled):active:indeterminate ~ .bp5-control-indicator{\n    background-color:#0e5a8a;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n    background:rgba(14, 90, 138, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-control.bp5-checkbox .bp5-control-indicator{\n    border-radius:3px; }\n  .bp5-control.bp5-checkbox input:checked ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 5c-.28 0-.53.11-.71.29L7 9.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29s.53-.11.71-.29l5-5A1.003 1.003 0 0012 5z' fill='white'/%3e%3c/svg%3e\"); }\n  .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator::before{\n    background-image:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 7H5c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1z' fill='white'/%3e%3c/svg%3e\"); }\n  .bp5-control.bp5-radio .bp5-control-indicator{\n    border-radius:50%; }\n  .bp5-control.bp5-radio input:checked ~ .bp5-control-indicator::before{\n    background-image:radial-gradient(#ffffff, #ffffff 28%, transparent 32%); }\n  .bp5-control.bp5-radio input:checked:disabled ~ .bp5-control-indicator::before{\n    opacity:0.5; }\n  .bp5-control.bp5-radio input:focus ~ .bp5-control-indicator{\n    -moz-outline-radius:16px; }\n  .bp5-control.bp5-switch input ~ .bp5-control-indicator{\n    background:rgba(167, 182, 194, 0.5); }\n  .bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n    background:rgba(115, 134, 148, 0.5); }\n  .bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n    background:rgba(92, 112, 128, 0.5); }\n  .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n    background:rgba(206, 217, 224, 0.5); }\n    .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n      background:rgba(255, 255, 255, 0.8); }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:#137cbd; }\n  .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:#106ba3; }\n  .bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n    background:#0e5a8a; }\n  .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background:rgba(19, 124, 189, 0.5); }\n    .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n      background:rgba(255, 255, 255, 0.8); }\n  .bp5-control.bp5-switch:not(.bp5-align-right){\n    padding-left:38px; }\n    .bp5-control.bp5-switch:not(.bp5-align-right) .bp5-control-indicator{\n      margin-left:-38px; }\n  .bp5-control.bp5-switch.bp5-align-right{\n    padding-right:38px; }\n    .bp5-control.bp5-switch.bp5-align-right .bp5-control-indicator{\n      margin-right:-38px; }\n  .bp5-control.bp5-switch .bp5-control-indicator{\n    border:none;\n    border-radius:1.75em;\n    -webkit-box-shadow:none !important;\n            box-shadow:none !important;\n    min-width:1.75em;\n    -webkit-transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    width:auto; }\n    .bp5-control.bp5-switch .bp5-control-indicator::before{\n      background:#ffffff;\n      border-radius:50%;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n      height:calc(1em - 4px);\n      left:0;\n      margin:2px;\n      position:absolute;\n      -webkit-transition:left 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n      transition:left 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n      width:calc(1em - 4px); }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n    left:calc(100% - 1em); }\n  .bp5-control.bp5-switch.bp5-large:not(.bp5-align-right){\n    padding-left:45px; }\n    .bp5-control.bp5-switch.bp5-large:not(.bp5-align-right) .bp5-control-indicator{\n      margin-left:-45px; }\n  .bp5-control.bp5-switch.bp5-large.bp5-align-right{\n    padding-right:45px; }\n    .bp5-control.bp5-switch.bp5-large.bp5-align-right .bp5-control-indicator{\n      margin-right:-45px; }\n  .bp5-dark .bp5-control.bp5-switch input ~ .bp5-control-indicator{\n    background:rgba(16, 22, 26, 0.5); }\n  .bp5-dark .bp5-control.bp5-switch:hover input ~ .bp5-control-indicator{\n    background:rgba(16, 22, 26, 0.7); }\n  .bp5-dark .bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator{\n    background:rgba(16, 22, 26, 0.9); }\n  .bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator{\n    background:rgba(57, 75, 89, 0.5); }\n    .bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before{\n      background:rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator{\n    background:#137cbd; }\n  .bp5-dark .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator{\n    background:#106ba3; }\n  .bp5-dark .bp5-control.bp5-switch input:checked:not(:disabled):active ~ .bp5-control-indicator{\n    background:#0e5a8a; }\n  .bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator{\n    background:rgba(14, 90, 138, 0.5); }\n    .bp5-dark .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator::before{\n      background:rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-switch .bp5-control-indicator::before{\n    background:#394b59;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-control.bp5-switch .bp5-switch-inner-text{\n    font-size:0.7em;\n    text-align:center; }\n  .bp5-control.bp5-switch .bp5-control-indicator-child:first-child{\n    line-height:0;\n    margin-left:0.5em;\n    margin-right:1.2em;\n    visibility:hidden; }\n  .bp5-control.bp5-switch .bp5-control-indicator-child:last-child{\n    line-height:1em;\n    margin-left:1.2em;\n    margin-right:0.5em;\n    visibility:visible; }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:first-child{\n    line-height:1em;\n    visibility:visible; }\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator .bp5-control-indicator-child:last-child{\n    line-height:0;\n    visibility:hidden; }\n  .bp5-dark .bp5-control{\n    color:#f5f8fa; }\n    .bp5-dark .bp5-control.bp5-disabled{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-control .bp5-control-indicator{\n      background-color:#394b59;\n      background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n      background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-control:hover .bp5-control-indicator{\n      background-color:#30404d; }\n    .bp5-dark .bp5-control input:not(:disabled):active ~ .bp5-control-indicator{\n      background:#202b33;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-control input:disabled ~ .bp5-control-indicator{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      cursor:not-allowed; }\n    .bp5-dark .bp5-control.bp5-checkbox input:disabled:checked ~ .bp5-control-indicator, .bp5-dark .bp5-control.bp5-checkbox input:disabled:indeterminate ~ .bp5-control-indicator{\n      color:rgba(167, 182, 194, 0.6); }\n.bp5-file-input{\n  cursor:pointer;\n  display:inline-block;\n  height:30px;\n  position:relative; }\n  .bp5-file-input input{\n    margin:0;\n    min-width:200px;\n    opacity:0; }\n    .bp5-file-input input:disabled + .bp5-file-upload-input,\n    .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n      background:rgba(206, 217, 224, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed;\n      resize:none; }\n      .bp5-file-input input:disabled + .bp5-file-upload-input::after,\n      .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n        background-color:rgba(206, 217, 224, 0.5);\n        background-image:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:rgba(92, 112, 128, 0.6);\n        cursor:not-allowed;\n        outline:none; }\n        .bp5-file-input input:disabled + .bp5-file-upload-input::after.bp5-active, .bp5-file-input input:disabled + .bp5-file-upload-input::after.bp5-active:hover,\n        .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after.bp5-active,\n        .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after.bp5-active:hover{\n          background:rgba(206, 217, 224, 0.7); }\n      .bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input, .bp5-dark\n      .bp5-file-input input.bp5-disabled + .bp5-file-upload-input{\n        background:rgba(57, 75, 89, 0.5);\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:rgba(167, 182, 194, 0.6); }\n        .bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input::after, .bp5-dark\n        .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after{\n          background-color:rgba(57, 75, 89, 0.5);\n          background-image:none;\n          -webkit-box-shadow:none;\n                  box-shadow:none;\n          color:rgba(167, 182, 194, 0.6); }\n          .bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input::after.bp5-active, .bp5-dark\n          .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after.bp5-active{\n            background:rgba(57, 75, 89, 0.7); }\n  .bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n    color:#182026; }\n  .bp5-dark .bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input{\n    color:#f5f8fa; }\n  .bp5-file-input.bp5-fill{\n    width:100%; }\n  .bp5-file-input.bp5-large,\n  .bp5-large .bp5-file-input{\n    height:40px; }\n  .bp5-file-input .bp5-file-upload-input-custom-text::after{\n    content:attr(bp5-button-text); }\n\n.bp5-file-upload-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  color:#182026;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  -webkit-transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  color:rgba(92, 112, 128, 0.6);\n  left:0;\n  padding-right:80px;\n  position:absolute;\n  right:0;\n  top:0;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-file-upload-input::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-file-upload-input:focus, .bp5-file-upload-input.bp5-active{\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-file-upload-input[type=\"search\"], .bp5-file-upload-input.bp5-round{\n    border-radius:30px;\n    -webkit-box-sizing:border-box;\n            box-sizing:border-box;\n    padding-left:10px; }\n  .bp5-file-upload-input[readonly]{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15); }\n  .bp5-file-upload-input:disabled, .bp5-file-upload-input.bp5-disabled{\n    background:rgba(206, 217, 224, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    resize:none; }\n  .bp5-file-upload-input::after{\n    background-color:#f5f8fa;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    color:#182026;\n    min-height:24px;\n    min-width:24px;\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    border-radius:3px;\n    content:\"Browse\";\n    line-height:24px;\n    margin:3px;\n    position:absolute;\n    right:0;\n    text-align:center;\n    top:0;\n    width:70px; }\n    .bp5-file-upload-input::after:hover{\n      background-clip:padding-box;\n      background-color:#ebf1f5;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n    .bp5-file-upload-input::after:active, .bp5-file-upload-input::after.bp5-active{\n      background-color:#d8e1e8;\n      background-image:none;\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-file-upload-input::after:disabled, .bp5-file-upload-input::after.bp5-disabled{\n      background-color:rgba(206, 217, 224, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(92, 112, 128, 0.6);\n      cursor:not-allowed;\n      outline:none; }\n      .bp5-file-upload-input::after:disabled.bp5-active, .bp5-file-upload-input::after:disabled.bp5-active:hover, .bp5-file-upload-input::after.bp5-disabled.bp5-active, .bp5-file-upload-input::after.bp5-disabled.bp5-active:hover{\n        background:rgba(206, 217, 224, 0.7); }\n  .bp5-file-upload-input:hover::after{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n  .bp5-file-upload-input:active::after{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-large .bp5-file-upload-input{\n    font-size:16px;\n    height:40px;\n    line-height:40px;\n    padding-right:95px; }\n    .bp5-large .bp5-file-upload-input[type=\"search\"], .bp5-large .bp5-file-upload-input.bp5-round{\n      padding:0 15px; }\n    .bp5-large .bp5-file-upload-input::after{\n      min-height:30px;\n      min-width:30px;\n      line-height:30px;\n      margin:5px;\n      width:85px; }\n  .bp5-dark .bp5-file-upload-input{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa;\n    color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-file-upload-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-file-upload-input:disabled, .bp5-dark .bp5-file-upload-input.bp5-disabled{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-file-upload-input::after{\n      background-color:#394b59;\n      background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n      background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n      color:#f5f8fa; }\n      .bp5-dark .bp5-file-upload-input::after:hover, .bp5-dark .bp5-file-upload-input::after:active, .bp5-dark .bp5-file-upload-input::after.bp5-active{\n        color:#f5f8fa; }\n      .bp5-dark .bp5-file-upload-input::after:hover{\n        background-color:#30404d;\n        -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-file-upload-input::after:active, .bp5-dark .bp5-file-upload-input::after.bp5-active{\n        background-color:#202b33;\n        background-image:none;\n        -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n                box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n      .bp5-dark .bp5-file-upload-input::after:disabled, .bp5-dark .bp5-file-upload-input::after.bp5-disabled{\n        background-color:rgba(57, 75, 89, 0.5);\n        background-image:none;\n        -webkit-box-shadow:none;\n                box-shadow:none;\n        color:rgba(167, 182, 194, 0.6); }\n        .bp5-dark .bp5-file-upload-input::after:disabled.bp5-active, .bp5-dark .bp5-file-upload-input::after.bp5-disabled.bp5-active{\n          background:rgba(57, 75, 89, 0.7); }\n      .bp5-dark .bp5-file-upload-input::after .bp5-button-spinner .bp5-spinner-head{\n        background:rgba(16, 22, 26, 0.5);\n        stroke:#8a9ba8; }\n    .bp5-dark .bp5-file-upload-input:hover::after{\n      background-color:#30404d;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-file-upload-input:active::after{\n      background-color:#202b33;\n      background-image:none;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n.bp5-file-upload-input::after{\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n.bp5-form-group{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin:0 0 15px; }\n  .bp5-form-group label.bp5-label{\n    margin-bottom:5px; }\n  .bp5-form-group .bp5-control{\n    margin-top:7px; }\n  .bp5-form-group .bp5-form-group-sub-label,\n  .bp5-form-group .bp5-form-helper-text{\n    color:#5c7080;\n    font-size:12px; }\n  .bp5-form-group .bp5-form-group-sub-label{\n    margin-bottom:5px; }\n  .bp5-form-group .bp5-form-helper-text{\n    margin-top:5px; }\n  .bp5-form-group.bp5-intent-primary .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n    color:#106ba3; }\n  .bp5-form-group.bp5-intent-success .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n    color:#0d8050; }\n  .bp5-form-group.bp5-intent-warning .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n    color:#bf7326; }\n  .bp5-form-group.bp5-intent-danger .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n    color:#c23030; }\n  .bp5-form-group.bp5-inline{\n    -webkit-box-align:start;\n        -ms-flex-align:start;\n            align-items:flex-start;\n    -webkit-box-orient:horizontal;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:row;\n            flex-direction:row; }\n    .bp5-form-group.bp5-inline.bp5-large label.bp5-label{\n      line-height:40px;\n      margin:0 10px 0 0; }\n    .bp5-form-group.bp5-inline label.bp5-label{\n      line-height:30px;\n      margin:0 10px 0 0; }\n  .bp5-form-group.bp5-disabled .bp5-label,\n  .bp5-form-group.bp5-disabled .bp5-text-muted,\n  .bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n  .bp5-form-group.bp5-disabled .bp5-form-helper-text{\n    color:rgba(92, 112, 128, 0.6) !important; }\n  .bp5-dark .bp5-form-group.bp5-intent-primary .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-primary .bp5-form-helper-text{\n    color:#48aff0; }\n  .bp5-dark .bp5-form-group.bp5-intent-success .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-success .bp5-form-helper-text{\n    color:#3dcc91; }\n  .bp5-dark .bp5-form-group.bp5-intent-warning .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-warning .bp5-form-helper-text{\n    color:#ffb366; }\n  .bp5-dark .bp5-form-group.bp5-intent-danger .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-intent-danger .bp5-form-helper-text{\n    color:#ff7373; }\n  .bp5-dark .bp5-form-group .bp5-form-helper-text{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-label,\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-text-muted,\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n  .bp5-dark .bp5-form-group.bp5-disabled .bp5-form-helper-text{\n    color:rgba(167, 182, 194, 0.6) !important; }\n.bp5-input-group{\n  display:block;\n  position:relative; }\n  .bp5-input-group .bp5-input{\n    position:relative;\n    width:100%; }\n    .bp5-input-group .bp5-input:not(:first-child){\n      padding-left:30px; }\n    .bp5-input-group .bp5-input:not(:last-child){\n      padding-right:30px; }\n  .bp5-input-group .bp5-input-action,\n  .bp5-input-group > .bp5-input-left-container,\n  .bp5-input-group > .bp5-button,\n  .bp5-input-group > .bp5-icon{\n    position:absolute;\n    top:0; }\n    .bp5-input-group .bp5-input-action:first-child,\n    .bp5-input-group > .bp5-input-left-container:first-child,\n    .bp5-input-group > .bp5-button:first-child,\n    .bp5-input-group > .bp5-icon:first-child{\n      left:0; }\n    .bp5-input-group .bp5-input-action:last-child,\n    .bp5-input-group > .bp5-input-left-container:last-child,\n    .bp5-input-group > .bp5-button:last-child,\n    .bp5-input-group > .bp5-icon:last-child{\n      right:0; }\n  .bp5-input-group .bp5-button{\n    min-height:24px;\n    min-width:24px;\n    margin:3px;\n    padding:0 7px; }\n    .bp5-input-group .bp5-button:empty{\n      padding:0; }\n  .bp5-input-group > .bp5-input-left-container,\n  .bp5-input-group > .bp5-icon{\n    z-index:1; }\n  .bp5-input-group > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group > .bp5-icon{\n    color:#5c7080; }\n    .bp5-input-group > .bp5-input-left-container > .bp5-icon:empty,\n    .bp5-input-group > .bp5-icon:empty{\n      font-family:\"blueprint-icons-16\", sans-serif;\n      font-size:16px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1;\n      -moz-osx-font-smoothing:grayscale;\n      -webkit-font-smoothing:antialiased; }\n  .bp5-input-group > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group > .bp5-icon,\n  .bp5-input-group .bp5-input-action > .bp5-spinner{\n    margin:7px; }\n  .bp5-input-group .bp5-tag{\n    margin:5px; }\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus),\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n    color:#5c7080; }\n    .bp5-dark .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus), .bp5-dark\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus){\n      color:#a7b6c2; }\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-standard,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:not(:hover):not(:focus) .bp5-icon-large{\n      color:#5c7080; }\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled,\n  .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled{\n    color:rgba(92, 112, 128, 0.6) !important; }\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-standard, .bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled .bp5-icon-large,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-standard,\n    .bp5-input-group .bp5-input:not(:focus) + .bp5-input-action .bp5-button.bp5-minimal:disabled .bp5-icon-large{\n      color:rgba(92, 112, 128, 0.6) !important; }\n  .bp5-input-group.bp5-disabled{\n    cursor:not-allowed; }\n    .bp5-input-group.bp5-disabled .bp5-icon{\n      color:rgba(92, 112, 128, 0.6); }\n  .bp5-input-group.bp5-large .bp5-button{\n    min-height:30px;\n    min-width:30px;\n    margin:5px; }\n  .bp5-input-group.bp5-large > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group.bp5-large > .bp5-icon,\n  .bp5-input-group.bp5-large .bp5-input-action > .bp5-spinner{\n    margin:12px; }\n  .bp5-input-group.bp5-large .bp5-input{\n    font-size:16px;\n    height:40px;\n    line-height:40px; }\n    .bp5-input-group.bp5-large .bp5-input[type=\"search\"], .bp5-input-group.bp5-large .bp5-input.bp5-round{\n      padding:0 15px; }\n    .bp5-input-group.bp5-large .bp5-input:not(:first-child){\n      padding-left:40px; }\n    .bp5-input-group.bp5-large .bp5-input:not(:last-child){\n      padding-right:40px; }\n  .bp5-input-group.bp5-small .bp5-button{\n    min-height:20px;\n    min-width:20px;\n    margin:2px; }\n  .bp5-input-group.bp5-small .bp5-tag{\n    min-height:20px;\n    min-width:20px;\n    margin:2px; }\n  .bp5-input-group.bp5-small > .bp5-input-left-container > .bp5-icon,\n  .bp5-input-group.bp5-small > .bp5-icon,\n  .bp5-input-group.bp5-small .bp5-input-action > .bp5-spinner{\n    margin:4px; }\n  .bp5-input-group.bp5-small .bp5-input{\n    font-size:12px;\n    height:24px;\n    line-height:24px;\n    padding-left:8px;\n    padding-right:8px; }\n    .bp5-input-group.bp5-small .bp5-input[type=\"search\"], .bp5-input-group.bp5-small .bp5-input.bp5-round{\n      padding:0 12px; }\n    .bp5-input-group.bp5-small .bp5-input:not(:first-child){\n      padding-left:24px; }\n    .bp5-input-group.bp5-small .bp5-input:not(:last-child){\n      padding-right:24px; }\n  .bp5-input-group.bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    width:100%; }\n  .bp5-input-group.bp5-round .bp5-button,\n  .bp5-input-group.bp5-round .bp5-input,\n  .bp5-input-group.bp5-round .bp5-tag{\n    border-radius:30px; }\n  .bp5-dark .bp5-input-group .bp5-icon{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-input-group.bp5-disabled .bp5-icon{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-input-group.bp5-intent-primary .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-primary .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-primary .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n              box-shadow:inset 0 0 0 1px #137cbd; }\n    .bp5-input-group.bp5-intent-primary .bp5-input:disabled, .bp5-input-group.bp5-intent-primary .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-primary > .bp5-icon{\n    color:#106ba3; }\n    .bp5-dark .bp5-input-group.bp5-intent-primary > .bp5-icon{\n      color:#48aff0; }\n  .bp5-input-group.bp5-intent-success .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-success .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-success .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n              box-shadow:inset 0 0 0 1px #0f9960; }\n    .bp5-input-group.bp5-intent-success .bp5-input:disabled, .bp5-input-group.bp5-intent-success .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-success > .bp5-icon{\n    color:#0d8050; }\n    .bp5-dark .bp5-input-group.bp5-intent-success > .bp5-icon{\n      color:#3dcc91; }\n  .bp5-input-group.bp5-intent-warning .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-warning .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-warning .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n              box-shadow:inset 0 0 0 1px #d9822b; }\n    .bp5-input-group.bp5-intent-warning .bp5-input:disabled, .bp5-input-group.bp5-intent-warning .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-warning > .bp5-icon{\n    color:#bf7326; }\n    .bp5-dark .bp5-input-group.bp5-intent-warning > .bp5-icon{\n      color:#ffb366; }\n  .bp5-input-group.bp5-intent-danger .bp5-input{\n    -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-danger .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input-group.bp5-intent-danger .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #db3737;\n              box-shadow:inset 0 0 0 1px #db3737; }\n    .bp5-input-group.bp5-intent-danger .bp5-input:disabled, .bp5-input-group.bp5-intent-danger .bp5-input.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-input-group.bp5-intent-danger > .bp5-icon{\n    color:#c23030; }\n    .bp5-dark .bp5-input-group.bp5-intent-danger > .bp5-icon{\n      color:#ff7373; }\n.bp5-input{\n  -webkit-appearance:none;\n     -moz-appearance:none;\n          appearance:none;\n  background:#ffffff;\n  border:none;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  color:#182026;\n  font-size:14px;\n  font-weight:400;\n  height:30px;\n  line-height:30px;\n  outline:none;\n  padding:0 10px;\n  -webkit-transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align:middle; }\n  .bp5-input::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input:focus, .bp5-input.bp5-active{\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-input[type=\"search\"], .bp5-input.bp5-round{\n    border-radius:30px;\n    -webkit-box-sizing:border-box;\n            box-sizing:border-box;\n    padding-left:10px; }\n  .bp5-input[readonly]{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15); }\n  .bp5-input:disabled, .bp5-input.bp5-disabled{\n    background:rgba(206, 217, 224, 0.5);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    resize:none; }\n  .bp5-input.bp5-large{\n    font-size:16px;\n    height:40px;\n    line-height:40px; }\n    .bp5-input.bp5-large[type=\"search\"], .bp5-input.bp5-large.bp5-round{\n      padding:0 15px; }\n  .bp5-input.bp5-small{\n    font-size:12px;\n    height:24px;\n    line-height:24px;\n    padding-left:8px;\n    padding-right:8px; }\n    .bp5-input.bp5-small[type=\"search\"], .bp5-input.bp5-small.bp5-round{\n      padding:0 12px; }\n  .bp5-input.bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    width:100%; }\n  .bp5-dark .bp5-input{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark .bp5-input::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-input:disabled, .bp5-dark .bp5-input.bp5-disabled{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n  .bp5-input.bp5-intent-primary{\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-primary:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-primary[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n              box-shadow:inset 0 0 0 1px #137cbd; }\n    .bp5-input.bp5-intent-primary:disabled, .bp5-input.bp5-intent-primary.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-primary{\n      -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-primary:focus{\n        -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-primary[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n                box-shadow:inset 0 0 0 1px #137cbd; }\n      .bp5-dark .bp5-input.bp5-intent-primary:disabled, .bp5-dark .bp5-input.bp5-intent-primary.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input.bp5-intent-success{\n    -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-success:focus{\n      -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-success[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n              box-shadow:inset 0 0 0 1px #0f9960; }\n    .bp5-input.bp5-intent-success:disabled, .bp5-input.bp5-intent-success.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-success{\n      -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-success:focus{\n        -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-success[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n                box-shadow:inset 0 0 0 1px #0f9960; }\n      .bp5-dark .bp5-input.bp5-intent-success:disabled, .bp5-dark .bp5-input.bp5-intent-success.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input.bp5-intent-warning{\n    -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-warning:focus{\n      -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-warning[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n              box-shadow:inset 0 0 0 1px #d9822b; }\n    .bp5-input.bp5-intent-warning:disabled, .bp5-input.bp5-intent-warning.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-warning{\n      -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-warning:focus{\n        -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-warning[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n                box-shadow:inset 0 0 0 1px #d9822b; }\n      .bp5-dark .bp5-input.bp5-intent-warning:disabled, .bp5-dark .bp5-input.bp5-intent-warning.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input.bp5-intent-danger{\n    -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-danger:focus{\n      -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-input.bp5-intent-danger[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px #db3737;\n              box-shadow:inset 0 0 0 1px #db3737; }\n    .bp5-input.bp5-intent-danger:disabled, .bp5-input.bp5-intent-danger.bp5-disabled{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-input.bp5-intent-danger{\n      -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-danger:focus{\n        -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n                box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n      .bp5-dark .bp5-input.bp5-intent-danger[readonly]{\n        -webkit-box-shadow:inset 0 0 0 1px #db3737;\n                box-shadow:inset 0 0 0 1px #db3737; }\n      .bp5-dark .bp5-input.bp5-intent-danger:disabled, .bp5-dark .bp5-input.bp5-intent-danger.bp5-disabled{\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n  .bp5-input::-ms-clear{\n    display:none; }\ntextarea.bp5-input{\n  max-width:100%;\n  padding:10px; }\n  textarea.bp5-input, textarea.bp5-input.bp5-large, textarea.bp5-input.bp5-small{\n    height:auto;\n    line-height:inherit; }\n  textarea.bp5-input.bp5-small{\n    padding:8px; }\n  .bp5-dark textarea.bp5-input{\n    background:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark textarea.bp5-input::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark textarea.bp5-input:focus{\n      -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark textarea.bp5-input[readonly]{\n      -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark textarea.bp5-input:disabled, .bp5-dark textarea.bp5-input.bp5-disabled{\n      background:rgba(57, 75, 89, 0.5);\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\nlabel.bp5-label{\n  display:block;\n  margin-bottom:15px;\n  margin-top:0; }\n  label.bp5-label .bp5-html-select,\n  label.bp5-label .bp5-input,\n  label.bp5-label .bp5-select,\n  label.bp5-label .bp5-slider,\n  label.bp5-label .bp5-popover-wrapper{\n    display:block;\n    margin-top:5px;\n    text-transform:none; }\n  label.bp5-label .bp5-button-group{\n    margin-top:5px; }\n  label.bp5-label .bp5-select select,\n  label.bp5-label .bp5-html-select select{\n    font-weight:400;\n    vertical-align:top;\n    width:100%; }\n  label.bp5-label.bp5-disabled,\n  label.bp5-label.bp5-disabled .bp5-text-muted{\n    color:rgba(92, 112, 128, 0.6); }\n  label.bp5-label.bp5-inline{\n    line-height:30px; }\n    label.bp5-label.bp5-inline .bp5-html-select,\n    label.bp5-label.bp5-inline .bp5-input,\n    label.bp5-label.bp5-inline .bp5-input-group,\n    label.bp5-label.bp5-inline .bp5-select,\n    label.bp5-label.bp5-inline .bp5-popover-wrapper{\n      display:inline-block;\n      margin:0 0 0 5px;\n      vertical-align:top; }\n    label.bp5-label.bp5-inline .bp5-button-group{\n      margin:0 0 0 5px; }\n    label.bp5-label.bp5-inline .bp5-input-group .bp5-input{\n      margin-left:0; }\n    label.bp5-label.bp5-inline.bp5-large{\n      line-height:40px; }\n  label.bp5-label:not(.bp5-inline) .bp5-popover-target{\n    display:block; }\n  .bp5-dark label.bp5-label{\n    color:#f5f8fa; }\n    .bp5-dark label.bp5-label.bp5-disabled,\n    .bp5-dark label.bp5-label.bp5-disabled .bp5-text-muted{\n      color:rgba(167, 182, 194, 0.6); }\n.bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 14px;\n          flex:1 1 14px;\n  min-height:0;\n  padding:0;\n  width:30px; }\n  .bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button:first-child{\n    border-radius:0 3px 0 0; }\n  .bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button:last-child{\n    border-radius:0 0 3px 0; }\n\n.bp5-numeric-input .bp5-button-group.bp5-vertical:first-child > .bp5-button:first-child{\n  border-radius:3px 0 0 0; }\n\n.bp5-numeric-input .bp5-button-group.bp5-vertical:first-child > .bp5-button:last-child{\n  border-radius:0 0 0 3px; }\n\n.bp5-numeric-input.bp5-large .bp5-button-group.bp5-vertical > .bp5-button{\n  width:40px; }\n\nform{\n  display:block; }\n.bp5-html-select select,\n.bp5-select select{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  border:none;\n  border-radius:3px;\n  cursor:pointer;\n  font-size:14px;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  padding:5px 10px;\n  text-align:left;\n  vertical-align:middle;\n  background-color:#f5f8fa;\n  background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n  background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n  color:#182026;\n  -moz-appearance:none;\n  -webkit-appearance:none;\n  border-radius:3px;\n  height:30px;\n  padding:0 25px 0 10px;\n  width:100%; }\n  .bp5-html-select select > *, .bp5-select select > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-html-select select > .bp5-fill, .bp5-select select > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-html-select select::before,\n  .bp5-select select::before, .bp5-html-select select > *, .bp5-select select > *{\n    margin-right:7px; }\n  .bp5-html-select select:empty::before,\n  .bp5-select select:empty::before,\n  .bp5-html-select select > :last-child,\n  .bp5-select select > :last-child{\n    margin-right:0; }\n  .bp5-html-select select:hover,\n  .bp5-select select:hover{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n  .bp5-html-select select:active,\n  .bp5-select select:active, .bp5-html-select select.bp5-active,\n  .bp5-select select.bp5-active{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-html-select select:disabled,\n  .bp5-select select:disabled, .bp5-html-select select.bp5-disabled,\n  .bp5-select select.bp5-disabled{\n    background-color:rgba(206, 217, 224, 0.5);\n    background-image:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    outline:none; }\n    .bp5-html-select select:disabled.bp5-active,\n    .bp5-select select:disabled.bp5-active, .bp5-html-select select:disabled.bp5-active:hover,\n    .bp5-select select:disabled.bp5-active:hover, .bp5-html-select select.bp5-disabled.bp5-active,\n    .bp5-select select.bp5-disabled.bp5-active, .bp5-html-select select.bp5-disabled.bp5-active:hover,\n    .bp5-select select.bp5-disabled.bp5-active:hover{\n      background:rgba(206, 217, 224, 0.7); }\n\n.bp5-html-select.bp5-minimal select,\n.bp5-select.bp5-minimal select{\n  background:none;\n  -webkit-box-shadow:none;\n          box-shadow:none; }\n  .bp5-html-select.bp5-minimal select:hover,\n  .bp5-select.bp5-minimal select:hover{\n    background:rgba(167, 182, 194, 0.3);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:#182026;\n    text-decoration:none; }\n  .bp5-html-select.bp5-minimal select:active,\n  .bp5-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal select.bp5-active,\n  .bp5-select.bp5-minimal select.bp5-active{\n    background:rgba(115, 134, 148, 0.3);\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:#182026; }\n  .bp5-html-select.bp5-minimal select:disabled,\n  .bp5-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal select:disabled:hover,\n  .bp5-select.bp5-minimal select:disabled:hover, .bp5-html-select.bp5-minimal select.bp5-disabled,\n  .bp5-select.bp5-minimal select.bp5-disabled, .bp5-html-select.bp5-minimal select.bp5-disabled:hover,\n  .bp5-select.bp5-minimal select.bp5-disabled:hover{\n    background:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n    .bp5-html-select.bp5-minimal select:disabled.bp5-active,\n    .bp5-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal select:disabled:hover.bp5-active,\n    .bp5-select.bp5-minimal select:disabled:hover.bp5-active, .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-disabled:hover.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-disabled:hover.bp5-active{\n      background:rgba(115, 134, 148, 0.3); }\n  .bp5-dark .bp5-html-select.bp5-minimal select, .bp5-html-select.bp5-minimal .bp5-dark select,\n  .bp5-dark .bp5-select.bp5-minimal select, .bp5-select.bp5-minimal .bp5-dark select{\n    background:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:inherit; }\n    .bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n    .bp5-dark .bp5-select.bp5-minimal select:hover, .bp5-select.bp5-minimal .bp5-dark select:hover, .bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n    .bp5-dark .bp5-select.bp5-minimal select:active, .bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n    .bp5-dark .bp5-html-select.bp5-minimal select:hover, .bp5-html-select.bp5-minimal .bp5-dark select:hover,\n    .bp5-dark .bp5-select.bp5-minimal select:hover, .bp5-select.bp5-minimal .bp5-dark select:hover{\n      background:rgba(138, 155, 168, 0.15); }\n    .bp5-dark .bp5-html-select.bp5-minimal select:active, .bp5-html-select.bp5-minimal .bp5-dark select:active,\n    .bp5-dark .bp5-select.bp5-minimal select:active, .bp5-select.bp5-minimal .bp5-dark select:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-active{\n      background:rgba(138, 155, 168, 0.3);\n      color:#f5f8fa; }\n    .bp5-dark .bp5-html-select.bp5-minimal select:disabled, .bp5-html-select.bp5-minimal .bp5-dark select:disabled,\n    .bp5-dark .bp5-select.bp5-minimal select:disabled, .bp5-select.bp5-minimal .bp5-dark select:disabled, .bp5-dark .bp5-html-select.bp5-minimal select:disabled:hover, .bp5-html-select.bp5-minimal .bp5-dark select:disabled:hover,\n    .bp5-dark .bp5-select.bp5-minimal select:disabled:hover, .bp5-select.bp5-minimal .bp5-dark select:disabled:hover, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled:hover,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled:hover{\n      background:none;\n      color:rgba(167, 182, 194, 0.6);\n      cursor:not-allowed; }\n      .bp5-dark .bp5-html-select.bp5-minimal select:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select:disabled.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select:disabled:hover.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select:disabled:hover.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select:disabled:hover.bp5-active, .bp5-select.bp5-minimal .bp5-dark select:disabled:hover.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled:hover.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled:hover.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-disabled:hover.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-disabled:hover.bp5-active{\n        background:rgba(138, 155, 168, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-primary,\n  .bp5-select.bp5-minimal select.bp5-intent-primary{\n    color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:hover{\n      background:rgba(19, 124, 189, 0.15);\n      color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active{\n      background:rgba(19, 124, 189, 0.3);\n      color:#106ba3; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled{\n      background:none;\n      color:rgba(16, 107, 163, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active{\n        background:rgba(19, 124, 189, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-primary .bp5-button-spinner .bp5-spinner-head{\n      stroke:#106ba3; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary{\n      color:#48aff0; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover{\n        background:rgba(19, 124, 189, 0.2);\n        color:#48aff0; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active{\n        background:rgba(19, 124, 189, 0.3);\n        color:#48aff0; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled{\n        background:none;\n        color:rgba(72, 175, 240, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled.bp5-active{\n          background:rgba(19, 124, 189, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-success,\n  .bp5-select.bp5-minimal select.bp5-intent-success{\n    color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n    .bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-success:hover{\n      background:rgba(15, 153, 96, 0.15);\n      color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n    .bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active{\n      background:rgba(15, 153, 96, 0.3);\n      color:#0d8050; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled{\n      background:none;\n      color:rgba(13, 128, 80, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active{\n        background:rgba(15, 153, 96, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-success .bp5-button-spinner .bp5-spinner-head{\n      stroke:#0d8050; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success{\n      color:#3dcc91; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover{\n        background:rgba(15, 153, 96, 0.2);\n        color:#3dcc91; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active{\n        background:rgba(15, 153, 96, 0.3);\n        color:#3dcc91; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled{\n        background:none;\n        color:rgba(61, 204, 145, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled.bp5-active{\n          background:rgba(15, 153, 96, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-warning,\n  .bp5-select.bp5-minimal select.bp5-intent-warning{\n    color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:hover{\n      background:rgba(217, 130, 43, 0.15);\n      color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active{\n      background:rgba(217, 130, 43, 0.3);\n      color:#bf7326; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled{\n      background:none;\n      color:rgba(191, 115, 38, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active{\n        background:rgba(217, 130, 43, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-warning .bp5-button-spinner .bp5-spinner-head{\n      stroke:#bf7326; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning{\n      color:#ffb366; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover{\n        background:rgba(217, 130, 43, 0.2);\n        color:#ffb366; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active{\n        background:rgba(217, 130, 43, 0.3);\n        color:#ffb366; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled{\n        background:none;\n        color:rgba(255, 179, 102, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled.bp5-active{\n          background:rgba(217, 130, 43, 0.3); }\n  .bp5-html-select.bp5-minimal select.bp5-intent-danger,\n  .bp5-select.bp5-minimal select.bp5-intent-danger{\n    color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n      background:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:hover{\n      background:rgba(219, 55, 55, 0.15);\n      color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n    .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active{\n      background:rgba(219, 55, 55, 0.3);\n      color:#c23030; }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n    .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled{\n      background:none;\n      color:rgba(194, 48, 48, 0.5); }\n      .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active,\n      .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active{\n        background:rgba(219, 55, 55, 0.3); }\n    .bp5-html-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head, .bp5-select.bp5-minimal select.bp5-intent-danger .bp5-button-spinner .bp5-spinner-head{\n      stroke:#c23030; }\n    .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger,\n    .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger{\n      color:#ff7373; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:hover, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover{\n        background:rgba(219, 55, 55, 0.2);\n        color:#ff7373; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active{\n        background:rgba(219, 55, 55, 0.3);\n        color:#ff7373; }\n      .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled,\n      .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled{\n        background:none;\n        color:rgba(255, 115, 115, 0.5); }\n        .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active, .bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active,\n        .bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active, .bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled.bp5-active{\n          background:rgba(219, 55, 55, 0.3); }\n\n.bp5-html-select.bp5-large select,\n.bp5-select.bp5-large select{\n  font-size:16px;\n  height:40px;\n  padding-right:35px; }\n\n.bp5-dark .bp5-html-select select, .bp5-dark .bp5-select select{\n  background-color:#394b59;\n  background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n  background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n  color:#f5f8fa; }\n  .bp5-dark .bp5-html-select select:hover, .bp5-dark .bp5-select select:hover, .bp5-dark .bp5-html-select select:active, .bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active, .bp5-dark .bp5-select select.bp5-active{\n    color:#f5f8fa; }\n  .bp5-dark .bp5-html-select select:hover, .bp5-dark .bp5-select select:hover{\n    background-color:#30404d;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-html-select select:active, .bp5-dark .bp5-select select:active, .bp5-dark .bp5-html-select select.bp5-active, .bp5-dark .bp5-select select.bp5-active{\n    background-color:#202b33;\n    background-image:none;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-html-select select:disabled, .bp5-dark .bp5-select select:disabled, .bp5-dark .bp5-html-select select.bp5-disabled, .bp5-dark .bp5-select select.bp5-disabled{\n    background-color:rgba(57, 75, 89, 0.5);\n    background-image:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-html-select select:disabled.bp5-active, .bp5-dark .bp5-select select:disabled.bp5-active, .bp5-dark .bp5-html-select select.bp5-disabled.bp5-active, .bp5-dark .bp5-select select.bp5-disabled.bp5-active{\n      background:rgba(57, 75, 89, 0.7); }\n  .bp5-dark .bp5-html-select select .bp5-button-spinner .bp5-spinner-head, .bp5-dark .bp5-select select .bp5-button-spinner .bp5-spinner-head{\n    background:rgba(16, 22, 26, 0.5);\n    stroke:#8a9ba8; }\n\n.bp5-html-select select:disabled,\n.bp5-select select:disabled{\n  background-color:rgba(206, 217, 224, 0.5);\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-html-select .bp5-icon,\n.bp5-select .bp5-icon, .bp5-select::after{\n  color:#5c7080;\n  pointer-events:none;\n  position:absolute;\n  right:7px;\n  top:7px; }\n  .bp5-html-select .bp5-disabled.bp5-icon,\n  .bp5-select .bp5-disabled.bp5-icon, .bp5-disabled.bp5-select::after{\n    color:rgba(92, 112, 128, 0.6); }\n.bp5-html-select,\n.bp5-select{\n  display:inline-block;\n  letter-spacing:normal;\n  position:relative;\n  vertical-align:middle; }\n  .bp5-html-select select::-ms-expand,\n  .bp5-select select::-ms-expand{\n    display:none; }\n  .bp5-html-select .bp5-icon,\n  .bp5-select .bp5-icon{\n    color:#5c7080; }\n    .bp5-html-select .bp5-icon:hover,\n    .bp5-select .bp5-icon:hover{\n      color:#182026; }\n    .bp5-dark .bp5-html-select .bp5-icon, .bp5-dark\n    .bp5-select .bp5-icon{\n      color:#a7b6c2; }\n      .bp5-dark .bp5-html-select .bp5-icon:hover, .bp5-dark\n      .bp5-select .bp5-icon:hover{\n        color:#f5f8fa; }\n  .bp5-html-select.bp5-large::after,\n  .bp5-html-select.bp5-large .bp5-icon,\n  .bp5-select.bp5-large::after,\n  .bp5-select.bp5-large .bp5-icon{\n    right:12px;\n    top:12px; }\n  .bp5-html-select.bp5-fill,\n  .bp5-html-select.bp5-fill select,\n  .bp5-select.bp5-fill,\n  .bp5-select.bp5-fill select{\n    width:100%; }\n  .bp5-dark .bp5-html-select option, .bp5-dark\n  .bp5-select option{\n    background-color:#30404d;\n    color:#f5f8fa; }\n  .bp5-dark .bp5-html-select option:disabled, .bp5-dark\n  .bp5-select option:disabled{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-dark .bp5-html-select::after, .bp5-dark\n  .bp5-select::after{\n    color:#a7b6c2; }\n\n.bp5-select::after{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-weight:400;\n  line-height:1;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  content:\"\\f184\"; }\n.bp5-running-text table, table.bp5-html-table{\n  border-spacing:0;\n  font-size:14px; }\n  .bp5-running-text table th, table.bp5-html-table th,\n  .bp5-running-text table td,\n  table.bp5-html-table td{\n    padding:11px;\n    text-align:left;\n    vertical-align:top; }\n  .bp5-running-text table th, table.bp5-html-table th{\n    color:#182026;\n    font-weight:600; }\n\n  .bp5-running-text table td,\n  table.bp5-html-table td{\n    color:#182026; }\n  .bp5-running-text table tbody tr:first-child th, table.bp5-html-table tbody tr:first-child th,\n  .bp5-running-text table tbody tr:first-child td,\n  table.bp5-html-table tbody tr:first-child td,\n  .bp5-running-text table tfoot tr:first-child th,\n  table.bp5-html-table tfoot tr:first-child th,\n  .bp5-running-text table tfoot tr:first-child td,\n  table.bp5-html-table tfoot tr:first-child td{\n    -webkit-box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15);\n            box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15); }\n  .bp5-dark .bp5-running-text table th, .bp5-running-text .bp5-dark table th, .bp5-dark table.bp5-html-table th{\n    color:#f5f8fa; }\n  .bp5-dark .bp5-running-text table td, .bp5-running-text .bp5-dark table td, .bp5-dark table.bp5-html-table td{\n    color:#f5f8fa; }\n  .bp5-dark .bp5-running-text table tbody tr:first-child th, .bp5-running-text .bp5-dark table tbody tr:first-child th, .bp5-dark table.bp5-html-table tbody tr:first-child th,\n  .bp5-dark .bp5-running-text table tbody tr:first-child td,\n  .bp5-running-text .bp5-dark table tbody tr:first-child td,\n  .bp5-dark table.bp5-html-table tbody tr:first-child td,\n  .bp5-dark .bp5-running-text table tfoot tr:first-child th,\n  .bp5-running-text .bp5-dark table tfoot tr:first-child th,\n  .bp5-dark table.bp5-html-table tfoot tr:first-child th,\n  .bp5-dark .bp5-running-text table tfoot tr:first-child td,\n  .bp5-running-text .bp5-dark table tfoot tr:first-child td,\n  .bp5-dark table.bp5-html-table tfoot tr:first-child td{\n    -webkit-box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-condensed th,\ntable.bp5-html-table.bp5-html-table-condensed td, table.bp5-html-table.bp5-small th,\ntable.bp5-html-table.bp5-small td{\n  padding-bottom:6px;\n  padding-top:6px; }\n\ntable.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n  background:rgba(191, 204, 214, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n  -webkit-box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15);\n          box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-bordered tbody tr td,\ntable.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n  -webkit-box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15);\n          box-shadow:inset 0 1px 0 0 rgba(16, 22, 26, 0.15); }\n  table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n  table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n    -webkit-box-shadow:inset 1px 1px 0 0 rgba(16, 22, 26, 0.15);\n            box-shadow:inset 1px 1px 0 0 rgba(16, 22, 26, 0.15); }\n\ntable.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n  -webkit-box-shadow:none;\n          box-shadow:none; }\n  table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:not(:first-child){\n    -webkit-box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15);\n            box-shadow:inset 1px 0 0 0 rgba(16, 22, 26, 0.15); }\n\ntable.bp5-html-table.bp5-interactive tbody tr:hover td{\n  background-color:rgba(191, 204, 214, 0.3);\n  cursor:pointer; }\n\ntable.bp5-html-table.bp5-interactive tbody tr:active td{\n  background-color:rgba(191, 204, 214, 0.4); }\n\n.bp5-dark table.bp5-html-table{ }\n  .bp5-dark table.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td{\n    background:rgba(92, 112, 128, 0.15); }\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered th:not(:first-child){\n    -webkit-box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15); }\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td,\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td{\n    -webkit-box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 0 1px 0 0 rgba(255, 255, 255, 0.15); }\n    .bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n    .bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child){\n      -webkit-box-shadow:inset 1px 1px 0 0 rgba(255, 255, 255, 0.15);\n              box-shadow:inset 1px 1px 0 0 rgba(255, 255, 255, 0.15); }\n  .bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td{\n    -webkit-box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15);\n            box-shadow:inset 1px 0 0 0 rgba(255, 255, 255, 0.15); }\n    .bp5-dark table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped tbody tr:not(:first-child) td:first-child{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-dark table.bp5-html-table.bp5-interactive tbody tr:hover td{\n    background-color:rgba(92, 112, 128, 0.3);\n    cursor:pointer; }\n  .bp5-dark table.bp5-html-table.bp5-interactive tbody tr:active td{\n    background-color:rgba(92, 112, 128, 0.4); }\n.bp5-key-combo{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center; }\n  .bp5-key-combo > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-key-combo > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-key-combo::before,\n  .bp5-key-combo > *{\n    margin-right:5px; }\n  .bp5-key-combo:empty::before,\n  .bp5-key-combo > :last-child{\n    margin-right:0; }\n\n.bp5-hotkey-dialog{\n  padding-bottom:0;\n  top:40px; }\n  .bp5-hotkey-dialog .bp5-dialog-body{\n    margin:0;\n    padding:0; }\n  .bp5-hotkey-dialog .bp5-hotkey-label{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1; }\n\n.bp5-hotkey-column{\n  margin:auto;\n  max-height:80vh;\n  overflow-y:auto;\n  padding:30px; }\n  .bp5-hotkey-column .bp5-heading{\n    margin-bottom:20px; }\n    .bp5-hotkey-column .bp5-heading:not(:first-child){\n      margin-top:40px; }\n\n.bp5-hotkey{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:justify;\n      -ms-flex-pack:justify;\n          justify-content:space-between;\n  margin-left:0;\n  margin-right:0; }\n  .bp5-hotkey:not(:last-child){\n    margin-bottom:10px; }\n.bp5-icon{\n  display:inline-block;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  vertical-align:text-bottom; }\n  .bp5-icon:not(:empty)::before{\n    content:\"\" !important;\n    content:unset !important; }\n  .bp5-icon > svg{\n    display:block; }\n    .bp5-icon > svg:not([fill]){\n      fill:currentColor; }\n\n.bp5-icon.bp5-intent-primary, .bp5-icon-standard.bp5-intent-primary, .bp5-icon-large.bp5-intent-primary{\n  color:#106ba3; }\n  .bp5-dark .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-icon-large.bp5-intent-primary{\n    color:#48aff0; }\n\n.bp5-icon.bp5-intent-success, .bp5-icon-standard.bp5-intent-success, .bp5-icon-large.bp5-intent-success{\n  color:#0d8050; }\n  .bp5-dark .bp5-icon.bp5-intent-success, .bp5-dark .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-icon-large.bp5-intent-success{\n    color:#3dcc91; }\n\n.bp5-icon.bp5-intent-warning, .bp5-icon-standard.bp5-intent-warning, .bp5-icon-large.bp5-intent-warning{\n  color:#bf7326; }\n  .bp5-dark .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-icon-large.bp5-intent-warning{\n    color:#ffb366; }\n\n.bp5-icon.bp5-intent-danger, .bp5-icon-standard.bp5-intent-danger, .bp5-icon-large.bp5-intent-danger{\n  color:#c23030; }\n  .bp5-dark .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-icon-large.bp5-intent-danger{\n    color:#ff7373; }\n\nspan.bp5-icon-standard{\n  font-family:\"blueprint-icons-16\", sans-serif;\n  font-size:16px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:16px;\n  line-height:1;\n  width:16px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon-large{\n  font-family:\"blueprint-icons-20\", sans-serif;\n  font-size:20px;\n  font-style:normal;\n  font-variant:normal;\n  font-weight:400;\n  height:20px;\n  line-height:1;\n  width:20px;\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n  display:inline-block;\n}\n\nspan.bp5-icon:empty{\n  font-family:\"blueprint-icons-20\";\n  font-size:inherit;\n  font-style:normal;\n  font-weight:400;\n  line-height:1;\n}\nspan.bp5-icon:empty::before{\n  -moz-osx-font-smoothing:grayscale;\n  -webkit-font-smoothing:antialiased;\n}\nspan.bp5-icon:empty.bp5-icon-standard{\n  font-size:16px;\n}\nspan.bp5-icon:empty.bp5-icon-large{\n  font-size:20px;\n}\n\n.bp5-icon-add::before{\n  content:\"\\f109\";\n}\n\n.bp5-icon-add-clip::before{\n  content:\"\\f101\";\n}\n\n.bp5-icon-add-column-left::before{\n  content:\"\\f102\";\n}\n\n.bp5-icon-add-column-right::before{\n  content:\"\\f103\";\n}\n\n.bp5-icon-add-location::before{\n  content:\"\\f104\";\n}\n\n.bp5-icon-add-row-bottom::before{\n  content:\"\\f105\";\n}\n\n.bp5-icon-add-row-top::before{\n  content:\"\\f106\";\n}\n\n.bp5-icon-add-to-artifact::before{\n  content:\"\\f107\";\n}\n\n.bp5-icon-add-to-folder::before{\n  content:\"\\f108\";\n}\n\n.bp5-icon-aimpoints-target::before{\n  content:\"\\f335\";\n}\n\n.bp5-icon-airplane::before{\n  content:\"\\f10a\";\n}\n\n.bp5-icon-align-center::before{\n  content:\"\\f10b\";\n}\n\n.bp5-icon-align-justify::before{\n  content:\"\\f10c\";\n}\n\n.bp5-icon-align-left::before{\n  content:\"\\f10d\";\n}\n\n.bp5-icon-align-right::before{\n  content:\"\\f10e\";\n}\n\n.bp5-icon-alignment-bottom::before{\n  content:\"\\f10f\";\n}\n\n.bp5-icon-alignment-horizontal-center::before{\n  content:\"\\f110\";\n}\n\n.bp5-icon-alignment-left::before{\n  content:\"\\f111\";\n}\n\n.bp5-icon-alignment-right::before{\n  content:\"\\f112\";\n}\n\n.bp5-icon-alignment-top::before{\n  content:\"\\f113\";\n}\n\n.bp5-icon-alignment-vertical-center::before{\n  content:\"\\f114\";\n}\n\n.bp5-icon-ammunition::before{\n  content:\"\\f342\";\n}\n\n.bp5-icon-anchor::before{\n  content:\"\\f330\";\n}\n\n.bp5-icon-annotation::before{\n  content:\"\\f115\";\n}\n\n.bp5-icon-announcement::before{\n  content:\"\\f396\";\n}\n\n.bp5-icon-antenna::before{\n  content:\"\\f116\";\n}\n\n.bp5-icon-app-header::before{\n  content:\"\\f117\";\n}\n\n.bp5-icon-application::before{\n  content:\"\\f118\";\n}\n\n.bp5-icon-applications::before{\n  content:\"\\f119\";\n}\n\n.bp5-icon-archive::before{\n  content:\"\\f11a\";\n}\n\n.bp5-icon-area-of-interest::before{\n  content:\"\\f11b\";\n}\n\n.bp5-icon-array::before{\n  content:\"\\f121\";\n}\n\n.bp5-icon-array-boolean::before{\n  content:\"\\f11c\";\n}\n\n.bp5-icon-array-date::before{\n  content:\"\\f11d\";\n}\n\n.bp5-icon-array-floating-point::before{\n  content:\"\\f32d\";\n}\n\n.bp5-icon-array-numeric::before{\n  content:\"\\f11e\";\n}\n\n.bp5-icon-array-object::before{\n  content:\"\\f394\";\n}\n\n.bp5-icon-array-string::before{\n  content:\"\\f11f\";\n}\n\n.bp5-icon-array-timestamp::before{\n  content:\"\\f120\";\n}\n\n.bp5-icon-arrow-bottom-left::before{\n  content:\"\\f122\";\n}\n\n.bp5-icon-arrow-bottom-right::before{\n  content:\"\\f123\";\n}\n\n.bp5-icon-arrow-down::before{\n  content:\"\\f124\";\n}\n\n.bp5-icon-arrow-left::before{\n  content:\"\\f125\";\n}\n\n.bp5-icon-arrow-right::before{\n  content:\"\\f126\";\n}\n\n.bp5-icon-arrow-top-left::before{\n  content:\"\\f127\";\n}\n\n.bp5-icon-arrow-top-right::before{\n  content:\"\\f128\";\n}\n\n.bp5-icon-arrow-up::before{\n  content:\"\\f129\";\n}\n\n.bp5-icon-arrows-arc::before{\n  content:\"\\f387\";\n}\n\n.bp5-icon-arrows-horizontal::before{\n  content:\"\\f12a\";\n}\n\n.bp5-icon-arrows-vertical::before{\n  content:\"\\f12b\";\n}\n\n.bp5-icon-asterisk::before{\n  content:\"\\f12c\";\n}\n\n.bp5-icon-at::before{\n  content:\"\\f331\";\n}\n\n.bp5-icon-automatic-updates::before{\n  content:\"\\f12d\";\n}\n\n.bp5-icon-axle::before{\n  content:\"\\f338\";\n}\n\n.bp5-icon-backlink::before{\n  content:\"\\f12e\";\n}\n\n.bp5-icon-backward-ten::before{\n  content:\"\\f35c\";\n}\n\n.bp5-icon-badge::before{\n  content:\"\\f12f\";\n}\n\n.bp5-icon-ban-circle::before{\n  content:\"\\f130\";\n}\n\n.bp5-icon-bank-account::before{\n  content:\"\\f131\";\n}\n\n.bp5-icon-barcode::before{\n  content:\"\\f132\";\n}\n\n.bp5-icon-binary-number::before{\n  content:\"\\f357\";\n}\n\n.bp5-icon-blank::before{\n  content:\"\\f133\";\n}\n\n.bp5-icon-block-promote::before{\n  content:\"\\f372\";\n}\n\n.bp5-icon-blocked-person::before{\n  content:\"\\f134\";\n}\n\n.bp5-icon-bold::before{\n  content:\"\\f135\";\n}\n\n.bp5-icon-book::before{\n  content:\"\\f136\";\n}\n\n.bp5-icon-bookmark::before{\n  content:\"\\f137\";\n}\n\n.bp5-icon-box::before{\n  content:\"\\f138\";\n}\n\n.bp5-icon-briefcase::before{\n  content:\"\\f139\";\n}\n\n.bp5-icon-bring-data::before{\n  content:\"\\f13a\";\n}\n\n.bp5-icon-bring-forward::before{\n  content:\"\\f354\";\n}\n\n.bp5-icon-british-pound::before{\n  content:\"\\f386\";\n}\n\n.bp5-icon-bug::before{\n  content:\"\\f32e\";\n}\n\n.bp5-icon-buggy::before{\n  content:\"\\f13b\";\n}\n\n.bp5-icon-build::before{\n  content:\"\\f13c\";\n}\n\n.bp5-icon-bullseye::before{\n  content:\"\\f359\";\n}\n\n.bp5-icon-calculator::before{\n  content:\"\\f13d\";\n}\n\n.bp5-icon-calendar::before{\n  content:\"\\f13e\";\n}\n\n.bp5-icon-camera::before{\n  content:\"\\f13f\";\n}\n\n.bp5-icon-caret-down::before{\n  content:\"\\f140\";\n}\n\n.bp5-icon-caret-left::before{\n  content:\"\\f141\";\n}\n\n.bp5-icon-caret-right::before{\n  content:\"\\f142\";\n}\n\n.bp5-icon-caret-up::before{\n  content:\"\\f143\";\n}\n\n.bp5-icon-cargo-ship::before{\n  content:\"\\f144\";\n}\n\n.bp5-icon-cell-tower::before{\n  content:\"\\f145\";\n}\n\n.bp5-icon-changes::before{\n  content:\"\\f146\";\n}\n\n.bp5-icon-chart::before{\n  content:\"\\f147\";\n}\n\n.bp5-icon-chat::before{\n  content:\"\\f148\";\n}\n\n.bp5-icon-chevron-backward::before{\n  content:\"\\f149\";\n}\n\n.bp5-icon-chevron-down::before{\n  content:\"\\f14a\";\n}\n\n.bp5-icon-chevron-forward::before{\n  content:\"\\f14b\";\n}\n\n.bp5-icon-chevron-left::before{\n  content:\"\\f14c\";\n}\n\n.bp5-icon-chevron-right::before{\n  content:\"\\f14d\";\n}\n\n.bp5-icon-chevron-up::before{\n  content:\"\\f14e\";\n}\n\n.bp5-icon-circle::before{\n  content:\"\\f153\";\n}\n\n.bp5-icon-circle-arrow-down::before{\n  content:\"\\f14f\";\n}\n\n.bp5-icon-circle-arrow-left::before{\n  content:\"\\f150\";\n}\n\n.bp5-icon-circle-arrow-right::before{\n  content:\"\\f151\";\n}\n\n.bp5-icon-circle-arrow-up::before{\n  content:\"\\f152\";\n}\n\n.bp5-icon-citation::before{\n  content:\"\\f154\";\n}\n\n.bp5-icon-clean::before{\n  content:\"\\f155\";\n}\n\n.bp5-icon-clip::before{\n  content:\"\\f156\";\n}\n\n.bp5-icon-clipboard::before{\n  content:\"\\f157\";\n}\n\n.bp5-icon-clipboard-file::before{\n  content:\"\\f35b\";\n}\n\n.bp5-icon-cloud::before{\n  content:\"\\f15a\";\n}\n\n.bp5-icon-cloud-download::before{\n  content:\"\\f158\";\n}\n\n.bp5-icon-cloud-server::before{\n  content:\"\\f35a\";\n}\n\n.bp5-icon-cloud-tick::before{\n  content:\"\\f34e\";\n}\n\n.bp5-icon-cloud-upload::before{\n  content:\"\\f159\";\n}\n\n.bp5-icon-code::before{\n  content:\"\\f15c\";\n}\n\n.bp5-icon-code-block::before{\n  content:\"\\f15b\";\n}\n\n.bp5-icon-cog::before{\n  content:\"\\f15d\";\n}\n\n.bp5-icon-collapse-all::before{\n  content:\"\\f15e\";\n}\n\n.bp5-icon-color-fill::before{\n  content:\"\\f328\";\n}\n\n.bp5-icon-column-layout::before{\n  content:\"\\f15f\";\n}\n\n.bp5-icon-comment::before{\n  content:\"\\f160\";\n}\n\n.bp5-icon-comparison::before{\n  content:\"\\f161\";\n}\n\n.bp5-icon-compass::before{\n  content:\"\\f162\";\n}\n\n.bp5-icon-compressed::before{\n  content:\"\\f163\";\n}\n\n.bp5-icon-confirm::before{\n  content:\"\\f164\";\n}\n\n.bp5-icon-console::before{\n  content:\"\\f165\";\n}\n\n.bp5-icon-construction::before{\n  content:\"\\f395\";\n}\n\n.bp5-icon-contrast::before{\n  content:\"\\f166\";\n}\n\n.bp5-icon-control::before{\n  content:\"\\f167\";\n}\n\n.bp5-icon-credit-card::before{\n  content:\"\\f168\";\n}\n\n.bp5-icon-crop::before{\n  content:\"\\f353\";\n}\n\n.bp5-icon-cross::before{\n  content:\"\\f169\";\n}\n\n.bp5-icon-cross-circle::before{\n  content:\"\\f336\";\n}\n\n.bp5-icon-crown::before{\n  content:\"\\f16a\";\n}\n\n.bp5-icon-css-style::before{\n  content:\"\\f36b\";\n}\n\n.bp5-icon-cube::before{\n  content:\"\\f16d\";\n}\n\n.bp5-icon-cube-add::before{\n  content:\"\\f16b\";\n}\n\n.bp5-icon-cube-edit::before{\n  content:\"\\f383\";\n}\n\n.bp5-icon-cube-remove::before{\n  content:\"\\f16c\";\n}\n\n.bp5-icon-cubes::before{\n  content:\"\\f373\";\n}\n\n.bp5-icon-curly-braces::before{\n  content:\"\\f358\";\n}\n\n.bp5-icon-curved-range-chart::before{\n  content:\"\\f16e\";\n}\n\n.bp5-icon-cut::before{\n  content:\"\\f16f\";\n}\n\n.bp5-icon-cycle::before{\n  content:\"\\f170\";\n}\n\n.bp5-icon-dashboard::before{\n  content:\"\\f171\";\n}\n\n.bp5-icon-data-cloud::before{\n  content:\"\\f391\";\n}\n\n.bp5-icon-data-connection::before{\n  content:\"\\f172\";\n}\n\n.bp5-icon-data-lineage::before{\n  content:\"\\f173\";\n}\n\n.bp5-icon-data-search::before{\n  content:\"\\f36f\";\n}\n\n.bp5-icon-data-sync::before{\n  content:\"\\f36c\";\n}\n\n.bp5-icon-database::before{\n  content:\"\\f174\";\n}\n\n.bp5-icon-delete::before{\n  content:\"\\f175\";\n}\n\n.bp5-icon-delta::before{\n  content:\"\\f176\";\n}\n\n.bp5-icon-derive-column::before{\n  content:\"\\f177\";\n}\n\n.bp5-icon-desktop::before{\n  content:\"\\f178\";\n}\n\n.bp5-icon-detection::before{\n  content:\"\\f341\";\n}\n\n.bp5-icon-diagnosis::before{\n  content:\"\\f179\";\n}\n\n.bp5-icon-diagram-tree::before{\n  content:\"\\f17a\";\n}\n\n.bp5-icon-direction-left::before{\n  content:\"\\f17b\";\n}\n\n.bp5-icon-direction-right::before{\n  content:\"\\f17c\";\n}\n\n.bp5-icon-disable::before{\n  content:\"\\f17d\";\n}\n\n.bp5-icon-divide::before{\n  content:\"\\f327\";\n}\n\n.bp5-icon-document::before{\n  content:\"\\f180\";\n}\n\n.bp5-icon-document-open::before{\n  content:\"\\f17e\";\n}\n\n.bp5-icon-document-share::before{\n  content:\"\\f17f\";\n}\n\n.bp5-icon-dollar::before{\n  content:\"\\f181\";\n}\n\n.bp5-icon-dot::before{\n  content:\"\\f182\";\n}\n\n.bp5-icon-double-caret-horizontal::before{\n  content:\"\\f183\";\n}\n\n.bp5-icon-double-caret-vertical::before{\n  content:\"\\f184\";\n}\n\n.bp5-icon-double-chevron-down::before{\n  content:\"\\f185\";\n}\n\n.bp5-icon-double-chevron-left::before{\n  content:\"\\f186\";\n}\n\n.bp5-icon-double-chevron-right::before{\n  content:\"\\f187\";\n}\n\n.bp5-icon-double-chevron-up::before{\n  content:\"\\f188\";\n}\n\n.bp5-icon-doughnut-chart::before{\n  content:\"\\f189\";\n}\n\n.bp5-icon-download::before{\n  content:\"\\f18a\";\n}\n\n.bp5-icon-drag-handle-horizontal::before{\n  content:\"\\f18b\";\n}\n\n.bp5-icon-drag-handle-vertical::before{\n  content:\"\\f18c\";\n}\n\n.bp5-icon-draw::before{\n  content:\"\\f18d\";\n}\n\n.bp5-icon-drawer-left::before{\n  content:\"\\f18f\";\n}\n\n.bp5-icon-drawer-left-filled::before{\n  content:\"\\f18e\";\n}\n\n.bp5-icon-drawer-right::before{\n  content:\"\\f191\";\n}\n\n.bp5-icon-drawer-right-filled::before{\n  content:\"\\f190\";\n}\n\n.bp5-icon-drive-time::before{\n  content:\"\\f192\";\n}\n\n.bp5-icon-duplicate::before{\n  content:\"\\f193\";\n}\n\n.bp5-icon-edit::before{\n  content:\"\\f194\";\n}\n\n.bp5-icon-eject::before{\n  content:\"\\f195\";\n}\n\n.bp5-icon-emoji::before{\n  content:\"\\f196\";\n}\n\n.bp5-icon-endnote::before{\n  content:\"\\f356\";\n}\n\n.bp5-icon-endorsed::before{\n  content:\"\\f197\";\n}\n\n.bp5-icon-envelope::before{\n  content:\"\\f198\";\n}\n\n.bp5-icon-equals::before{\n  content:\"\\f199\";\n}\n\n.bp5-icon-eraser::before{\n  content:\"\\f19a\";\n}\n\n.bp5-icon-error::before{\n  content:\"\\f19b\";\n}\n\n.bp5-icon-euro::before{\n  content:\"\\f19c\";\n}\n\n.bp5-icon-excavator::before{\n  content:\"\\f36d\";\n}\n\n.bp5-icon-exchange::before{\n  content:\"\\f19d\";\n}\n\n.bp5-icon-exclude-row::before{\n  content:\"\\f19e\";\n}\n\n.bp5-icon-expand-all::before{\n  content:\"\\f19f\";\n}\n\n.bp5-icon-explain::before{\n  content:\"\\f34d\";\n}\n\n.bp5-icon-export::before{\n  content:\"\\f1a0\";\n}\n\n.bp5-icon-eye-off::before{\n  content:\"\\f1a1\";\n}\n\n.bp5-icon-eye-on::before{\n  content:\"\\f1a2\";\n}\n\n.bp5-icon-eye-open::before{\n  content:\"\\f1a3\";\n}\n\n.bp5-icon-fast-backward::before{\n  content:\"\\f1a4\";\n}\n\n.bp5-icon-fast-forward::before{\n  content:\"\\f1a5\";\n}\n\n.bp5-icon-feed::before{\n  content:\"\\f1a7\";\n}\n\n.bp5-icon-feed-subscribed::before{\n  content:\"\\f1a6\";\n}\n\n.bp5-icon-fighter-jet::before{\n  content:\"\\f384\";\n}\n\n.bp5-icon-film::before{\n  content:\"\\f1a8\";\n}\n\n.bp5-icon-filter::before{\n  content:\"\\f1ad\";\n}\n\n.bp5-icon-filter-keep::before{\n  content:\"\\f1a9\";\n}\n\n.bp5-icon-filter-list::before{\n  content:\"\\f1aa\";\n}\n\n.bp5-icon-filter-open::before{\n  content:\"\\f1ab\";\n}\n\n.bp5-icon-filter-remove::before{\n  content:\"\\f1ac\";\n}\n\n.bp5-icon-filter-sort-asc::before{\n  content:\"\\f38e\";\n}\n\n.bp5-icon-filter-sort-desc::before{\n  content:\"\\f38f\";\n}\n\n.bp5-icon-flag::before{\n  content:\"\\f1ae\";\n}\n\n.bp5-icon-flame::before{\n  content:\"\\f1af\";\n}\n\n.bp5-icon-flash::before{\n  content:\"\\f1b0\";\n}\n\n.bp5-icon-floating-point::before{\n  content:\"\\f32c\";\n}\n\n.bp5-icon-floppy-disk::before{\n  content:\"\\f1b1\";\n}\n\n.bp5-icon-flow-branch::before{\n  content:\"\\f1b2\";\n}\n\n.bp5-icon-flow-end::before{\n  content:\"\\f1b3\";\n}\n\n.bp5-icon-flow-linear::before{\n  content:\"\\f1b4\";\n}\n\n.bp5-icon-flow-review::before{\n  content:\"\\f1b6\";\n}\n\n.bp5-icon-flow-review-branch::before{\n  content:\"\\f1b5\";\n}\n\n.bp5-icon-flows::before{\n  content:\"\\f1b7\";\n}\n\n.bp5-icon-folder-close::before{\n  content:\"\\f1b8\";\n}\n\n.bp5-icon-folder-new::before{\n  content:\"\\f1b9\";\n}\n\n.bp5-icon-folder-open::before{\n  content:\"\\f1ba\";\n}\n\n.bp5-icon-folder-shared::before{\n  content:\"\\f1bc\";\n}\n\n.bp5-icon-folder-shared-open::before{\n  content:\"\\f1bb\";\n}\n\n.bp5-icon-follower::before{\n  content:\"\\f1bd\";\n}\n\n.bp5-icon-following::before{\n  content:\"\\f1be\";\n}\n\n.bp5-icon-font::before{\n  content:\"\\f1bf\";\n}\n\n.bp5-icon-fork::before{\n  content:\"\\f1c0\";\n}\n\n.bp5-icon-form::before{\n  content:\"\\f1c1\";\n}\n\n.bp5-icon-forward-ten::before{\n  content:\"\\f35d\";\n}\n\n.bp5-icon-fuel::before{\n  content:\"\\f323\";\n}\n\n.bp5-icon-full-circle::before{\n  content:\"\\f1c2\";\n}\n\n.bp5-icon-full-stacked-chart::before{\n  content:\"\\f1c3\";\n}\n\n.bp5-icon-fullscreen::before{\n  content:\"\\f1c4\";\n}\n\n.bp5-icon-function::before{\n  content:\"\\f1c5\";\n}\n\n.bp5-icon-gantt-chart::before{\n  content:\"\\f1c6\";\n}\n\n.bp5-icon-generate::before{\n  content:\"\\f34c\";\n}\n\n.bp5-icon-geofence::before{\n  content:\"\\f1c7\";\n}\n\n.bp5-icon-geolocation::before{\n  content:\"\\f1c8\";\n}\n\n.bp5-icon-geosearch::before{\n  content:\"\\f1c9\";\n}\n\n.bp5-icon-geotime::before{\n  content:\"\\f344\";\n}\n\n.bp5-icon-git-branch::before{\n  content:\"\\f1ca\";\n}\n\n.bp5-icon-git-commit::before{\n  content:\"\\f1cb\";\n}\n\n.bp5-icon-git-merge::before{\n  content:\"\\f1cc\";\n}\n\n.bp5-icon-git-new-branch::before{\n  content:\"\\f1cd\";\n}\n\n.bp5-icon-git-pull::before{\n  content:\"\\f1ce\";\n}\n\n.bp5-icon-git-push::before{\n  content:\"\\f1cf\";\n}\n\n.bp5-icon-git-repo::before{\n  content:\"\\f1d0\";\n}\n\n.bp5-icon-glass::before{\n  content:\"\\f1d1\";\n}\n\n.bp5-icon-globe::before{\n  content:\"\\f1d3\";\n}\n\n.bp5-icon-globe-network::before{\n  content:\"\\f1d2\";\n}\n\n.bp5-icon-globe-network-add::before{\n  content:\"\\f382\";\n}\n\n.bp5-icon-graph::before{\n  content:\"\\f1d5\";\n}\n\n.bp5-icon-graph-remove::before{\n  content:\"\\f1d4\";\n}\n\n.bp5-icon-greater-than::before{\n  content:\"\\f1d7\";\n}\n\n.bp5-icon-greater-than-or-equal-to::before{\n  content:\"\\f1d6\";\n}\n\n.bp5-icon-grid::before{\n  content:\"\\f1d9\";\n}\n\n.bp5-icon-grid-view::before{\n  content:\"\\f1d8\";\n}\n\n.bp5-icon-group-item::before{\n  content:\"\\f34a\";\n}\n\n.bp5-icon-group-objects::before{\n  content:\"\\f1da\";\n}\n\n.bp5-icon-grouped-bar-chart::before{\n  content:\"\\f1db\";\n}\n\n.bp5-icon-hand::before{\n  content:\"\\f1e0\";\n}\n\n.bp5-icon-hand-down::before{\n  content:\"\\f1dc\";\n}\n\n.bp5-icon-hand-left::before{\n  content:\"\\f1dd\";\n}\n\n.bp5-icon-hand-right::before{\n  content:\"\\f1de\";\n}\n\n.bp5-icon-hand-up::before{\n  content:\"\\f1df\";\n}\n\n.bp5-icon-hat::before{\n  content:\"\\f1e1\";\n}\n\n.bp5-icon-header::before{\n  content:\"\\f1e5\";\n}\n\n.bp5-icon-header-one::before{\n  content:\"\\f1e2\";\n}\n\n.bp5-icon-header-three::before{\n  content:\"\\f1e3\";\n}\n\n.bp5-icon-header-two::before{\n  content:\"\\f1e4\";\n}\n\n.bp5-icon-headset::before{\n  content:\"\\f1e6\";\n}\n\n.bp5-icon-heart::before{\n  content:\"\\f1e8\";\n}\n\n.bp5-icon-heart-broken::before{\n  content:\"\\f1e7\";\n}\n\n.bp5-icon-heat-grid::before{\n  content:\"\\f1e9\";\n}\n\n.bp5-icon-heatmap::before{\n  content:\"\\f1ea\";\n}\n\n.bp5-icon-helicopter::before{\n  content:\"\\f1eb\";\n}\n\n.bp5-icon-help::before{\n  content:\"\\f1ec\";\n}\n\n.bp5-icon-helper-management::before{\n  content:\"\\f1ed\";\n}\n\n.bp5-icon-hexagon::before{\n  content:\"\\f374\";\n}\n\n.bp5-icon-high-priority::before{\n  content:\"\\f1ee\";\n}\n\n.bp5-icon-high-voltage-pole::before{\n  content:\"\\f333\";\n}\n\n.bp5-icon-highlight::before{\n  content:\"\\f1ef\";\n}\n\n.bp5-icon-history::before{\n  content:\"\\f1f0\";\n}\n\n.bp5-icon-home::before{\n  content:\"\\f1f1\";\n}\n\n.bp5-icon-horizontal-bar-chart::before{\n  content:\"\\f1f4\";\n}\n\n.bp5-icon-horizontal-bar-chart-asc::before{\n  content:\"\\f1f2\";\n}\n\n.bp5-icon-horizontal-bar-chart-desc::before{\n  content:\"\\f1f3\";\n}\n\n.bp5-icon-horizontal-distribution::before{\n  content:\"\\f1f5\";\n}\n\n.bp5-icon-horizontal-inbetween::before{\n  content:\"\\f329\";\n}\n\n.bp5-icon-hurricane::before{\n  content:\"\\f1f6\";\n}\n\n.bp5-icon-id-number::before{\n  content:\"\\f1f7\";\n}\n\n.bp5-icon-image-rotate-left::before{\n  content:\"\\f1f8\";\n}\n\n.bp5-icon-image-rotate-right::before{\n  content:\"\\f1f9\";\n}\n\n.bp5-icon-import::before{\n  content:\"\\f1fa\";\n}\n\n.bp5-icon-inbox::before{\n  content:\"\\f1ff\";\n}\n\n.bp5-icon-inbox-filtered::before{\n  content:\"\\f1fb\";\n}\n\n.bp5-icon-inbox-geo::before{\n  content:\"\\f1fc\";\n}\n\n.bp5-icon-inbox-search::before{\n  content:\"\\f1fd\";\n}\n\n.bp5-icon-inbox-update::before{\n  content:\"\\f1fe\";\n}\n\n.bp5-icon-info-sign::before{\n  content:\"\\f200\";\n}\n\n.bp5-icon-inheritance::before{\n  content:\"\\f201\";\n}\n\n.bp5-icon-inherited-group::before{\n  content:\"\\f202\";\n}\n\n.bp5-icon-inner-join::before{\n  content:\"\\f203\";\n}\n\n.bp5-icon-input::before{\n  content:\"\\f34b\";\n}\n\n.bp5-icon-insert::before{\n  content:\"\\f204\";\n}\n\n.bp5-icon-intelligence::before{\n  content:\"\\f337\";\n}\n\n.bp5-icon-intersection::before{\n  content:\"\\f205\";\n}\n\n.bp5-icon-ip-address::before{\n  content:\"\\f206\";\n}\n\n.bp5-icon-issue::before{\n  content:\"\\f209\";\n}\n\n.bp5-icon-issue-closed::before{\n  content:\"\\f207\";\n}\n\n.bp5-icon-issue-new::before{\n  content:\"\\f208\";\n}\n\n.bp5-icon-italic::before{\n  content:\"\\f20a\";\n}\n\n.bp5-icon-join-table::before{\n  content:\"\\f20b\";\n}\n\n.bp5-icon-key::before{\n  content:\"\\f215\";\n}\n\n.bp5-icon-key-backspace::before{\n  content:\"\\f20c\";\n}\n\n.bp5-icon-key-command::before{\n  content:\"\\f20d\";\n}\n\n.bp5-icon-key-control::before{\n  content:\"\\f20e\";\n}\n\n.bp5-icon-key-delete::before{\n  content:\"\\f20f\";\n}\n\n.bp5-icon-key-enter::before{\n  content:\"\\f210\";\n}\n\n.bp5-icon-key-escape::before{\n  content:\"\\f211\";\n}\n\n.bp5-icon-key-option::before{\n  content:\"\\f212\";\n}\n\n.bp5-icon-key-shift::before{\n  content:\"\\f213\";\n}\n\n.bp5-icon-key-tab::before{\n  content:\"\\f214\";\n}\n\n.bp5-icon-known-vehicle::before{\n  content:\"\\f216\";\n}\n\n.bp5-icon-lab-test::before{\n  content:\"\\f217\";\n}\n\n.bp5-icon-label::before{\n  content:\"\\f218\";\n}\n\n.bp5-icon-layer::before{\n  content:\"\\f21a\";\n}\n\n.bp5-icon-layer-outline::before{\n  content:\"\\f219\";\n}\n\n.bp5-icon-layers::before{\n  content:\"\\f21b\";\n}\n\n.bp5-icon-layout::before{\n  content:\"\\f225\";\n}\n\n.bp5-icon-layout-auto::before{\n  content:\"\\f21c\";\n}\n\n.bp5-icon-layout-balloon::before{\n  content:\"\\f21d\";\n}\n\n.bp5-icon-layout-bottom-row-three-tiles::before{\n  content:\"\\f364\";\n}\n\n.bp5-icon-layout-bottom-row-two-tiles::before{\n  content:\"\\f363\";\n}\n\n.bp5-icon-layout-circle::before{\n  content:\"\\f21e\";\n}\n\n.bp5-icon-layout-grid::before{\n  content:\"\\f21f\";\n}\n\n.bp5-icon-layout-group-by::before{\n  content:\"\\f220\";\n}\n\n.bp5-icon-layout-hierarchy::before{\n  content:\"\\f221\";\n}\n\n.bp5-icon-layout-left-column-three-tiles::before{\n  content:\"\\f366\";\n}\n\n.bp5-icon-layout-left-column-two-tiles::before{\n  content:\"\\f365\";\n}\n\n.bp5-icon-layout-linear::before{\n  content:\"\\f222\";\n}\n\n.bp5-icon-layout-right-column-three-tiles::before{\n  content:\"\\f368\";\n}\n\n.bp5-icon-layout-right-column-two-tiles::before{\n  content:\"\\f367\";\n}\n\n.bp5-icon-layout-skew-grid::before{\n  content:\"\\f223\";\n}\n\n.bp5-icon-layout-sorted-clusters::before{\n  content:\"\\f224\";\n}\n\n.bp5-icon-layout-three-columns::before{\n  content:\"\\f361\";\n}\n\n.bp5-icon-layout-three-rows::before{\n  content:\"\\f362\";\n}\n\n.bp5-icon-layout-top-row-three-tiles::before{\n  content:\"\\f36a\";\n}\n\n.bp5-icon-layout-top-row-two-tiles::before{\n  content:\"\\f369\";\n}\n\n.bp5-icon-layout-two-columns::before{\n  content:\"\\f35f\";\n}\n\n.bp5-icon-layout-two-rows::before{\n  content:\"\\f360\";\n}\n\n.bp5-icon-learning::before{\n  content:\"\\f226\";\n}\n\n.bp5-icon-left-join::before{\n  content:\"\\f227\";\n}\n\n.bp5-icon-lengthen-text::before{\n  content:\"\\f33e\";\n}\n\n.bp5-icon-less-than::before{\n  content:\"\\f229\";\n}\n\n.bp5-icon-less-than-or-equal-to::before{\n  content:\"\\f228\";\n}\n\n.bp5-icon-lifesaver::before{\n  content:\"\\f22a\";\n}\n\n.bp5-icon-lightbulb::before{\n  content:\"\\f22b\";\n}\n\n.bp5-icon-lightning::before{\n  content:\"\\f22c\";\n}\n\n.bp5-icon-link::before{\n  content:\"\\f22d\";\n}\n\n.bp5-icon-linked-squares::before{\n  content:\"\\f385\";\n}\n\n.bp5-icon-list::before{\n  content:\"\\f230\";\n}\n\n.bp5-icon-list-columns::before{\n  content:\"\\f22e\";\n}\n\n.bp5-icon-list-detail-view::before{\n  content:\"\\f22f\";\n}\n\n.bp5-icon-locate::before{\n  content:\"\\f231\";\n}\n\n.bp5-icon-lock::before{\n  content:\"\\f232\";\n}\n\n.bp5-icon-locomotive::before{\n  content:\"\\f33b\";\n}\n\n.bp5-icon-log-in::before{\n  content:\"\\f233\";\n}\n\n.bp5-icon-log-out::before{\n  content:\"\\f234\";\n}\n\n.bp5-icon-low-voltage-pole::before{\n  content:\"\\f332\";\n}\n\n.bp5-icon-manual::before{\n  content:\"\\f235\";\n}\n\n.bp5-icon-manually-entered-data::before{\n  content:\"\\f236\";\n}\n\n.bp5-icon-many-to-many::before{\n  content:\"\\f237\";\n}\n\n.bp5-icon-many-to-one::before{\n  content:\"\\f238\";\n}\n\n.bp5-icon-map::before{\n  content:\"\\f23b\";\n}\n\n.bp5-icon-map-create::before{\n  content:\"\\f239\";\n}\n\n.bp5-icon-map-marker::before{\n  content:\"\\f23a\";\n}\n\n.bp5-icon-maximize::before{\n  content:\"\\f23c\";\n}\n\n.bp5-icon-media::before{\n  content:\"\\f23d\";\n}\n\n.bp5-icon-menu::before{\n  content:\"\\f240\";\n}\n\n.bp5-icon-menu-closed::before{\n  content:\"\\f23e\";\n}\n\n.bp5-icon-menu-open::before{\n  content:\"\\f23f\";\n}\n\n.bp5-icon-merge-columns::before{\n  content:\"\\f241\";\n}\n\n.bp5-icon-merge-links::before{\n  content:\"\\f242\";\n}\n\n.bp5-icon-microphone::before{\n  content:\"\\f343\";\n}\n\n.bp5-icon-minimize::before{\n  content:\"\\f243\";\n}\n\n.bp5-icon-minus::before{\n  content:\"\\f244\";\n}\n\n.bp5-icon-mobile-phone::before{\n  content:\"\\f245\";\n}\n\n.bp5-icon-mobile-video::before{\n  content:\"\\f246\";\n}\n\n.bp5-icon-modal::before{\n  content:\"\\f248\";\n}\n\n.bp5-icon-modal-filled::before{\n  content:\"\\f247\";\n}\n\n.bp5-icon-model::before{\n  content:\"\\f33d\";\n}\n\n.bp5-icon-moon::before{\n  content:\"\\f249\";\n}\n\n.bp5-icon-more::before{\n  content:\"\\f24a\";\n}\n\n.bp5-icon-mountain::before{\n  content:\"\\f24b\";\n}\n\n.bp5-icon-move::before{\n  content:\"\\f24c\";\n}\n\n.bp5-icon-mugshot::before{\n  content:\"\\f24d\";\n}\n\n.bp5-icon-multi-select::before{\n  content:\"\\f24e\";\n}\n\n.bp5-icon-music::before{\n  content:\"\\f24f\";\n}\n\n.bp5-icon-nest::before{\n  content:\"\\f250\";\n}\n\n.bp5-icon-new-drawing::before{\n  content:\"\\f251\";\n}\n\n.bp5-icon-new-grid-item::before{\n  content:\"\\f252\";\n}\n\n.bp5-icon-new-layer::before{\n  content:\"\\f253\";\n}\n\n.bp5-icon-new-layers::before{\n  content:\"\\f254\";\n}\n\n.bp5-icon-new-link::before{\n  content:\"\\f255\";\n}\n\n.bp5-icon-new-object::before{\n  content:\"\\f256\";\n}\n\n.bp5-icon-new-person::before{\n  content:\"\\f257\";\n}\n\n.bp5-icon-new-prescription::before{\n  content:\"\\f258\";\n}\n\n.bp5-icon-new-shield::before{\n  content:\"\\f349\";\n}\n\n.bp5-icon-new-text-box::before{\n  content:\"\\f259\";\n}\n\n.bp5-icon-ninja::before{\n  content:\"\\f25a\";\n}\n\n.bp5-icon-not-equal-to::before{\n  content:\"\\f25b\";\n}\n\n.bp5-icon-notifications::before{\n  content:\"\\f25e\";\n}\n\n.bp5-icon-notifications-add::before{\n  content:\"\\f398\";\n}\n\n.bp5-icon-notifications-snooze::before{\n  content:\"\\f25c\";\n}\n\n.bp5-icon-notifications-updated::before{\n  content:\"\\f25d\";\n}\n\n.bp5-icon-numbered-list::before{\n  content:\"\\f25f\";\n}\n\n.bp5-icon-numerical::before{\n  content:\"\\f260\";\n}\n\n.bp5-icon-object-view::before{\n  content:\"\\f390\";\n}\n\n.bp5-icon-office::before{\n  content:\"\\f261\";\n}\n\n.bp5-icon-offline::before{\n  content:\"\\f262\";\n}\n\n.bp5-icon-oil-field::before{\n  content:\"\\f263\";\n}\n\n.bp5-icon-one-column::before{\n  content:\"\\f264\";\n}\n\n.bp5-icon-one-to-many::before{\n  content:\"\\f265\";\n}\n\n.bp5-icon-one-to-one::before{\n  content:\"\\f266\";\n}\n\n.bp5-icon-open-application::before{\n  content:\"\\f32b\";\n}\n\n.bp5-icon-outdated::before{\n  content:\"\\f267\";\n}\n\n.bp5-icon-output::before{\n  content:\"\\f370\";\n}\n\n.bp5-icon-package::before{\n  content:\"\\f375\";\n}\n\n.bp5-icon-page-layout::before{\n  content:\"\\f268\";\n}\n\n.bp5-icon-panel::before{\n  content:\"\\f381\";\n}\n\n.bp5-icon-panel-stats::before{\n  content:\"\\f269\";\n}\n\n.bp5-icon-panel-table::before{\n  content:\"\\f26a\";\n}\n\n.bp5-icon-paperclip::before{\n  content:\"\\f26b\";\n}\n\n.bp5-icon-paragraph::before{\n  content:\"\\f26c\";\n}\n\n.bp5-icon-paste-variable::before{\n  content:\"\\f346\";\n}\n\n.bp5-icon-path::before{\n  content:\"\\f26e\";\n}\n\n.bp5-icon-path-search::before{\n  content:\"\\f26d\";\n}\n\n.bp5-icon-pause::before{\n  content:\"\\f26f\";\n}\n\n.bp5-icon-people::before{\n  content:\"\\f270\";\n}\n\n.bp5-icon-percentage::before{\n  content:\"\\f271\";\n}\n\n.bp5-icon-person::before{\n  content:\"\\f272\";\n}\n\n.bp5-icon-phone::before{\n  content:\"\\f273\";\n}\n\n.bp5-icon-phone-call::before{\n  content:\"\\f347\";\n}\n\n.bp5-icon-phone-forward::before{\n  content:\"\\f348\";\n}\n\n.bp5-icon-phone-search::before{\n  content:\"\\f397\";\n}\n\n.bp5-icon-pie-chart::before{\n  content:\"\\f274\";\n}\n\n.bp5-icon-pill::before{\n  content:\"\\f376\";\n}\n\n.bp5-icon-pin::before{\n  content:\"\\f275\";\n}\n\n.bp5-icon-pivot::before{\n  content:\"\\f277\";\n}\n\n.bp5-icon-pivot-table::before{\n  content:\"\\f276\";\n}\n\n.bp5-icon-play::before{\n  content:\"\\f278\";\n}\n\n.bp5-icon-playbook::before{\n  content:\"\\f324\";\n}\n\n.bp5-icon-plus::before{\n  content:\"\\f279\";\n}\n\n.bp5-icon-polygon-filter::before{\n  content:\"\\f27a\";\n}\n\n.bp5-icon-power::before{\n  content:\"\\f27b\";\n}\n\n.bp5-icon-predictive-analysis::before{\n  content:\"\\f27c\";\n}\n\n.bp5-icon-prescription::before{\n  content:\"\\f27d\";\n}\n\n.bp5-icon-presentation::before{\n  content:\"\\f27e\";\n}\n\n.bp5-icon-print::before{\n  content:\"\\f27f\";\n}\n\n.bp5-icon-projects::before{\n  content:\"\\f280\";\n}\n\n.bp5-icon-properties::before{\n  content:\"\\f281\";\n}\n\n.bp5-icon-property::before{\n  content:\"\\f282\";\n}\n\n.bp5-icon-publish-function::before{\n  content:\"\\f283\";\n}\n\n.bp5-icon-pulse::before{\n  content:\"\\f284\";\n}\n\n.bp5-icon-rain::before{\n  content:\"\\f285\";\n}\n\n.bp5-icon-random::before{\n  content:\"\\f286\";\n}\n\n.bp5-icon-range-ring::before{\n  content:\"\\f371\";\n}\n\n.bp5-icon-record::before{\n  content:\"\\f287\";\n}\n\n.bp5-icon-rect-height::before{\n  content:\"\\f325\";\n}\n\n.bp5-icon-rect-width::before{\n  content:\"\\f326\";\n}\n\n.bp5-icon-rectangle::before{\n  content:\"\\f321\";\n}\n\n.bp5-icon-redo::before{\n  content:\"\\f288\";\n}\n\n.bp5-icon-refresh::before{\n  content:\"\\f289\";\n}\n\n.bp5-icon-regex::before{\n  content:\"\\f32f\";\n}\n\n.bp5-icon-regression-chart::before{\n  content:\"\\f28a\";\n}\n\n.bp5-icon-remove::before{\n  content:\"\\f290\";\n}\n\n.bp5-icon-remove-column::before{\n  content:\"\\f28d\";\n}\n\n.bp5-icon-remove-column-left::before{\n  content:\"\\f28b\";\n}\n\n.bp5-icon-remove-column-right::before{\n  content:\"\\f28c\";\n}\n\n.bp5-icon-remove-row-bottom::before{\n  content:\"\\f28e\";\n}\n\n.bp5-icon-remove-row-top::before{\n  content:\"\\f28f\";\n}\n\n.bp5-icon-repeat::before{\n  content:\"\\f291\";\n}\n\n.bp5-icon-reset::before{\n  content:\"\\f292\";\n}\n\n.bp5-icon-resolve::before{\n  content:\"\\f293\";\n}\n\n.bp5-icon-rig::before{\n  content:\"\\f294\";\n}\n\n.bp5-icon-right-join::before{\n  content:\"\\f295\";\n}\n\n.bp5-icon-ring::before{\n  content:\"\\f296\";\n}\n\n.bp5-icon-rocket::before{\n  content:\"\\f298\";\n}\n\n.bp5-icon-rocket-slant::before{\n  content:\"\\f297\";\n}\n\n.bp5-icon-rotate-ccw::before{\n  content:\"\\f389\";\n}\n\n.bp5-icon-rotate-cw::before{\n  content:\"\\f388\";\n}\n\n.bp5-icon-rotate-document::before{\n  content:\"\\f299\";\n}\n\n.bp5-icon-rotate-page::before{\n  content:\"\\f29a\";\n}\n\n.bp5-icon-route::before{\n  content:\"\\f29b\";\n}\n\n.bp5-icon-run-history::before{\n  content:\"\\f393\";\n}\n\n.bp5-icon-satellite::before{\n  content:\"\\f29c\";\n}\n\n.bp5-icon-saved::before{\n  content:\"\\f29d\";\n}\n\n.bp5-icon-scatter-plot::before{\n  content:\"\\f29e\";\n}\n\n.bp5-icon-search::before{\n  content:\"\\f2a2\";\n}\n\n.bp5-icon-search-around::before{\n  content:\"\\f29f\";\n}\n\n.bp5-icon-search-template::before{\n  content:\"\\f2a0\";\n}\n\n.bp5-icon-search-text::before{\n  content:\"\\f2a1\";\n}\n\n.bp5-icon-segmented-control::before{\n  content:\"\\f2a3\";\n}\n\n.bp5-icon-select::before{\n  content:\"\\f2a4\";\n}\n\n.bp5-icon-selection::before{\n  content:\"\\f2a5\";\n}\n\n.bp5-icon-selection-box::before{\n  content:\"\\f399\";\n}\n\n.bp5-icon-selection-box-add::before{\n  content:\"\\f39a\";\n}\n\n.bp5-icon-selection-box-edit::before{\n  content:\"\\f39b\";\n}\n\n.bp5-icon-send-backward::before{\n  content:\"\\f355\";\n}\n\n.bp5-icon-send-message::before{\n  content:\"\\f2a6\";\n}\n\n.bp5-icon-send-to::before{\n  content:\"\\f2a9\";\n}\n\n.bp5-icon-send-to-graph::before{\n  content:\"\\f2a7\";\n}\n\n.bp5-icon-send-to-map::before{\n  content:\"\\f2a8\";\n}\n\n.bp5-icon-sensor::before{\n  content:\"\\f33c\";\n}\n\n.bp5-icon-series-add::before{\n  content:\"\\f2aa\";\n}\n\n.bp5-icon-series-configuration::before{\n  content:\"\\f2ab\";\n}\n\n.bp5-icon-series-derived::before{\n  content:\"\\f2ac\";\n}\n\n.bp5-icon-series-filtered::before{\n  content:\"\\f2ad\";\n}\n\n.bp5-icon-series-search::before{\n  content:\"\\f2ae\";\n}\n\n.bp5-icon-server::before{\n  content:\"\\f378\";\n}\n\n.bp5-icon-server-install::before{\n  content:\"\\f377\";\n}\n\n.bp5-icon-settings::before{\n  content:\"\\f2af\";\n}\n\n.bp5-icon-shapes::before{\n  content:\"\\f2b0\";\n}\n\n.bp5-icon-share::before{\n  content:\"\\f2b1\";\n}\n\n.bp5-icon-shared-filter::before{\n  content:\"\\f2b2\";\n}\n\n.bp5-icon-shield::before{\n  content:\"\\f2b3\";\n}\n\n.bp5-icon-ship::before{\n  content:\"\\f2b4\";\n}\n\n.bp5-icon-shop::before{\n  content:\"\\f2b5\";\n}\n\n.bp5-icon-shopping-cart::before{\n  content:\"\\f2b6\";\n}\n\n.bp5-icon-shorten-text::before{\n  content:\"\\f33f\";\n}\n\n.bp5-icon-signal-search::before{\n  content:\"\\f2b7\";\n}\n\n.bp5-icon-sim-card::before{\n  content:\"\\f2b8\";\n}\n\n.bp5-icon-slash::before{\n  content:\"\\f2b9\";\n}\n\n.bp5-icon-small-cross::before{\n  content:\"\\f2ba\";\n}\n\n.bp5-icon-small-info-sign::before{\n  content:\"\\f334\";\n}\n\n.bp5-icon-small-minus::before{\n  content:\"\\f2bb\";\n}\n\n.bp5-icon-small-plus::before{\n  content:\"\\f2bc\";\n}\n\n.bp5-icon-small-square::before{\n  content:\"\\f2bd\";\n}\n\n.bp5-icon-small-tick::before{\n  content:\"\\f2be\";\n}\n\n.bp5-icon-snowflake::before{\n  content:\"\\f2bf\";\n}\n\n.bp5-icon-soccer-ball::before{\n  content:\"\\f350\";\n}\n\n.bp5-icon-social-media::before{\n  content:\"\\f2c0\";\n}\n\n.bp5-icon-sort::before{\n  content:\"\\f2c7\";\n}\n\n.bp5-icon-sort-alphabetical::before{\n  content:\"\\f2c2\";\n}\n\n.bp5-icon-sort-alphabetical-desc::before{\n  content:\"\\f2c1\";\n}\n\n.bp5-icon-sort-asc::before{\n  content:\"\\f2c3\";\n}\n\n.bp5-icon-sort-desc::before{\n  content:\"\\f2c4\";\n}\n\n.bp5-icon-sort-numerical::before{\n  content:\"\\f2c6\";\n}\n\n.bp5-icon-sort-numerical-desc::before{\n  content:\"\\f2c5\";\n}\n\n.bp5-icon-spell-check::before{\n  content:\"\\f340\";\n}\n\n.bp5-icon-split-columns::before{\n  content:\"\\f2c8\";\n}\n\n.bp5-icon-sports-stadium::before{\n  content:\"\\f351\";\n}\n\n.bp5-icon-square::before{\n  content:\"\\f2c9\";\n}\n\n.bp5-icon-stacked-chart::before{\n  content:\"\\f2ca\";\n}\n\n.bp5-icon-stadium-geometry::before{\n  content:\"\\f2cb\";\n}\n\n.bp5-icon-star::before{\n  content:\"\\f2cd\";\n}\n\n.bp5-icon-star-empty::before{\n  content:\"\\f2cc\";\n}\n\n.bp5-icon-step-backward::before{\n  content:\"\\f2ce\";\n}\n\n.bp5-icon-step-chart::before{\n  content:\"\\f2cf\";\n}\n\n.bp5-icon-step-forward::before{\n  content:\"\\f2d0\";\n}\n\n.bp5-icon-stop::before{\n  content:\"\\f2d1\";\n}\n\n.bp5-icon-stopwatch::before{\n  content:\"\\f2d2\";\n}\n\n.bp5-icon-strikethrough::before{\n  content:\"\\f2d3\";\n}\n\n.bp5-icon-style::before{\n  content:\"\\f2d4\";\n}\n\n.bp5-icon-subscript::before{\n  content:\"\\f339\";\n}\n\n.bp5-icon-subtract-right-join::before{\n  content:\"\\f392\";\n}\n\n.bp5-icon-superscript::before{\n  content:\"\\f33a\";\n}\n\n.bp5-icon-swap-horizontal::before{\n  content:\"\\f2d5\";\n}\n\n.bp5-icon-swap-vertical::before{\n  content:\"\\f2d6\";\n}\n\n.bp5-icon-switch::before{\n  content:\"\\f2d7\";\n}\n\n.bp5-icon-symbol-circle::before{\n  content:\"\\f2d8\";\n}\n\n.bp5-icon-symbol-cross::before{\n  content:\"\\f2d9\";\n}\n\n.bp5-icon-symbol-diamond::before{\n  content:\"\\f2da\";\n}\n\n.bp5-icon-symbol-rectangle::before{\n  content:\"\\f322\";\n}\n\n.bp5-icon-symbol-square::before{\n  content:\"\\f2db\";\n}\n\n.bp5-icon-symbol-triangle-down::before{\n  content:\"\\f2dc\";\n}\n\n.bp5-icon-symbol-triangle-up::before{\n  content:\"\\f2dd\";\n}\n\n.bp5-icon-syringe::before{\n  content:\"\\f2de\";\n}\n\n.bp5-icon-table-sync::before{\n  content:\"\\f36e\";\n}\n\n.bp5-icon-tag::before{\n  content:\"\\f2df\";\n}\n\n.bp5-icon-tag-add::before{\n  content:\"\\f379\";\n}\n\n.bp5-icon-tag-promote::before{\n  content:\"\\f37a\";\n}\n\n.bp5-icon-tag-refresh::before{\n  content:\"\\f37b\";\n}\n\n.bp5-icon-tag-undo::before{\n  content:\"\\f37c\";\n}\n\n.bp5-icon-tags::before{\n  content:\"\\f37d\";\n}\n\n.bp5-icon-take-action::before{\n  content:\"\\f2e0\";\n}\n\n.bp5-icon-tank::before{\n  content:\"\\f2e1\";\n}\n\n.bp5-icon-target::before{\n  content:\"\\f2e2\";\n}\n\n.bp5-icon-taxi::before{\n  content:\"\\f2e3\";\n}\n\n.bp5-icon-team::before{\n  content:\"\\f352\";\n}\n\n.bp5-icon-temperature::before{\n  content:\"\\f2e4\";\n}\n\n.bp5-icon-text-highlight::before{\n  content:\"\\f2e5\";\n}\n\n.bp5-icon-th::before{\n  content:\"\\f2ea\";\n}\n\n.bp5-icon-th-add::before{\n  content:\"\\f38a\";\n}\n\n.bp5-icon-th-derived::before{\n  content:\"\\f2e6\";\n}\n\n.bp5-icon-th-disconnect::before{\n  content:\"\\f2e7\";\n}\n\n.bp5-icon-th-filtered::before{\n  content:\"\\f2e8\";\n}\n\n.bp5-icon-th-list::before{\n  content:\"\\f2e9\";\n}\n\n.bp5-icon-th-list-add::before{\n  content:\"\\f38b\";\n}\n\n.bp5-icon-th-virtual::before{\n  content:\"\\f38c\";\n}\n\n.bp5-icon-th-virtual-add::before{\n  content:\"\\f38d\";\n}\n\n.bp5-icon-third-party::before{\n  content:\"\\f2eb\";\n}\n\n.bp5-icon-thumbs-down::before{\n  content:\"\\f2ec\";\n}\n\n.bp5-icon-thumbs-up::before{\n  content:\"\\f2ed\";\n}\n\n.bp5-icon-tick::before{\n  content:\"\\f2ef\";\n}\n\n.bp5-icon-tick-circle::before{\n  content:\"\\f2ee\";\n}\n\n.bp5-icon-time::before{\n  content:\"\\f2f0\";\n}\n\n.bp5-icon-timeline-area-chart::before{\n  content:\"\\f2f1\";\n}\n\n.bp5-icon-timeline-bar-chart::before{\n  content:\"\\f2f2\";\n}\n\n.bp5-icon-timeline-events::before{\n  content:\"\\f2f3\";\n}\n\n.bp5-icon-timeline-line-chart::before{\n  content:\"\\f2f4\";\n}\n\n.bp5-icon-tint::before{\n  content:\"\\f2f5\";\n}\n\n.bp5-icon-torch::before{\n  content:\"\\f2f6\";\n}\n\n.bp5-icon-tractor::before{\n  content:\"\\f2f7\";\n}\n\n.bp5-icon-train::before{\n  content:\"\\f2f8\";\n}\n\n.bp5-icon-translate::before{\n  content:\"\\f2f9\";\n}\n\n.bp5-icon-trash::before{\n  content:\"\\f2fa\";\n}\n\n.bp5-icon-tree::before{\n  content:\"\\f2fb\";\n}\n\n.bp5-icon-trending-down::before{\n  content:\"\\f2fc\";\n}\n\n.bp5-icon-trending-up::before{\n  content:\"\\f2fd\";\n}\n\n.bp5-icon-trophy::before{\n  content:\"\\f34f\";\n}\n\n.bp5-icon-truck::before{\n  content:\"\\f2fe\";\n}\n\n.bp5-icon-two-columns::before{\n  content:\"\\f2ff\";\n}\n\n.bp5-icon-unarchive::before{\n  content:\"\\f300\";\n}\n\n.bp5-icon-underline::before{\n  content:\"\\f301\";\n}\n\n.bp5-icon-undo::before{\n  content:\"\\f302\";\n}\n\n.bp5-icon-ungroup-objects::before{\n  content:\"\\f303\";\n}\n\n.bp5-icon-unknown-vehicle::before{\n  content:\"\\f304\";\n}\n\n.bp5-icon-unlink::before{\n  content:\"\\f345\";\n}\n\n.bp5-icon-unlock::before{\n  content:\"\\f305\";\n}\n\n.bp5-icon-unpin::before{\n  content:\"\\f306\";\n}\n\n.bp5-icon-unresolve::before{\n  content:\"\\f307\";\n}\n\n.bp5-icon-updated::before{\n  content:\"\\f308\";\n}\n\n.bp5-icon-upload::before{\n  content:\"\\f309\";\n}\n\n.bp5-icon-user::before{\n  content:\"\\f30a\";\n}\n\n.bp5-icon-variable::before{\n  content:\"\\f30b\";\n}\n\n.bp5-icon-vector::before{\n  content:\"\\f35e\";\n}\n\n.bp5-icon-vertical-bar-chart-asc::before{\n  content:\"\\f30c\";\n}\n\n.bp5-icon-vertical-bar-chart-desc::before{\n  content:\"\\f30d\";\n}\n\n.bp5-icon-vertical-distribution::before{\n  content:\"\\f30e\";\n}\n\n.bp5-icon-vertical-inbetween::before{\n  content:\"\\f32a\";\n}\n\n.bp5-icon-video::before{\n  content:\"\\f30f\";\n}\n\n.bp5-icon-virus::before{\n  content:\"\\f310\";\n}\n\n.bp5-icon-volume-down::before{\n  content:\"\\f311\";\n}\n\n.bp5-icon-volume-off::before{\n  content:\"\\f312\";\n}\n\n.bp5-icon-volume-up::before{\n  content:\"\\f313\";\n}\n\n.bp5-icon-walk::before{\n  content:\"\\f314\";\n}\n\n.bp5-icon-warning-sign::before{\n  content:\"\\f315\";\n}\n\n.bp5-icon-waterfall-chart::before{\n  content:\"\\f316\";\n}\n\n.bp5-icon-waves::before{\n  content:\"\\f317\";\n}\n\n.bp5-icon-widget::before{\n  content:\"\\f31b\";\n}\n\n.bp5-icon-widget-button::before{\n  content:\"\\f318\";\n}\n\n.bp5-icon-widget-footer::before{\n  content:\"\\f319\";\n}\n\n.bp5-icon-widget-header::before{\n  content:\"\\f31a\";\n}\n\n.bp5-icon-wind::before{\n  content:\"\\f31c\";\n}\n\n.bp5-icon-wrench::before{\n  content:\"\\f31d\";\n}\n\n.bp5-icon-wrench-redo::before{\n  content:\"\\f37e\";\n}\n\n.bp5-icon-wrench-snooze::before{\n  content:\"\\f37f\";\n}\n\n.bp5-icon-wrench-time::before{\n  content:\"\\f380\";\n}\n\n.bp5-icon-zoom-in::before{\n  content:\"\\f31e\";\n}\n\n.bp5-icon-zoom-out::before{\n  content:\"\\f31f\";\n}\n\n.bp5-icon-zoom-to-fit::before{\n  content:\"\\f320\";\n}\n\n.bp5-submenu > .bp5-popover-wrapper{\n  display:block; }\n\n.bp5-submenu .bp5-popover-target{\n  display:block; }\n  .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{ }\n\n.bp5-submenu.bp5-popover{\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  padding:0 5px; }\n  .bp5-submenu.bp5-popover > .bp5-popover-content{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-submenu.bp5-popover, .bp5-submenu.bp5-popover.bp5-dark{\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n    .bp5-dark .bp5-submenu.bp5-popover > .bp5-popover-content, .bp5-submenu.bp5-popover.bp5-dark > .bp5-popover-content{\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n.bp5-menu{\n  background:#ffffff;\n  border-radius:3px;\n  color:#182026;\n  list-style:none;\n  margin:0;\n  min-width:180px;\n  padding:5px;\n  text-align:left; }\n\n.bp5-menu-divider{\n  border-top:1px solid rgba(16, 22, 26, 0.15);\n  display:block;\n  margin:5px; }\n  .bp5-dark .bp5-menu-divider{\n    border-top-color:rgba(255, 255, 255, 0.15); }\n\n.bp5-menu-item{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:start;\n      -ms-flex-align:start;\n          align-items:flex-start;\n  border-radius:2px;\n  color:inherit;\n  line-height:20px;\n  padding:5px 7px;\n  text-decoration:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-menu-item > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-menu-item > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-menu-item::before,\n  .bp5-menu-item > *{\n    margin-right:7px; }\n  .bp5-menu-item:empty::before,\n  .bp5-menu-item > :last-child{\n    margin-right:0; }\n  .bp5-menu-item > .bp5-fill{\n    word-break:break-word; }\n  .bp5-menu-item:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n    background-color:rgba(167, 182, 194, 0.3);\n    cursor:pointer;\n    text-decoration:none; }\n  .bp5-menu-item.bp5-disabled{\n    background-color:inherit;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n  .bp5-dark .bp5-menu-item{\n    color:inherit; }\n    .bp5-dark .bp5-menu-item:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n      background-color:rgba(138, 155, 168, 0.15);\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-disabled{\n      background-color:inherit;\n      color:rgba(167, 182, 194, 0.6); }\n  .bp5-menu-item.bp5-intent-primary{\n    color:#106ba3; }\n    .bp5-menu-item.bp5-intent-primary .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-primary::before, .bp5-menu-item.bp5-intent-primary::after,\n    .bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n      color:#106ba3; }\n    .bp5-menu-item.bp5-intent-primary:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-menu-item.bp5-intent-primary.bp5-active{\n      background-color:#137cbd; }\n    .bp5-menu-item.bp5-intent-primary:active{\n      background-color:#106ba3; }\n    .bp5-menu-item.bp5-intent-primary:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-menu-item.bp5-intent-primary:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::before, .bp5-menu-item.bp5-intent-primary:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-primary:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-primary:active, .bp5-menu-item.bp5-intent-primary:active::before, .bp5-menu-item.bp5-intent-primary:active::after,\n    .bp5-menu-item.bp5-intent-primary:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-primary.bp5-active, .bp5-menu-item.bp5-intent-primary.bp5-active::before, .bp5-menu-item.bp5-intent-primary.bp5-active::after,\n    .bp5-menu-item.bp5-intent-primary.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item.bp5-intent-success{\n    color:#0d8050; }\n    .bp5-menu-item.bp5-intent-success .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-success::before, .bp5-menu-item.bp5-intent-success::after,\n    .bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n      color:#0d8050; }\n    .bp5-menu-item.bp5-intent-success:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-menu-item.bp5-intent-success.bp5-active{\n      background-color:#0f9960; }\n    .bp5-menu-item.bp5-intent-success:active{\n      background-color:#0d8050; }\n    .bp5-menu-item.bp5-intent-success:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-menu-item.bp5-intent-success:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::before, .bp5-menu-item.bp5-intent-success:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-success:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-success:active, .bp5-menu-item.bp5-intent-success:active::before, .bp5-menu-item.bp5-intent-success:active::after,\n    .bp5-menu-item.bp5-intent-success:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-success.bp5-active, .bp5-menu-item.bp5-intent-success.bp5-active::before, .bp5-menu-item.bp5-intent-success.bp5-active::after,\n    .bp5-menu-item.bp5-intent-success.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item.bp5-intent-warning{\n    color:#bf7326; }\n    .bp5-menu-item.bp5-intent-warning .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-warning::before, .bp5-menu-item.bp5-intent-warning::after,\n    .bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n      color:#bf7326; }\n    .bp5-menu-item.bp5-intent-warning:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-menu-item.bp5-intent-warning.bp5-active{\n      background-color:#d9822b; }\n    .bp5-menu-item.bp5-intent-warning:active{\n      background-color:#bf7326; }\n    .bp5-menu-item.bp5-intent-warning:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-menu-item.bp5-intent-warning:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::before, .bp5-menu-item.bp5-intent-warning:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-warning:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-warning:active, .bp5-menu-item.bp5-intent-warning:active::before, .bp5-menu-item.bp5-intent-warning:active::after,\n    .bp5-menu-item.bp5-intent-warning:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-warning.bp5-active, .bp5-menu-item.bp5-intent-warning.bp5-active::before, .bp5-menu-item.bp5-intent-warning.bp5-active::after,\n    .bp5-menu-item.bp5-intent-warning.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item.bp5-intent-danger{\n    color:#c23030; }\n    .bp5-menu-item.bp5-intent-danger .bp5-icon{\n      color:inherit; }\n    .bp5-menu-item.bp5-intent-danger::before, .bp5-menu-item.bp5-intent-danger::after,\n    .bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n      color:#c23030; }\n    .bp5-menu-item.bp5-intent-danger:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-menu-item.bp5-intent-danger.bp5-active{\n      background-color:#db3737; }\n    .bp5-menu-item.bp5-intent-danger:active{\n      background-color:#c23030; }\n    .bp5-menu-item.bp5-intent-danger:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-menu-item.bp5-intent-danger:hover::before, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::before, .bp5-menu-item.bp5-intent-danger:hover::after, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::after,\n    .bp5-menu-item.bp5-intent-danger:hover .bp5-menu-item-label,\n    .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item .bp5-menu-item-label, .bp5-menu-item.bp5-intent-danger:active, .bp5-menu-item.bp5-intent-danger:active::before, .bp5-menu-item.bp5-intent-danger:active::after,\n    .bp5-menu-item.bp5-intent-danger:active .bp5-menu-item-label, .bp5-menu-item.bp5-intent-danger.bp5-active, .bp5-menu-item.bp5-intent-danger.bp5-active::before, .bp5-menu-item.bp5-intent-danger.bp5-active::after,\n    .bp5-menu-item.bp5-intent-danger.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-menu-item::before{\n    font-family:\"blueprint-icons-16\", sans-serif;\n    font-size:16px;\n    font-style:normal;\n    font-weight:400;\n    line-height:1;\n    -moz-osx-font-smoothing:grayscale;\n    -webkit-font-smoothing:antialiased;\n    margin-right:7px; }\n  .bp5-menu-item::before,\n  .bp5-menu-item > .bp5-icon{\n    color:#5c7080;\n    margin-top:2px; }\n  .bp5-menu-item .bp5-menu-item-label{\n    color:#5c7080; }\n  .bp5-menu-item:hover, .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item{\n    color:inherit; }\n  .bp5-menu-item.bp5-active, .bp5-menu-item:active{\n    background-color:rgba(115, 134, 148, 0.3); }\n  .bp5-menu-item.bp5-disabled{\n    background-color:inherit !important;\n    color:rgba(92, 112, 128, 0.6) !important;\n    cursor:not-allowed !important;\n    outline:none !important; }\n    .bp5-menu-item.bp5-disabled::before,\n    .bp5-menu-item.bp5-disabled > .bp5-icon,\n    .bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n      color:rgba(92, 112, 128, 0.6) !important; }\n  .bp5-large .bp5-menu-item{\n    font-size:16px;\n    line-height:22px;\n    padding:9px 7px; }\n    .bp5-large .bp5-menu-item .bp5-icon{\n      margin-top:3px; }\n    .bp5-large .bp5-menu-item::before{\n      font-family:\"blueprint-icons-20\", sans-serif;\n      font-size:20px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1;\n      -moz-osx-font-smoothing:grayscale;\n      -webkit-font-smoothing:antialiased;\n      margin-right:10px;\n      margin-top:1px; }\n\nbutton.bp5-menu-item{\n  background:none;\n  border:none;\n  text-align:left;\n  width:100%; }\n.bp5-menu-header{\n  border-top:1px solid rgba(16, 22, 26, 0.15);\n  display:block;\n  margin:5px;\n  cursor:default;\n  padding-left:2px; }\n  .bp5-dark .bp5-menu-header{\n    border-top-color:rgba(255, 255, 255, 0.15); }\n  .bp5-menu-header:first-of-type{\n    border-top:none; }\n  .bp5-menu-header > h6{\n    color:#182026;\n    font-weight:600;\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap;\n    word-wrap:normal;\n    line-height:17px;\n    margin:0;\n    padding:10px 7px 0 1px; }\n    .bp5-dark .bp5-menu-header > h6{\n      color:#f5f8fa; }\n  .bp5-menu-header:first-of-type > h6{\n    padding-top:0; }\n  .bp5-large .bp5-menu-header > h6{\n    font-size:18px;\n    padding-bottom:5px;\n    padding-top:15px; }\n  .bp5-large .bp5-menu-header:first-of-type > h6{\n    padding-top:0; }\n\n.bp5-dark .bp5-menu{\n  background:#30404d;\n  color:#f5f8fa; }\n\n.bp5-dark .bp5-menu-item{ }\n  .bp5-dark .bp5-menu-item.bp5-intent-primary{\n    color:#48aff0; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary::before, .bp5-dark .bp5-menu-item.bp5-intent-primary::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-label{\n      color:#48aff0; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active{\n      background-color:#137cbd; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:active{\n      background-color:#106ba3; }\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-primary:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-primary:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-primary.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-primary:active, .bp5-dark .bp5-menu-item.bp5-intent-primary:active::before, .bp5-dark .bp5-menu-item.bp5-intent-primary:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item.bp5-intent-success{\n    color:#3dcc91; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success::before, .bp5-dark .bp5-menu-item.bp5-intent-success::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-label{\n      color:#3dcc91; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active{\n      background-color:#0f9960; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success:active{\n      background-color:#0d8050; }\n    .bp5-dark .bp5-menu-item.bp5-intent-success:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-success:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-success:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-success.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-success:active, .bp5-dark .bp5-menu-item.bp5-intent-success:active::before, .bp5-dark .bp5-menu-item.bp5-intent-success:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item.bp5-intent-warning{\n    color:#ffb366; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning::before, .bp5-dark .bp5-menu-item.bp5-intent-warning::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-label{\n      color:#ffb366; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active{\n      background-color:#d9822b; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:active{\n      background-color:#bf7326; }\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-warning:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-warning:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-warning.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-warning:active, .bp5-dark .bp5-menu-item.bp5-intent-warning:active::before, .bp5-dark .bp5-menu-item.bp5-intent-warning:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item.bp5-intent-danger{\n    color:#ff7373; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-icon{\n      color:inherit; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger::before, .bp5-dark .bp5-menu-item.bp5-intent-danger::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-label{\n      color:#ff7373; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active{\n      background-color:#db3737; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:active{\n      background-color:#c23030; }\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:hover, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item, .bp5-dark .bp5-menu-item.bp5-intent-danger:hover::before, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::before, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::before, .bp5-dark .bp5-menu-item.bp5-intent-danger:hover::after, .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::after, .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:hover .bp5-menu-item-label,\n    .bp5-dark .bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item .bp5-menu-item-label,\n    .bp5-submenu .bp5-dark .bp5-popover-target.bp5-popover-open > .bp5-intent-danger.bp5-menu-item .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-danger:active, .bp5-dark .bp5-menu-item.bp5-intent-danger:active::before, .bp5-dark .bp5-menu-item.bp5-intent-danger:active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger:active .bp5-menu-item-label, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active::before, .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active::after,\n    .bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active .bp5-menu-item-label{\n      color:#ffffff; }\n  .bp5-dark .bp5-menu-item::before,\n  .bp5-dark .bp5-menu-item > .bp5-icon{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-menu-item .bp5-menu-item-label{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-menu-item.bp5-active, .bp5-dark .bp5-menu-item:active{\n    background-color:rgba(138, 155, 168, 0.3); }\n  .bp5-dark .bp5-menu-item.bp5-disabled{\n    color:rgba(167, 182, 194, 0.6) !important; }\n    .bp5-dark .bp5-menu-item.bp5-disabled::before,\n    .bp5-dark .bp5-menu-item.bp5-disabled > .bp5-icon,\n    .bp5-dark .bp5-menu-item.bp5-disabled .bp5-menu-item-label{\n      color:rgba(167, 182, 194, 0.6) !important; }\n\n.bp5-dark .bp5-menu-divider,\n.bp5-dark .bp5-menu-header{\n  border-color:rgba(255, 255, 255, 0.15); }\n\n.bp5-dark .bp5-menu-header > h6{\n  color:#f5f8fa; }\n\n.bp5-label .bp5-menu{\n  margin-top:5px; }\n.bp5-navbar{\n  background-color:#ffffff;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.2);\n  height:50px;\n  padding:0 15px;\n  position:relative;\n  width:100%;\n  z-index:10; }\n  .bp5-navbar.bp5-dark,\n  .bp5-dark .bp5-navbar{\n    background-color:#394b59; }\n  .bp5-navbar.bp5-dark{\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-dark .bp5-navbar{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4); }\n  .bp5-navbar.bp5-fixed-top{\n    left:0;\n    position:fixed;\n    right:0;\n    top:0; }\n\n.bp5-navbar-heading{\n  font-size:16px;\n  margin-right:15px; }\n\n.bp5-navbar-group{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:50px; }\n  .bp5-navbar-group.bp5-align-left{\n    float:left; }\n  .bp5-navbar-group.bp5-align-right{\n    float:right; }\n\n.bp5-navbar-divider{\n  border-left:1px solid rgba(16, 22, 26, 0.15);\n  height:20px;\n  margin:0 10px; }\n  .bp5-dark .bp5-navbar-divider{\n    border-left-color:rgba(255, 255, 255, 0.15); }\n.bp5-non-ideal-state{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  height:100%;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  text-align:center;\n  width:100%; }\n  .bp5-non-ideal-state > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-non-ideal-state > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-non-ideal-state::before,\n  .bp5-non-ideal-state > *{\n    margin-bottom:20px; }\n  .bp5-non-ideal-state:empty::before,\n  .bp5-non-ideal-state > :last-child{\n    margin-bottom:0; }\n  .bp5-non-ideal-state > *{\n    max-width:400px; }\n\n.bp5-non-ideal-state-visual{\n  color:rgba(92, 112, 128, 0.6);\n  font-size:60px; }\n  .bp5-dark .bp5-non-ideal-state-visual{\n    color:rgba(167, 182, 194, 0.6); }\n\n.bp5-overflow-list{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-wrap:nowrap;\n      flex-wrap:nowrap;\n  min-width:0; }\n\n.bp5-overflow-list-spacer{\n  -ms-flex-negative:1;\n      flex-shrink:1;\n  width:1px; }\nbody.bp5-overlay-open{\n  overflow:hidden; }\n\n.bp5-overlay{\n  bottom:0;\n  left:0;\n  position:static;\n  right:0;\n  top:0;\n  z-index:20; }\n  .bp5-overlay:not(.bp5-overlay-open){\n    pointer-events:none; }\n  .bp5-overlay.bp5-overlay-container{\n    overflow:hidden;\n    position:fixed; }\n    .bp5-overlay.bp5-overlay-container.bp5-overlay-inline{\n      position:absolute; }\n  .bp5-overlay.bp5-overlay-scroll-container{\n    overflow:auto;\n    position:fixed; }\n    .bp5-overlay.bp5-overlay-scroll-container.bp5-overlay-inline{\n      position:absolute; }\n  .bp5-overlay.bp5-overlay-inline{\n    display:inline;\n    overflow:visible; }\n\n.bp5-overlay-content{\n  position:fixed;\n  z-index:20; }\n  .bp5-overlay-inline .bp5-overlay-content,\n  .bp5-overlay-scroll-container .bp5-overlay-content{\n    position:absolute; }\n\n.bp5-overlay-backdrop{\n  bottom:0;\n  left:0;\n  position:fixed;\n  right:0;\n  top:0;\n  opacity:1;\n  background-color:rgba(16, 22, 26, 0.7);\n  overflow:auto;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none;\n  z-index:20; }\n  .bp5-overlay-backdrop.bp5-overlay-enter, .bp5-overlay-backdrop.bp5-overlay-appear{\n    opacity:0; }\n  .bp5-overlay-backdrop.bp5-overlay-enter-active, .bp5-overlay-backdrop.bp5-overlay-appear-active{\n    opacity:1;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:200ms;\n            transition-duration:200ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-overlay-backdrop.bp5-overlay-exit{\n    opacity:1; }\n  .bp5-overlay-backdrop.bp5-overlay-exit-active{\n    opacity:0;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:200ms;\n            transition-duration:200ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-overlay-backdrop:focus{\n    outline:none; }\n  .bp5-overlay-inline .bp5-overlay-backdrop{\n    position:absolute; }\n.bp5-panel-stack{\n  overflow:hidden;\n  position:relative; }\n\n.bp5-panel-stack-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  -webkit-box-shadow:0 1px rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-negative:0;\n      flex-shrink:0;\n  height:30px;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack-header{\n    -webkit-box-shadow:0 1px rgba(255, 255, 255, 0.15);\n            box-shadow:0 1px rgba(255, 255, 255, 0.15); }\n  .bp5-panel-stack-header > span{\n    -webkit-box-align:stretch;\n        -ms-flex-align:stretch;\n            align-items:stretch;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-flex:1;\n        -ms-flex:1;\n            flex:1; }\n  .bp5-panel-stack-header .bp5-heading{\n    margin:0 5px; }\n\n.bp5-button.bp5-panel-stack-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap; }\n  .bp5-button.bp5-panel-stack-header-back .bp5-icon{\n    margin:0 2px; }\n\n.bp5-panel-stack-view{\n  bottom:0;\n  left:0;\n  position:absolute;\n  right:0;\n  top:0;\n  background-color:#ffffff;\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack-view{\n    background-color:#30404d; }\n  .bp5-panel-stack-view:nth-last-child(n + 4){\n    display:none; }\n\n.bp5-panel-stack-push .bp5-panel-stack-enter, .bp5-panel-stack-push .bp5-panel-stack-appear{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0; }\n\n.bp5-panel-stack-push .bp5-panel-stack-enter-active, .bp5-panel-stack-push .bp5-panel-stack-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack-push .bp5-panel-stack-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack-push .bp5-panel-stack-exit-active{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-enter, .bp5-panel-stack-pop .bp5-panel-stack-appear{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-enter-active, .bp5-panel-stack-pop .bp5-panel-stack-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack-pop .bp5-panel-stack-exit-active{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n.bp5-panel-stack2{\n  overflow:hidden;\n  position:relative; }\n\n.bp5-panel-stack2-header{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  -webkit-box-shadow:0 1px rgba(16, 22, 26, 0.15);\n          box-shadow:0 1px rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-negative:0;\n      flex-shrink:0;\n  height:30px;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack2-header{\n    -webkit-box-shadow:0 1px rgba(255, 255, 255, 0.15);\n            box-shadow:0 1px rgba(255, 255, 255, 0.15); }\n  .bp5-panel-stack2-header > span{\n    -webkit-box-align:stretch;\n        -ms-flex-align:stretch;\n            align-items:stretch;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-flex:1;\n        -ms-flex:1;\n            flex:1; }\n  .bp5-panel-stack2-header .bp5-heading{\n    margin:0 5px; }\n\n.bp5-button.bp5-panel-stack2-header-back{\n  margin-left:5px;\n  padding-left:0;\n  white-space:nowrap; }\n  .bp5-button.bp5-panel-stack2-header-back .bp5-icon{\n    margin:0 2px; }\n\n.bp5-panel-stack2-view{\n  bottom:0;\n  left:0;\n  position:absolute;\n  right:0;\n  top:0;\n  background-color:#ffffff;\n  border-right:1px solid rgba(16, 22, 26, 0.15);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  margin-right:-1px;\n  overflow-y:auto;\n  z-index:1; }\n  .bp5-dark .bp5-panel-stack2-view{\n    background-color:#30404d; }\n  .bp5-panel-stack2-view:nth-last-child(n + 4){\n    display:none; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-enter, .bp5-panel-stack2-push .bp5-panel-stack2-appear{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-enter-active, .bp5-panel-stack2-push .bp5-panel-stack2-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack2-push .bp5-panel-stack2-exit-active{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter, .bp5-panel-stack2-pop .bp5-panel-stack2-appear{\n  -webkit-transform:translateX(-50%);\n          transform:translateX(-50%);\n  opacity:0; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter-active, .bp5-panel-stack2-pop .bp5-panel-stack2-appear-active{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit{\n  -webkit-transform:translate(0%);\n          transform:translate(0%);\n  opacity:1; }\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit-active{\n  -webkit-transform:translateX(100%);\n          transform:translateX(100%);\n  opacity:0;\n  -webkit-transition-delay:0;\n          transition-delay:0;\n  -webkit-transition-duration:400ms;\n          transition-duration:400ms;\n  -webkit-transition-property:opacity, -webkit-transform;\n  transition-property:opacity, -webkit-transform;\n  transition-property:transform, opacity;\n  transition-property:transform, opacity, -webkit-transform;\n  -webkit-transition-timing-function:ease;\n          transition-timing-function:ease; }\n.bp5-popover{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  -webkit-transform:scale(1);\n          transform:scale(1);\n  border-radius:3px;\n  display:inline-block;\n  z-index:20; }\n  .bp5-popover .bp5-popover-arrow{\n    height:30px;\n    position:absolute;\n    width:30px; }\n    .bp5-popover .bp5-popover-arrow::before{\n      height:20px;\n      margin:5px;\n      width:20px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-popover{\n    margin-bottom:17px;\n    margin-top:-17px; }\n    .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-popover > .bp5-popover-arrow{\n      bottom:-11px; }\n      .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(-90deg);\n                transform:rotate(-90deg); }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-popover{\n    margin-left:17px; }\n    .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-popover > .bp5-popover-arrow{\n      left:-11px; }\n      .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(0);\n                transform:rotate(0); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-popover{\n    margin-top:17px; }\n    .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-popover > .bp5-popover-arrow{\n      top:-11px; }\n      .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(90deg);\n                transform:rotate(90deg); }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-popover{\n    margin-left:-17px;\n    margin-right:17px; }\n    .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-popover > .bp5-popover-arrow{\n      right:-11px; }\n      .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-popover > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(180deg);\n                transform:rotate(180deg); }\n  .bp5-tether-element-attached-middle > .bp5-popover > .bp5-popover-arrow{\n    top:50%;\n    -webkit-transform:translateY(-50%);\n            transform:translateY(-50%); }\n  .bp5-tether-element-attached-center > .bp5-popover > .bp5-popover-arrow{\n    right:50%;\n    -webkit-transform:translateX(50%);\n            transform:translateX(50%); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-top > .bp5-popover > .bp5-popover-arrow{\n    top:-0.3934px; }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-right > .bp5-popover > .bp5-popover-arrow{\n    right:-0.3934px; }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-left > .bp5-popover > .bp5-popover-arrow{\n    left:-0.3934px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-bottom > .bp5-popover > .bp5-popover-arrow{\n    bottom:-0.3934px; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-left > .bp5-popover{\n    -webkit-transform-origin:top left;\n            transform-origin:top left; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-center > .bp5-popover{\n    -webkit-transform-origin:top center;\n            transform-origin:top center; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-right > .bp5-popover{\n    -webkit-transform-origin:top right;\n            transform-origin:top right; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-left > .bp5-popover{\n    -webkit-transform-origin:center left;\n            transform-origin:center left; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-center > .bp5-popover{\n    -webkit-transform-origin:center center;\n            transform-origin:center center; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-right > .bp5-popover{\n    -webkit-transform-origin:center right;\n            transform-origin:center right; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-left > .bp5-popover{\n    -webkit-transform-origin:bottom left;\n            transform-origin:bottom left; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-center > .bp5-popover{\n    -webkit-transform-origin:bottom center;\n            transform-origin:bottom center; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-right > .bp5-popover{\n    -webkit-transform-origin:bottom right;\n            transform-origin:bottom right; }\n  .bp5-popover .bp5-popover-content{\n    background:#ffffff; }\n  .bp5-popover .bp5-popover-content,\n  .bp5-popover .bp5-heading{\n    color:inherit; }\n  .bp5-popover .bp5-popover-arrow::before{\n    -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2);\n            box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-popover .bp5-popover-arrow-border{\n    fill:#10161a;\n    fill-opacity:0.1; }\n  .bp5-popover .bp5-popover-arrow-fill{\n    fill:#ffffff; }\n  .bp5-popover-enter > .bp5-popover, .bp5-popover-appear > .bp5-popover{\n    -webkit-transform:scale(0.3);\n            transform:scale(0.3); }\n  .bp5-popover-enter-active > .bp5-popover, .bp5-popover-appear-active > .bp5-popover{\n    -webkit-transform:scale(1);\n            transform:scale(1);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-popover-exit > .bp5-popover{\n    -webkit-transform:scale(1);\n            transform:scale(1); }\n  .bp5-popover-exit-active > .bp5-popover{\n    -webkit-transform:scale(0.3);\n            transform:scale(0.3);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-popover .bp5-popover-content{\n    border-radius:3px;\n    position:relative; }\n  .bp5-popover.bp5-popover-content-sizing .bp5-popover-content{\n    max-width:350px;\n    padding:20px; }\n  .bp5-popover-target + .bp5-overlay .bp5-popover.bp5-popover-content-sizing{\n    width:350px; }\n  .bp5-popover.bp5-minimal{\n    margin:0 !important; }\n    .bp5-popover.bp5-minimal .bp5-popover-arrow{\n      display:none; }\n    .bp5-popover.bp5-minimal.bp5-popover{\n      -webkit-transform:scale(1);\n              transform:scale(1); }\n      .bp5-popover-enter > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1); }\n      .bp5-popover-enter-active > .bp5-popover.bp5-minimal.bp5-popover, .bp5-popover-appear-active > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1);\n        -webkit-transition-delay:0;\n                transition-delay:0;\n        -webkit-transition-duration:100ms;\n                transition-duration:100ms;\n        -webkit-transition-property:-webkit-transform;\n        transition-property:-webkit-transform;\n        transition-property:transform;\n        transition-property:transform, -webkit-transform;\n        -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n                transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n      .bp5-popover-exit > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1); }\n      .bp5-popover-exit-active > .bp5-popover.bp5-minimal.bp5-popover{\n        -webkit-transform:scale(1);\n                transform:scale(1);\n        -webkit-transition-delay:0;\n                transition-delay:0;\n        -webkit-transition-duration:100ms;\n                transition-duration:100ms;\n        -webkit-transition-property:-webkit-transform;\n        transition-property:-webkit-transform;\n        transition-property:transform;\n        transition-property:transform, -webkit-transform;\n        -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n                transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-popover.bp5-dark,\n  .bp5-dark .bp5-popover{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n    .bp5-popover.bp5-dark .bp5-popover-content,\n    .bp5-dark .bp5-popover .bp5-popover-content{\n      background:#30404d; }\n    .bp5-popover.bp5-dark .bp5-popover-content,\n    .bp5-popover.bp5-dark .bp5-heading,\n    .bp5-dark .bp5-popover .bp5-popover-content,\n    .bp5-dark .bp5-popover .bp5-heading{\n      color:inherit; }\n    .bp5-popover.bp5-dark .bp5-popover-arrow::before,\n    .bp5-dark .bp5-popover .bp5-popover-arrow::before{\n      -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4);\n              box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4); }\n    .bp5-popover.bp5-dark .bp5-popover-arrow-border,\n    .bp5-dark .bp5-popover .bp5-popover-arrow-border{\n      fill:#10161a;\n      fill-opacity:0.2; }\n    .bp5-popover.bp5-dark .bp5-popover-arrow-fill,\n    .bp5-dark .bp5-popover .bp5-popover-arrow-fill{\n      fill:#30404d; }\n\n.bp5-popover-arrow::before{\n  border-radius:2px;\n  content:\"\";\n  display:block;\n  position:absolute;\n  -webkit-transform:rotate(45deg);\n          transform:rotate(45deg); }\n\n.bp5-tether-pinned .bp5-popover-arrow{\n  display:none; }\n\n.bp5-popover-backdrop{\n  background:rgba(255, 255, 255, 0); }\n\n.bp5-transition-container{\n  opacity:1;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  z-index:20; }\n  .bp5-transition-container.bp5-popover-enter, .bp5-transition-container.bp5-popover-appear{\n    opacity:0; }\n  .bp5-transition-container.bp5-popover-enter-active, .bp5-transition-container.bp5-popover-appear-active{\n    opacity:1;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-transition-container.bp5-popover-exit{\n    opacity:1; }\n  .bp5-transition-container.bp5-popover-exit-active{\n    opacity:0;\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:opacity;\n    transition-property:opacity;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-transition-container:focus{\n    outline:none; }\n  .bp5-transition-container.bp5-popover-leave .bp5-popover-content{\n    pointer-events:none; }\n  .bp5-transition-container[data-x-out-of-boundaries]{\n    display:none; }\n\nspan.bp5-popover-target{\n  display:inline-block; }\n\n.bp5-popover-wrapper.bp5-fill{\n  width:100%; }\n\n.bp5-portal{\n  left:0;\n  position:absolute;\n  right:0;\n  top:0; }\n@-webkit-keyframes linear-progress-bar-stripes{\n  from{\n    background-position:0 0; }\n  to{\n    background-position:30px 0; } }\n@keyframes linear-progress-bar-stripes{\n  from{\n    background-position:0 0; }\n  to{\n    background-position:30px 0; } }\n\n.bp5-progress-bar{\n  background:rgba(92, 112, 128, 0.2);\n  border-radius:40px;\n  display:block;\n  height:8px;\n  overflow:hidden;\n  position:relative;\n  width:100%; }\n  .bp5-progress-bar .bp5-progress-meter{\n    background:linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%);\n    background-color:rgba(92, 112, 128, 0.8);\n    background-size:30px 30px;\n    border-radius:40px;\n    height:100%;\n    position:absolute;\n    -webkit-transition:width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    width:100%; }\n  .bp5-progress-bar:not(.bp5-no-animation):not(.bp5-no-stripes) .bp5-progress-meter{\n    animation:linear-progress-bar-stripes 300ms linear infinite reverse; }\n  .bp5-progress-bar.bp5-no-stripes .bp5-progress-meter{\n    background-image:none; }\n\n.bp5-dark .bp5-progress-bar{\n  background:rgba(16, 22, 26, 0.5); }\n  .bp5-dark .bp5-progress-bar .bp5-progress-meter{\n    background-color:#8a9ba8; }\n\n.bp5-progress-bar.bp5-intent-primary .bp5-progress-meter{\n  background-color:#137cbd; }\n\n.bp5-progress-bar.bp5-intent-success .bp5-progress-meter{\n  background-color:#0f9960; }\n\n.bp5-progress-bar.bp5-intent-warning .bp5-progress-meter{\n  background-color:#d9822b; }\n\n.bp5-progress-bar.bp5-intent-danger .bp5-progress-meter{\n  background-color:#db3737; }\n@-webkit-keyframes skeleton-glow{\n  from{\n    background:rgba(206, 217, 224, 0.2);\n    border-color:rgba(206, 217, 224, 0.2); }\n  to{\n    background:rgba(92, 112, 128, 0.2);\n    border-color:rgba(92, 112, 128, 0.2); } }\n@keyframes skeleton-glow{\n  from{\n    background:rgba(206, 217, 224, 0.2);\n    border-color:rgba(206, 217, 224, 0.2); }\n  to{\n    background:rgba(92, 112, 128, 0.2);\n    border-color:rgba(92, 112, 128, 0.2); } }\n.bp5-skeleton{\n  -webkit-animation:1000ms linear infinite alternate skeleton-glow;\n          animation:1000ms linear infinite alternate skeleton-glow;\n  background:rgba(206, 217, 224, 0.2);\n  background-clip:padding-box !important;\n  border-color:rgba(206, 217, 224, 0.2) !important;\n  border-radius:2px;\n  -webkit-box-shadow:none !important;\n          box-shadow:none !important;\n  color:transparent !important;\n  cursor:default;\n  pointer-events:none;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-skeleton::before, .bp5-skeleton::after,\n  .bp5-skeleton *{\n    visibility:hidden !important; }\n.bp5-slider{\n  height:40px;\n  min-width:150px;\n  width:100%;\n  cursor:default;\n  outline:none;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-slider:hover{\n    cursor:pointer; }\n  .bp5-slider:active{\n    cursor:-webkit-grabbing;\n    cursor:grabbing; }\n  .bp5-slider.bp5-disabled{\n    cursor:not-allowed;\n    opacity:0.5; }\n  .bp5-slider.bp5-slider-unlabeled{\n    height:16px; }\n\n.bp5-slider-track,\n.bp5-slider-progress{\n  height:6px;\n  left:0;\n  right:0;\n  top:5px;\n  position:absolute; }\n\n.bp5-slider-track{\n  border-radius:3px;\n  overflow:hidden; }\n\n.bp5-slider-progress{\n  background:rgba(92, 112, 128, 0.2); }\n  .bp5-dark .bp5-slider-progress{\n    background:rgba(16, 22, 26, 0.5); }\n  .bp5-slider-progress.bp5-intent-primary{\n    background-color:#137cbd; }\n  .bp5-slider-progress.bp5-intent-success{\n    background-color:#0f9960; }\n  .bp5-slider-progress.bp5-intent-warning{\n    background-color:#d9822b; }\n  .bp5-slider-progress.bp5-intent-danger{\n    background-color:#db3737; }\n\n.bp5-slider-handle{\n  background-color:#f5f8fa;\n  background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.8)), to(rgba(255, 255, 255, 0)));\n  background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n          box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n  color:#182026;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n  cursor:pointer;\n  height:16px;\n  left:0;\n  position:absolute;\n  top:0;\n  width:16px; }\n  .bp5-slider-handle:hover{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1); }\n  .bp5-slider-handle:active, .bp5-slider-handle.bp5-active{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n  .bp5-slider-handle:disabled, .bp5-slider-handle.bp5-disabled{\n    background-color:rgba(206, 217, 224, 0.5);\n    background-image:none;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed;\n    outline:none; }\n    .bp5-slider-handle:disabled.bp5-active, .bp5-slider-handle:disabled.bp5-active:hover, .bp5-slider-handle.bp5-disabled.bp5-active, .bp5-slider-handle.bp5-disabled.bp5-active:hover{\n      background:rgba(206, 217, 224, 0.7); }\n  .bp5-slider-handle:focus{\n    z-index:1; }\n  .bp5-slider-handle:hover{\n    background-clip:padding-box;\n    background-color:#ebf1f5;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 -1px 0 rgba(16, 22, 26, 0.1);\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 1px 1px rgba(16, 22, 26, 0.2);\n    cursor:-webkit-grab;\n    cursor:grab;\n    z-index:2; }\n  .bp5-slider-handle.bp5-active{\n    background-color:#d8e1e8;\n    background-image:none;\n    -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n            box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 1px rgba(16, 22, 26, 0.1);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), inset 0 1px 1px rgba(16, 22, 26, 0.1);\n    cursor:-webkit-grabbing;\n    cursor:grabbing; }\n  .bp5-disabled .bp5-slider-handle{\n    background:#bfccd6;\n    -webkit-box-shadow:none;\n            box-shadow:none;\n    pointer-events:none; }\n  .bp5-dark .bp5-slider-handle{\n    background-color:#394b59;\n    background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.05)), to(rgba(255, 255, 255, 0)));\n    background-image:linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n    color:#f5f8fa; }\n    .bp5-dark .bp5-slider-handle:hover, .bp5-dark .bp5-slider-handle:active, .bp5-dark .bp5-slider-handle.bp5-active{\n      color:#f5f8fa; }\n    .bp5-dark .bp5-slider-handle:hover{\n      background-color:#30404d;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-slider-handle:active, .bp5-dark .bp5-slider-handle.bp5-active{\n      background-color:#202b33;\n      background-image:none;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.6), inset 0 1px 2px rgba(16, 22, 26, 0.2); }\n    .bp5-dark .bp5-slider-handle:disabled, .bp5-dark .bp5-slider-handle.bp5-disabled{\n      background-color:rgba(57, 75, 89, 0.5);\n      background-image:none;\n      -webkit-box-shadow:none;\n              box-shadow:none;\n      color:rgba(167, 182, 194, 0.6); }\n      .bp5-dark .bp5-slider-handle:disabled.bp5-active, .bp5-dark .bp5-slider-handle.bp5-disabled.bp5-active{\n        background:rgba(57, 75, 89, 0.7); }\n    .bp5-dark .bp5-slider-handle .bp5-button-spinner .bp5-spinner-head{\n      background:rgba(16, 22, 26, 0.5);\n      stroke:#8a9ba8; }\n    .bp5-dark .bp5-slider-handle, .bp5-dark .bp5-slider-handle:hover{\n      background-color:#394b59; }\n    .bp5-dark .bp5-slider-handle.bp5-active{\n      background-color:#293742; }\n  .bp5-dark .bp5-disabled .bp5-slider-handle{\n    background:#5c7080;\n    border-color:#5c7080;\n    -webkit-box-shadow:none;\n            box-shadow:none; }\n  .bp5-slider-handle .bp5-slider-label{\n    background:#394b59;\n    border-radius:3px;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n    color:#f5f8fa;\n    margin-left:8px; }\n    .bp5-dark .bp5-slider-handle .bp5-slider-label{\n      background:#e1e8ed;\n      -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n      color:#394b59; }\n    .bp5-disabled .bp5-slider-handle .bp5-slider-label{\n      -webkit-box-shadow:none;\n              box-shadow:none; }\n  .bp5-slider-handle.bp5-start, .bp5-slider-handle.bp5-end{\n    width:8px; }\n  .bp5-slider-handle.bp5-start{\n    border-bottom-right-radius:0;\n    border-top-right-radius:0; }\n  .bp5-slider-handle.bp5-end{\n    border-bottom-left-radius:0;\n    border-top-left-radius:0;\n    margin-left:8px; }\n    .bp5-slider-handle.bp5-end .bp5-slider-label{\n      margin-left:0; }\n\n.bp5-slider-label{\n  -webkit-transform:translate(-50%, 20px);\n          transform:translate(-50%, 20px);\n  display:inline-block;\n  font-size:12px;\n  line-height:1;\n  padding:2px 5px;\n  position:absolute;\n  vertical-align:top; }\n\n.bp5-slider.bp5-vertical{\n  height:150px;\n  min-width:40px;\n  width:40px; }\n  .bp5-slider.bp5-vertical .bp5-slider-track,\n  .bp5-slider.bp5-vertical .bp5-slider-progress{\n    bottom:0;\n    height:auto;\n    left:5px;\n    top:0;\n    width:6px; }\n  .bp5-slider.bp5-vertical .bp5-slider-progress{\n    top:auto; }\n  .bp5-slider.bp5-vertical .bp5-slider-label{\n    -webkit-transform:translate(20px, 50%);\n            transform:translate(20px, 50%); }\n  .bp5-slider.bp5-vertical .bp5-slider-handle{\n    top:auto; }\n    .bp5-slider.bp5-vertical .bp5-slider-handle .bp5-slider-label{\n      margin-left:0;\n      margin-top:-8px; }\n    .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end, .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n      height:8px;\n      margin-left:0;\n      width:16px; }\n    .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start{\n      border-bottom-right-radius:3px;\n      border-top-left-radius:0; }\n      .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start .bp5-slider-label{\n        -webkit-transform:translate(20px);\n                transform:translate(20px); }\n    .bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end{\n      border-bottom-left-radius:0;\n      border-bottom-right-radius:0;\n      border-top-left-radius:3px;\n      margin-bottom:8px; }\n@-webkit-keyframes pt-spinner-animation{\n  from{\n    -webkit-transform:rotate(0deg);\n            transform:rotate(0deg); }\n  to{\n    -webkit-transform:rotate(360deg);\n            transform:rotate(360deg); } }\n@keyframes pt-spinner-animation{\n  from{\n    -webkit-transform:rotate(0deg);\n            transform:rotate(0deg); }\n  to{\n    -webkit-transform:rotate(360deg);\n            transform:rotate(360deg); } }\n\n.bp5-spinner{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:center;\n      -ms-flex-pack:center;\n          justify-content:center;\n  overflow:visible;\n  vertical-align:middle; }\n  .bp5-spinner svg{\n    display:block; }\n  .bp5-spinner path{\n    fill-opacity:0; }\n  .bp5-spinner .bp5-spinner-head{\n    stroke:rgba(92, 112, 128, 0.8);\n    stroke-linecap:round;\n    -webkit-transform-origin:center;\n            transform-origin:center;\n    -webkit-transition:stroke-dashoffset 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n    transition:stroke-dashoffset 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-spinner .bp5-spinner-track{\n    stroke:rgba(92, 112, 128, 0.2); }\n\n.bp5-spinner-animation{\n  -webkit-animation:pt-spinner-animation 500ms linear infinite;\n          animation:pt-spinner-animation 500ms linear infinite; }\n  .bp5-no-spin > .bp5-spinner-animation{\n    -webkit-animation:none;\n            animation:none; }\n\n.bp5-dark .bp5-spinner .bp5-spinner-head{\n  stroke:#8a9ba8; }\n\n.bp5-dark .bp5-spinner .bp5-spinner-track{\n  stroke:rgba(16, 22, 26, 0.5); }\n\n.bp5-spinner.bp5-intent-primary .bp5-spinner-head{\n  stroke:#137cbd; }\n\n.bp5-spinner.bp5-intent-success .bp5-spinner-head{\n  stroke:#0f9960; }\n\n.bp5-spinner.bp5-intent-warning .bp5-spinner-head{\n  stroke:#d9822b; }\n\n.bp5-spinner.bp5-intent-danger .bp5-spinner-head{\n  stroke:#db3737; }\n.bp5-tabs.bp5-vertical{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex; }\n  .bp5-tabs.bp5-vertical > .bp5-tab-list{\n    -webkit-box-align:start;\n        -ms-flex-align:start;\n            align-items:flex-start;\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:column;\n            flex-direction:column; }\n    .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab{\n      border-radius:3px;\n      padding:0 10px;\n      width:100%; }\n      .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab[aria-selected=\"true\"]{\n        background-color:rgba(19, 124, 189, 0.2);\n        -webkit-box-shadow:none;\n                box-shadow:none; }\n    .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n      background-color:rgba(19, 124, 189, 0.2);\n      border-radius:3px;\n      bottom:0;\n      height:auto;\n      left:0;\n      right:0;\n      top:0; }\n  .bp5-tabs.bp5-vertical > .bp5-tab-panel{\n    margin-top:0;\n    padding-left:20px; }\n\n.bp5-tab-list{\n  -webkit-box-align:end;\n      -ms-flex-align:end;\n          align-items:flex-end;\n  border:none;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  list-style:none;\n  margin:0;\n  padding:0;\n  position:relative; }\n  .bp5-tab-list > *:not(:last-child){\n    margin-right:20px; }\n\n.bp5-tab{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  color:#182026;\n  cursor:pointer;\n  -webkit-box-flex:0;\n      -ms-flex:0 0 auto;\n          flex:0 0 auto;\n  font-size:14px;\n  line-height:30px;\n  max-width:100%;\n  position:relative;\n  vertical-align:top; }\n  .bp5-tab a{\n    color:inherit;\n    display:block;\n    text-decoration:none; }\n  .bp5-tab-indicator-wrapper ~ .bp5-tab{\n    background-color:transparent !important;\n    -webkit-box-shadow:none !important;\n            box-shadow:none !important; }\n  .bp5-tab[aria-disabled=\"true\"]{\n    color:rgba(92, 112, 128, 0.6);\n    cursor:not-allowed; }\n  .bp5-tab[aria-selected=\"true\"]{\n    border-radius:0;\n    -webkit-box-shadow:inset 0 -3px 0 #106ba3;\n            box-shadow:inset 0 -3px 0 #106ba3; }\n  .bp5-tab[aria-selected=\"true\"], .bp5-tab:not([aria-disabled=\"true\"]):hover{\n    color:#106ba3; }\n  .bp5-tab:focus{\n    -moz-outline-radius:0; }\n  .bp5-large > .bp5-tab{\n    font-size:16px;\n    line-height:40px; }\n\n.bp5-tab-panel{\n  margin-top:20px; }\n  .bp5-tab-panel[aria-hidden=\"true\"]{\n    display:none; }\n\n.bp5-tab-indicator-wrapper{\n  left:0;\n  pointer-events:none;\n  position:absolute;\n  top:0;\n  -webkit-transform:translateX(0), translateY(0);\n          transform:translateX(0), translateY(0);\n  -webkit-transition:height, width, -webkit-transform;\n  transition:height, width, -webkit-transform;\n  transition:height, transform, width;\n  transition:height, transform, width, -webkit-transform;\n  -webkit-transition-duration:200ms;\n          transition-duration:200ms;\n  -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n          transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-tab-indicator-wrapper .bp5-tab-indicator{\n    background-color:#106ba3;\n    bottom:0;\n    height:3px;\n    left:0;\n    position:absolute;\n    right:0; }\n  .bp5-tab-indicator-wrapper.bp5-no-animation{\n    -webkit-transition:none;\n    transition:none; }\n\n.bp5-dark .bp5-tab{\n  color:#f5f8fa; }\n  .bp5-dark .bp5-tab[aria-disabled=\"true\"]{\n    color:rgba(167, 182, 194, 0.6); }\n  .bp5-dark .bp5-tab[aria-selected=\"true\"]{\n    -webkit-box-shadow:inset 0 -3px 0 #48aff0;\n            box-shadow:inset 0 -3px 0 #48aff0; }\n  .bp5-dark .bp5-tab[aria-selected=\"true\"], .bp5-dark .bp5-tab:not([aria-disabled=\"true\"]):hover{\n    color:#48aff0; }\n\n.bp5-dark .bp5-tab-indicator{\n  background-color:#48aff0; }\n\n.bp5-flex-expander{\n  -webkit-box-flex:1;\n      -ms-flex:1 1;\n          flex:1 1; }\n.bp5-tag{\n  display:-webkit-inline-box;\n  display:-ms-inline-flexbox;\n  display:inline-flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  background-color:#5c7080;\n  border:none;\n  border-radius:3px;\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  color:#f5f8fa;\n  font-size:12px;\n  line-height:16px;\n  max-width:100%;\n  min-height:20px;\n  min-width:20px;\n  padding:2px 6px;\n  position:relative; }\n  .bp5-tag.bp5-interactive{\n    cursor:pointer; }\n    .bp5-tag.bp5-interactive:hover{\n      background-color:rgba(92, 112, 128, 0.85); }\n    .bp5-tag.bp5-interactive.bp5-active, .bp5-tag.bp5-interactive:active{\n      background-color:rgba(92, 112, 128, 0.7); }\n  .bp5-tag > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-tag > .bp5-fill{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-tag::before,\n  .bp5-tag > *{\n    margin-right:4px; }\n  .bp5-tag:empty::before,\n  .bp5-tag > :last-child{\n    margin-right:0; }\n  .bp5-tag:focus{\n    outline:rgba(19, 124, 189, 0.6) auto 2px;\n    outline-offset:0;\n    -moz-outline-radius:6px; }\n  .bp5-tag.bp5-round{\n    border-radius:30px;\n    padding-left:8px;\n    padding-right:8px; }\n  .bp5-dark .bp5-tag{\n    background-color:#bfccd6;\n    color:#182026; }\n    .bp5-dark .bp5-tag.bp5-interactive{\n      cursor:pointer; }\n      .bp5-dark .bp5-tag.bp5-interactive:hover{\n        background-color:rgba(191, 204, 214, 0.85); }\n      .bp5-dark .bp5-tag.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-interactive:active{\n        background-color:rgba(191, 204, 214, 0.7); }\n    .bp5-dark .bp5-tag > .bp5-icon, .bp5-dark .bp5-tag .bp5-icon-standard, .bp5-dark .bp5-tag .bp5-icon-large{\n      fill:currentColor; }\n  .bp5-tag > .bp5-icon, .bp5-tag .bp5-icon-standard, .bp5-tag .bp5-icon-large{\n    fill:#ffffff; }\n  .bp5-tag.bp5-large,\n  .bp5-large .bp5-tag{\n    font-size:14px;\n    line-height:20px;\n    min-height:30px;\n    min-width:30px;\n    padding:5px 10px; }\n    .bp5-tag.bp5-large::before,\n    .bp5-tag.bp5-large > *,\n    .bp5-large .bp5-tag::before,\n    .bp5-large .bp5-tag > *{\n      margin-right:7px; }\n    .bp5-tag.bp5-large:empty::before,\n    .bp5-tag.bp5-large > :last-child,\n    .bp5-large .bp5-tag:empty::before,\n    .bp5-large .bp5-tag > :last-child{\n      margin-right:0; }\n    .bp5-tag.bp5-large.bp5-round,\n    .bp5-large .bp5-tag.bp5-round{\n      padding-left:12px;\n      padding-right:12px; }\n  .bp5-tag.bp5-intent-primary{\n    background:#137cbd;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-primary.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-primary.bp5-interactive:hover{\n        background-color:rgba(19, 124, 189, 0.85); }\n      .bp5-tag.bp5-intent-primary.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-primary.bp5-interactive:active{\n        background-color:rgba(19, 124, 189, 0.7); }\n  .bp5-tag.bp5-intent-success{\n    background:#0f9960;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-success.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-success.bp5-interactive:hover{\n        background-color:rgba(15, 153, 96, 0.85); }\n      .bp5-tag.bp5-intent-success.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-success.bp5-interactive:active{\n        background-color:rgba(15, 153, 96, 0.7); }\n  .bp5-tag.bp5-intent-warning{\n    background:#d9822b;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-warning.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-warning.bp5-interactive:hover{\n        background-color:rgba(217, 130, 43, 0.85); }\n      .bp5-tag.bp5-intent-warning.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-warning.bp5-interactive:active{\n        background-color:rgba(217, 130, 43, 0.7); }\n  .bp5-tag.bp5-intent-danger{\n    background:#db3737;\n    color:#ffffff; }\n    .bp5-tag.bp5-intent-danger.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-intent-danger.bp5-interactive:hover{\n        background-color:rgba(219, 55, 55, 0.85); }\n      .bp5-tag.bp5-intent-danger.bp5-interactive.bp5-active, .bp5-tag.bp5-intent-danger.bp5-interactive:active{\n        background-color:rgba(219, 55, 55, 0.7); }\n  .bp5-tag.bp5-fill{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    width:100%; }\n  .bp5-tag.bp5-minimal > .bp5-icon, .bp5-tag.bp5-minimal .bp5-icon-standard, .bp5-tag.bp5-minimal .bp5-icon-large{\n    fill:#5c7080; }\n  .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]){\n    background-color:rgba(138, 155, 168, 0.2);\n    color:#182026; }\n    .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:hover{\n        background-color:rgba(92, 112, 128, 0.3); }\n      .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive.bp5-active, .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:active{\n        background-color:rgba(92, 112, 128, 0.4); }\n    .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]){\n      color:#f5f8fa; }\n      .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:hover{\n          background-color:rgba(191, 204, 214, 0.3); }\n        .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]).bp5-interactive:active{\n          background-color:rgba(191, 204, 214, 0.4); }\n      .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]) > .bp5-icon, .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]) .bp5-icon-standard, .bp5-dark .bp5-tag.bp5-minimal:not([class*=\"bp5-intent-\"]) .bp5-icon-large{\n        fill:#a7b6c2; }\n  .bp5-tag.bp5-minimal.bp5-intent-primary{\n    background-color:rgba(19, 124, 189, 0.15);\n    color:#106ba3; }\n    .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n        background-color:rgba(19, 124, 189, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active{\n        background-color:rgba(19, 124, 189, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-primary > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-large{\n      fill:#137cbd; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary{\n      background-color:rgba(19, 124, 189, 0.25);\n      color:#48aff0; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover{\n          background-color:rgba(19, 124, 189, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active{\n          background-color:rgba(19, 124, 189, 0.45); }\n  .bp5-tag.bp5-minimal.bp5-intent-success{\n    background-color:rgba(15, 153, 96, 0.15);\n    color:#0d8050; }\n    .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n        background-color:rgba(15, 153, 96, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active{\n        background-color:rgba(15, 153, 96, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-success > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-large{\n      fill:#0f9960; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success{\n      background-color:rgba(15, 153, 96, 0.25);\n      color:#3dcc91; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover{\n          background-color:rgba(15, 153, 96, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active{\n          background-color:rgba(15, 153, 96, 0.45); }\n  .bp5-tag.bp5-minimal.bp5-intent-warning{\n    background-color:rgba(217, 130, 43, 0.15);\n    color:#bf7326; }\n    .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n        background-color:rgba(217, 130, 43, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active{\n        background-color:rgba(217, 130, 43, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-warning > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-large{\n      fill:#d9822b; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning{\n      background-color:rgba(217, 130, 43, 0.25);\n      color:#ffb366; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover{\n          background-color:rgba(217, 130, 43, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active{\n          background-color:rgba(217, 130, 43, 0.45); }\n  .bp5-tag.bp5-minimal.bp5-intent-danger{\n    background-color:rgba(219, 55, 55, 0.15);\n    color:#c23030; }\n    .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive{\n      cursor:pointer; }\n      .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n        background-color:rgba(219, 55, 55, 0.25); }\n      .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active, .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active{\n        background-color:rgba(219, 55, 55, 0.35); }\n    .bp5-tag.bp5-minimal.bp5-intent-danger > .bp5-icon, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-standard, .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-large{\n      fill:#db3737; }\n    .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger{\n      background-color:rgba(219, 55, 55, 0.25);\n      color:#ff7373; }\n      .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive{\n        cursor:pointer; }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover{\n          background-color:rgba(219, 55, 55, 0.35); }\n        .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active, .bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active{\n          background-color:rgba(219, 55, 55, 0.45); }\n\n.bp5-tag-remove{\n  background:none;\n  border:none;\n  color:inherit;\n  cursor:pointer;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  margin-bottom:-2px;\n  margin-right:-6px !important;\n  margin-top:-2px;\n  opacity:0.5;\n  padding:2px;\n  padding-left:0; }\n  .bp5-tag-remove:hover{\n    background:none;\n    opacity:0.8;\n    text-decoration:none; }\n  .bp5-tag-remove:active{\n    opacity:1; }\n  .bp5-tag-remove:empty::before{\n    font-family:\"blueprint-icons-16\", sans-serif;\n    font-size:16px;\n    font-style:normal;\n    font-weight:400;\n    line-height:1;\n    -moz-osx-font-smoothing:grayscale;\n    -webkit-font-smoothing:antialiased;\n    content:\"\\f2ba\"; }\n  .bp5-large .bp5-tag-remove{\n    margin-right:-10px !important;\n    padding:0 5px 0 0; }\n    .bp5-large .bp5-tag-remove:empty::before{\n      font-family:\"blueprint-icons-20\", sans-serif;\n      font-size:20px;\n      font-style:normal;\n      font-weight:400;\n      line-height:1; }\n.bp5-tag-input{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:row;\n          flex-direction:row;\n  -webkit-box-align:start;\n      -ms-flex-align:start;\n          align-items:flex-start;\n  cursor:text;\n  height:auto;\n  line-height:inherit;\n  min-height:30px;\n  padding-left:5px;\n  padding-right:0; }\n  .bp5-tag-input > *{\n    -webkit-box-flex:0;\n        -ms-flex-positive:0;\n            flex-grow:0;\n    -ms-flex-negative:0;\n        flex-shrink:0; }\n  .bp5-tag-input > .bp5-tag-input-values{\n    -webkit-box-flex:1;\n        -ms-flex-positive:1;\n            flex-grow:1;\n    -ms-flex-negative:1;\n        flex-shrink:1; }\n  .bp5-tag-input .bp5-tag-input-icon{\n    color:#5c7080;\n    margin-left:2px;\n    margin-right:7px;\n    margin-top:7px; }\n  .bp5-tag-input .bp5-tag-input-values{\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex;\n    -webkit-box-orient:horizontal;\n    -webkit-box-direction:normal;\n        -ms-flex-direction:row;\n            flex-direction:row;\n    -webkit-box-align:center;\n        -ms-flex-align:center;\n            align-items:center;\n    -ms-flex-item-align:stretch;\n        align-self:stretch;\n    -ms-flex-wrap:wrap;\n        flex-wrap:wrap;\n    margin-right:7px;\n    margin-top:5px;\n    min-width:0; }\n    .bp5-tag-input .bp5-tag-input-values > *{\n      -webkit-box-flex:0;\n          -ms-flex-positive:0;\n              flex-grow:0;\n      -ms-flex-negative:0;\n          flex-shrink:0; }\n    .bp5-tag-input .bp5-tag-input-values > .bp5-fill{\n      -webkit-box-flex:1;\n          -ms-flex-positive:1;\n              flex-grow:1;\n      -ms-flex-negative:1;\n          flex-shrink:1; }\n    .bp5-tag-input .bp5-tag-input-values::before,\n    .bp5-tag-input .bp5-tag-input-values > *{\n      margin-right:5px; }\n    .bp5-tag-input .bp5-tag-input-values:empty::before,\n    .bp5-tag-input .bp5-tag-input-values > :last-child{\n      margin-right:0; }\n    .bp5-tag-input .bp5-tag-input-values:first-child .bp5-input-ghost:first-child{\n      padding-left:5px; }\n    .bp5-tag-input .bp5-tag-input-values > *{\n      margin-bottom:5px; }\n  .bp5-tag-input .bp5-tag{\n    overflow-wrap:break-word; }\n    .bp5-tag-input .bp5-tag.bp5-active{\n      outline:rgba(19, 124, 189, 0.6) auto 2px;\n      outline-offset:0;\n      -moz-outline-radius:6px; }\n  .bp5-tag-input .bp5-input-ghost{\n    -webkit-box-flex:1;\n        -ms-flex:1 1 auto;\n            flex:1 1 auto;\n    line-height:20px;\n    width:80px; }\n    .bp5-tag-input .bp5-input-ghost:disabled, .bp5-tag-input .bp5-input-ghost.bp5-disabled{\n      cursor:not-allowed; }\n  .bp5-tag-input .bp5-button,\n  .bp5-tag-input .bp5-spinner{\n    margin:3px;\n    margin-left:0; }\n  .bp5-tag-input .bp5-button{\n    min-height:24px;\n    min-width:24px;\n    padding:0 7px; }\n  .bp5-tag-input.bp5-large{\n    height:auto;\n    min-height:40px; }\n    .bp5-tag-input.bp5-large::before,\n    .bp5-tag-input.bp5-large > *{\n      margin-right:10px; }\n    .bp5-tag-input.bp5-large:empty::before,\n    .bp5-tag-input.bp5-large > :last-child{\n      margin-right:0; }\n    .bp5-tag-input.bp5-large .bp5-tag-input-icon{\n      margin-left:5px;\n      margin-top:10px; }\n    .bp5-tag-input.bp5-large .bp5-input-ghost{\n      line-height:30px; }\n    .bp5-tag-input.bp5-large .bp5-button{\n      min-height:30px;\n      min-width:30px;\n      padding:5px 10px;\n      margin:5px;\n      margin-left:0; }\n    .bp5-tag-input.bp5-large .bp5-spinner{\n      margin:8px;\n      margin-left:0; }\n  .bp5-tag-input.bp5-active{\n    background-color:#ffffff;\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-primary{\n      -webkit-box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-success{\n      -webkit-box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-warning{\n      -webkit-box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n    .bp5-tag-input.bp5-active.bp5-intent-danger{\n      -webkit-box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n              box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n  .bp5-dark .bp5-tag-input .bp5-tag-input-icon, .bp5-tag-input.bp5-dark .bp5-tag-input-icon{\n    color:#a7b6c2; }\n  .bp5-dark .bp5-tag-input .bp5-input-ghost, .bp5-tag-input.bp5-dark .bp5-input-ghost{\n    color:#f5f8fa; }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::-webkit-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-webkit-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::-moz-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-moz-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost:-ms-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost:-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::-ms-input-placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::-ms-input-placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n    .bp5-dark .bp5-tag-input .bp5-input-ghost::placeholder, .bp5-tag-input.bp5-dark .bp5-input-ghost::placeholder{\n      color:rgba(167, 182, 194, 0.6); }\n  .bp5-dark .bp5-tag-input.bp5-active, .bp5-tag-input.bp5-dark.bp5-active{\n    background-color:rgba(16, 22, 26, 0.3);\n    -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-primary, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-primary{\n      -webkit-box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #106ba3, 0 0 0 3px rgba(16, 107, 163, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-success, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-success{\n      -webkit-box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #0d8050, 0 0 0 3px rgba(13, 128, 80, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-warning, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-warning{\n      -webkit-box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #bf7326, 0 0 0 3px rgba(191, 115, 38, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n    .bp5-dark .bp5-tag-input.bp5-active.bp5-intent-danger, .bp5-tag-input.bp5-dark.bp5-active.bp5-intent-danger{\n      -webkit-box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n              box-shadow:0 0 0 1px #c23030, 0 0 0 3px rgba(194, 48, 48, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-input-ghost{\n  background:none;\n  border:none;\n  -webkit-box-shadow:none;\n          box-shadow:none;\n  padding:0; }\n  .bp5-input-ghost::-webkit-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost::-moz-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost:-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost::-ms-input-placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost::placeholder{\n    color:rgba(92, 112, 128, 0.6);\n    opacity:1; }\n  .bp5-input-ghost:focus{\n    outline:none !important; }\n.bp5-toast{\n  -webkit-box-align:start;\n      -ms-flex-align:start;\n          align-items:flex-start;\n  background-color:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  margin:20px 0 0;\n  max-width:500px;\n  min-width:300px;\n  pointer-events:all;\n  position:relative !important; }\n  .bp5-toast.bp5-toast-enter, .bp5-toast.bp5-toast-appear{\n    -webkit-transform:translateY(-40px);\n            transform:translateY(-40px); }\n  .bp5-toast.bp5-toast-enter-active, .bp5-toast.bp5-toast-appear-active{\n    -webkit-transform:translateY(0);\n            transform:translateY(0);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-toast.bp5-toast-enter ~ .bp5-toast, .bp5-toast.bp5-toast-appear ~ .bp5-toast{\n    -webkit-transform:translateY(-40px);\n            transform:translateY(-40px); }\n  .bp5-toast.bp5-toast-enter-active ~ .bp5-toast, .bp5-toast.bp5-toast-appear-active ~ .bp5-toast{\n    -webkit-transform:translateY(0);\n            transform:translateY(0);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11);\n            transition-timing-function:cubic-bezier(0.54, 1.12, 0.38, 1.11); }\n  .bp5-toast.bp5-toast-exit{\n    opacity:1;\n    -webkit-filter:blur(0);\n            filter:blur(0); }\n  .bp5-toast.bp5-toast-exit-active{\n    opacity:0;\n    -webkit-filter:blur(10px);\n            filter:blur(10px);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:300ms;\n            transition-duration:300ms;\n    -webkit-transition-property:opacity, -webkit-filter;\n    transition-property:opacity, -webkit-filter;\n    transition-property:opacity, filter;\n    transition-property:opacity, filter, -webkit-filter;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-toast.bp5-toast-exit ~ .bp5-toast{\n    -webkit-transform:translateY(0);\n            transform:translateY(0); }\n  .bp5-toast.bp5-toast-exit-active ~ .bp5-toast{\n    -webkit-transform:translateY(-40px);\n            transform:translateY(-40px);\n    -webkit-transition-delay:50ms;\n            transition-delay:50ms;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-toast .bp5-button-group{\n    -webkit-box-flex:0;\n        -ms-flex:0 0 auto;\n            flex:0 0 auto;\n    padding:5px;\n    padding-left:0; }\n  .bp5-toast > .bp5-icon{\n    color:#5c7080;\n    margin:12px;\n    margin-right:0; }\n  .bp5-toast.bp5-dark,\n  .bp5-dark .bp5-toast{\n    background-color:#394b59;\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n    .bp5-toast.bp5-dark > .bp5-icon,\n    .bp5-dark .bp5-toast > .bp5-icon{\n      color:#a7b6c2; }\n  .bp5-toast[class*=\"bp5-intent-\"] a{\n    color:rgba(255, 255, 255, 0.7); }\n    .bp5-toast[class*=\"bp5-intent-\"] a:hover{\n      color:#ffffff; }\n  .bp5-toast[class*=\"bp5-intent-\"] > .bp5-icon{\n    color:#ffffff; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button, .bp5-toast[class*=\"bp5-intent-\"] .bp5-button::before,\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button .bp5-icon, .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:active{\n    color:rgba(255, 255, 255, 0.7) !important; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:focus{\n    outline-color:rgba(255, 255, 255, 0.5); }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:hover{\n    background-color:rgba(255, 255, 255, 0.15) !important;\n    color:#ffffff !important; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button:active{\n    background-color:rgba(255, 255, 255, 0.3) !important;\n    color:#ffffff !important; }\n  .bp5-toast[class*=\"bp5-intent-\"] .bp5-button::after{\n    background:rgba(255, 255, 255, 0.3) !important; }\n  .bp5-toast.bp5-intent-primary{\n    background-color:#137cbd;\n    color:#ffffff; }\n  .bp5-toast.bp5-intent-success{\n    background-color:#0f9960;\n    color:#ffffff; }\n  .bp5-toast.bp5-intent-warning{\n    background-color:#d9822b;\n    color:#ffffff; }\n  .bp5-toast.bp5-intent-danger{\n    background-color:#db3737;\n    color:#ffffff; }\n\n.bp5-toast-message{\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  padding:11px;\n  word-break:break-word; }\n\n.bp5-toast-container{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box !important;\n  display:-ms-flexbox !important;\n  display:flex !important;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n      -ms-flex-direction:column;\n          flex-direction:column;\n  left:0;\n  overflow:hidden;\n  padding:0 20px 20px;\n  pointer-events:none;\n  right:0;\n  z-index:40; }\n  .bp5-toast-container.bp5-toast-container-in-portal{\n    position:fixed; }\n  .bp5-toast-container.bp5-toast-container-inline{\n    position:absolute; }\n  .bp5-toast-container.bp5-toast-container-top{\n    top:0; }\n  .bp5-toast-container.bp5-toast-container-bottom{\n    bottom:0;\n    -webkit-box-orient:vertical;\n    -webkit-box-direction:reverse;\n        -ms-flex-direction:column-reverse;\n            flex-direction:column-reverse;\n    top:auto; }\n  .bp5-toast-container.bp5-toast-container-left{\n    -webkit-box-align:start;\n        -ms-flex-align:start;\n            align-items:flex-start; }\n  .bp5-toast-container.bp5-toast-container-right{\n    -webkit-box-align:end;\n        -ms-flex-align:end;\n            align-items:flex-end; }\n\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active),\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active) ~ .bp5-toast, .bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active),\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active) ~ .bp5-toast,\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-exit-active ~ .bp5-toast,\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-leave-active ~ .bp5-toast{\n  -webkit-transform:translateY(60px);\n          transform:translateY(60px); }\n.bp5-tooltip{\n  -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n          box-shadow:0 0 0 1px rgba(16, 22, 26, 0.1), 0 2px 4px rgba(16, 22, 26, 0.2), 0 8px 24px rgba(16, 22, 26, 0.2);\n  -webkit-transform:scale(1);\n          transform:scale(1); }\n  .bp5-tooltip .bp5-popover-arrow{\n    height:22px;\n    position:absolute;\n    width:22px; }\n    .bp5-tooltip .bp5-popover-arrow::before{\n      height:14px;\n      margin:4px;\n      width:14px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-tooltip{\n    margin-bottom:11px;\n    margin-top:-11px; }\n    .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-tooltip > .bp5-popover-arrow{\n      bottom:-8px; }\n      .bp5-tether-element-attached-bottom.bp5-tether-target-attached-top > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(-90deg);\n                transform:rotate(-90deg); }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-tooltip{\n    margin-left:11px; }\n    .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-tooltip > .bp5-popover-arrow{\n      left:-8px; }\n      .bp5-tether-element-attached-left.bp5-tether-target-attached-right > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(0);\n                transform:rotate(0); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-tooltip{\n    margin-top:11px; }\n    .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-tooltip > .bp5-popover-arrow{\n      top:-8px; }\n      .bp5-tether-element-attached-top.bp5-tether-target-attached-bottom > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(90deg);\n                transform:rotate(90deg); }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-tooltip{\n    margin-left:-11px;\n    margin-right:11px; }\n    .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-tooltip > .bp5-popover-arrow{\n      right:-8px; }\n      .bp5-tether-element-attached-right.bp5-tether-target-attached-left > .bp5-tooltip > .bp5-popover-arrow svg{\n        -webkit-transform:rotate(180deg);\n                transform:rotate(180deg); }\n  .bp5-tether-element-attached-middle > .bp5-tooltip > .bp5-popover-arrow{\n    top:50%;\n    -webkit-transform:translateY(-50%);\n            transform:translateY(-50%); }\n  .bp5-tether-element-attached-center > .bp5-tooltip > .bp5-popover-arrow{\n    right:50%;\n    -webkit-transform:translateX(50%);\n            transform:translateX(50%); }\n  .bp5-tether-element-attached-top.bp5-tether-target-attached-top > .bp5-tooltip > .bp5-popover-arrow{\n    top:-0.22183px; }\n  .bp5-tether-element-attached-right.bp5-tether-target-attached-right > .bp5-tooltip > .bp5-popover-arrow{\n    right:-0.22183px; }\n  .bp5-tether-element-attached-left.bp5-tether-target-attached-left > .bp5-tooltip > .bp5-popover-arrow{\n    left:-0.22183px; }\n  .bp5-tether-element-attached-bottom.bp5-tether-target-attached-bottom > .bp5-tooltip > .bp5-popover-arrow{\n    bottom:-0.22183px; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-left > .bp5-tooltip{\n    -webkit-transform-origin:top left;\n            transform-origin:top left; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-center > .bp5-tooltip{\n    -webkit-transform-origin:top center;\n            transform-origin:top center; }\n  .bp5-tether-element-attached-top.bp5-tether-element-attached-right > .bp5-tooltip{\n    -webkit-transform-origin:top right;\n            transform-origin:top right; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-left > .bp5-tooltip{\n    -webkit-transform-origin:center left;\n            transform-origin:center left; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-center > .bp5-tooltip{\n    -webkit-transform-origin:center center;\n            transform-origin:center center; }\n  .bp5-tether-element-attached-middle.bp5-tether-element-attached-right > .bp5-tooltip{\n    -webkit-transform-origin:center right;\n            transform-origin:center right; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-left > .bp5-tooltip{\n    -webkit-transform-origin:bottom left;\n            transform-origin:bottom left; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-center > .bp5-tooltip{\n    -webkit-transform-origin:bottom center;\n            transform-origin:bottom center; }\n  .bp5-tether-element-attached-bottom.bp5-tether-element-attached-right > .bp5-tooltip{\n    -webkit-transform-origin:bottom right;\n            transform-origin:bottom right; }\n  .bp5-tooltip .bp5-popover-content{\n    background:#394b59; }\n  .bp5-tooltip .bp5-popover-content,\n  .bp5-tooltip .bp5-heading{\n    color:#f5f8fa; }\n  .bp5-tooltip .bp5-popover-arrow::before{\n    -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2);\n            box-shadow:1px 1px 6px rgba(16, 22, 26, 0.2); }\n  .bp5-tooltip .bp5-popover-arrow-border{\n    fill:#10161a;\n    fill-opacity:0.1; }\n  .bp5-tooltip .bp5-popover-arrow-fill{\n    fill:#394b59; }\n  .bp5-popover-enter > .bp5-tooltip, .bp5-popover-appear > .bp5-tooltip{\n    -webkit-transform:scale(0.8);\n            transform:scale(0.8); }\n  .bp5-popover-enter-active > .bp5-tooltip, .bp5-popover-appear-active > .bp5-tooltip{\n    -webkit-transform:scale(1);\n            transform:scale(1);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-popover-exit > .bp5-tooltip{\n    -webkit-transform:scale(1);\n            transform:scale(1); }\n  .bp5-popover-exit-active > .bp5-tooltip{\n    -webkit-transform:scale(0.8);\n            transform:scale(0.8);\n    -webkit-transition-delay:0;\n            transition-delay:0;\n    -webkit-transition-duration:100ms;\n            transition-duration:100ms;\n    -webkit-transition-property:-webkit-transform;\n    transition-property:-webkit-transform;\n    transition-property:transform;\n    transition-property:transform, -webkit-transform;\n    -webkit-transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9);\n            transition-timing-function:cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-tooltip .bp5-popover-content{\n    padding:10px 12px; }\n  .bp5-tooltip.bp5-dark,\n  .bp5-dark .bp5-tooltip{\n    -webkit-box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4);\n            box-shadow:0 0 0 1px rgba(16, 22, 26, 0.2), 0 2px 4px rgba(16, 22, 26, 0.4), 0 8px 24px rgba(16, 22, 26, 0.4); }\n    .bp5-tooltip.bp5-dark .bp5-popover-content,\n    .bp5-dark .bp5-tooltip .bp5-popover-content{\n      background:#e1e8ed; }\n    .bp5-tooltip.bp5-dark .bp5-popover-content,\n    .bp5-tooltip.bp5-dark .bp5-heading,\n    .bp5-dark .bp5-tooltip .bp5-popover-content,\n    .bp5-dark .bp5-tooltip .bp5-heading{\n      color:#394b59; }\n    .bp5-tooltip.bp5-dark .bp5-popover-arrow::before,\n    .bp5-dark .bp5-tooltip .bp5-popover-arrow::before{\n      -webkit-box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4);\n              box-shadow:1px 1px 6px rgba(16, 22, 26, 0.4); }\n    .bp5-tooltip.bp5-dark .bp5-popover-arrow-border,\n    .bp5-dark .bp5-tooltip .bp5-popover-arrow-border{\n      fill:#10161a;\n      fill-opacity:0.2; }\n    .bp5-tooltip.bp5-dark .bp5-popover-arrow-fill,\n    .bp5-dark .bp5-tooltip .bp5-popover-arrow-fill{\n      fill:#e1e8ed; }\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-content{\n    background:#137cbd;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill{\n    fill:#137cbd; }\n  .bp5-tooltip.bp5-intent-success .bp5-popover-content{\n    background:#0f9960;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill{\n    fill:#0f9960; }\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-content{\n    background:#d9822b;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill{\n    fill:#d9822b; }\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-content{\n    background:#db3737;\n    color:#ffffff; }\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill{\n    fill:#db3737; }\n\n.bp5-tooltip-indicator{\n  border-bottom:dotted 1px;\n  cursor:help; }\n.bp5-tree .bp5-icon, .bp5-tree .bp5-icon-standard, .bp5-tree .bp5-icon-large{\n  color:#5c7080; }\n  .bp5-tree .bp5-icon.bp5-intent-primary, .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-tree .bp5-icon-large.bp5-intent-primary{\n    color:#137cbd; }\n  .bp5-tree .bp5-icon.bp5-intent-success, .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-tree .bp5-icon-large.bp5-intent-success{\n    color:#0f9960; }\n  .bp5-tree .bp5-icon.bp5-intent-warning, .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-tree .bp5-icon-large.bp5-intent-warning{\n    color:#d9822b; }\n  .bp5-tree .bp5-icon.bp5-intent-danger, .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-tree .bp5-icon-large.bp5-intent-danger{\n    color:#db3737; }\n\n.bp5-tree-node-list{\n  list-style:none;\n  margin:0;\n  padding-left:0; }\n\n.bp5-tree-root{\n  background-color:transparent;\n  cursor:default;\n  padding-left:0;\n  position:relative; }\n\n.bp5-tree-node-content-0{\n  padding-left:0px; }\n\n.bp5-tree-node-content-1{\n  padding-left:23px; }\n\n.bp5-tree-node-content-2{\n  padding-left:46px; }\n\n.bp5-tree-node-content-3{\n  padding-left:69px; }\n\n.bp5-tree-node-content-4{\n  padding-left:92px; }\n\n.bp5-tree-node-content-5{\n  padding-left:115px; }\n\n.bp5-tree-node-content-6{\n  padding-left:138px; }\n\n.bp5-tree-node-content-7{\n  padding-left:161px; }\n\n.bp5-tree-node-content-8{\n  padding-left:184px; }\n\n.bp5-tree-node-content-9{\n  padding-left:207px; }\n\n.bp5-tree-node-content-10{\n  padding-left:230px; }\n\n.bp5-tree-node-content-11{\n  padding-left:253px; }\n\n.bp5-tree-node-content-12{\n  padding-left:276px; }\n\n.bp5-tree-node-content-13{\n  padding-left:299px; }\n\n.bp5-tree-node-content-14{\n  padding-left:322px; }\n\n.bp5-tree-node-content-15{\n  padding-left:345px; }\n\n.bp5-tree-node-content-16{\n  padding-left:368px; }\n\n.bp5-tree-node-content-17{\n  padding-left:391px; }\n\n.bp5-tree-node-content-18{\n  padding-left:414px; }\n\n.bp5-tree-node-content-19{\n  padding-left:437px; }\n\n.bp5-tree-node-content-20{\n  padding-left:460px; }\n\n.bp5-tree-node-content{\n  -webkit-box-align:center;\n      -ms-flex-align:center;\n          align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:30px;\n  padding-right:5px;\n  width:100%; }\n  .bp5-tree-node-content:hover{\n    background-color:rgba(191, 204, 214, 0.4); }\n\n.bp5-tree-node-caret,\n.bp5-tree-node-caret-none{\n  min-width:30px; }\n\n.bp5-tree-node-caret{\n  color:#5c7080;\n  cursor:pointer;\n  padding:7px;\n  -webkit-transform:rotate(0deg);\n          transform:rotate(0deg);\n  -webkit-transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9); }\n  .bp5-tree-node-caret:hover{\n    color:#182026; }\n  .bp5-dark .bp5-tree-node-caret{\n    color:#a7b6c2; }\n    .bp5-dark .bp5-tree-node-caret:hover{\n      color:#f5f8fa; }\n  .bp5-tree-node-caret.bp5-tree-node-caret-open{\n    -webkit-transform:rotate(90deg);\n            transform:rotate(90deg); }\n  .bp5-tree-node-caret.bp5-icon-standard::before{\n    content:\"\\f14d\"; }\n\n.bp5-tree-node-icon{\n  margin-right:7px;\n  position:relative; }\n\n.bp5-tree-node-label{\n  overflow:hidden;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n  word-wrap:normal;\n  -webkit-box-flex:1;\n      -ms-flex:1 1 auto;\n          flex:1 1 auto;\n  position:relative;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-tree-node-label span{\n    display:inline; }\n\n.bp5-tree-node-secondary-label{\n  padding:0 5px;\n  -webkit-user-select:none;\n     -moz-user-select:none;\n      -ms-user-select:none;\n          user-select:none; }\n  .bp5-tree-node-secondary-label .bp5-popover-wrapper,\n  .bp5-tree-node-secondary-label .bp5-popover-target{\n    -webkit-box-align:center;\n        -ms-flex-align:center;\n            align-items:center;\n    display:-webkit-box;\n    display:-ms-flexbox;\n    display:flex; }\n\n.bp5-tree-node.bp5-disabled .bp5-tree-node-content{\n  background-color:inherit;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-tree-node.bp5-disabled .bp5-tree-node-caret,\n.bp5-tree-node.bp5-disabled .bp5-tree-node-icon{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#137cbd; }\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content,\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-standard, .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-large{\n    color:#ffffff; }\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret::before{\n    color:rgba(255, 255, 255, 0.7); }\n  .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-tree-node-caret:hover::before{\n    color:#ffffff; }\n\n.bp5-dark .bp5-tree-node-content:hover{\n  background-color:rgba(92, 112, 128, 0.3); }\n\n.bp5-dark .bp5-tree .bp5-icon, .bp5-dark .bp5-tree .bp5-icon-standard, .bp5-dark .bp5-tree .bp5-icon-large{\n  color:#a7b6c2; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-primary, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-primary{\n    color:#137cbd; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-success, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-success{\n    color:#0f9960; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-warning, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-warning{\n    color:#d9822b; }\n  .bp5-dark .bp5-tree .bp5-icon.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-danger, .bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-danger{\n    color:#db3737; }\n\n.bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content{\n  background-color:#137cbd; }\n/*# sourceMappingURL=blueprint.css.map */"
  },
  {
    "path": "www/styles/global.css",
    "content": "@font-face {\n  font-family: \"Roboto Mono\";\n  src: url(\"RobotoMono-Regular.ttf\") format(\"truetype\");\n  font-weight: 400;\n}\n\n@font-face {\n  font-family: \"Roboto Mono\";\n  src: url(\"RobotoMono-Medium.ttf\") format(\"truetype\");\n  font-weight: 500;\n}\n\n*, *:before, *:after {\n  -webkit-box-sizing: border-box;\n  -webkit-appearance: none;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n}\n\nhtml {\n  scrollbar-color: #5f6b7c #ffffff;\n}\nhtml.dark3-scroll {\n  scrollbar-color: #555b63 #30404d;\n}\nhtml.dark5-scroll {\n  scrollbar-color: #555b63 #2f343c;\n}\n\nbody {\n  font-family: Roboto, Noto, sans-serif;\n  min-width: 1100px;\n}\n\nbody {\n  background-color: #f6f7f9;\n}\nbody.bp5-dark {\n  background-color: #252a31;\n}\n\ncode {\n  font-family: Consolas, Menlo, 'Roboto Mono', 'DejaVu Sans Mono';\n  font-weight: 400;\n  white-space: pre-wrap;\n  word-wrap: break-word;\n  overflow-wrap: break-word;\n}\n\nh2 {\n  font-size: 28px;\n}\n\nh5 {\n  font-size: 16px;\n}\n\n.no-select {\n  user-select: none;\n}\n\n.logo-dark {\n  display: none;\n}\n\n.hljs {\n  border-radius: 3px;\n}\n\ncode.intent-primary {\n  background-color: #ecf8ff;\n}\n.bp5-dark code.intent-primary {\n  background-color: #183345;\n}\n\ncode.intent-secondary {\n  background-color: #fff1e7;\n}\n.bp5-dark code.intent-secondary {\n  background-color: #3a322c\n}\n\n.bp5-dark .logo-light {\n  display: none;\n}\n\n.bp5-dark .logo-dark {\n  display: block;\n}\n\n.bp5-button:disabled, .bp5-button.bp5-disabled {\n  cursor: default !important;\n}\n\n.bp5-input:disabled, .bp5-input.bp5-disabled {\n  cursor: default !important;\n}\n\n.bp5-select select:disabled, .bp5-select select.bp5-disabled {\n  cursor: default !important;\n}\n\n.bp5-text-intent-primary {\n  color: #106ba3;\n}\n.bp5-dark .bp5-text-intent-primary {\n  color: #2b95d6;\n}\n\n.bp5-text-intent-success {\n  color: #0d8050;\n}\n.bp5-dark .bp5-text-intent-success {\n  color: #15b371;\n}\n\n.bp5-text-intent-warning {\n  color: #bf7326;\n}\n.bp5-dark .bp5-text-intent-warning {\n  color: #f29d49;\n}\n\n.bp5-text-intent-danger {\n  color: #c23030;\n}\n.bp5-dark .bp5-text-intent-danger {\n  color: #f55656;\n}\n\n.bp5-border {\n  border-width: 1px;\n  border-color: rgba(16, 22, 26, 0.15);\n}\n\n.bp5-dark .bp5-border {\n  border-color: rgba(255, 255, 255, 0.15);\n}\n\n.bp5-button.bp5-button-empty[class*=\"bp5-icon-\"] {\n  padding: 0;\n}\n\n.bp5-button.bp5-button-empty[class*=\"bp5-icon-\"]::before {\n  margin-right: 0;\n}\n\n.bp5-row .bp5-cell span {\n  vertical-align: middle;\n}\n\n.bp5-row .bp5-cell {\n  border-top: 1px solid rgba(16, 22, 26, 0.15);\n  border-bottom: 1px solid rgba(16, 22, 26, 0.15);\n}\n\n.bp5-row .bp5-cell:first-child {\n  border-top-left-radius: 3px;\n  border-bottom-left-radius: 3px;\n  border-left: 1px solid rgba(16, 22, 26, 0.15);\n}\n\n.bp5-row .bp5-cell:last-child {\n  border-top-right-radius: 3px;\n  border-bottom-right-radius: 3px;\n  border-right: 1px solid rgba(16, 22, 26, 0.15);\n}\n\n.bp5-dark .bp5-row .bp5-cell {\n  border-top: 1px solid rgba(16, 22, 26, 0.5);\n  border-bottom: 1px solid rgba(16, 22, 26, 0.5);\n}\n\n.bp5-dark .bp5-row .bp5-cell:first-child {\n  border-left: 1px solid rgba(16, 22, 26, 0.5);\n}\n\n.bp5-dark .bp5-row .bp5-cell:last-child {\n  border-right: 1px solid rgba(16, 22, 26, 0.5);\n}\n\n.bp5-progress-bar .bp5-loading-bar {\n  animation: bp5-loading-bar-frames 2s linear infinite;\n}\n\nlabel.bp5-label .bp5-button-group {\n  margin-top: 5px !important;\n}\n\n.bp5-select-popover .bp5-menu {\n  max-height: 400px;\n  overflow-y: auto;\n}\n\n@keyframes bp5-loading-bar-frames {\n  from {\n    left: -200px;\n    width: 30%;\n  }\n  50% {\n    width: 30%;\n  }\n  70% {\n    width: 70%;\n  }\n  80% {\n    left: 50%;\n  }\n  95% {\n    left: 120%;\n  }\n  to {\n    left: 100%;\n  }\n}\n\n.bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.06)\n}\n\n.bp5-dark .bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.13)\n}\n\n.bp5-toast-container.bp5-toast-container-bottom {\n  position: fixed !important;\n}\n.bp5-toast-container.bp5-toast-container-inline {\n  position: fixed !important;\n}\n\n.bp3-theme.bp5-dark .bp5-datepicker {\n  background: #30404d !important;\n}\n\n#chartjs-tooltip {\n   background-color: #fcfcfc;\n }\n\n.bp5-dark #chartjs-tooltip {\n  background-color: #232f38;\n}\n\n#chartjs-tooltip {\n  padding: 0 3px 3px 3px;\n}\n\n#chartjs-tooltip th {\n  font-size: 12px;\n}\n\n#chartjs-tooltip td {\n  font-size: 10px;\n  padding: 4px 4px 2px 6px;\n  position: relative;\n}\n\n#chartjs-tooltip td.line-box {\n  padding: 4px 4px 2px 17px;\n}\n\n#chartjs-tooltip td span {\n  width: 10px;\n  height: 10px;\n  border-width: 2px;\n  border-style: solid;\n  top: 4px;\n  left: 3px;\n  position: absolute;\n}\n\n/*******************************\n          Flex Layout\n*******************************/\n.layout.horizontal,\n.layout.horizontal-reverse,\n.layout.vertical,\n.layout.vertical-reverse {\n  display: -ms-flexbox;\n  display: -webkit-flex;\n  display: flex;\n}\n.layout.inline {\n  display: -ms-inline-flexbox;\n  display: -webkit-inline-flex;\n  display: inline-flex;\n}\n.layout.horizontal {\n  -ms-flex-direction: row;\n  -webkit-flex-direction: row;\n  flex-direction: row;\n}\n.layout.horizontal-reverse {\n  -ms-flex-direction: row-reverse;\n  -webkit-flex-direction: row-reverse;\n  flex-direction: row-reverse;\n}\n.layout.vertical {\n  -ms-flex-direction: column;\n  -webkit-flex-direction: column;\n  flex-direction: column;\n}\n.layout.vertical-reverse {\n  -ms-flex-direction: column-reverse;\n  -webkit-flex-direction: column-reverse;\n  flex-direction: column-reverse;\n}\n.layout.wrap {\n  -ms-flex-wrap: wrap;\n  -webkit-flex-wrap: wrap;\n  flex-wrap: wrap;\n}\n.layout.no-wrap {\n  -ms-flex-wrap: nowrap;\n  -webkit-flex-wrap: nowrap;\n  flex-wrap: nowrap;\n}\n.layout.wrap-reverse {\n  -ms-flex-wrap: wrap-reverse;\n  -webkit-flex-wrap: wrap-reverse;\n  flex-wrap: wrap-reverse;\n}\n.flex-auto {\n  -ms-flex: 1 1 auto;\n  -webkit-flex: 1 1 auto;\n  flex: 1 1 auto;\n}\n.flex-none {\n  -ms-flex: none;\n  -webkit-flex: none;\n  flex: none;\n}\n.flex,\n.flex-1 {\n  -ms-flex: 1;\n  -webkit-flex: 1;\n  flex: 1;\n}\n.flex-2 {\n  -ms-flex: 2;\n  -webkit-flex: 2;\n  flex: 2;\n}\n.flex-3 {\n  -ms-flex: 3;\n  -webkit-flex: 3;\n  flex: 3;\n}\n.flex-4 {\n  -ms-flex: 4;\n  -webkit-flex: 4;\n  flex: 4;\n}\n.flex-5 {\n  -ms-flex: 5;\n  -webkit-flex: 5;\n  flex: 5;\n}\n.flex-6 {\n  -ms-flex: 6;\n  -webkit-flex: 6;\n  flex: 6;\n}\n.flex-7 {\n  -ms-flex: 7;\n  -webkit-flex: 7;\n  flex: 7;\n}\n.flex-8 {\n  -ms-flex: 8;\n  -webkit-flex: 8;\n  flex: 8;\n}\n.flex-9 {\n  -ms-flex: 9;\n  -webkit-flex: 9;\n  flex: 9;\n}\n.flex-10 {\n  -ms-flex: 10;\n  -webkit-flex: 10;\n  flex: 10;\n}\n.flex-11 {\n  -ms-flex: 11;\n  -webkit-flex: 11;\n  flex: 11;\n}\n.flex-12 {\n  -ms-flex: 12;\n  -webkit-flex: 12;\n  flex: 12;\n}\n.layout.start {\n  -ms-flex-align: start;\n  -webkit-align-items: flex-start;\n  align-items: flex-start;\n}\n.layout.center,\n.layout.center-center {\n  -ms-flex-align: center;\n  -webkit-align-items: center;\n  align-items: center;\n}\n.layout.end {\n  -ms-flex-align: end;\n  -webkit-align-items: flex-end;\n  align-items: flex-end;\n}\n.layout.start-justified {\n  -ms-flex-pack: start;\n  -webkit-justify-content: flex-start;\n  justify-content: flex-start;\n}\n.layout.center-justified,\n.layout.center-center {\n  -ms-flex-pack: center;\n  -webkit-justify-content: center;\n  justify-content: center;\n}\n.layout.end-justified {\n  -ms-flex-pack: end;\n  -webkit-justify-content: flex-end;\n  justify-content: flex-end;\n}\n.layout.around-justified {\n  -ms-flex-pack: around;\n  -webkit-justify-content: space-around;\n  justify-content: space-around;\n}\n.layout.justified {\n  -ms-flex-pack: justify;\n  -webkit-justify-content: space-between;\n  justify-content: space-between;\n}\n.self-start {\n  -ms-align-self: flex-start;\n  -webkit-align-self: flex-start;\n  align-self: flex-start;\n}\n.self-center {\n  -ms-align-self: center;\n  -webkit-align-self: center;\n  align-self: center;\n}\n.self-end {\n  -ms-align-self: flex-end;\n  -webkit-align-self: flex-end;\n  align-self: flex-end;\n}\n.self-stretch {\n  -ms-align-self: stretch;\n  -webkit-align-self: stretch;\n  align-self: stretch;\n}\n.block {\n  display: block;\n}\n[hidden] {\n  display: none !important;\n}\n.invisible {\n  visibility: hidden !important;\n}\n.relative {\n  position: relative;\n}\n.fit {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n}\nbody.fullbleed {\n  margin: 0;\n  height: 100vh;\n}\n.scroll {\n  -webkit-overflow-scrolling: touch;\n  overflow: auto;\n}\n.fixed-bottom,\n.fixed-left,\n.fixed-right,\n.fixed-top {\n  position: fixed;\n}\n.fixed-top {\n  top: 0;\n  left: 0;\n  right: 0;\n}\n.fixed-right {\n  top: 0;\n  right: 0;\n  bottom: 0;\n}\n.fixed-bottom {\n  right: 0;\n  bottom: 0;\n  left: 0;\n}\n.fixed-left {\n  top: 0;\n  bottom: 0;\n  left: 0;\n}\n\n/* FIXES */\n\nlabel.bp5-label .bp5-button-group {\n  margin-top: 5px;\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-card {\n  background-color:#2f343c;\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-navbar {\n  background-color: rgb(47, 52, 60);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *) {\n  background-color: rgb(56, 62, 71);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *):hover {\n  background-color: rgb(47, 52, 60);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button.bp5-minimal:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *) {\n  background: none;\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-button.bp5-minimal:not([class*=\"bp5-intent-\"]):not(.bp5-datepicker *):hover {\n  background: rgba(143, 153, 168, 0.15);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-select select:not(.bp5-datepicker *) {\n  background-color: rgb(56, 62, 71);\n}\n\n.bp5-dark:not(.bp3-theme) .bp5-select select:not(.bp5-datepicker *):hover {\n  background-color: rgb(47, 52, 60);\n}\n\n.bp5-card .bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.04);\n}\n\n.bp5-dark .bp5-card .bp5-card-header {\n  background-color: rgba(0, 0, 0, 0.13);\n}\n\n.monaco-editor .codicon.codicon-diff-insert {\n  width: 5px !important;\n  background-color: #2ea043;\n  left: unset !important;\n  right: 0 !important;\n}\n.monaco-editor .codicon.codicon-diff-insert:before {\n  content: \"\" !important;\n}\n.monaco-editor .codicon.codicon-diff-remove {\n  width: 5px !important;\n  background-color: #f85149;\n  left: unset !important;\n  right: 0 !important;\n}\n.monaco-editor .codicon.codicon-diff-remove:before {\n  content: \"\" !important;\n}\n\n/***********************************************************************\nhighlight.js\n***********************************************************************/\npre code.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 1em\n}\ncode.hljs {\n  padding: 3px 5px\n}\n/*\n\nAtom One Light by Daniel Gamage\nOriginal One Light Syntax theme from https://github.com/atom/one-light-syntax\n\nbase:    #fafafa\nmono-1:  #383a42\nmono-2:  #686b77\nmono-3:  #a0a1a7\nhue-1:   #0184bb\nhue-2:   #4078f2\nhue-3:   #a626a4\nhue-4:   #50a14f\nhue-5:   #e45649\nhue-5-2: #c91243\nhue-6:   #986801\nhue-6-2: #c18401\n\n*/\n.hljs {\n  color: #383a42;\n  background: #fafafa\n}\n.hljs-comment,\n.hljs-quote {\n  color: #a0a1a7;\n  font-style: italic\n}\n.hljs-doctag,\n.hljs-keyword,\n.hljs-formula {\n  color: #a626a4\n}\n.hljs-section,\n.hljs-name,\n.hljs-selector-tag,\n.hljs-deletion,\n.hljs-subst {\n  color: #e45649\n}\n.hljs-literal {\n  color: #0184bb\n}\n.hljs-string,\n.hljs-regexp,\n.hljs-addition,\n.hljs-attribute,\n.hljs-meta .hljs-string {\n  color: #50a14f\n}\n.hljs-attr,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-type,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-number {\n  color: #986801\n}\n.hljs-symbol,\n.hljs-bullet,\n.hljs-link,\n.hljs-meta,\n.hljs-selector-id,\n.hljs-title {\n  color: #4078f2\n}\n.hljs-built_in,\n.hljs-title.class_,\n.hljs-class .hljs-title {\n  color: #c18401\n}\n.hljs-emphasis {\n  font-style: italic\n}\n.hljs-strong {\n  font-weight: bold\n}\n.hljs-link {\n  text-decoration: underline\n}\n/*\n\nAtom One Dark by Daniel Gamage\nOriginal One Dark Syntax theme from https://github.com/atom/one-dark-syntax\n\nbase:    #282c34\nmono-1:  #abb2bf\nmono-2:  #818896\nmono-3:  #5c6370\nhue-1:   #56b6c2\nhue-2:   #61aeee\nhue-3:   #c678dd\nhue-4:   #98c379\nhue-5:   #e06c75\nhue-5-2: #be5046\nhue-6:   #d19a66\nhue-6-2: #e6c07b\n\n*/\n.bp5-dark .hljs {\n  color: #abb2bf;\n  background: #282c34\n}\n.bp5-dark .hljs-comment,\n.bp5-dark .hljs-quote {\n  color: #5c6370;\n  font-style: italic\n}\n.bp5-dark .hljs-doctag,\n.bp5-dark .hljs-keyword,\n.bp5-dark .hljs-formula {\n  color: #c678dd\n}\n.bp5-dark .hljs-section,\n.bp5-dark .hljs-name,\n.bp5-dark .hljs-selector-tag,\n.bp5-dark .hljs-deletion,\n.bp5-dark .hljs-subst {\n  color: #e06c75\n}\n.bp5-dark .hljs-literal {\n  color: #56b6c2\n}\n.bp5-dark .hljs-string,\n.bp5-dark .hljs-regexp,\n.bp5-dark .hljs-addition,\n.bp5-dark .hljs-attribute,\n.bp5-dark .hljs-meta .hljs-string {\n  color: #98c379\n}\n.bp5-dark .hljs-attr,\n.bp5-dark .hljs-variable,\n.bp5-dark .hljs-template-variable,\n.bp5-dark .hljs-type,\n.bp5-dark .hljs-selector-class,\n.bp5-dark .hljs-selector-attr,\n.bp5-dark .hljs-selector-pseudo,\n.bp5-dark .hljs-number {\n  color: #d19a66\n}\n.bp5-dark .hljs-symbol,\n.bp5-dark .hljs-bullet,\n.bp5-dark .hljs-link,\n.bp5-dark .hljs-meta,\n.bp5-dark .hljs-selector-id,\n.bp5-dark .hljs-title {\n  color: #61aeee\n}\n.bp5-dark .hljs-built_in,\n.bp5-dark .hljs-title.class_,\n.bp5-dark .hljs-class .hljs-title {\n  color: #e6c07b\n}\n.bp5-dark .hljs-emphasis {\n  font-style: italic\n}\n.bp5-dark .hljs-strong {\n  font-weight: bold\n}\n.bp5-dark .hljs-link {\n  text-decoration: underline\n}\n\n/***********************************************************************\nbp5 datepicker\n***********************************************************************/\n.bp5-datepicker input[type=number]::-webkit-inner-spin-button,\n.bp5-datepicker input[type=number]::-webkit-outer-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\n.bp5-datepicker{\n  background:#ffffff;\n  border-radius:3px;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  padding:5px;\n  position:relative;\n  -webkit-user-select:none;\n  -moz-user-select:none;\n  -ms-user-select:none;\n  user-select:none; }\n.bp5-datepicker .DayPicker{\n  display:inline-block;\n  min-width:210px;\n  position:relative;\n  vertical-align:top; }\n.bp5-datepicker .DayPicker:focus{\n  outline:none; }\n.bp5-datepicker .bp5-datepicker-day-wrapper{\n  border-radius:3px;\n  padding:7px; }\n.bp5-datepicker .DayPicker-Month{\n  border-collapse:collapse;\n  border-spacing:0;\n  display:inline-table;\n  margin:0 5px 5px;\n  -webkit-user-select:none;\n  -moz-user-select:none;\n  -ms-user-select:none;\n  user-select:none; }\n.bp5-datepicker .DayPicker-Month + .bp5-datepicker .DayPicker-Month{\n  margin-left:10px; }\n.bp5-datepicker .DayPicker-Caption{\n  display:table-caption; }\n.bp5-datepicker .DayPicker-Weekdays{\n  display:table-header-group; }\n.bp5-datepicker .DayPicker-WeekdaysRow{\n  display:table-row; }\n.bp5-datepicker .DayPicker-Weekday{\n  display:table-cell;\n  height:30px;\n  line-height:1;\n  text-align:center;\n  vertical-align:middle;\n  width:30px;\n  font-weight:600;\n  padding-top:5px; }\n.bp5-datepicker .DayPicker-Weekday abbr[title]{\n  text-decoration:none; }\n.bp5-datepicker .DayPicker-Body{\n  display:table-row-group; }\n.bp5-datepicker .DayPicker-Week{\n  display:table-row; }\n.bp5-datepicker .DayPicker-WeekNumber{\n  display:table-cell;\n  height:30px;\n  line-height:1;\n  text-align:center;\n  vertical-align:middle;\n  width:30px;\n  color:rgba(92, 112, 128, 0.6);\n  font-size:14px; }\n.bp5-datepicker .DayPicker-Day{\n  display:table-cell;\n  height:30px;\n  line-height:1;\n  text-align:center;\n  vertical-align:middle;\n  width:30px;\n  border-radius:3px;\n  cursor:pointer; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--outside{\n  color:rgba(92, 112, 128, 0.6); }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--isToday .bp5-datepicker-day-wrapper{\n  border:1px solid rgba(16, 22, 26, 0.15); }\n.bp5-datepicker .DayPicker-Day:hover, .bp5-datepicker .DayPicker-Day:focus{\n  background:#d8e1e8;\n  color:#182026; }\n.bp5-datepicker .DayPicker-Day:active{\n  background:#ced9e0; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--selected{\n  background-color:#137cbd;\n  border-radius:3px;\n  color:#ffffff; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--selected:hover{\n  background-color:#106ba3; }\n.bp5-datepicker .DayPicker-Day.DayPicker-Day--disabled{\n  background:none;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-datepicker-navbar{\n  -webkit-box-align:center;\n  -ms-flex-align:center;\n  align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  height:30px;\n  left:0;\n  position:absolute;\n  right:0;\n  top:0; }\n.bp5-datepicker-navbar > .DayPicker-NavButton--prev{\n  margin-right:auto; }\n.bp5-datepicker-navbar > .DayPicker-NavButton--next{\n  margin-left:auto; }\n\n.bp5-datepicker-caption{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:horizontal;\n  -webkit-box-direction:normal;\n  -ms-flex-direction:row;\n  flex-direction:row;\n  -webkit-box-pack:justify;\n  -ms-flex-pack:justify;\n  justify-content:space-between;\n  margin:0 25px 5px; }\n.bp5-datepicker-caption > *{\n  -webkit-box-flex:0;\n  -ms-flex-positive:0;\n  flex-grow:0;\n  -ms-flex-negative:0;\n  flex-shrink:0; }\n.bp5-datepicker-caption > :first-child{\n  -webkit-box-flex:1;\n  -ms-flex-positive:1;\n  flex-grow:1;\n  -ms-flex-negative:1;\n  flex-shrink:1; }\n.bp5-datepicker-caption select{\n  font-weight:600;\n  padding-left:5px;\n  padding-right:16px; }\n.bp5-datepicker-caption select + .bp5-icon{\n  right:2px; }\n.bp5-datepicker-caption + .bp5-divider{\n  margin:0; }\n\n.bp5-datepicker-month-select{\n  -ms-flex-negative:1;\n  flex-shrink:1; }\n\n.bp5-datepicker-year-select{\n  -ms-flex-negative:1;\n  flex-shrink:1;\n  min-width:60px; }\n\n.bp5-datepicker-caption-measure{\n  font-weight:600;\n  padding-left:5px; }\n\n.bp5-datepicker-footer{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-pack:justify;\n  -ms-flex-pack:justify;\n  justify-content:space-between; }\n\n.bp5-dark .bp5-datepicker{\n  background:#30404d; }\n.bp5-dark .bp5-datepicker .DayPicker-WeekNumber{\n  color:rgba(167, 182, 194, 0.6); }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--outside{\n  color:rgba(167, 182, 194, 0.6); }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--isToday .bp5-datepicker-day-wrapper{\n  border:1px solid rgba(255, 255, 255, 0.15); }\n.bp5-dark .bp5-datepicker .DayPicker-Day:hover, .bp5-dark .bp5-datepicker .DayPicker-Day:focus{\n  background:#5c7080;\n  color:#ffffff; }\n.bp5-dark .bp5-datepicker .DayPicker-Day:active{\n  background:#738694; }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--selected{\n  background-color:#137cbd; }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--selected:hover{\n  background-color:#2b95d6; }\n.bp5-dark .bp5-datepicker .DayPicker-Day.DayPicker-Day--disabled{\n  background:none;\n  color:rgba(167, 182, 194, 0.6); }\n.bp5-dark .bp5-datepicker .bp5-datepicker-footer{\n  border-top-color:rgba(16, 22, 26, 0.4); }\n\n.bp5-datepicker-timepicker-wrapper{\n  -webkit-box-align:center;\n  -ms-flex-align:center;\n  align-items:center;\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -webkit-box-orient:vertical;\n  -webkit-box-direction:normal;\n  -ms-flex-direction:column;\n  flex-direction:column; }\n.bp5-daterangepicker{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  white-space:nowrap; }\n.bp5-daterangepicker .DayPicker-NavButton--interactionDisabled{\n  display:none; }\n.bp5-daterangepicker .bp5-daterangepicker-timepickers{\n  display:-webkit-box;\n  display:-ms-flexbox;\n  display:flex;\n  -ms-flex-pack:distribute;\n  justify-content:space-around; }\n.bp5-daterangepicker.bp5-daterangepicker-contiguous .DayPicker{\n  min-width:220px; }\n.bp5-daterangepicker.bp5-daterangepicker-single-month .DayPicker{\n  min-width:210px; }\n.bp5-daterangepicker .DayPicker-Day{ }\n.bp5-daterangepicker .DayPicker-Day--outside{\n  visibility:hidden; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range{\n  border-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range:not(.DayPicker-Day--selected):not(.DayPicker-Day--selected-range):not(.DayPicker-Day--selected-range-start):not(.DayPicker-Day--selected-range-end){\n  background-color:#ebf1f5; }\n.bp5-daterangepicker .DayPicker-Day--selected-range{\n  background-color:#ebf1f5;\n  border-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--selected-range:hover{\n  background-color:#ced9e0; }\n.bp5-daterangepicker .DayPicker-Day--selected-range-start:not(.DayPicker-Day--selected-range-end):not(.DayPicker-Day--hovered-range-end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--selected-range-end:not(.DayPicker-Day--selected-range-start):not(.DayPicker-Day--hovered-range-start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range-start:not(.DayPicker-Day--hovered-range-end){\n  border-bottom-right-radius:0;\n  border-top-right-radius:0; }\n.bp5-daterangepicker .DayPicker-Day--hovered-range-end:not(.DayPicker-Day--hovered-range-start){\n  border-bottom-left-radius:0;\n  border-top-left-radius:0; }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--hovered-range{ }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--hovered-range:not(.DayPicker-Day--selected):not(.DayPicker-Day--selected-range):not(.DayPicker-Day--selected-range-start):not(.DayPicker-Day--selected-range-end){\n  background-color:#394b59; }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--selected-range{\n  background-color:#394b59; }\n.bp5-dark .bp5-daterangepicker .DayPicker-Day--selected-range:hover{\n  background-color:#5c7080; }\n\n.bp5-menu.bp5-daterangepicker-shortcuts{\n  min-width:120px;\n  padding:0; }\n.bp5-timepicker{\n  white-space:nowrap; }\n.bp5-timepicker .bp5-timepicker-arrow-row{\n  padding:0 1px; }\n.bp5-timepicker .bp5-timepicker-arrow-button{\n  color:#5c7080;\n  display:inline-block;\n  padding:4px 0;\n  text-align:center;\n  width:33px; }\n.bp5-timepicker .bp5-timepicker-arrow-button:hover{\n  color:#182026; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-arrow-button{\n  color:#a7b6c2; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-arrow-button:hover{\n  color:#f5f8fa; }\n.bp5-timepicker .bp5-timepicker-arrow-button + .bp5-timepicker-arrow-button{\n  margin-left:11px; }\n.bp5-timepicker .bp5-timepicker-arrow-button:hover{\n  cursor:pointer; }\n.bp5-timepicker .bp5-timepicker-input-row{\n  background:#ffffff;\n  border-radius:3px;\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  display:inline-block;\n  height:30px;\n  line-height:28px;\n  padding:0 1px;\n  vertical-align:middle; }\n.bp5-timepicker .bp5-timepicker-divider-text{\n  color:#5c7080;\n  display:inline-block;\n  font-size:16px;\n  text-align:center;\n  width:11px; }\n.bp5-timepicker .bp5-timepicker-input{\n  background:transparent;\n  border:0;\n  border-radius:3px;\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0);\n  box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0);\n  color:#182026;\n  height:28px;\n  outline:0;\n  padding:0;\n  text-align:center;\n  -webkit-transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:-webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  transition:box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9), -webkit-box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width:33px; }\n.bp5-timepicker .bp5-timepicker-input:focus{\n  -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3);\n  box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary{\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:focus{\n  -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n  box-shadow:inset 0 0 0 1px #137cbd; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary{\n  -webkit-box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), 0 0 0 0 rgba(19, 124, 189, 0), inset 0 0 0 1px #137cbd, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:focus{\n  -webkit-box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #137cbd, 0 0 0 1px #137cbd, 0 0 0 3px rgba(19, 124, 189, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #137cbd;\n  box-shadow:inset 0 0 0 1px #137cbd; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-primary.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success{\n  -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success:focus{\n  -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n  box-shadow:inset 0 0 0 1px #0f9960; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-success:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-success.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success{\n  -webkit-box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), 0 0 0 0 rgba(15, 153, 96, 0), inset 0 0 0 1px #0f9960, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success:focus{\n  -webkit-box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #0f9960, 0 0 0 1px #0f9960, 0 0 0 3px rgba(15, 153, 96, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #0f9960;\n  box-shadow:inset 0 0 0 1px #0f9960; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-success.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning{\n  -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:focus{\n  -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n  box-shadow:inset 0 0 0 1px #d9822b; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning{\n  -webkit-box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), 0 0 0 0 rgba(217, 130, 43, 0), inset 0 0 0 1px #d9822b, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:focus{\n  -webkit-box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #d9822b, 0 0 0 1px #d9822b, 0 0 0 3px rgba(217, 130, 43, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #d9822b;\n  box-shadow:inset 0 0 0 1px #d9822b; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-warning.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger{\n  -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.15), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:focus{\n  -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2);\n  box-shadow:0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.2); }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #db3737;\n  box-shadow:inset 0 0 0 1px #db3737; }\n.bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:disabled, .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger{\n  -webkit-box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), 0 0 0 0 rgba(219, 55, 55, 0), inset 0 0 0 1px #db3737, inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:focus{\n  -webkit-box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:0 0 0 1px #db3737, 0 0 0 1px #db3737, 0 0 0 3px rgba(219, 55, 55, 0.3), inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger[readonly]{\n  -webkit-box-shadow:inset 0 0 0 1px #db3737;\n  box-shadow:inset 0 0 0 1px #db3737; }\n.bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger:disabled, .bp5-dark .bp5-timepicker .bp5-timepicker-input.bp5-intent-danger.bp5-disabled{\n  -webkit-box-shadow:none;\n  box-shadow:none; }\n.bp5-timepicker .bp5-timepicker-ampm-select{\n  margin-left:5px; }\n.bp5-timepicker.bp5-disabled .bp5-timepicker-input-row{\n  background:rgba(206, 217, 224, 0.5);\n  -webkit-box-shadow:none;\n  box-shadow:none;\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed;\n  resize:none; }\n.bp5-timepicker.bp5-disabled .bp5-timepicker-input,\n.bp5-timepicker.bp5-disabled .bp5-timepicker-divider-text{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n.bp5-timepicker.bp5-disabled .bp5-timepicker-arrow-button,\n.bp5-timepicker.bp5-disabled .bp5-timepicker-arrow-button:hover{\n  color:rgba(92, 112, 128, 0.6);\n  cursor:not-allowed; }\n\n.bp5-dark .bp5-timepicker .bp5-timepicker-input-row{\n  background:rgba(16, 22, 26, 0.3);\n  -webkit-box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4);\n  box-shadow:inset 0 0 0 1px rgba(16, 22, 26, 0.3), inset 0 1px 1px rgba(16, 22, 26, 0.4); }\n\n.bp5-dark .bp5-timepicker .bp5-timepicker-divider-text{\n  color:#a7b6c2; }\n\n.bp5-dark .bp5-timepicker .bp5-timepicker-input{\n  color:#f5f8fa; }\n\n.bp5-datepicker .bp5-timepicker{\n  margin-bottom:10px;\n  margin-top:5px; }\n.bp5-datepicker .bp5-timepicker:last-child{\n  margin-bottom:5px; }\n.bp5-datetimepicker{\n  background-color:#ffffff;\n  border-radius:3px;\n  padding:10px;\n  text-align:center; }\n.bp5-dark .bp5-datetimepicker{\n  background:#30404d; }\n.bp5-dark .bp5-datetimepicker .bp5-datepicker{\n  border-bottom:1px solid rgba(16, 22, 26, 0.4); }\n.bp5-datetimepicker .bp5-datepicker{\n  border-bottom:1px solid rgba(16, 22, 26, 0.15);\n  padding:0 0 10px; }\n.bp5-datetimepicker .bp5-timepicker{\n  margin-top:10px; }\n\n.bp5-dateinput-popover{\n  padding:0; }\n/*# sourceMappingURL=blueprint-datetime.css.map */\n\n.bp5-dark .almalinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20id%3D%27Layer_1%27%20x%3D%270%27%20y%3D%270%27%20version%3D%271.1%27%20viewBox%3D%270%200%2061.028%2059.731%27%3E%3Cstyle%20id%3D%27style2%27%20type%3D%27text%2Fcss%27%3E.st1%7Bfill%3A%2386da2f%7D.st2%7Bfill%3A%2324c2ff%7D.st3%7Bfill%3A%23ffcb12%7D.st4%7Bfill%3A%230069da%7D.st5%7Bfill%3A%23ff4649%7D%3C%2Fstyle%3E%3Cpath%20id%3D%27path22%27%20d%3D%27M56.114%2033.731c2.6-.2%204.7%201.5%204.9%204.1.2%202.7-1.7%204.9-4.3%205.1-2.5.2-4.7-1.7-4.9-4.2-.2-2.7%201.6-4.7%204.3-5z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path24%27%20d%3D%27M24.514%2055.031c0-2.6%202-4.6%204.4-4.6%202.4%200%204.7%202.2%204.7%204.7%200%202.4-2%204.5-4.3%204.6-2.9%200-4.8-1.8-4.8-4.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path26%27%20d%3D%27M31.614%2025.831c-.4.2-.6-.1-.7-.4-3.7-6.9-2.6-15.6%203.9-20.8%201.7-1.4%204.9-1.7%206.3-.3.6.5.7%201.1.8%201.8.2%201.5.5%203%201.5%204.2%201.1%201.3%202.5%201.8%204.1%201.7%201.4%200%202.8-.2%203.7%201.4.5.9.3%204.4-.5%205.1-.4.3-.7.1-1%200-2.3-.9-4.7-.9-7.1-.5-.8.1-1.2-.1-1.2-1-.1-1.5-.4-2.9-1.2-4.2-1.5-2.7-4.3-2.8-6.1-.3-1.5%202-1.9%204.4-2.3%206.8-.4%202.1-.3%204.3-.2%206.5%200%200-.1%200%200%200z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path28%27%20d%3D%27M34.114%2027.331c-.2-.3-.1-.6.2-.8%205.7-5.2%2014.2-6.2%2020.8-1.1%201.7%201.4%202.8%204.3%201.9%206-.4.7-.9%201-1.5%201.2-1.4.6-2.7%201.2-3.6%202.5-.9%201.3-1.1%202.8-.7%204.4.3%201.3.8%202.7-.5%203.9-.7.7-4.1%201.3-5%20.7-.4-.3-.3-.6-.2-1%20.3-2.5-.3-4.8-1.2-7-.3-.8-.2-1.2.6-1.4%201.4-.4%202.7-1.1%203.7-2.1%202.2-2.1%201.7-4.8-1.2-6-2.3-1-4.7-.8-7-.6-2.2.1-4.3.7-6.3%201.3z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path30%27%20d%3D%27M32.814%2029.931c.3-.3.5-.2.8%200%206.6%204%2010%2011.9%207%2019.6-.8%202-3.4%204-5.3%203.5-.8-.2-1.2-.6-1.6-1.1-.9-1.2-1.9-2.3-3.4-2.8-1.6-.5-3-.2-4.4.6-1.2.7-2.4%201.6-3.9.7-.9-.5-2.4-3.6-2.1-4.6.2-.4.6-.4%201-.4%202.5-.4%204.5-1.6%206.4-3.2.6-.5%201.1-.5%201.6.2.8%201.2%201.8%202.2%203.1%202.9%202.6%201.5%205.1.2%205.4-2.8.3-2.5-.6-4.7-1.4-6.9-.9-2-2-3.9-3.2-5.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path32%27%20d%3D%27M29.614%2030.531c-.4%202-1.3%203.9-2.5%205.6-3.6%205.4-8.8%207.6-15.2%207-2.3-.2-4.2-2.1-4.4-4-.1-.8.1-1.4.6-2%20.7-.9%201.3-1.7%201.6-2.8.6-2.2-.2-4-1.8-5.6-2.2-2.2-1.9-4.2.7-5.8.3-.2.7-.4%201.1-.6.6-.3%201.1-.3%201.3.4.9%202.3%202.7%204%204.7%205.4.7.6.7%201%20.1%201.7-1.2%201.3-1.9%202.9-2%204.7-.2%202.2%201.1%203.6%203.3%203.6%201.4%200%202.7-.5%203.9-1.1%203.1-1.6%205.5-3.9%207.8-6.3.3-.1.4-.3.8-.2z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path34%27%20d%3D%27M13.214%209.531c.2%200%20.7.1%201.2.2%203.7.7%206-.6%207.2-4.1.8-2.3%202.5-3%204.7-1.8.1%200%20.1.1.2.1%202.3%201.3%202.3%201.5.9%203.5-1.2%201.6-1.8%203.4-2.1%205.3-.2%201.1-.6%201.3-1.6.9-1.6-.6-3.3-.6-5%200-1.9.6-2.7%202.3-2.1%204.2.8%202.5%203%203.6%204.9%204.9%201.9%201.3%204.1%202%206.2%202.9.3.1.8.1.7.6-.1.3-.5.3-.9.3-4.5.2-8.8-.5-12.3-3.5-3.3-2.7-5.7-6-5.3-10.6.3-1.5%201.4-2.6%203.3-2.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path36%27%20d%3D%27M5.014%2037.631c-2.4.3-4.8-1.7-5-4.2-.2-2.4%201.8-4.8%204.1-5%202.6-.3%205%201.5%205.2%203.9.1%202.3-1.4%205.1-4.3%205.3z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path38%27%20d%3D%27M47.014%202.031c2.5-.2%204.9%201.8%205.1%204.3.2%202.4-1.8%204.7-4.2%204.9-2.6.2-4.9-1.7-5.1-4.2-.2-2.5%201.6-4.8%204.2-5z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path40%27%20d%3D%27M20.914%203.931c.3%202.6-1.5%204.8-4.2%205.2-2.3.3-4.7-1.6-5-3.8-.3-2.9%201.3-5%204-5.3%202.5-.3%204.9%201.6%205.2%203.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.almalinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20id%3D%27Layer_1%27%20x%3D%270%27%20y%3D%270%27%20version%3D%271.1%27%20viewBox%3D%270%200%2061.028%2059.731%27%3E%3Cstyle%20id%3D%27style2%27%20type%3D%27text%2Fcss%27%3E.st1%7Bfill%3A%2386da2f%7D.st2%7Bfill%3A%2324c2ff%7D.st3%7Bfill%3A%23ffcb12%7D.st4%7Bfill%3A%230069da%7D.st5%7Bfill%3A%23ff4649%7D%3C%2Fstyle%3E%3Cpath%20id%3D%27path22%27%20d%3D%27M56.114%2033.731c2.6-.2%204.7%201.5%204.9%204.1.2%202.7-1.7%204.9-4.3%205.1-2.5.2-4.7-1.7-4.9-4.2-.2-2.7%201.6-4.7%204.3-5z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path24%27%20d%3D%27M24.514%2055.031c0-2.6%202-4.6%204.4-4.6%202.4%200%204.7%202.2%204.7%204.7%200%202.4-2%204.5-4.3%204.6-2.9%200-4.8-1.8-4.8-4.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path26%27%20d%3D%27M31.614%2025.831c-.4.2-.6-.1-.7-.4-3.7-6.9-2.6-15.6%203.9-20.8%201.7-1.4%204.9-1.7%206.3-.3.6.5.7%201.1.8%201.8.2%201.5.5%203%201.5%204.2%201.1%201.3%202.5%201.8%204.1%201.7%201.4%200%202.8-.2%203.7%201.4.5.9.3%204.4-.5%205.1-.4.3-.7.1-1%200-2.3-.9-4.7-.9-7.1-.5-.8.1-1.2-.1-1.2-1-.1-1.5-.4-2.9-1.2-4.2-1.5-2.7-4.3-2.8-6.1-.3-1.5%202-1.9%204.4-2.3%206.8-.4%202.1-.3%204.3-.2%206.5%200%200-.1%200%200%200z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path28%27%20d%3D%27M34.114%2027.331c-.2-.3-.1-.6.2-.8%205.7-5.2%2014.2-6.2%2020.8-1.1%201.7%201.4%202.8%204.3%201.9%206-.4.7-.9%201-1.5%201.2-1.4.6-2.7%201.2-3.6%202.5-.9%201.3-1.1%202.8-.7%204.4.3%201.3.8%202.7-.5%203.9-.7.7-4.1%201.3-5%20.7-.4-.3-.3-.6-.2-1%20.3-2.5-.3-4.8-1.2-7-.3-.8-.2-1.2.6-1.4%201.4-.4%202.7-1.1%203.7-2.1%202.2-2.1%201.7-4.8-1.2-6-2.3-1-4.7-.8-7-.6-2.2.1-4.3.7-6.3%201.3z%27%20class%3D%27st1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path30%27%20d%3D%27M32.814%2029.931c.3-.3.5-.2.8%200%206.6%204%2010%2011.9%207%2019.6-.8%202-3.4%204-5.3%203.5-.8-.2-1.2-.6-1.6-1.1-.9-1.2-1.9-2.3-3.4-2.8-1.6-.5-3-.2-4.4.6-1.2.7-2.4%201.6-3.9.7-.9-.5-2.4-3.6-2.1-4.6.2-.4.6-.4%201-.4%202.5-.4%204.5-1.6%206.4-3.2.6-.5%201.1-.5%201.6.2.8%201.2%201.8%202.2%203.1%202.9%202.6%201.5%205.1.2%205.4-2.8.3-2.5-.6-4.7-1.4-6.9-.9-2-2-3.9-3.2-5.7z%27%20class%3D%27st2%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path32%27%20d%3D%27M29.614%2030.531c-.4%202-1.3%203.9-2.5%205.6-3.6%205.4-8.8%207.6-15.2%207-2.3-.2-4.2-2.1-4.4-4-.1-.8.1-1.4.6-2%20.7-.9%201.3-1.7%201.6-2.8.6-2.2-.2-4-1.8-5.6-2.2-2.2-1.9-4.2.7-5.8.3-.2.7-.4%201.1-.6.6-.3%201.1-.3%201.3.4.9%202.3%202.7%204%204.7%205.4.7.6.7%201%20.1%201.7-1.2%201.3-1.9%202.9-2%204.7-.2%202.2%201.1%203.6%203.3%203.6%201.4%200%202.7-.5%203.9-1.1%203.1-1.6%205.5-3.9%207.8-6.3.3-.1.4-.3.8-.2z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path34%27%20d%3D%27M13.214%209.531c.2%200%20.7.1%201.2.2%203.7.7%206-.6%207.2-4.1.8-2.3%202.5-3%204.7-1.8.1%200%20.1.1.2.1%202.3%201.3%202.3%201.5.9%203.5-1.2%201.6-1.8%203.4-2.1%205.3-.2%201.1-.6%201.3-1.6.9-1.6-.6-3.3-.6-5%200-1.9.6-2.7%202.3-2.1%204.2.8%202.5%203%203.6%204.9%204.9%201.9%201.3%204.1%202%206.2%202.9.3.1.8.1.7.6-.1.3-.5.3-.9.3-4.5.2-8.8-.5-12.3-3.5-3.3-2.7-5.7-6-5.3-10.6.3-1.5%201.4-2.6%203.3-2.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path36%27%20d%3D%27M5.014%2037.631c-2.4.3-4.8-1.7-5-4.2-.2-2.4%201.8-4.8%204.1-5%202.6-.3%205%201.5%205.2%203.9.1%202.3-1.4%205.1-4.3%205.3z%27%20class%3D%27st4%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path38%27%20d%3D%27M47.014%202.031c2.5-.2%204.9%201.8%205.1%204.3.2%202.4-1.8%204.7-4.2%204.9-2.6.2-4.9-1.7-5.1-4.2-.2-2.5%201.6-4.8%204.2-5z%27%20class%3D%27st3%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path40%27%20d%3D%27M20.914%203.931c.3%202.6-1.5%204.8-4.2%205.2-2.3.3-4.7-1.6-5-3.8-.3-2.9%201.3-5%204-5.3%202.5-.3%204.9%201.6%205.2%203.9z%27%20class%3D%27st5%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .alpinelinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20width%3D%27186.125%27%20height%3D%27161.25%27%3E%3Cpath%20d%3D%27M0%200v15.7L-11.3%204.4a33.27%2033.27%200%200%201%203.44-2.11c1.08-.567%202.09-1%203.03-1.34.941-.334%201.81-.562%202.62-.71C-1.406.093-.67.027%200%20.018m57.8%201.3c.02-.017.13-.11.333-.239a5.89%205.89%200%200%201%20.898-.462%208.098%208.098%200%200%201%201.48-.448c.596-.122%201.29-.202%202.1-.202.671%200%201.41.059%202.22.2.812.142%201.69.367%202.64.699a23.4%2023.4%200%200%201%203.07%201.34c1.09.572%202.26%201.28%203.5%202.14l-8.57%208.4-28.4%2028.5-12.5-12.5-16.8%2017.3-42.2-41.7c1.24-.86%202.4-1.56%203.49-2.14%201.09-.571%202.12-1.01%203.07-1.34a16.71%2016.71%200%200%201%202.64-.698%2012.85%2012.85%200%200%201%202.22-.201c.804%200%201.5.08%202.1.202a7.78%207.78%200%200%201%201.48.449c.396.164.693.331.897.461.204.13.314.223.334.24l19.1%2019.1%206.81%206.53%2018.9-18.9%206.59-6.78c.02-.017.13-.11.334-.239a5.87%205.87%200%200%201%20.897-.462c.396-.164.89-.327%201.48-.448.596-.122%201.3-.202%202.1-.202.67%200%201.41.059%202.22.2.811.142%201.69.367%202.64.699.952.333%201.98.773%203.07%201.34%201.09.572%202.26%201.28%203.5%202.14l-15.1%2014.8%202.82%202.82%2013.1-13.1%207.64-7.57m-.163%2079%2037.2-64.5-37.2-64.5h-74.5l-37.2%2064.5%2037.2%2064.5z%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%2067.45%20100.309%29%27%2F%3E%3Cpath%20d%3D%27m0%200-9.91%209.9.705.709L.775.739z%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%20111.2%2076.559%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.alpinelinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xml%3Aspace%3D%27preserve%27%20width%3D%27186.125%27%20height%3D%27161.25%27%3E%3Cpath%20d%3D%27M0%200v15.7L-11.3%204.4a33.27%2033.27%200%200%201%203.44-2.11c1.08-.567%202.09-1%203.03-1.34.941-.334%201.81-.562%202.62-.71C-1.406.093-.67.027%200%20.018m57.8%201.3c.02-.017.13-.11.333-.239a5.89%205.89%200%200%201%20.898-.462%208.098%208.098%200%200%201%201.48-.448c.596-.122%201.29-.202%202.1-.202.671%200%201.41.059%202.22.2.812.142%201.69.367%202.64.699a23.4%2023.4%200%200%201%203.07%201.34c1.09.572%202.26%201.28%203.5%202.14l-8.57%208.4-28.4%2028.5-12.5-12.5-16.8%2017.3-42.2-41.7c1.24-.86%202.4-1.56%203.49-2.14%201.09-.571%202.12-1.01%203.07-1.34a16.71%2016.71%200%200%201%202.64-.698%2012.85%2012.85%200%200%201%202.22-.201c.804%200%201.5.08%202.1.202a7.78%207.78%200%200%201%201.48.449c.396.164.693.331.897.461.204.13.314.223.334.24l19.1%2019.1%206.81%206.53%2018.9-18.9%206.59-6.78c.02-.017.13-.11.334-.239a5.87%205.87%200%200%201%20.897-.462c.396-.164.89-.327%201.48-.448.596-.122%201.3-.202%202.1-.202.67%200%201.41.059%202.22.2.811.142%201.69.367%202.64.699.952.333%201.98.773%203.07%201.34%201.09.572%202.26%201.28%203.5%202.14l-15.1%2014.8%202.82%202.82%2013.1-13.1%207.64-7.57m-.163%2079%2037.2-64.5-37.2-64.5h-74.5l-37.2%2064.5%2037.2%2064.5z%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%2067.45%20100.309%29%27%2F%3E%3Cpath%20d%3D%27m0%200-9.91%209.9.705.709L.775.739z%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27matrix%281.25%200%200%20-1.25%20111.2%2076.559%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .archlinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%22256%22%20height%3D%22256%22%3E%3Cpath%20d%3D%22M128%200c-11.395%2027.937-18.313%2046.237-31%2073.344%207.779%208.245%2017.338%2017.812%2032.844%2028.656-16.67-6.86-28.034-13.729-36.531-20.875C77.076%20115.005%2051.622%20163.246%200%20256c40.563-23.418%2072.008-37.862%20101.313-43.375-1.254-5.404-1.924-11.278-1.876-17.375l.032-1.281c.644-25.994%2014.17-45.982%2030.187-44.625s28.488%2023.537%2027.844%2049.531c-.121%204.896-.69%209.6-1.656%2013.969C184.834%20218.517%20215.982%20232.897%20256%20256a14843%2014843%200%200%201-21.687-40.094c-10.597-8.212-21.644-18.897-44.188-30.469%2015.496%204.027%2026.602%208.683%2035.25%2013.875C156.98%2071.973%20151.455%2055.04%20128%200%22%20style%3D%22fill%3A%23fff%3Bfill-opacity%3A1%22/%3E%3Cpath%20d%3D%22M818.226%20548.553c-41.181-55.895-50.727-100.945-53.145-111.7%2021.968%2050.668%2021.818%2051.29%2053.145%20111.7%22%20style%3D%22fill%3A%23fff%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3Cpath%20d%3D%22M765.098%20436.435c-1.056%202.597-2.086%205.117-3.061%207.515-1.082%202.655-2.11%205.19-3.132%207.677a1118%201118%200%200%201-3.038%207.306%20752%20752%200%200%201-3.038%207.143c-1.028%202.382-2.062%204.789-3.155%207.236a619%20619%200%200%201-3.432%207.538%20686%20686%200%200%201-3.804%208.095c-.186.39-.413.834-.603%201.229%205.755%206.096%2012.841%2013.15%2024.284%2021.152-12.34-5.078-20.766-10.157-27.067-15.446-.327.667-.614%201.264-.951%201.948-.444.9-.976%201.923-1.438%202.853-.81%201.66-1.656%203.365-2.528%205.125-.335.667-.63%201.243-.974%201.925-5.508%2011.053-12.34%2024.283-21.13%2040.728%2024.096-13.576%2050.086-33.163%2097.297-16.305a1624%201624%200%200%201-6.587-12.64%201193%201193%200%200%201-5.706-11.156%20865%20865%200%200%201-4.917-9.927%20671%20671%200%200%201-4.267-8.906%20563%20563%200%200%201-3.758-8.165%20521%20521%200%200%201-3.363-7.584%20548%20548%200%200%201-3.084-7.306%20692%20692%200%200%201-2.946-7.236c-.348-.866-.695-1.769-1.044-2.644-2.663-6.253-5.398-12.732-8.558-20.155%22%20style%3D%22fill%3A%23fff%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3C/svg%3E\");\n}\n\n.archlinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%22256%22%20height%3D%22256%22%3E%3Cpath%20d%3D%22M128%200c-11.395%2027.937-18.313%2046.237-31%2073.344%207.779%208.245%2017.338%2017.812%2032.844%2028.656-16.67-6.86-28.034-13.729-36.531-20.875C77.076%20115.005%2051.622%20163.246%200%20256c40.563-23.418%2072.008-37.862%20101.313-43.375-1.254-5.404-1.924-11.278-1.876-17.375l.032-1.281c.644-25.994%2014.17-45.982%2030.187-44.625s28.488%2023.537%2027.844%2049.531c-.121%204.896-.69%209.6-1.656%2013.969C184.834%20218.517%20215.982%20232.897%20256%20256a14843%2014843%200%200%201-21.687-40.094c-10.597-8.212-21.644-18.897-44.188-30.469%2015.496%204.027%2026.602%208.683%2035.25%2013.875C156.98%2071.973%20151.455%2055.04%20128%200%22%20style%3D%22fill%3A%23000%3Bfill-opacity%3A1%22/%3E%3Cpath%20d%3D%22M818.226%20548.553c-41.181-55.895-50.727-100.945-53.145-111.7%2021.968%2050.668%2021.818%2051.29%2053.145%20111.7%22%20style%3D%22fill%3A%23000%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3Cpath%20d%3D%22M765.098%20436.435c-1.056%202.597-2.086%205.117-3.061%207.515-1.082%202.655-2.11%205.19-3.132%207.677a1118%201118%200%200%201-3.038%207.306%20752%20752%200%200%201-3.038%207.143c-1.028%202.382-2.062%204.789-3.155%207.236a619%20619%200%200%201-3.432%207.538%20686%20686%200%200%201-3.804%208.095c-.186.39-.413.834-.603%201.229%205.755%206.096%2012.841%2013.15%2024.284%2021.152-12.34-5.078-20.766-10.157-27.067-15.446-.327.667-.614%201.264-.951%201.948-.444.9-.976%201.923-1.438%202.853-.81%201.66-1.656%203.365-2.528%205.125-.335.667-.63%201.243-.974%201.925-5.508%2011.053-12.34%2024.283-21.13%2040.728%2024.096-13.576%2050.086-33.163%2097.297-16.305a1624%201624%200%200%201-6.587-12.64%201193%201193%200%200%201-5.706-11.156%20865%20865%200%200%201-4.917-9.927%20671%20671%200%200%201-4.267-8.906%20563%20563%200%200%201-3.758-8.165%20521%20521%200%200%201-3.363-7.584%20548%20548%200%200%201-3.084-7.306%20692%20692%200%200%201-2.946-7.236c-.348-.866-.695-1.769-1.044-2.644-2.663-6.253-5.398-12.732-8.558-20.155%22%20style%3D%22fill%3A%23000%3Bfill-opacity%3A1%22%20transform%3D%22translate%28-902.4%20-586.945%29scale%281.34737%29%22/%3E%3C/svg%3E\");\n}\n\n.bp5-dark .fedora-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27512.041%27%20height%3D%27502.439%27%20viewBox%3D%270%200%20135.478%20132.937%27%3E%3Cpath%20d%3D%27M67.775%200C30.366%200%20.079%2029.732.012%2066.439H0v51.43h.012c.018%208.325%206.903%2015.068%2015.393%2015.068H67.78c37.415-.024%2067.697-29.752%2067.697-66.439C135.478%2029.792%20105.157.06%2067.715.06zm13.771%2027.338-.007.02c11.376%200%2022.116%208.543%2022.116%2020.328%200%201.094.007%202.187-.176%203.427-.315%203.135-3.236%205.388-6.417%204.946-3.181-.447-5.34-3.41-4.752-6.509.054-.354.074-.91.074-1.865%200-6.683-5.576-9.264-10.846-9.264-5.27%200-10.019%204.346-10.026%209.264.091%205.687%200%2011.33%200%2017.007l9.78-.071c7.637-.155%207.723%2011.123.089%2011.07l-9.867.072c-.024%204.574.036%203.747.012%206.05%200%200%20.083%205.595-.087%209.834-1.18%2012.454-11.99%2022.407-24.98%2022.407-13.772%200-25.114-11.038-25.114-24.577.413-13.923%2011.733-24.875%2025.993-24.75l7.954-.058v11.05l-7.954.073h-.042c-7.835.227-14.552%205.447-14.677%2013.678%200%207.498%206.176%2013.506%2013.843%2013.506%207.657%200%2013.784-5.464%2013.784-13.493l-.012-41.855c.005-.777.03-1.394.117-2.251%201.292-10.238%2010.627-18.04%2021.195-18.04z%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.fedora-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27512.041%27%20height%3D%27502.439%27%20viewBox%3D%270%200%20135.478%20132.937%27%3E%3Cpath%20d%3D%27M67.775%200C30.366%200%20.079%2029.732.012%2066.439H0v51.43h.012c.018%208.325%206.903%2015.068%2015.393%2015.068H67.78c37.415-.024%2067.697-29.752%2067.697-66.439C135.478%2029.792%20105.157.06%2067.715.06zm13.771%2027.338-.007.02c11.376%200%2022.116%208.543%2022.116%2020.328%200%201.094.007%202.187-.176%203.427-.315%203.135-3.236%205.388-6.417%204.946-3.181-.447-5.34-3.41-4.752-6.509.054-.354.074-.91.074-1.865%200-6.683-5.576-9.264-10.846-9.264-5.27%200-10.019%204.346-10.026%209.264.091%205.687%200%2011.33%200%2017.007l9.78-.071c7.637-.155%207.723%2011.123.089%2011.07l-9.867.072c-.024%204.574.036%203.747.012%206.05%200%200%20.083%205.595-.087%209.834-1.18%2012.454-11.99%2022.407-24.98%2022.407-13.772%200-25.114-11.038-25.114-24.577.413-13.923%2011.733-24.875%2025.993-24.75l7.954-.058v11.05l-7.954.073h-.042c-7.835.227-14.552%205.447-14.677%2013.678%200%207.498%206.176%2013.506%2013.843%2013.506%207.657%200%2013.784-5.464%2013.784-13.493l-.012-41.855c.005-.777.03-1.394.117-2.251%201.292-10.238%2010.627-18.04%2021.195-18.04z%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .freebsd-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272500%27%20height%3D%272461%27%20preserveAspectRatio%3D%27xMidYMid%27%20viewBox%3D%270%200%20256%20252%27%3E%3Cg%20fill%3D%27%23B5010F%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%3E%3Cpath%20d%3D%27M252.723%205.11c13.508%2013.5-23.939%2072.848-30.27%2079.182-6.33%206.321-22.409.505-35.91-13-13.508-13.5-19.327-29.583-12.996-35.914%206.327-6.333%2065.671-43.777%2079.176-30.269M63.305%2019.394c-20.622-11.7-49.966-24.716-59.3-15.38-9.458%209.454%204.034%2039.458%2015.858%2060.117a126.812%20126.812%200%200%201%2043.442-44.737%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20d%3D%27M232.123%2079.636c1.899%206.44%201.558%2011.76-1.522%2014.834-7.193%207.196-26.624-.464-44.14-17.134a89.383%2089.383%200%200%201-3.627-3.428c-6.334-6.336-11.262-13.08-14.414-19.291-6.135-11.006-7.67-20.726-3.033-25.364%202.527-2.524%206.57-3.212%2011.502-2.325%203.216-2.034%207.013-4.3%2011.176-6.621-16.929-8.83-36.176-13.817-56.593-13.817C63.753%206.49%208.854%2061.38%208.854%20129.105c0%2067.713%2054.9%20122.61%20122.618%20122.61%2067.72%200%20122.616-54.897%20122.616-122.61%200-21.87-5.74-42.377-15.767-60.156-2.167%203.955-4.274%207.578-6.198%2010.687%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\");\n}\n\n.freebsd-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272500%27%20height%3D%272461%27%20preserveAspectRatio%3D%27xMidYMid%27%20viewBox%3D%270%200%20256%20252%27%3E%3Cg%20fill%3D%27%23B5010F%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%3E%3Cpath%20d%3D%27M252.723%205.11c13.508%2013.5-23.939%2072.848-30.27%2079.182-6.33%206.321-22.409.505-35.91-13-13.508-13.5-19.327-29.583-12.996-35.914%206.327-6.333%2065.671-43.777%2079.176-30.269M63.305%2019.394c-20.622-11.7-49.966-24.716-59.3-15.38-9.458%209.454%204.034%2039.458%2015.858%2060.117a126.812%20126.812%200%200%201%2043.442-44.737%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20d%3D%27M232.123%2079.636c1.899%206.44%201.558%2011.76-1.522%2014.834-7.193%207.196-26.624-.464-44.14-17.134a89.383%2089.383%200%200%201-3.627-3.428c-6.334-6.336-11.262-13.08-14.414-19.291-6.135-11.006-7.67-20.726-3.033-25.364%202.527-2.524%206.57-3.212%2011.502-2.325%203.216-2.034%207.013-4.3%2011.176-6.621-16.929-8.83-36.176-13.817-56.593-13.817C63.753%206.49%208.854%2061.38%208.854%20129.105c0%2067.713%2054.9%20122.61%20122.618%20122.61%2067.72%200%20122.616-54.897%20122.616-122.61%200-21.87-5.74-42.377-15.767-60.156-2.167%203.955-4.274%207.578-6.198%2010.687%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .oraclelinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2732%27%20height%3D%2720%27%3E%3Cpath%20fill%3D%27none%27%20stroke%3D%27%23C74634%27%20stroke-width%3D%274%27%20d%3D%27M10%202a8%208%200%201%200%200%2016h12a8%208%200%201%200%200-16z%27%20style%3D%27stroke%3A%23fff%3Bstroke-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.oraclelinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2732%27%20height%3D%2720%27%3E%3Cpath%20fill%3D%27none%27%20stroke%3D%27%23C74634%27%20stroke-width%3D%274%27%20d%3D%27M10%202a8%208%200%201%200%200%2016h12a8%208%200%201%200%200-16z%27%20style%3D%27stroke%3A%23000%3Bstroke-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .rockylinux-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%274096%27%20height%3D%274096%27%20fill%3D%27none%27%20viewBox%3D%270%200%20192%20192%27%3E%3Cpath%20fill%3D%27%2310B981%27%20fill-rule%3D%27evenodd%27%20d%3D%27M186.658%20127.658C190.119%20117.746%20192%20107.093%20192%2096c0-53.02-42.981-96-96-96C42.98%200%200%2042.98%200%2096c0%2026.234%2010.523%2050.011%2027.578%2067.338l97.38-97.38L149%2090l37.658%2037.658Zm-17.536%2030.547-44.164-44.163-69.16%2069.16C68.027%20188.849%2081.645%20192%2096%20192c29.288%200%2055.514-13.116%2073.122-33.795Z%27%20clip-rule%3D%27evenodd%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.rockylinux-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%274096%27%20height%3D%274096%27%20fill%3D%27none%27%20viewBox%3D%270%200%20192%20192%27%3E%3Cpath%20fill%3D%27%2310B981%27%20fill-rule%3D%27evenodd%27%20d%3D%27M186.658%20127.658C190.119%20117.746%20192%20107.093%20192%2096c0-53.02-42.981-96-96-96C42.98%200%200%2042.98%200%2096c0%2026.234%2010.523%2050.011%2027.578%2067.338l97.38-97.38L149%2090l37.658%2037.658Zm-17.536%2030.547-44.164-44.163-69.16%2069.16C68.027%20188.849%2081.645%20192%2096%20192c29.288%200%2055.514-13.116%2073.122-33.795Z%27%20clip-rule%3D%27evenodd%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.bp5-dark .ubuntu-logo {\n  opacity: 0.8;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20id%3D%27Layer_1%27%20data-name%3D%27Layer%201%27%20version%3D%271.1%27%20viewBox%3D%270%200%20767.407%20738.314%27%3E%3Cdefs%20id%3D%27defs1%27%3E%3Cstyle%20id%3D%27style1%27%3E.cls-1%7Bfill%3A%23e9500e%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ccircle%20id%3D%27circle1%27%20cx%3D%27109.027%27%20cy%3D%27348.094%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Ccircle%20id%3D%27circle2%27%20cx%3D%27563.084%27%20cy%3D%27109.027%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path2%27%20d%3D%27M492.482%201336.666a265.525%20265.525%200%200%201-181.071-138.098%20156.948%20156.948%200%200%201-93.22%2011.458A354.935%20354.935%200%200%200%20473.73%201424.19a359.208%20359.208%200%200%200%2077.42%207.967%20156.003%20156.003%200%200%201-31.923-91.154%20271.194%20271.194%200%200%201-26.744-4.337Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Ccircle%20id%3D%27circle3%27%20cx%3D%27538.52%27%20cy%3D%27629.287%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path3%27%20d%3D%27M827.798%201297.22a356.7%20356.7%200%200%200%2017.875-418.518%20157.144%20157.144%200%200%201-61.31%2071.803%20267.293%20267.293%200%200%201-8.737%20265.488%20156.347%20156.347%200%200%201%2052.172%2081.228Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Cpath%20id%3D%27path4%27%20d%3D%27M238.176%20899.72q4.15-.229%208.306-.226a157.346%20157.346%200%200%201%2082.682%2023.545%20265.907%20265.907%200%200%201%20214.108-114.51%20158.088%20158.088%200%200%201%2033.076-88.42c-137.103-10.857-270.324%2059.892-338.172%20179.61Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23fff%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n\n.ubuntu-logo {\n  opacity: 0.5;\n  background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20id%3D%27Layer_1%27%20data-name%3D%27Layer%201%27%20version%3D%271.1%27%20viewBox%3D%270%200%20767.407%20738.314%27%3E%3Cdefs%20id%3D%27defs1%27%3E%3Cstyle%20id%3D%27style1%27%3E.cls-1%7Bfill%3A%23e9500e%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ccircle%20id%3D%27circle1%27%20cx%3D%27109.027%27%20cy%3D%27348.094%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Ccircle%20id%3D%27circle2%27%20cx%3D%27563.084%27%20cy%3D%27109.027%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path2%27%20d%3D%27M492.482%201336.666a265.525%20265.525%200%200%201-181.071-138.098%20156.948%20156.948%200%200%201-93.22%2011.458A354.935%20354.935%200%200%200%20473.73%201424.19a359.208%20359.208%200%200%200%2077.42%207.967%20156.003%20156.003%200%200%201-31.923-91.154%20271.194%20271.194%200%200%201-26.744-4.337Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Ccircle%20id%3D%27circle3%27%20cx%3D%27538.52%27%20cy%3D%27629.287%27%20r%3D%27109.027%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%2F%3E%3Cpath%20id%3D%27path3%27%20d%3D%27M827.798%201297.22a356.7%20356.7%200%200%200%2017.875-418.518%20157.144%20157.144%200%200%201-61.31%2071.803%20267.293%20267.293%200%200%201-8.737%20265.488%20156.347%20156.347%200%200%201%2052.172%2081.228Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3Cpath%20id%3D%27path4%27%20d%3D%27M238.176%20899.72q4.15-.229%208.306-.226a157.346%20157.346%200%200%201%2082.682%2023.545%20265.907%20265.907%200%200%201%20214.108-114.51%20158.088%20158.088%200%200%201%2033.076-88.42c-137.103-10.857-270.324%2059.892-338.172%20179.61Z%27%20class%3D%27cls-1%27%20style%3D%27fill%3A%23000%3Bfill-opacity%3A1%27%20transform%3D%27translate%28-137.562%20-707.98%29%27%2F%3E%3C%2Fsvg%3E\");\n}\n"
  },
  {
    "path": "www/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2021\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"allowSyntheticDefaultImports\": true,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"removeComments\": true,\n    \"sourceMap\": true,\n    \"skipLibCheck\": true,\n    \"jsx\": \"react\",\n    \"strict\": false\n  },\n  \"exclude\": [\n    \"node_modules\",\n    \"vendor\"\n  ]\n}\n"
  },
  {
    "path": "www/uindex.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud Dev</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/normalize.css\"/>\n    <link id=\"blueprint3-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint3.css\" disabled/>\n    <link id=\"blueprint5-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint5.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/global.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-datetime2.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-icons.css\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"toaster\"></div>\n    <script>\n      window.user = true;\n    </script>\n    <script src=\"static/app.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/uindex_dist.html",
    "content": "<html>\n  <head>\n    <title>Pritunl Cloud</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"data:image/vnd.microsoft.icon;base64,AAABAAIAICAAAAEAGACoDAAAJgAAABAQAAABABgAaAMAAM4MAAAoAAAAIAAAAEAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////VUMwU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////YVA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////Z1dGVEIvZFVDh3pumpCFmo+DgnVnW0o4U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////jIF03tnW////////////////////////wry0WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////Pz8////////////////////////////////+/r6b19QU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////dWdXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+fn4X007U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////5eLgU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////f3NkU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f38v7mynZKGq6KZ5ODe////////////////////////////4uDcU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/////////////////////////////v79g3ZnU0EuU0EuU0EuWUc1xsG7////////////////////////////W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////vrewU0EuU0EuU0EuU0EuU0EuXU088fHv////////////////////////bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////j4R3U0EuU0EuU0EuU0EuU0EuU0EuyMO9////////////////////////em1eU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////lIp9U0EuU0EuU0EuU0EuU0EuU0EuzMbC////////////////////////eWxdU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////z8rFU0EuU0EuU0EuU0EuU0EuYVA+9vTz////////////////////////a1xLU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////h3ttU0EuU0EuU0EuU0Euwbu0////////////////////////////WUg2U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////squihHdpjIBy3NnV////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////////fXBhU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////////3drXU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////////////////////////////////////////+/r6YFA+U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu/f39/////////////////////////////////////////////////////////////////f37bF1NU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu5ePg/////////////////////Pv73tzX////////////////////////////////9vX0ZVVFU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Euhnpt////////////////////vLOsYFA9xb+4////////////////////////vrixVkQyU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EualtLjoJ1mY+EqaCXh3ptVEMwU0EuU0EuZ1dHn5OKta6lsaqhk4h7W0o3U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAEAAAACAAAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////h3puU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////raSbVEIvYE4+YVA/U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////49/d9vX0////////8O/tbF1NU0EuU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////c2VVU0EuU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VkQyU0EuU0EuU0EuU0EuU0Eu/////////////v7+oZeOeGpbvrew////////////gnVnU0EuU0EuU0EuU0EuU0Eu////////////0c3IU0EuU0EuVUMx7evq////////qqKYU0EuU0EuU0EuU0EuU0Eu////////////2NTQU0EuU0EuVEIv7+3r////////qaCWU0EuU0EuU0EuU0EuU0Eu////////////////lYp/YFA/r6ad////////////gHNlU0EuU0EuU0EuU0EuU0Eu////////////////////////////////////9/b1VUQwU0EuU0EuU0EuU0EuU0Eu/////////////////////////////////f39b19QU0EuU0EuU0EuU0EuU0EuU0Eu0szH////////urSs7evp////////6+nmalpJU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0IuX007Wkg2U0EuU0EuXU06Xk07U0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuU0EuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/normalize.css\"/>\n    <link id=\"blueprint3-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint3.css\" disabled/>\n    <link id=\"blueprint5-theme\" rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint5.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/global.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-datetime2.css\"/>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"static/blueprint-icons.css\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <div id=\"toaster\"></div>\n    <script>\n      window.user = true;\n    </script>\n    <script src=\"static/app.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "www/webpack.config.js",
    "content": "const path = require('path');\nconst webpack = require('webpack');\nconst TerserPlugin = require('terser-webpack-plugin');\nconst MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');\n\nmodule.exports = {\n  mode: 'production',\n  devtool: 'source-map',\n  entry: {\n    app: {\n      import: './app/App.js',\n    },\n  },\n  optimization: {\n    minimize: true,\n    minimizer: [\n      new TerserPlugin({\n        extractComments: false,\n      }),\n    ],\n  },\n  output: {\n    path: path.resolve(__dirname, 'dist', 'static'),\n    publicPath: '/static/',\n    filename: '[name].js',\n    globalObject: 'self',\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        enforce: 'pre',\n        use: ['source-map-loader'],\n      },\n      {\n        test: /\\.css$/,\n        use: ['style-loader', 'css-loader'],\n      },\n      {\n        test: /\\.(ttf|woff|woff2|eot|svg)$/,\n        type: 'asset/resource',\n      },\n    ],\n  },\n  stats: {\n    warningsFilter: [/Failed to parse source map/],\n  },\n  plugins: [\n    new webpack.DefinePlugin({\n      'process.env': JSON.stringify({}),\n    }),\n    new MonacoWebpackPlugin({\n      publicPath: '/static/',\n      languages: ['markdown', 'yaml', 'python', 'json'],\n      features: ['all'],\n      customLanguages: [\n        {\n          label: 'yaml',\n          entry: 'monaco-yaml',\n          worker: {\n            id: 'monaco-yaml/yamlWorker',\n            entry: 'monaco-yaml/yaml.worker',\n          },\n        },\n      ],\n    })\n  ],\n};\n"
  },
  {
    "path": "www/webpack.dev.config.js",
    "content": "const path = require('path');\nconst webpack = require('webpack');\nconst TerserPlugin = require('terser-webpack-plugin');\nconst MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');\n\nmodule.exports = {\n  mode: 'development',\n  devtool: 'eval-source-map',\n  entry: {\n    app: {\n      import: './app/App.js',\n    },\n  },\n  output: {\n    path: path.resolve(__dirname, 'dist-dev', 'static'),\n    publicPath: '/static/',\n    filename: '[name].js',\n    globalObject: 'self',\n  },\n  watchOptions: {\n    aggregateTimeout: 100,\n    ignored: [\n      path.resolve(__dirname, 'node_modules'),\n    ],\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        enforce: 'pre',\n        use: ['source-map-loader'],\n      },\n      {\n        test: /\\.css$/,\n        use: ['style-loader', 'css-loader'],\n      },\n      {\n        test: /\\.(ttf|woff|woff2|eot|svg)$/,\n        type: 'asset/resource',\n      },\n    ],\n  },\n  stats: {\n    warningsFilter: [/Failed to parse source map/],\n  },\n  plugins: [\n    new webpack.DefinePlugin({\n      'process.env': JSON.stringify({}),\n    }),\n    new MonacoWebpackPlugin({\n      publicPath: '/static/',\n      languages: ['markdown', 'yaml', 'python', 'json'],\n      features: ['all'],\n      customLanguages: [\n        {\n          label: 'yaml',\n          entry: 'monaco-yaml',\n          worker: {\n            id: 'monaco-yaml/yamlWorker',\n            entry: 'monaco-yaml/yaml.worker',\n          },\n        },\n      ],\n    })\n  ],\n};\n"
  },
  {
    "path": "zone/constants.go",
    "content": "package zone\n\nconst (\n\tDefault   = \"default\"\n\tVxlanVlan = \"vxlan_vlan\"\n)\n"
  },
  {
    "path": "zone/utils.go",
    "content": "package zone\n\nimport (\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/mongo-go-driver/v2/mongo/options\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\nfunc Get(db *database.Database, zoneId bson.ObjectID) (\n\tzne *Zone, err error) {\n\n\tcoll := db.Zones()\n\tzne = &Zone{}\n\n\terr = coll.FindOneId(zoneId, zne)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetOne(db *database.Database, query *bson.M) (zne *Zone, err error) {\n\tcoll := db.Zones()\n\tzne = &Zone{}\n\n\terr = coll.FindOne(db, query).Decode(zne)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAll(db *database.Database) (zones []*Zone, err error) {\n\tcoll := db.Zones()\n\tzones = []*Zone{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{},\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tzne := &Zone{}\n\t\terr = cursor.Decode(zne)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tzones = append(zones, zne)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNames(db *database.Database, query *bson.M) (\n\tznes []*Completion, err error) {\n\n\tcoll := db.Certificates()\n\tznes = []*Completion{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetProjection(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"datacenter\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tzne := &Completion{}\n\t\terr = cursor.Decode(zne)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tznes = append(znes, zne)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllPaged(db *database.Database, query *bson.M,\n\tpage, pageCount int64) (znes []*Zone, count int64, err error) {\n\n\tcoll := db.Zones()\n\tznes = []*Zone{}\n\n\tif len(*query) == 0 {\n\t\tcount, err = coll.EstimatedDocumentCount(db)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tcount, err = coll.CountDocuments(db, query)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif pageCount == 0 {\n\t\tpageCount = 20\n\t}\n\tmaxPage := count / pageCount\n\tif count == pageCount {\n\t\tmaxPage = 0\n\t}\n\tpage = utils.Min64(page, maxPage)\n\tskip := utils.Min64(page*pageCount, count)\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\tquery,\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetSkip(skip).\n\t\t\tSetLimit(pageCount),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tzne := &Zone{}\n\t\terr = cursor.Decode(zne)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tznes = append(znes, zne)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllDatacenter(db *database.Database, dcId bson.ObjectID) (\n\tzones []*Zone, err error) {\n\n\tcoll := db.Zones()\n\tzones = []*Zone{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"datacenter\": dcId,\n\t\t},\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tzne := &Zone{}\n\t\terr = cursor.Decode(zne)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tzones = append(zones, zne)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc GetAllNamedDc(db *database.Database, dcIds []bson.ObjectID) (\n\tzones []*Completion, err error) {\n\n\tcoll := db.Zones()\n\tzones = []*Completion{}\n\n\tcursor, err := coll.Find(\n\t\tdb,\n\t\t&bson.M{\n\t\t\t\"datacenter\": &bson.M{\n\t\t\t\t\"$in\": dcIds,\n\t\t\t},\n\t\t},\n\t\toptions.Find().\n\t\t\tSetSort(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t}).\n\t\t\tSetProjection(&bson.D{\n\t\t\t\t{\"name\", 1},\n\t\t\t\t{\"datacenter\", 1},\n\t\t\t}),\n\t)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\tdefer cursor.Close(db)\n\n\tfor cursor.Next(db) {\n\t\tzne := &Completion{}\n\t\terr = cursor.Decode(zne)\n\t\tif err != nil {\n\t\t\terr = database.ParseError(err)\n\t\t\treturn\n\t\t}\n\n\t\tzones = append(zones, zne)\n\t}\n\n\terr = cursor.Err()\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc Remove(db *database.Database, zoneId bson.ObjectID) (err error) {\n\tcoll := db.Zones()\n\n\t_, err = coll.DeleteOne(db, &bson.M{\n\t\t\"_id\": zoneId,\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\tswitch err.(type) {\n\t\tcase *database.NotFoundError:\n\t\t\terr = nil\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc RemoveMulti(db *database.Database, zneIds []bson.ObjectID) (\n\terr error) {\n\tcoll := db.Zones()\n\n\t_, err = coll.DeleteMany(db, &bson.M{\n\t\t\"_id\": &bson.M{\n\t\t\t\"$in\": zneIds,\n\t\t},\n\t})\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "zone/zone.go",
    "content": "package zone\n\nimport (\n\t\"net\"\n\n\t\"github.com/dropbox/godropbox/container/set\"\n\t\"github.com/dropbox/godropbox/errors\"\n\t\"github.com/pritunl/mongo-go-driver/v2/bson\"\n\t\"github.com/pritunl/pritunl-cloud/database\"\n\t\"github.com/pritunl/pritunl-cloud/errortypes\"\n\t\"github.com/pritunl/pritunl-cloud/settings\"\n\t\"github.com/pritunl/pritunl-cloud/utils\"\n)\n\ntype Zone struct {\n\tId          bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tDatacenter  bson.ObjectID `bson:\"datacenter\" json:\"datacenter\"`\n\tName        string        `bson:\"name\" json:\"name\"`\n\tComment     string        `bson:\"comment\" json:\"comment\"`\n\tDnsServers  []string      `bson:\"dns_servers\" json:\"dns_servers\"`\n\tDnsServers6 []string      `bson:\"dns_servers6\" json:\"dns_servers6\"`\n}\n\ntype Completion struct {\n\tId         bson.ObjectID `bson:\"_id,omitempty\" json:\"id\"`\n\tDatacenter bson.ObjectID `bson:\"datacenter\" json:\"datacenter\"`\n\tName       string        `bson:\"name\" json:\"name\"`\n}\n\nfunc (z *Zone) Validate(db *database.Database) (\n\terrData *errortypes.ErrorData, err error) {\n\n\tz.Name = utils.FilterName(z.Name)\n\n\tif z.Datacenter.IsZero() {\n\t\terrData = &errortypes.ErrorData{\n\t\t\tError:   \"datacenter_required\",\n\t\t\tMessage: \"Missing required datacenter\",\n\t\t}\n\t\treturn\n\t}\n\n\tdnsServers := []string{}\n\tfor i, dnsServer := range z.DnsServers {\n\t\tif i > 1 {\n\t\t\tbreak\n\t\t}\n\n\t\tip := net.ParseIP(dnsServer)\n\t\tif ip == nil || ip.To4() == nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_dns_server\",\n\t\t\t\tMessage: \"DNS IPv4 server address is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdnsServers = append(dnsServers, ip.String())\n\t}\n\tz.DnsServers = dnsServers\n\n\tdnsServers6 := []string{}\n\tfor i, dnsServer := range z.DnsServers6 {\n\t\tif i > 1 {\n\t\t\tbreak\n\t\t}\n\n\t\tip := net.ParseIP(dnsServer)\n\t\tif ip == nil || ip.To4() != nil {\n\t\t\terrData = &errortypes.ErrorData{\n\t\t\t\tError:   \"invalid_dns_server6\",\n\t\t\t\tMessage: \"DNS IPv6 server address is invalid\",\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tdnsServers6 = append(dnsServers6, ip.String())\n\t}\n\tz.DnsServers6 = dnsServers6\n\n\treturn\n}\n\nfunc (z *Zone) GetDnsServerPrimary() string {\n\tif len(z.DnsServers) > 0 {\n\t\treturn z.DnsServers[0]\n\t}\n\treturn settings.Hypervisor.DnsServerPrimary\n}\n\nfunc (z *Zone) GetDnsServerSecondary() string {\n\tif len(z.DnsServers) > 1 {\n\t\treturn z.DnsServers[1]\n\t}\n\treturn settings.Hypervisor.DnsServerSecondary\n}\n\nfunc (z *Zone) GetDnsServerPrimary6() string {\n\tif len(z.DnsServers6) > 0 {\n\t\treturn z.DnsServers6[0]\n\t}\n\treturn settings.Hypervisor.DnsServerPrimary6\n}\n\nfunc (z *Zone) GetDnsServerSecondary6() string {\n\tif len(z.DnsServers6) > 1 {\n\t\treturn z.DnsServers6[1]\n\t}\n\treturn settings.Hypervisor.DnsServerSecondary6\n}\n\nfunc (z *Zone) Commit(db *database.Database) (err error) {\n\tcoll := db.Zones()\n\n\terr = coll.Commit(z.Id, z)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (z *Zone) CommitFields(db *database.Database, fields set.Set) (\n\terr error) {\n\n\tcoll := db.Zones()\n\n\terr = coll.CommitFields(z.Id, z, fields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (z *Zone) Insert(db *database.Database) (err error) {\n\tcoll := db.Zones()\n\n\tif !z.Id.IsZero() {\n\t\terr = &errortypes.DatabaseError{\n\t\t\terrors.New(\"zone: Zone already exists\"),\n\t\t}\n\t\treturn\n\t}\n\n\tresp, err := coll.InsertOne(db, z)\n\tif err != nil {\n\t\terr = database.ParseError(err)\n\t\treturn\n\t}\n\n\tz.Id = resp.InsertedID.(bson.ObjectID)\n\n\treturn\n}\n"
  }
]